Skip to content
Merged
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.17",
"version": "0.2.18",
"author": {
"name": "JFrog Ltd.",
"email": "devrel@jfrog.com",
Expand Down
109 changes: 65 additions & 44 deletions skills/jfrog-setup-package-managers/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
---
Expand All @@ -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)
Expand All @@ -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

Expand All @@ -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 <img>` hits Docker Hub. Complete setup, then pull via
`<host>/<repoKey>/<img>`.
- **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

Expand All @@ -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 `<pm>`, recover `<repoKey>` and `<serverId>` from the first source
For each `<package-manager>`, recover `<repoKey>` and `<serverId>` from the first source
available:

1. **"Resolved URLs for this session"** table (default). Parse `<repoKey>`
from URL; `<serverId>` from host.
2. **Workspace binding** — if table was trimmed. `repositories.<type>`.
2. **Workspace binding** — if table was trimmed. `repositories.<type>`
(`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).

Expand All @@ -126,37 +144,39 @@ 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=<pm>"` (filter by
repo type — prefer `virtual` — and package type), then let the user choose; the
`jf api "/artifactory/api/repositories?type=virtual&packageType=<pkgType>"`
(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 `<pm>` on `<SID>`.
> No default repo for `<package-manager>` on `<SID>`.
> Failure: `<verbatim 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
<pm> → <repoKey> on <SID> (source: resolver)
<pm> → <repoKey> on <SID> (source: user-supplied)
<package-manager> → <repoKey> on <SID> (source: resolver)
<package-manager> → <repoKey> on <SID> (source: user-supplied)
```

2. Show binding diffs when the repo key changes.

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 <pm> --server-id <SID> --repo <repoKey> [--project <key>]
jf setup <package-manager> --server-id <SID> --repo <repoKey> [--project <key>]
```

5. **Exit code `0` = success** — merge binding (step 6). On non-zero, **stop**,
Expand All @@ -169,7 +189,8 @@ Cap at **2 answers per PM**, then abort. User may override repo only, never serv
{ "repositories": { "<pkgType>": "<repoKey>" } }
```

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -53,20 +54,22 @@ only `repositories`. The map key **is** the `serverId`.
| Field | Meaning |
|---|---|
| `schemaVersion` | Always `1` for this schema. |
| `servers.<serverId>.repositories.<pkgType>` | Resolver's chosen repo key for this package type, on this server. **Missing key = `unresolved`** for that PM. |
| `servers.<serverId>.repositories.<pkgType>` | Resolver's chosen repo key for this package type, on this server. **Missing key = `unresolved`** for that package manager. |
| `servers.<serverId>.cached_at` | ISO-8601 timestamp of the last refresh. TTL from `packageResolution.cacheTtlDays` in agents-conf.json (default 7). |
| `servers.<serverId>.agentsConfigMtimeMs` | Invalidates cache when `~/.jfrog/agents-conf.json` changes. |
| `servers.<serverId>.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? |
|---|---|---|---|
Expand Down Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <pm> --server-id <SID> --repo <repo-key> [--project <project-key>]
jf setup <package-manager> --server-id <SID> --repo <repo-key> [--project <project-key>]
```

Always pass `--server-id` and `--repo`. Without `--repo`, multiple matching
Expand All @@ -17,7 +17,7 @@ name using '--repo' flag`).
`GET /artifactory/api/repositories/<key>` 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:

Expand All @@ -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 `<SID>` | 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.
Loading
Loading