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
593 changes: 593 additions & 0 deletions skills/jfrog-init/SKILL.md

Large diffs are not rendered by default.

38 changes: 38 additions & 0 deletions skills/jfrog-init/references/batch-walk.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Running everything at once — jfrog-detect-all.mjs

`node scripts/jfrog-detect-all.mjs [server-id] [project-key]` runs Steps 1–7
in order and stops at the first non-green result — except Step 5 going
red/error, Step 6 going red (ambiguous/404/403), and Step 7 going red
in either of its two non-blocking shapes (exit 1: catalog not hosted /
unreachable / 5xx, or exit 4: reachable but not entitled), all of which
are non-blocking: Steps 1-4 passing is what "green" means here,
and the MCP-plugin, project-resolution, and catalog-availability gaps
are each reported as separate signals. This script makes exactly one
project-resolution attempt per invocation and has no way to tell a
first attempt from a last one, so it always treats a Step 6 red as
non-blocking — the interactive walk (see Step 6 in `SKILL.md`) is what
enforces the one-retry cap before giving up. Steps 5's, 6's, and 7's own
`ask`/`error` outcomes (ambiguous server-id, no project input passed,
`jf` missing/credentials rejected) still block, same as every other
step's genuine stop. If no project key is passed, Step 6 emits `ask`
with candidates and the walk halts; the caller re-invokes with the
picked project as arg 2 — unless that `ask` carries `"unresolved":
"server"`, in which case it's a server pick (see Step 6's branches) and
the re-invocation picks server-id (arg 1) instead.

Exit 0 = Steps 1-4 green (MCP configured or not, project resolved
or not, catalog entitled or not); exit 1 = something needs fixing. The
final JSON line adds `mcpConfigured: true|false`, `projectResolved:
true|false`, and `catalogEntitled: true|false` so a caller can tell the
exit-0 cases apart — plus `catalogReason: "unreachable" | "not_entitled"`
whenever `catalogEntitled` is `false`, so the Final Summary can name the
specific gap instead of a generic one. Writes the `~/.jfrog/setup.json`
state-file hint whenever Steps 1-4 are green, **regardless of
`projectResolved`, `mcpConfigured`, or `catalogEntitled`** — the server
and JPD URL are worth remembering on their own, independent of whether
a project got picked, the MCP plugin is wired up, or the AI Catalog is
reachable/the user is entitled to it. An unresolved project is passed
to `jfrog-state-file.mjs` as an empty key, which leaves any previously
recorded `currentActiveProject` alone rather than erasing it (see
`jfrog-state-file.mjs`); it's never written as a fresh, unvalidated
value.
68 changes: 68 additions & 0 deletions skills/jfrog-init/references/catalog-runtime-branches.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Step 7 — AI Catalog reachable & entitled: mechanics and branches

