Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "jfrog",
"displayName": "JFrog",
"description": "Official JFrog plugin. Connect Claude Code to JFrog to manage, secure, and govern your software supply chain. Give agents the context to build secure, compliant software.",
"version": "0.2.5",
"version": "0.2.6",
"author": {
"name": "JFrog Ltd.",
"email": "devrel@jfrog.com",
Expand Down
22 changes: 11 additions & 11 deletions skills/jfrog-package-safety-and-download/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
name: jfrog-package-safety-and-download
description: >-
Check JFrog Public Catalog and stored packages for a version, interpret
catalog security signals, and download through Artifactory (JFrog Platform
catalog security signals, and download through Artifactory (Jfrog Platform
locations, remote cache, curation-aware package managers, or repo proxy).
Use when the user asks whether a package is safe, allowed, curated, or
wants to download npm, Maven, PyPI, Go, or similar packages via JFrog.
Expand Down Expand Up @@ -37,12 +37,12 @@ When to read this file:
flowchart TD
A[User requests package check / download] --> B{Package in Public Catalog?}
B -->|Yes| C[Get latest version from Catalog]
B -->|No| D{Package in JFrog Platform Stored Packages?}
B -->|No| D{Package in Jfrog Platform Stored Packages?}
D -->|Yes| E[Get latest version from Stored Packages]
D -->|No| F[Package not found — stop]
C --> G{Latest version in JFrog Platform?}
C --> G{Latest version in Jfrog Platform?}
E --> G
G -->|Yes| H[Safe — download from JFrog Platform]
G -->|Yes| H[Safe — download from Jfrog Platform]
G -->|No| I{Curation entitled?}
I -->|Yes| J[Check curation policy via API]
I -->|No| K[Download via remote repo]
Expand All @@ -60,9 +60,9 @@ reduce total latency:
parallel. Use whichever returns data; if the Public Catalog returns a hit,
prefer its `latestVersion` for Step 2.
- **Step 3 + Step 5**: After determining the version, query stored package
versions (JFrog Platform check) and curation entitlement
versions (Jfrog Platform check) and curation entitlement
(`/api/system/version`) in parallel. Both are independent reads — the
curation result is needed immediately if the JFrog Platform check returns
curation result is needed immediately if the Jfrog Platform check returns
empty.

When issuing parallel Shell calls, each `jf api` call authenticates
Expand Down Expand Up @@ -104,9 +104,9 @@ type they mean.
| Source | Version field |
|--------|--------------|
| Public Catalog | `latestVersion.version` (object selection required) |
| JFrog Platform Stored Packages | `latestVersionName` on `StoredPackage`, or highest entry from `versionsConnection` |
| Jfrog Platform Stored Packages | `latestVersionName` on `StoredPackage`, or highest entry from `versionsConnection` |

## Step 3: Check if package + latest version exists in JFrog Platform
## Step 3: Check if package + latest version exists in Jfrog Platform

Query stored package versions using `storedPackages.searchPackageVersions`
with a `hasPackageWith` filter (see `../jfrog/references/onemodel-query-examples.md`
Expand All @@ -117,11 +117,11 @@ details (`repositoryKey`, `repositoryType`, `leadArtifactPath`).
Execute the query through `jf api` (see
`../jfrog/references/onemodel-graphql.md` for the invocation pattern).

- **Found with locations** → package is in the JFrog Platform. Report as **safe to
- **Found with locations** → package is in the Jfrog Platform. Report as **safe to
download**. Proceed to Step 4.
- **Not found** → proceed to Step 5.

## Step 4: Download from JFrog Platform
## Step 4: Download from Jfrog Platform

Use the location info from Step 3. Binary artifact downloads go through
`jf rt dl` — **not** `jf api`. `jf api` is the unified entry point for the
Expand Down Expand Up @@ -224,7 +224,7 @@ fi

## Step 6b: Download without curation

When curation is not entitled and the package is not in the JFrog Platform,
When curation is not entitled and the package is not in the Jfrog Platform,
download directly through a remote repo.

1. **Find a remote repo** of the right package type:
Expand Down
1 change: 1 addition & 0 deletions skills/jfrog/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
local-cache/
445 changes: 208 additions & 237 deletions skills/jfrog/SKILL.md

Large diffs are not rendered by default.

112 changes: 53 additions & 59 deletions skills/jfrog/references/artifactory-operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,17 +52,6 @@ narrow the search further.

## Build info

**Project scoping rule:** Append `?project=<key>` to **every** build detail
API call. When the user provides a project key, use it. When no project key
is provided, use `?project=default` (the built-in default project that covers
the `artifactory-build-info` repo). For AQL queries, scope by
`"repo":"<project-key>-build-info"` (or `"repo":"artifactory-build-info"` for
the default project).

**Server rule:** A 404 from a `?project=<key>` build call is **not** a signal
to try a different server. Use only the resolved server; on any failure,
report and stop. See `SKILL.md` § *Server selection rules*.

### Publishing builds

- Collect env: `jf rt build-collect-env <name> <number>`
Expand All @@ -71,79 +60,84 @@ report and stop. See `SKILL.md` § *Server selection rules*.
- Promote: `jf rt build-promote <name> <number> <target-repo>`
- Discard: `jf rt build-discard <name>`

### Listing build names
### Retrieving build info

**Do not use `GET /api/build`** — it has no pagination and times out on large
instances. Always use AQL with `limit` and `offset`.
The build detail API (`GET /api/build/{name}/{number}`) returns 404 when the
build is stored in a non-default build-info repo or belongs to a JFrog
Project. **Always resolve the scope before calling the build API:**

**All builds** (no project scope):
1. If the user provided a project key or build-info repo, use it directly.
2. If you need to **list** build names or run numbers and you have a **project
key**, follow [Listing builds when the project key is known](#listing-builds-when-the-project-key-is-known) (REST first — do not jump to AQL).
3. If the project key and build-info repo are still unknown, discover scope
via AQL (see [Discovering build scope without a project key](#discovering-build-scope-without-a-project-key) below).
4. For **detail**, use a scoped detail GET — never call `GET /api/build/<name>/<number>` without `?project=` or `?buildRepo=` when the build requires it.

```bash
jf api /artifactory/api/search/aql \
-X POST -H "Content-Type: text/plain" \
-d 'builds.find().include("name","number","repo","created").sort({"$desc":["created"]}).offset(0).limit(100)'
jf api "/artifactory/api/build/<name>/<number>?buildRepo=<repo>"
jf api "/artifactory/api/build/<name>/<number>?project=<key>"
```

**Project-scoped** — filter by the project's build-info repository
(`<project-key>-build-info`, or `artifactory-build-info` for the default
project):
Scope parameters:

```bash
jf api /artifactory/api/search/aql \
-X POST -H "Content-Type: text/plain" \
-d 'builds.find({"repo":"<project-key>-build-info"}).include("name","number","repo","created").sort({"$desc":["created"]}).offset(0).limit(100)'
```
- `?buildRepo=<build-info-repo>` — when the build info is stored in a
non-default build-info repository (anything other than
`artifactory-build-info`)
- `?project=<project-key>` — when the build belongs to a JFrog Project

**Pagination:** The response includes a `range` object with `total` (total
matching records). If `total` exceeds the `limit`, tell the user: *"Showing
first 100 of N results (paginated). Ask for the next batch if needed."*
For subsequent pages, increment `offset` by 100.
### Listing builds when the project key is known

**Output rule (mandatory):** AQL returns one row per name+number pair.
Extract **unique build names** client-side (e.g.
`jq '[.[].builds.name] | unique'`). Present **only the deduplicated list of
build names** to the user. **Do not** include build numbers, timestamps, run
counts, or any per-run details in the response — not even as a "bonus" or
"most recent" table. The user is asking "what builds exist", not "what runs
happened". Only show run-level details if the user explicitly asks for them
in a follow-up.
When you have a **project key**, use this REST sequence before AQL. It scopes
the server’s work and avoids **unscoped** listing pitfalls (see below).

### Listing runs of a specific build
1. **Build names** (one row per logical build):
`GET /api/build?project=<project-key>`
Response includes `builds[]` with `uri` (path suffix per name) and
`lastStarted` (latest run for that name).

2. **Run numbers for one name**:
`GET /api/build/<name>?project=<project-key>`
Response uses the field **`buildsNumbers`** (exact spelling from the API);
each entry has `uri` (e.g. `/33`) and `started`. The same number may appear
more than once with different `started` values — do not assume uniqueness
by number alone.

3. **Full build info** (unchanged):
`GET /api/build/<name>/<number>?project=<project-key>`

```bash
jf api /artifactory/api/search/aql \
-X POST -H "Content-Type: text/plain" \
-d 'builds.find({"name":"<build-name>"}).include("name","number","repo","created").sort({"$desc":["created"]}).offset(0).limit(100)'
jf api "/artifactory/api/build?project=<project-key>"
jf api "/artifactory/api/build/<name>?project=<project-key>"
jf api "/artifactory/api/build/<name>/<number>?project=<project-key>"
```

Add `"repo":"<project-key>-build-info"` to the criteria when a project key
is known. Apply the same pagination rules as above.
### Discovering build scope without a project key

### Retrieving full build info
When the user has not provided the project key or build-info repo, discover
it via AQL. **Do not** use **unscoped** `GET /artifactory/api/build` (no
`?project=` or `?buildRepo=`) to list all builds — it can time out on large
instances with thousands of builds.

Use the REST detail endpoint for a **single** build run. Always include
`?project=<key>` (or `?project=default` when no key is provided):
Use AQL `builds.find()` instead. The builds domain **requires** `name`,
`number`, and `repo` in `.include()` for permission reasons — omitting `repo`
produces an error.

```bash
jf api "/artifactory/api/build/<name>/<number>?project=<key>"
jf api /artifactory/api/search/aql \
-X POST -H "Content-Type: text/plain" \
-d 'builds.find({"name":"<build-name>"}).include("name","number","repo").sort({"$desc":["number"]}).limit(10)'
```

This is the only `/api/build` endpoint that should be used — it returns a
single record and does not need pagination.

### When a build is not found

If the detail call returns 404, the build likely belongs to a different
project. **Ask the user for the project key** rather than searching across
repos or servers.
The `build.repo` field in the response tells you which build-info repository
the build resides in. Use that value as the `buildRepo` parameter in the
detail GET.

### Repository listing vs build-info

`GET /artifactory/api/repositories?project=<key>&type=buildinfo` may return
an empty list even when project-scoped build info exists (for example under
a `*-build-info` repository). Prefer AQL to
discover builds; do not treat an empty repository
list as proof that no
a `*-build-info` repository). Prefer the **build** endpoints above or AQL to
discover builds; do not treat an empty repository list as proof that no
builds exist.

## Permissions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,19 @@ line and break parsers (e.g. JSON "Extra data" errors).
- One temp file per worker or chunk, then concatenate; or
- Use advisory locking (`flock`) if one file must be shared.

## Agent sandboxes and the environment check

`scripts/check-environment.sh` does **not** call your JFrog server, but it may
make an outbound request to `releases.jfrog.io` for version checking and may
**write**
`<skill_path>/local-cache/jfrog-skill-state.json` when the cache is stale or missing. In a
restricted agent sandbox, **workspace write** access can fail even when
`full_network` is granted. Request permissions that allow writing `<skill_path>/local-cache`
when the check fails with a filesystem error.

For bulk API or CLI output files, use `/tmp` or `mktemp`; do not use
`~/.jfrog/skills-cache/` except for `jfrog-skill-state.json` and the OneModel
schema file (see main SKILL.md).
`local-cache/` except for `jfrog-skill-state.json` and the OneModel schema file
(see main SKILL.md).

## Shell hygiene

Expand Down
2 changes: 2 additions & 0 deletions skills/jfrog/references/general-parallel-execution.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ merges their results into a unified report.
expanded path, and return a structured summary.
4. Specify what to return (counts, lists, specific fields) so the parent can
assemble the final output without re-reading raw data.
5. Remind the subagent to use `required_permissions: ["full_network"]` on
every Shell call that contacts the JFrog server.

### Subagent type selection

Expand Down
3 changes: 2 additions & 1 deletion skills/jfrog/references/general-use-case-hints.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ workflow). Keep entries short and actionable.

| Area | Symptom | Cause | Mitigation | Notes |
|------|---------|-------|------------|-------|
| Agent sandbox | `check-environment.sh` fails with permission denied on `local-cache/` | Cache refresh tries to write `jfrog-skill-state.json` under `<skill_path>/local-cache`; sandbox blocks workspace writes | Run with permissions that allow writing the skill's `local-cache` directory, not only `full_network` | The script does not call your JFrog server but may contact `releases.jfrog.io` for version checking |
| Parallel I/O | JSONL or ndjson parse errors ("Extra data", merged objects on one line) | Concurrent unsynchronized `>>` to the same file from parallel jobs | Sequential writes, one file per worker then `cat`, or `flock` | Generic pattern for any bulk CLI output |
| APIs (general) | Report or script missing fields that appear in the UI or docs | List endpoint omitted fields; detail GET has the full record | List then GET by id/key when needed; see `general-bulk-operations-and-agent-patterns.md` | Applies across products |
| Access / Projects | Project groups list looks empty in code | Response may use `members` for the group entries, not `groups` | Accept both keys when parsing | See `projects-api.md` |
| Bulk detail fetches | `jq` parse error ("Extra data" or "Invalid ...") on slurped detail array | One or more detail GETs returned empty/HTML/error body; `jq -s` chokes on non-JSON mixed in | Validate each response with `jq -e .` before appending; write error placeholder for failures; see `general-bulk-operations-and-agent-patterns.md` | Applies to any per-item loop (repos, builds, users) |
| Agent timeouts | Shell job silently moves to background; no captured output | `block_until_ms` too low for N sequential API calls (~1-2s each) | Estimate `N * 1.5s + 30s` buffer; set `block_until_ms` accordingly; or use parallel execution | Default 30s insufficient for >20 items |
| Sandbox + workspace writes | Generated report JSON or HTML not written; script exits 0 but file missing | Sandbox blocks some paths; agents sometimes wrongly target `~/.jfrog/skills-cache/` for scratch files (disallowed — only state + schema belong there) | Write reports and API responses under `/tmp` or the user workspace; only the two allowed cache files (`jfrog-skill-state.json`, `onemodel-schema-<id>.graphql`) belong in `~/.jfrog/skills-cache/` | `skills-cache/` is not a temp directory; see SKILL.md **`~/.jfrog/skills-cache/` — allowed files only** |
| Sandbox + workspace writes | Generated report JSON or HTML not written; script exits 0 but file missing | Sandbox blocks some paths; agents sometimes wrongly target `local-cache/` for scratch files (disallowed — only state + schema belong there) | Write reports and API responses under `/tmp` or the user workspace; use `required_permissions: ["all"]` only when you must write inside the skill tree for the two allowed cache files | `local-cache/` is not a temp directory; see SKILL.md **`local-cache/` — allowed files only** |
| `jf api` and redirects | Responses are empty or contain redirect HTML instead of expected content | `jf api` does not expose `-L`; it follows the redirect chain the Artifactory REST API returns by default but will not transparently hop across an unexpected 3xx to a binary URL | For remote-repo artifact content (e.g. fetching a file via `/artifactory/<repo>/<path>`), use `jf rt dl` instead of `jf api` — it handles 302s to CDN hosts. Reserve `jf api` for REST metadata/operation endpoints. | Applies when reading artifact **content**, not REST metadata |
| Curation testing | `jf curation-audit` or `jf npm install` through a curated remote shows 1 download for the tested package even though no user downloaded it | The curation test itself fetches the package through Artifactory, which creates a cache entry and increments download stats | Account for this in download history analysis — the download was the curation test, not a real consumer pull | Also applies to `jf npmc` + `jf npm install` flows |
| Agent-invented mutations | Agent copies or moves artifacts into a local repo to satisfy a precondition for a different operation (e.g., copy a package so evidence can be created on it) | Requested operation failed because the artifact was not in the specified repo; agent autonomously performed a copy/move/upload to "fix" the gap | **Never** perform unrequested copy, move, upload, or create-repo to work around a failed precondition — stop and report the gap to the user. See SKILL.md § Cautious execution rule 6 | Copying into a local repo can silently change virtual repo resolution for all consumers, trigger replication, and affect Xray indexing |
Expand Down
Loading
Loading