diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json
index 730fdef..bc44b45 100644
--- a/.claude-plugin/plugin.json
+++ b/.claude-plugin/plugin.json
@@ -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.17",
+ "version": "0.2.18",
"author": {
"name": "JFrog Ltd.",
"email": "devrel@jfrog.com",
diff --git a/skills/jfrog-setup-package-managers/SKILL.md b/skills/jfrog-setup-package-managers/SKILL.md
index e7eeaec..bcd511e 100644
--- a/skills/jfrog-setup-package-managers/SKILL.md
+++ b/skills/jfrog-setup-package-managers/SKILL.md
@@ -2,14 +2,18 @@
name: jfrog-setup-package-managers
description: >-
Use this skill when the user asks to set up, configure, bind, or connect a
- package manager (npm, pip, maven, gradle, go, docker, helm, …) to JFrog
- Artifactory via `jf setup` and `.jfrog/local/package-resolution.json`; when a
- workspace manifest exists with no matching binding entry; or when a session
- hook reports PM config missing. Skip when the binding already has the same
- repo key — the session hook reapplies each start. Never pick a repo by
- discovery; use resolver output only (unless the user explicitly names or
- asks to browse repos). On unresolved or failed setup, ask for a repo key
- with the failure verbatim — never switch servers.
+ package manager (npm, pip, uv, pipenv, maven, gradle, go, docker, helm, ...)
+ to JFrog Artifactory via `jf setup` and
+ `.jfrog/local/package-resolution.json`; when a workspace manifest exists with
+ no matching binding entry; or when a session hook reports package-manager
+ config missing. Prefer uv for `uv.lock` / `[tool.uv]` — do not substitute pip
+ for uv when those signals exist; still bind pip when its own manifest (e.g.
+ `requirements.txt`) selects it. Yarn and Poetry are not part of Agent Package
+ Resolution zero-touch — bind only on explicit user request. Skip when the
+ binding already has the same repo key. Never pick a repo by discovery; use
+ resolver output only (unless the user names or asks to browse repos). On
+ unresolved or failed setup, ask with the failure verbatim — never switch
+ servers.
metadata:
role: workflow
---
@@ -18,7 +22,7 @@ metadata:
Apply the session hook's repo pick via [`jf setup`](references/jf-setup-command.md),
then record it in [`.jfrog/local/package-resolution.json`](references/workspace-binding.md).
-`jf setup` writes PM-native config (`.npmrc`, `pip.conf`, …); the binding
+`jf setup` writes package-manager-native config (`.npmrc`, `pip.conf`, `uv.toml`, …); the binding
lets the hook re-apply on later sessions.
## Scope (this skill vs session hook)
@@ -29,14 +33,14 @@ renderer is available on demand via `modules/package-resolution/scripts/print-po
notice embeds the exact command), so the policy can be loaded after setup.
**This skill:** reads that output, runs `jf setup`, and persists the workspace
-binding at `.jfrog/local/package-resolution.json` when PM config is still missing.
+binding at `.jfrog/local/package-resolution.json` when package-manager config is still missing.
**Honor the injected policy's governed scope.** The session policy lists the
-package managers it governs. Do **not** *proactively* onboard a PM the policy
+package managers it governs. Do **not** *proactively* onboard a package manager the policy
doesn't govern (e.g. a stray `Dockerfile` when only `pypi`/`npm` are governed) —
those are intentionally out of scope. An **explicit user request** to set up any
-PM still works (Step 1's user-mention signal and Step 2's AskQuestion for an
-unlisted PM apply as usual).
+package manager still works (Step 1's user-mention signal and Step 2's AskQuestion for an
+unlisted package manager apply as usual).
## Prerequisites
@@ -51,21 +55,23 @@ unlisted PM apply as usual).
- **Always pass `--repo` and `--server-id`** — omitting `--repo` fails when
multiple repos match. See [`jf-setup-command.md`](references/jf-setup-command.md).
-- **`jf setup` overwrites PM config** without backup — skip PMs whose binding
+- **`jf setup` overwrites package-manager config** without backup — skip package managers whose binding
already matches (Step 1, signal 2).
- **Docker / Podman — prefix or stop.** `jf setup docker` writes creds only;
bare `docker pull
` hits Docker Hub. Complete setup, then pull via
`//
`.
- **Binding holds decisions, not credentials** — never write tokens into
`.jfrog/local/package-resolution.json`.
+- **`gradle` ≠ `maven`.** Bind under `repositories.gradle`, never `repositories.maven`.
+- **Yarn / Poetry** — not APR zero-touch; bind only on explicit user ask (Step 1).
## References
| File | When to read |
|------|--------------|
-| [`references/jf-setup-command.md`](references/jf-setup-command.md) | CLI flags, supported PMs, exit-code contract, `jf setup --help` |
+| [`references/jf-setup-command.md`](references/jf-setup-command.md) | CLI flags, supported package managers, exit-code contract, `jf setup --help` |
| [`references/global-cache-file.md`](references/global-cache-file.md) | Global cache shape, resolution classes, jq one-liners |
-| [`references/workspace-binding.md`](references/workspace-binding.md) | Workspace binding schema, PM → type map, merge semantics |
+| [`references/workspace-binding.md`](references/workspace-binding.md) | Workspace binding schema, package-manager → type map, merge semantics |
## Step 0 — Read the base skill, then ensure `jf` is ready
@@ -87,37 +93,49 @@ unlisted PM apply as usual).
Combine four signals, in order; intersect with `jf setup --help` supported list:
-1. **Explicit user mention.** Map aliases: python → `pip`/`poetry`; java →
- `maven`/`gradle`; node → `npm`/`yarn`/`pnpm` by lockfile.
-2. **Workspace binding** — read `.jfrog/local/package-resolution.json`. Drop PMs
- already bound to the same key unless recovering from 401/403 (re-run same
- key). PM → type table: [`workspace-binding.md`](references/workspace-binding.md).
-3. **Workspace manifests** when still ambiguous:
-
- | Manifest file | Package manager |
+1. **Explicit user mention.** Map aliases: python → `pip`/`uv`/`pipenv` (and
+ `poetry` only if the user named Poetry); java → `maven`/`gradle`; node →
+ `npm`/`pnpm` by lockfile (`yarn` only if the user named Yarn).
+2. **Workspace binding** — read `.jfrog/local/package-resolution.json`. Drop
+ package managers already bound to the same key unless recovering from 401/403
+ (re-run same key). Package-manager → type table:
+ [`workspace-binding.md`](references/workspace-binding.md).
+3. **Workspace manifests** when still ambiguous (several package managers of one
+ type may apply — e.g. `requirements.txt` **and** `uv.lock`):
+
+ | Manifest / signal | Package manager |
|---|---|
- | `package.json`, `pnpm-lock.yaml`, `yarn.lock` | `npm` (+ `yarn`/`pnpm` if lockfiles present) |
- | `requirements.txt`, `Pipfile` | `pip` (`pipenv` for `Pipfile`) |
- | `pyproject.toml` | `poetry` if `[tool.poetry]`; else `pip` |
+ | `package.json`, `pnpm-lock.yaml` | `npm` (+ `pnpm` if `pnpm-lock.yaml` present) |
+ | `yarn.lock` (alone) | `npm` — do **not** auto-select `yarn` |
+ | `requirements.txt` | `pip` |
+ | `Pipfile` | `pipenv` |
+ | `uv.lock` | `uv` — suppresses bare `pyproject.toml` → `pip`; keep `requirements.txt` + `uv.lock` as multi-PM |
+ | `pyproject.toml` | `[tool.uv]` → `uv`; `[tool.poetry]` → `poetry` only on explicit user ask, else **not applicable** (do not select `pip`); bare PEP 621 with **no** `uv.lock` → `pip` |
| `pom.xml` | `maven` |
- | `build.gradle`, `build.gradle.kts` | `gradle` |
+ | `build.gradle`, `build.gradle.kts` | `gradle` (bind under type **`gradle`**) |
| `go.mod` | `go` |
| `Dockerfile`, `compose.yaml`, `docker-compose.yml` | `docker` / `podman` |
| `*.csproj`, `NuGet.Config` | `nuget` / `dotnet` |
| `Chart.yaml` | `helm` |
-4. **`jf setup --help`** — filter candidates; never hardcode the PM list. See
- [`jf-setup-command.md`](references/jf-setup-command.md). Unsupported PM →
- report gap, skip.
+ **Binary gate (client tools only):** missing client on `PATH` → skip as not
+ applicable; do **not** substitute another package manager or report setup
+ success. **Exempt `maven` / `gradle`** (config-only). Details:
+ [`jf-setup-command.md`](references/jf-setup-command.md).
+
+4. **`jf setup --help`** — filter candidates; never hardcode the list. See
+ [`jf-setup-command.md`](references/jf-setup-command.md). Unsupported → report
+ gap, skip.
## Step 2 — Get the resolved repo
-For each ``, recover `` and `` from the first source
+For each ``, recover `` and `` from the first source
available:
1. **"Resolved URLs for this session"** table (default). Parse ``
from URL; `` from host.
-2. **Workspace binding** — if table was trimmed. `repositories.`.
+2. **Workspace binding** — if table was trimmed. `repositories.`
+ (`gradle` → `repositories.gradle`, not `maven`).
3. **Global cache** — last resort only; never overrides (1) or (2). See
[`global-cache-file.md`](references/global-cache-file.md).
@@ -126,26 +144,28 @@ Cache disagreeing with (1)/(2) is not a reason to change the repo.
**Don't choose a repo yourself:** no listing, enumerating, probing, or iterating
`--server-id` to pick one, and don't second-guess the resolver — use resolver
output only. If the user explicitly asks to browse repos, list them via
-`jf api "/artifactory/api/repositories?type=virtual&packageType="` (filter by
-repo type — prefer `virtual` — and package type), then let the user choose; the
+`jf api "/artifactory/api/repositories?type=virtual&packageType="`
+(Artifactory **package type** from the binding map — `gradle` not `maven`;
+`uv` / `pip` / `pipenv` / `poetry` → `pypi`), then let the user choose; the
agent still never makes the choice on its own.
### Unresolved repo key
-Ask via AskQuestion:
+Ask via AskQuestion (include the resolver/setup failure text verbatim):
-> No default repo for `` on ``.
+> No default repo for `` on ``.
+> Failure: ``
> Which Artifactory repository should I use? (repo key, or `abort`.)
-Cap at **2 answers per PM**, then abort. User may override repo only, never server.
+Cap at **2 answers per package manager**, then abort. User may override repo only, never server.
## Step 3 — Confirm, run `jf setup`, persist binding
-1. Present the plan, one row per PM:
+1. Present the plan, one row per package manager:
```text
- → on (source: resolver)
- → on (source: user-supplied)
+ → on (source: resolver)
+ → on (source: user-supplied)
```
2. Show binding diffs when the repo key changes.
@@ -153,10 +173,10 @@ Cap at **2 answers per PM**, then abort. User may override repo only, never serv
3. **Confirm** via AskQuestion (`apply` / `change repos` / `abort`) unless the
user explicitly requested silent/non-interactive setup — then run directly.
-4. Sequentially, one PM at a time:
+4. Sequentially, one package manager at a time:
```bash
- jf setup --server-id --repo [--project ]
+ jf setup --server-id --repo [--project ]
```
5. **Exit code `0` = success** — merge binding (step 6). On non-zero, **stop**,
@@ -169,7 +189,8 @@ Cap at **2 answers per PM**, then abort. User may override repo only, never serv
{ "repositories": { "": "" } }
```
- Map PM → type via the reference table. Merge atomically.
+ Map package manager → type via the reference table (`gradle` → `gradle`).
+ Merge atomically.
## Step 4 — Load the routing policy
diff --git a/skills/jfrog-setup-package-managers/references/global-cache-file.md b/skills/jfrog-setup-package-managers/references/global-cache-file.md
index 1287964..7cb7869 100644
--- a/skills/jfrog-setup-package-managers/references/global-cache-file.md
+++ b/skills/jfrog-setup-package-managers/references/global-cache-file.md
@@ -31,6 +31,7 @@ pruning, the file cannot.
"npm": "npm-virtual",
"pypi": "pypi-virtual",
"maven": "libs-release",
+ "gradle":"gradle-virtual",
"go": "go-virtual",
"docker":"docker-virtual",
"helm": "helm-virtual",
@@ -53,20 +54,22 @@ only `repositories`. The map key **is** the `serverId`.
| Field | Meaning |
|---|---|
| `schemaVersion` | Always `1` for this schema. |
-| `servers..repositories.` | Resolver's chosen repo key for this package type, on this server. **Missing key = `unresolved`** for that PM. |
+| `servers..repositories.` | Resolver's chosen repo key for this package type, on this server. **Missing key = `unresolved`** for that package manager. |
| `servers..cached_at` | ISO-8601 timestamp of the last refresh. TTL from `packageResolution.cacheTtlDays` in agents-conf.json (default 7). |
| `servers..agentsConfigMtimeMs` | Invalidates cache when `~/.jfrog/agents-conf.json` changes. |
| `servers..source` | `verified` = keys from agents-conf.json checked via `GET /api/repositories/{key}`; `agents-config` = trusted without HTTP (`verifyRepos: false`). |
-Package type keys used in the file are `npm`, `pypi`, `maven`, `go`,
+Package type keys used in the file are `npm`, `pypi`, `maven`, `gradle`, `go`,
`docker`, `helm`, `nuget`. Note `pypi` (not `pip`) — same convention the
-JFrog API uses. The PM names accepted by `jf setup` (`pip`, `poetry`,
-`gradle`, `pnpm`, `yarn`, `podman`, `dotnet`, `pipenv`, `twine`) collapse
-onto these package-type keys.
+JFrog API uses. The package-manager names accepted by `jf setup` (`pip`, `uv`,
+`pnpm`, `podman`, `dotnet`, `pipenv`, `twine`, and optionally `yarn` / `poetry`
+when the user asks) collapse onto these package-type keys — **`gradle` maps to
+`gradle`**, not `maven`.
-## Three result classes per PM
-When you look up a PM in this file, you get one of:
+## Three result classes per package manager
+
+When you look up a package manager in this file, you get one of:
| Class | Detect | What the resolver did | HTTP-verified? |
|---|---|---|---|
@@ -100,7 +103,7 @@ If `$CACHE` does not exist, or the SID branch is missing, the hook has
not yet resolved on this machine for this server — fall back to reading
the injected "Resolved URLs for this session" table in agent context
(parse the URL to recover `repoKey`), and if that is also absent, treat
-every PM as `unresolved` and prompt the user (Step 2).
+every package manager as `unresolved` and prompt the user (Step 2).
The resolver refreshes stale entries on session start (TTL + agents-conf.json mtime).
This skill never invalidates the cache — if `jf setup` fails on a repo key, ask the user.
diff --git a/skills/jfrog-setup-package-managers/references/jf-setup-command.md b/skills/jfrog-setup-package-managers/references/jf-setup-command.md
index 12eeaf1..da87292 100644
--- a/skills/jfrog-setup-package-managers/references/jf-setup-command.md
+++ b/skills/jfrog-setup-package-managers/references/jf-setup-command.md
@@ -1,12 +1,12 @@
# `jf setup` Command Reference
-Configures a local PM to resolve from / publish to Artifactory. CLI install
-and server config: [`../../jfrog/SKILL.md`](../../jfrog/SKILL.md).
+Configures a local package manager to resolve from / publish to Artifactory. CLI
+install and server config: [`../../jfrog/SKILL.md`](../../jfrog/SKILL.md).
## Invocation
```bash
-jf setup --server-id --repo [--project ]
+jf setup --server-id --repo [--project ]
```
Always pass `--server-id` and `--repo`. Without `--repo`, multiple matching
@@ -17,7 +17,7 @@ name using '--repo' flag`).
`GET /artifactory/api/repositories/` before configuring. Record
`repositories.docker` in the workspace marker for pull URL composition.
-## Supported PM list
+## Supported package-manager list
Drifts across CLI versions — always parse from the installed binary:
@@ -37,9 +37,32 @@ Look for the "Supported package managers are:" line. Never hardcode.
| `401` / `403` | Token issue | Re-login same server — [`jfrog-login-flow.md`](../../jfrog/references/jfrog-login-flow.md) |
| Wrong server `404` | Bad `` | Stop — never iterate servers |
-Do not continue to the next PM after a failure.
+Do not continue to the next package manager after a failure.
## Agent notes
-- `pyproject.toml` with `[tool.poetry]` → `poetry`; plain PEP 621 → `pip`.
+### Python / Node detection (composition)
+
+- `uv.lock` → `uv` (writes `uv.toml`, not `pip.conf`). Takes precedence over a
+ bare `pyproject.toml` pip fallback — common layout is `uv.lock` + PEP 621
+ **without** `[tool.uv]`; select `uv` only, never also `pip`.
+- `requirements.txt` + `uv.lock` → bind **both** `pip` and `uv` (independent
+ manifests). Missing `uv` binary → skip `uv` as not applicable; do **not**
+ substitute `pip` for the uv candidate (pip still binds from its own file).
+- `pyproject.toml`:
+ 1. `[tool.uv]` → `uv`
+ 2. `[tool.poetry]` → `poetry` **only** on explicit user ask; otherwise **not
+ applicable** (do not fall through to `pip`)
+ 3. Bare PEP 621 with **neither** uv signal and **no** `uv.lock` → `pip`
+- Prefer `npm` / `pnpm` for Node; `yarn.lock` alone → `npm`. Do not proactively
+ run `jf setup yarn` / `jf setup poetry` (APR zero-touch omits both).
+
+### Binary gate / types
+
+- Missing package-manager binary → skip that candidate; do not substitute another.
+ Exception: `maven` / `gradle` need no client binary (`jf setup` writes config
+ only; wrappers/`pom.xml`/Gradle files are enough). Bind `gradle` under the
+ **`gradle`** package type (not `maven`).
+- Browse repos with Artifactory `packageType` from the binding map (`uv` →
+ `pypi`, not `uv`).
- `jf setup --help` is the authoritative flag reference.
diff --git a/skills/jfrog-setup-package-managers/references/workspace-binding.md b/skills/jfrog-setup-package-managers/references/workspace-binding.md
index 2b97767..24f135c 100644
--- a/skills/jfrog-setup-package-managers/references/workspace-binding.md
+++ b/skills/jfrog-setup-package-managers/references/workspace-binding.md
@@ -4,7 +4,7 @@ This skill records workspace repo bindings in a file the session-start hook
reads to override org defaults from `~/.jfrog/skills-cache/package-resolution.json`.
The file is the **decisions** record, not a credential store. Tokens live
-in `jf config` and in PM-native files written by `jf setup` itself.
+in `jf config` and in package-manager-native files written by `jf setup` itself.
## Location
@@ -24,6 +24,7 @@ different Artifactory repos.
"npm": "",
"pypi": "",
"maven": "",
+ "gradle": "",
"go": "",
"docker": "",
"helm": "",
@@ -36,13 +37,19 @@ different Artifactory repos.
|---|---|---|
| `repositories` | yes | Map keyed by **package type** — same keys as `servers..repositories` in the global resolver cache. Omit package types you do not override. |
-### PM name → package type (when merging after `jf setup`)
+### Package-manager name → package type (when merging after `jf setup`)
-| `jf setup` PM | `repositories` key |
+Aligned with Agent Package Resolution (`PACKAGE_TYPES` / eager families).
+`gradle` is its **own** Artifactory package type — never fold it under `maven`.
+
+| `jf setup` package manager | `repositories` key |
|---|---|
-| `npm`, `yarn`, `pnpm` | `npm` |
-| `pip`, `pipenv`, `poetry`, `twine` | `pypi` |
-| `maven`, `gradle` | `maven` |
+| `npm`, `pnpm` | `npm` |
+| `yarn` | `npm` (CLI may still accept `jf setup yarn`; APR zero-touch does **not** auto-setup yarn — only bind on explicit user request) |
+| `pip`, `pipenv`, `uv`, `twine` | `pypi` |
+| `poetry` | `pypi` (CLI may accept it; APR zero-touch does **not** auto-setup poetry — bind only on explicit user request) |
+| `maven` | `maven` |
+| `gradle` | `gradle` |
| `go` | `go` |
| `docker`, `podman` | `docker` |
| `helm` | `helm` |
@@ -52,8 +59,8 @@ different Artifactory repos.
### 1. Load
-Before setup, **read** the file (if it exists). For each PM in the
-to-bind set, map the PM to a package type and compare
+Before setup, **read** the file (if it exists). For each package manager in the
+to-bind set, map it to a package type and compare
`repositories.` against what the resolver chose in Step 2:
| Case | Action |
@@ -67,7 +74,7 @@ to-bind set, map the PM to a package type and compare
After each successful `jf setup`:
1. Read the current file (treat ENOENT as `{ "repositories": {} }`).
-2. Set `repositories[] = ` using the PM → type table above.
+2. Set `repositories[] = ` using the package-manager → type table above.
3. Atomically write `{ "repositories": { ... } }` — preserve other package
types already in the map.
@@ -76,7 +83,7 @@ JSON must use 2-space indent.
### 3. Never write
- Credentials (`accessToken`, passwords, …).
-- PM-native config paths — those are owned by `jf setup`.
+- Package-manager-native config paths — those are owned by `jf setup`.
## Integration contract
diff --git a/skills/jfrog/SKILL.md b/skills/jfrog/SKILL.md
index 68c0dae..bde4918 100644
--- a/skills/jfrog/SKILL.md
+++ b/skills/jfrog/SKILL.md
@@ -18,7 +18,7 @@ compatibility: >-
Requires jq on PATH.
metadata:
role: base
- version: "0.20.0"
+ version: "0.21.0"
---
# JFrog Skill
diff --git a/skills/jfrog/references/jfrog-cli-install-upgrade.md b/skills/jfrog/references/jfrog-cli-install-upgrade.md
index 071a19f..ba87338 100644
--- a/skills/jfrog/references/jfrog-cli-install-upgrade.md
+++ b/skills/jfrog/references/jfrog-cli-install-upgrade.md
@@ -1,5 +1,13 @@
# JFrog CLI Install & Upgrade
+## Minimum version for skills
+
+Skills that call `jf api` require JFrog CLI **2.100.0** or later. On an older CLI
+`jf api` is an unknown command, so the login flow stops as a prerequisite failure
+rather than reaching the platform. Web login itself needs **2.86.0** or later.
+
+Check with `jf --version`, and upgrade below that floor using the steps below.
+
## Installing the JFrog CLI
If `jf` is not installed (environment check exits with code 2), guide the user:
diff --git a/skills/jfrog/scripts/jfrog-login-register-session.sh b/skills/jfrog/scripts/jfrog-login-register-session.sh
index 7e91bf0..d02e4cf 100755
--- a/skills/jfrog/scripts/jfrog-login-register-session.sh
+++ b/skills/jfrog/scripts/jfrog-login-register-session.sh
@@ -49,6 +49,17 @@ if ! command -v jf &>/dev/null; then
exit 1
fi
+# `jf api` was added in JFrog CLI 2.100.0 and every request below depends on it.
+# Check it explicitly: on an older CLI the ping fails with an unknown-command
+# error that carries no HTTP status, which would otherwise be reported as an
+# unreachable server and send the user looking at the network instead of the CLI.
+if ! jf api --help >/dev/null 2>&1; then
+ echo "ERROR: this jf ($(jf --version 2>/dev/null || echo 'version unknown')) does not support 'jf api'," >&2
+ echo "which this login flow requires (JFrog CLI 2.100.0 or later)." >&2
+ echo "Upgrade the JFrog CLI, then retry. See references/jfrog-cli-install-upgrade.md." >&2
+ exit 1
+fi
+
if ! command -v uuidgen &>/dev/null; then
echo "ERROR: uuidgen is not installed" >&2
exit 1