Two sub-checks against
`<JPD>/ml/core/api/v1/mcp-registry/ml-projects?pageSize=1` (where
`<JPD>` is the URL stored in `jf config` for the resolved server —
this skill never uses a separate `JFROG_PLATFORM_URL` env var), both
must pass. This step is **purely about AI Catalog access** — no
runtime checks (Node lives in Step 1; `uv` / `docker` are per-MCP
concerns, not this skill's).

1. **Anonymous** GET — proves the endpoint is deployed at this JPD.
`2xx/401/403/405/406` = up; `404` / connection failure = red.
2. **Authenticated** GET to the same path, with the bearer token (or
user+password) extracted from `jf config export` — same credential
source `jf` itself uses (token / user+password / SSO refresh,
whatever's stored), so the skill never asks for or invents
credentials, and the token only lives in memory for this one
request. `2xx` = user is **entitled** to read the AI Catalog on
this JPD; `403` = reachable but **not entitled**, non-blocking.
`401` means the credentials themselves were rejected — that says
nothing about entitlement, so it is treated as a blocking error
instead (see Exit 3 below).

Splitting reachability from entitlement produces two distinct
outcomes: check 1 red = "this JPD doesn't host AI Catalog, or it's
unreachable right now"; check 2's `403` = "catalog is up but your
user isn't entitled." Neither is a setup failure — both are
non-blocking permissions/availability gaps the rest of the walk doesn't
depend on (see the exit-code branches below). Check 2's `401` is
different — it means `jf`'s own credentials are invalid or expired,
which is a genuine setup problem (Exit 3).

**Required branches:**

- **Exit 0 (green)** → done. All checks pass, including entitlement.
- **Exit 1 (red)** → anon probe 404 / connection failure / 5xx — the
platform may not host AI Catalog, or it's unreachable right now.
**Non-blocking** — same reasoning as Exit 4 below: Steps 1-4 are
this skill's core prerequisites, and none of them depend on the AI
Catalog being present. Proceed to the Final Summary, but append a
note naming the gap (`catalogReason: "unreachable"` in
`jfrog-detect-all.mjs`'s summary — see `batch-walk.md`).
- **Exit 2 (ask)** → multiple servers configured, none marked
`isDefault`, no server-id passed. Same handling as Step 4's exit 2:
**stop and read `references/server-picker.md` in full**, then
re-invoke Step 7 with the pick as either the positional argument or
`JF_SERVER_ID`.
- **Exit 3 (error)** → `jf` missing, the authed probe returned `401`
(credentials themselves rejected — re-run `jf config add
--interactive`), or an unexpected HTTP code (e.g. a broken `jf`
config surfacing here instead of at Step 4). This is the one genuine
stop this step has — a real error, not a "catalog isn't available"
gap.
- **Exit 4 (not entitled)** → catalog is reachable but the authed probe
returned `403`. **Non-blocking** — proceed to the Final Summary, but
append the entitlement note (`catalogReason:
"not_entitled"`). The detector's `detail` names the specific endpoint
path (`/ml/core/api/v1/mcp-registry`) and the role the admin needs to
grant (typically "AI Catalog Read" or "Application Admin"), so the
user can forward an actionable request rather than a shrug.

Exit 1 and Exit 4 are deliberately handled the same way at the walk
level (see `batch-walk.md`) — both leave `catalogEntitled: false`, and
only differ in `catalogReason`, so the Final Summary can say "no AI
Catalog here" vs. "you're not entitled" accurately instead of collapsing
both into one generic gap.

**Never** grant a role or invent a project.
114 changes: 114 additions & 0 deletions skills/jfrog-init/references/flow-diagram.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# /jfrog-init — full flow diagram

Visual companion to the numbered Steps in `SKILL.md`. Every decision
node here is also fully documented — including the exact user-facing
wording — in the corresponding Step section of `SKILL.md`; this diagram
adds nothing new, it's a compressed map of that same prose for
at-a-glance orientation. `SKILL.md`'s Step-by-step text is the
authoritative source for wording and behavior — follow it literally.

```mermaid
flowchart TD
Start(["/jfrog-init"]) --> S1

S1["1. Node.js >= 18 installed? (no script — node --version / npx --version)"]:::stepBox
S1 -->|no| ASKNODE["AskUserQuestion: Install Node.js now? Yes/No"]:::fixBox
ASKNODE -->|Yes, macOS/Linux| NVMINSTALL["Install nvm (pinned version) + nvm install --lts"]:::autoBox
ASKNODE -->|Yes, Windows| WINGETNODE["winget install OpenJS.NodeJS.LTS"]:::autoBox
ASKNODE -->|No| STOP0["STOP: cannot proceed without Node"]:::stopBox
NVMINSTALL -->|failed| F1["Fall back: ask user to install Node.js >= 18 manually"]:::fixBox
NVMINSTALL -->|ok| S2
WINGETNODE --> F1W["Tell user: open a new terminal, re-run /jfrog-init"]:::fixBox
S1 -->|yes| S2

S2["2. jf CLI installed and >= 2.106.0?"]:::stepBox
S2 -->|missing| ASKJF["AskUserQuestion: Install JFrog CLI? Yes/No"]:::fixBox
S2 -->|outdated| ASKJFU["AskUserQuestion: Update JFrog CLI? Yes/No"]:::fixBox
ASKJF -->|Yes| INSTJF["jfrog-install-jf-cli.mjs: Plan A npm install -g jfrog-cli-v2-jf"]:::autoBox
ASKJFU -->|Yes| INSTJF
ASKJF -->|No| STOP0J["STOP: cannot proceed without jf"]:::stopBox
ASKJFU -->|No| STOP0J
INSTJF -->|npm exited 0, but resolving jf is shadowed by an earlier install on PATH| SHADOW["Report shadowing — move npm's bin ahead on PATH, or remove the other install"]:::fixBox
SHADOW --> PLANC
INSTJF -->|npm failed, or exited 0 but jf still isn't resolving up to date and not shadowed, and a private registry is configured| PLANB["Plan B: retry npm install against the public registry"]:::autoBox
PLANB -->|jf now resolves up to date| S2RE
PLANB -->|still not up to date| PLANC
INSTJF -->|npm missing, or Plan A/B failed to leave an up-to-date jf on PATH| PLANC["Plan C: direct binary download to ~/.jfrog/bin, checksum-verified, runs jf --version to confirm"]:::autoBox
PLANC -->|Windows| WINPS["Print PowerShell one-liner — user runs it themselves, then re-runs /jfrog-init"]:::fixBox
PLANC -->|macOS/Linux| S2RE
INSTJF -->|npm succeeded and jf now resolves >= 2.106.0| S2RE["Re-check: installed and >= 2.106.0?"]:::stepBox
S2RE -->|still missing/outdated| STOP1["STOP: show raw install error"]:::stopBox
S2RE -->|ok| S3
S2 -->|yes| S3

S3["3. jf connected to a server?"]:::stepBox
S3 -->|no| ASKMETHOD["AskUserQuestion: Web login or Access token?"]:::fixBox
ASKMETHOD -->|Web login| WEBLOGIN["Register session, show code/link, retrieve + save credentials — all in-session, this skill's own local scripts"]:::autoBox
WEBLOGIN --> S3ASKW["AskUserQuestion: Did you finish logging in? Yes/No"]:::fixBox
S3ASKW -->|Yes| S3
S3ASKW -->|No| STOP0B["STOP: user cancelled"]:::stopBox
ASKMETHOD -->|Access token| F3["Print one command, --url pre-filled — user runs it themselves in their own terminal, pastes the token"]:::fixBox
F3 --> S3ASK["AskUserQuestion: Did you finish running that command? Yes/No"]:::fixBox
S3ASK -->|Yes| S3
S3ASK -->|No| STOP0B
S3 -->|yes| S4

S4["4. Server reachable and credentials valid?"]:::stepBox
S4 -->|multiple servers, no default| ASKSRV["AskUserQuestion: pick server-id"]:::fixBox
ASKSRV --> S4
S4 -->|reachable, but token invalid/expired| F4["Print one command, --url and --server-id pre-filled (token-only, no web option — avoids a duplicate server) — user runs it themselves"]:::fixBox
F4 --> S4ASK["AskUserQuestion: Did you finish running that command? Yes/No"]:::fixBox
S4ASK -->|Yes| S4
S4ASK -->|No| STOP0C["STOP: user cancelled"]:::stopBox
S4 -->|unreachable/timeout/other| STOP2["STOP: show raw error (network/URL hint included)"]:::stopBox
S4 -->|yes| S5

S5["5. Plugin mcp.json has mcpServers.jfrog? (auto-substitutes a JFROG_PLATFORM_URL/JFROG_URL placeholder inline, if present)"]:::stepBox
S5 -->|substitution needed, server-id ambiguous| ASKSRV5["AskUserQuestion: pick server-id"]:::fixBox
ASKSRV5 --> S5
S5 -->|missing/invalid/no entry, incl. substitution failure| F5["Note: reinstall or update the JFrog plugin, or resolve jf config (non-blocking)"]:::fixBox
F5 --> S6
S5 -->|yes, valid url| S6

S6["6. Project resolved?"]:::stepBox
S6 -->|state file has current project| ASKREUSE["AskUserQuestion: reuse CURRENT or pick different"]:::fixBox
ASKREUSE -->|reuse| VALPROJ["Validate via authenticated GET /access/api/v1/projects/KEY"]:::stepBox
ASKREUSE -->|different| ASKPROJ["AskUserQuestion: first 2 projects, or Other to type one"]:::fixBox
S6 -->|no state file| ASKPROJ
ASKPROJ --> RESOLVE["Resolve name-or-key (case-insensitive) against project list from authenticated GET /access/api/v1/projects"]:::stepBox
RESOLVE -->|no match, 1st attempt| ASKPROJ
RESOLVE -->|no match again, 2nd attempt: give up| F6
RESOLVE -->|matched| VALPROJ
VALPROJ -->|404 or 403, 1st attempt| ASKPROJ
VALPROJ -->|404 or 403 again, 2nd attempt: give up| F6
VALPROJ -->|401, credentials rejected| STOPCREDS["STOP: show raw error (re-auth via the Step 3/4 picker)"]:::stopBox
VALPROJ -->|2xx| S7

F6["Note: no project resolved after 1 retry — continue without one (non-blocking)"]:::fixBox
F6 --> S7

S7["7. AI Catalog reachable and user entitled?"]:::stepBox
S7 -->|anon 404 / connection failure / 5xx, exit 1| F7U["Note: catalogReason=unreachable — JPD may not host AI Catalog, or it's down right now (non-blocking)"]:::fixBox
F7U --> WRITE
S7 -->|authed 401 or 403, not entitled, exit 4| F7E["Note: catalogReason=not_entitled — ask JFrog admin for AI Catalog Read role (non-blocking)"]:::fixBox
F7E --> WRITE
S7 -->|ambiguous server-id, exit 2| ASKSRV7["AskUserQuestion: which jf server? (server-picker.md), then re-run"]:::fixBox
ASKSRV7 --> S7
S7 -->|jf missing / credentials rejected / unexpected response shape, exit 3| STOP7["STOP: show raw error — no state file written"]:::stopBox
S7 -->|yes, entitled, exit 0| WRITE["Write state file: server, jpdUrl, and currentActiveProject if resolved (else previous project, if any, is kept) — written on every NON-blocking path out of Step 7"]:::autoBox

WRITE --> S8CHECK{"Step 7 green AND harness == Claude Code? (detectHarness() reused from Step 5)"}:::stepBox
S8CHECK -->|no| DONE
S8CHECK -->|yes| S8["8. Claude agent-plugin marketplace registered?"]:::stepBox
S8 -->|exit 0, success| F8OK["Reply: Added the JFrog marketplace ... — trailing sentence after Final Summary"]:::autoBox
S8 -->|exit 1 or 3, failed| DONE
F8OK --> DONE

DONE(["JFrog init complete"]):::doneBox

classDef stepBox fill:#e8f0fe,stroke:#1a73e8,color:#000
classDef fixBox fill:#fff4e5,stroke:#f9a825,color:#000
classDef autoBox fill:#e6f4ea,stroke:#137333,color:#000
classDef doneBox fill:#e6f4ea,stroke:#137333,color:#000
classDef stopBox fill:#fce8e6,stroke:#c5221f,color:#000
```
75 changes: 75 additions & 0 deletions skills/jfrog-init/references/jf-cli-install-internals.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# jfrog-install-jf-cli.mjs — installation internals

Background for Step 2 of `/jfrog-init` (`SKILL.md`). The model doesn't
need this to execute the step — `jfrog-install-jf-cli.mjs` handles all
of it and reports success/failure on stdout and its exit code — but it
explains what the script actually does, for debugging or when a user
asks how the install works.

**Deliberately does not use** the base skill's
[`../jfrog/references/jfrog-cli-install-upgrade.md`](../../jfrog/references/jfrog-cli-install-upgrade.md)
(`brew install jfrog-cli` / a Linux-only curl one-liner, no Windows
guidance). This walk's primary method is one command that behaves
identically across macOS, Linux, and Windows without branching on OS —
`npm install -g jfrog-cli-v2-jf` — so it uses that instead. If npm
itself can't complete the install (missing, or a permissions error like
a global prefix that needs `sudo`), the script falls back to a
checksum-verified direct binary download with its own Windows handling.

`jfrog-install-jf-cli.mjs` tries progressively more self-contained
install methods, falling through only when one genuinely fails:

1. **Plan A — npm** (JFrog's own documented method:
docs.jfrog.com/integrations/docs/download-and-install-the-jfrog-cli#npm):
`npm install -g jfrog-cli-v2-jf` against whatever registry npm is
already configured for. No PATH/shell-rc changes here: npm's global
bin directory is expected to already be on PATH. This works
identically on macOS, Linux, and Windows, so there's no OS-specific
branch for this plan.
2. **Plan B — public registry retry**: triggered whenever Plan A's `npm`
command itself fails, *or* it exits 0 but the `jf` that resolves on
PATH afterward still isn't at the required version — provided that
stale `jf` is npm's own install and not a different, older `jf`
earlier on PATH shadowing it. Shadowing is reported directly and
skips straight to Plan C instead: retrying against a different
registry can't fix a PATH-ordering problem. If npm is configured for
a registry other than the
public one (common on a company machine, pointed at a
private/corporate mirror), the exact same install is retried with
`--registry=https://registry.npmjs.org/` — this one command only,
never touching the user's saved npm config. `jfrog-cli-v2-jf` is a
public package, so a private registry's own (possibly stale) auth
says nothing about whether the package itself is reachable.
3. **Plan C — direct binary download**: if npm is missing, or both A
and B failed for any other reason (observed in practice: a global
npm prefix that requires `sudo`), downloads the first-party `jf`
binary from `releases.jfrog.io` straight to `~/.jfrog/bin` — a
user-owned prefix that never needs elevated permissions — and
verifies it against the SHA-256 checksum Artifactory reports for that
same artifact (catches a truncated/corrupted transfer, not an
independent signature). Unlike Plans A/B, `~/.jfrog/bin` isn't on
PATH by default, so a successful Plan C also appends a PATH line to
the user's shell rc file (idempotent) and prints one for the caller
to `eval` immediately, so `jf` resolves both in future terminals and
in the *current* process without the user doing anything.
**Windows**: Plan C's direct-download path isn't reliable there, so
it instead prints a PowerShell one-liner — installing to a user-owned
path and prepending to the user-scope `Path` via
`[Environment]::SetEnvironmentVariable(..., 'User')`, no elevation
needed. Reads the existing user-scope value first rather than using
`setx PATH "...;$env:Path"`, which would copy the *combined*
machine+user PATH into the user variable (duplicating every
machine-level entry into it, permanently) and silently truncate past
`setx`'s 1024-character limit. The whole script exits 1 so Step 2 can
relay it to the user.

Only if all three plans fail does the script print the plain
`npm install -g jfrog-cli-v2-jf` command and exit 1, for the user to
diagnose and run themselves.

**Known trade-off of Plans A/B, called out in JFrog's own docs and not
something this script can detect or fix**: if the user relies on a
shim-based version manager (`nvm` / Volta) alongside another `jf`
install (Homebrew, curl, or Plan C itself), the version manager's
`bin/` takes PATH priority, so the npm-installed `jf` silently wins
regardless of what those other installs report.
Loading
Loading