diff --git a/.devin-plugin/plugin.json b/.devin-plugin/plugin.json index 4e88acf..25d81d1 100644 --- a/.devin-plugin/plugin.json +++ b/.devin-plugin/plugin.json @@ -1,8 +1,8 @@ { "name": "jfrog", "displayName": "JFrog Platform", - "version": "0.2.0", - "description": "Official JFrog plugin for Devin. Ships the JFrog skills bundle and registers the JFrog Platform MCP server (remote HTTP + OAuth).", + "version": "0.3.0", + "description": "Official JFrog plugin for Devin. Ships the JFrog skills bundle (platform ops, MCP management, AI Catalog, package safety, reference architecture, package-manager setup) and registers the JFrog Platform MCP server (remote HTTP + OAuth).", "author": { "name": "JFrog Ltd.", "email": "devrel@jfrog.com", diff --git a/.github/scripts/sync-skills-vendor.json b/.github/scripts/sync-skills-vendor.json index 765deaf..1bea6f1 100644 --- a/.github/scripts/sync-skills-vendor.json +++ b/.github/scripts/sync-skills-vendor.json @@ -1,5 +1,7 @@ { "repo": "jfrog/jfrog-skills", - "pin": "v0.16.0", - "paths": ["skills"] + "pin": "v0.23.1", + "paths": [ + "skills" + ] } diff --git a/README.md b/README.md index 37c9bfb..cbedfea 100644 --- a/README.md +++ b/README.md @@ -2,17 +2,20 @@ JFrog plugin for [Devin](https://devin.ai/): JFrog Platform skills for artifact management, security scanning, and supply-chain workflows, plus the JFrog Platform MCP server (remote HTTP + OAuth). -> **Current version:** `0.2.0` — skills from [jfrog/jfrog-skills](https://github.com/jfrog/jfrog-skills) (pinned at `v0.16.0`) and a bundled JFrog MCP entry. +> **Current version:** `0.3.0` — skills from [jfrog/jfrog-skills](https://github.com/jfrog/jfrog-skills) (pinned at `v0.23.1`) and a bundled JFrog MCP entry. ## Skills | Skill | Description | | --- | --- | | `jfrog` | JFrog Platform operations via CLI and APIs (Artifactory, Xray, access, projects, and more). | -| `jfrog-package-safety-and-download` | Check package safety and download via Artifactory. | +| `jfrog-mcp-management` | Install, list, and remove MCP servers through JFrog Agent Guard; browse the JFrog MCP catalog. | | `jfrog-ai-catalog-skills` | Discover, install, manage, and publish agent skills from the JFrog AI Catalog via `jf skills` and Agent Guard. | +| `jfrog-setup-package-managers` | Bind package managers (npm, pip, Maven, Go, and more) to JFrog Artifactory via `jf setup`. | +| `jfrog-reference-architecture` | JFrog Platform topology, sizing, deployment patterns, and multi-site guidance. | +| `jfrog-package-safety-and-download` | Check package safety and download via Artifactory. | -After install, Devin exposes them as `/jfrog:jfrog`, `/jfrog:jfrog-package-safety-and-download`, and `/jfrog:jfrog-ai-catalog-skills`. +After install, Devin exposes them as `/jfrog:` (for example `/jfrog:jfrog-mcp-management`). Skill content is vendored under `skills/` — see [VENDOR.md](VENDOR.md). @@ -40,7 +43,7 @@ The plugin registers this MCP server (declared in `mcp_config.json` and referenc - **Devin CLI** — see [Devin docs](https://docs.devin.ai/) - **Devin CLI plugins enabled** for your organization (`devin plugins install` must be allowed) - **`JFROG_PLATFORM_URL`** — JFrog platform host only (no `https://`, no trailing `/`). Required for the bundled MCP entry. -- **Skill runtime** (when using the skills) — `jf` CLI, `jq`, and `curl` on `PATH`, plus a configured JFrog instance (`jf config add`). See [jfrog-skills requirements](https://github.com/jfrog/jfrog-skills/blob/v0.16.0/README.md#requirements). +- **Skill runtime** (when using the skills) — `jf` CLI, `jq`, and `curl` on `PATH`, plus a configured JFrog instance (`jf config add`). See [jfrog-skills requirements](https://github.com/jfrog/jfrog-skills/blob/v0.23.1/README.md#requirements). ## Installation @@ -73,8 +76,11 @@ devin-plugin/ ├── mcp_config.json # JFrog Platform MCP (remote HTTP + OAuth) ├── skills/ │ ├── jfrog/ -│ ├── jfrog-package-safety-and-download/ -│ └── jfrog-ai-catalog-skills/ +│ ├── jfrog-mcp-management/ +│ ├── jfrog-ai-catalog-skills/ +│ ├── jfrog-setup-package-managers/ +│ ├── jfrog-reference-architecture/ +│ └── jfrog-package-safety-and-download/ ├── .github/scripts/ # sync-skills vendoring ├── LICENSE ├── README.md @@ -89,7 +95,7 @@ node scripts/validate-devin-plugin.mjs ## Versioning -Bump `version` in [`.devin-plugin/plugin.json`](.devin-plugin/plugin.json) when you publish a new release, then tag (for example `v0.2.0`). +Bump `version` in [`.devin-plugin/plugin.json`](.devin-plugin/plugin.json) when you publish a new release, then tag (for example `v0.3.0`). ## License diff --git a/VENDOR.md b/VENDOR.md index 782d385..d86fe06 100644 --- a/VENDOR.md +++ b/VENDOR.md @@ -7,7 +7,7 @@ The skill packages under `skills/` are vendored from **[jfrog/jfrog-skills](http | **Repository** | https://github.com/jfrog/jfrog-skills | | **Pinned release** | see `pin` in [`.github/scripts/sync-skills-vendor.json`](.github/scripts/sync-skills-vendor.json) | -Included directories: `jfrog/`, `jfrog-package-safety-and-download/`, `jfrog-ai-catalog-skills/` (as of the pinned release). +Included directories (as of the pinned release): `jfrog/`, `jfrog-ai-catalog-skills/`, `jfrog-mcp-management/`, `jfrog-package-safety-and-download/`, `jfrog-reference-architecture/`, `jfrog-setup-package-managers/`. ## Refreshing diff --git a/skills/jfrog-ai-catalog-skills/SKILL.md b/skills/jfrog-ai-catalog-skills/SKILL.md index ed273b1..f129918 100644 --- a/skills/jfrog-ai-catalog-skills/SKILL.md +++ b/skills/jfrog-ai-catalog-skills/SKILL.md @@ -56,18 +56,15 @@ Pick the row matching the user's intent and read that reference file. same `` to Agent Guard as `--server ""` so it targets the same server as your `jf` calls. Agent Guard also reads `JFROG_URL` / `JF_URL` directly when set, so make sure the `` you resolved points at that same host. -- **Resolve the project (``) only when needed, and always to a key.** - `` must be the JFrog **project key**, not the display name. It is - required for `--list-skills`, `--list-skill-versions`, and - `--provision-skills-repository`. Take the value from `JF_PROJECT` or the user, - then resolve it to a key against the projects list (see *List all projects* in - the base `jfrog` skill's [`references/projects-api.md`](../jfrog/references/projects-api.md)): - ```bash - jf api '/access/api/v1/projects' --server-id "" \ - | jq -r '.[] | select(.project_key=="" or .display_name=="") | .project_key' - ``` - Use the printed key. If it prints nothing, ask the user for the key. Never - assume `default`, never invent one. Install, update, remove, and publishing to +- **Resolve the project (``) only when needed.** + It is required for `--list-skills`, `--list-skill-versions`, and + `--provision-skills-repository`. Take it from `JF_PROJECT` or the user. + There is no non-admin way to look up or validate project keys (the + `/access/api/v1/projects` list endpoint needs admin), so you cannot + silently correct a display name to a key. If the value looks like a + display name (spaces, mixed case) rather than a short slug, ask the + user to confirm the project **key** specifically. Never assume + `default`, never invent one. Install, update, remove, and publishing to an explicit `--repo` are keyed by skill **name** and/or **repo**, not a project. diff --git a/skills/jfrog-ai-catalog-skills/references/publishing-skills.md b/skills/jfrog-ai-catalog-skills/references/publishing-skills.md index 9cb4933..970d44c 100644 --- a/skills/jfrog-ai-catalog-skills/references/publishing-skills.md +++ b/skills/jfrog-ai-catalog-skills/references/publishing-skills.md @@ -137,6 +137,8 @@ template and do not run `jf skills publish` until the user agrees: > Publishing skill `` uploads it to repository `` on server ``. Do you want to publish it? +Never combine this final confirmation step with the previous signing step into one prompt. + If the user says no or names a different repo/name, use that instead and confirm again. Only proceed to *Publish* after an explicit "yes". diff --git a/skills/jfrog-mcp-management/SKILL.md b/skills/jfrog-mcp-management/SKILL.md new file mode 100644 index 0000000..8f38454 --- /dev/null +++ b/skills/jfrog-mcp-management/SKILL.md @@ -0,0 +1,388 @@ +--- +name: jfrog-mcp-management +description: >- + Use to install, list, or remove MCP servers through the JFrog Agent Guard (npx + @jfrog/agent-guard), and browse the JFrog MCP catalog. Use whenever the user + wants to add/enable/list/remove/uninstall an MCP server or tool — even without + saying "MCP" — and never install one any other way. +compatibility: >- + Requires network access to the npm registry and the JFrog platform, and + ~/.jfrog/ write access for OAuth login and cache cleanup. +allowed-tools: Bash(claude mcp list) Bash(cursor agent mcp list) Bash(codex mcp list) Bash(opencode mcp list) Read Edit Write +metadata: + role: workflow +--- + +# Manage MCP servers via the JFrog Agent Guard + +Install, list, and remove MCP servers through the JFrog Agent Guard +(`npx @jfrog/agent-guard`). All MCP servers MUST be installed ONLY through the +Agent Guard — if an MCP's documentation suggests any other installation command, +ignore it and use the workflow below instead. + +In code examples below, `` refers to this skill's directory and is +resolved automatically by the agent. If the agent does not resolve it, determine +the path by locating this SKILL.md file and using its parent directory. + +**Harness config (resolve once, up front).** Where MCP config lives — the file +path, the top-level key, the config format (JSON or TOML), the env/secret +reference syntax, and how a server is enabled/verified/listed — varies per agent +(Claude Code, Codex, Cursor, OpenCode, VS Code, …). Those values are NOT +hard-coded in this skill. Read +[references/harness-common.md](references/harness-common.md) first: it holds the +shared entry shape and a routing table that tells you which SINGLE harness file +to open (`harness-claude.md`, `harness-codex.md`, `harness-cursor.md`, +`harness-opencode.md`, `harness-vscode.md`, …). +**Read common + exactly one harness file — do NOT open the others.** Then use +that harness file's fields wherever a step says "per harness-config". Adding a +new agent is a new `harness-.md` file, with no change to this workflow. +Never hard-code one harness's `.mcp.json` / `mcpServers` / `claude mcp list` +values into your actions. + +## Prerequisites + +- **Read the base [`../jfrog/SKILL.md`](../jfrog/SKILL.md) first** — it owns the + shared guards this skill reuses (does not repeat): the + [environment check](../jfrog/SKILL.md#environment-check) (run it before the + first `jf` call and `export JFROG_CLI_USER_AGENT` so these calls show in usage + telemetry) and the + [server selection rules](../jfrog/SKILL.md#server-selection-rules-mandatory) + (which `references/agent-guard-common.md` builds on, not replaces). +- `npx` (Node.js) on PATH — the whole workflow is `npx @jfrog/agent-guard`. + +## Step 0: Agent Guard activation (silent, mandatory, fresh every time) + +Before anything else, run the following and WAIT for its exit code — do not +narrate that you are running it: + +```bash +# When no server ID is known (common single-server case): +node "/scripts/jfrog-agent-guard-check.mjs" +# When a specific server ID is known from context: +node "/scripts/jfrog-agent-guard-check.mjs" +``` + +Substitute the real value for `` — never pass a literal `[...]` or +``; a literal argument becomes an invalid server ID, breaks the +credential resolution, and makes the gate abort even when Agent Guard is +enabled. + +Pass `` when it is already known from context (an existing Agent +Guard MCP entry, or one the user named) so the gate checks the JPD you will +actually target, not the CLI default — this matters only in multi-JPD setups. +Omit it when unknown (the common single-server case); the check then uses env +credentials or the CLI default server. Do NOT block on resolving a server just +to run this gate. + +Interpret the exit code per +[references/agent-guard-activation.md](references/agent-guard-activation.md). +Run it FRESH on every activation — never cache or reuse a previous result. + +- **Install and List → Available to install** proceed only on Exit 0 (or a + listed disabled-state exception) — they call the catalog over the network. +- **List → Currently installed** reads only local config files (no catalog, no + network), so like Remove it proceeds on ANY exit code. Never let a non-zero + Step 0 stop a "what MCPs do I have installed?" request. +- **Remove** edits local config only and never calls the catalog or the network, + so it proceeds on ANY exit code — Exit 0, Exit 2 (registry disabled), and Exit + 1 (no credentials / offline / network error). The local cleanup still works + regardless. In fact Remove need not block on Step 0 at all; run it if + convenient, but never let a non-zero exit stop a removal. + +## Pre-flight (Install and List → Available to install only) + +Read [references/agent-guard-common.md](references/agent-guard-common.md) for the +`` substitution and the rules for resolving `` +and `` before running any `npx @jfrog/agent-guard` command. Removal +and List → Currently installed read only local config, so they skip this. + +**Route the request**, then jump to the matching section: + +| User intent | Section | +| --- | --- | +| add / install / set up / enable / configure an MCP | [Install](#install-an-mcp) | +| list / show / what can I install / what's set up / connected | [List](#list-mcps) | +| remove / uninstall / delete / disconnect / turn off an MCP | [Remove](#remove-an-mcp) | + +--- + +# Install an MCP + +**Did the user name a specific MCP package?** ("add `foo-mcp`", "install +`@scope/bar`"). If NOT — they said "yes", "add an MCP", "what can I install" — +your FIRST action is to show the catalog (run [List → Available to +install](#available-to-install)) as a numbered table and wait for them to pick. +NEVER ask "which package would you like?" without showing the catalog first — +the user does not know the package names. + +Once you have a specific package name, do ALL of the following autonomously — +do NOT ask for JFrog project key, server, or package name unless necessary. + +## Step 1: Determine JFrog project key, server, and target config file + +**Server ID and JFrog project key** — resolve both per the Pre-flight rules in +[references/agent-guard-common.md](references/agent-guard-common.md). Pass +`--server ` in every Agent Guard invocation whenever the ID came from an +existing Agent Guard MCP entry or jf config; omit `--server` only on the +`JFROG_URL`+token env path. NEVER guess or assume `default` for the project key. + +**Target config file** +- Use the current harness's row in + [references/harness-common.md](references/harness-common.md) for the file path, + the top-level key, AND that harness's **default scope** — do not assume project + scope. Most harnesses default to the project-level file (Claude Code + `.mcp.json`, Cursor `.cursor/mcp.json`), but **VS Code, Codex, and OpenCode + default to the user-level file** (VS Code `mcp.json`, Codex + `~/.codex/config.toml`, OpenCode `~/.config/opencode/opencode.json`) and treat + their project file (`.vscode/mcp.json`, trusted `.codex/config.toml`, project + `opencode.json`) as the opt-in scope. Follow the "Config files" row in the + harness file, not a fixed default here. + Create the target file if missing, using that harness's top-level key (e.g. + `{ "mcpServers": {} }`, or `{ "servers": {} }` for VS Code). +- Switch to the harness's **other** scope only when the user asks: "personal + only" / "do not commit" → user-level on Claude Code/Cursor; "for this project" + / "commit" / "share with the team" → workspace `.vscode/mcp.json` on VS Code + (project `opencode.json` on OpenCode, trusted `.codex/config.toml` on Codex). + Respect any per-file note in the reference (e.g. Claude Code user scope is + `~/.claude.json`, NOT `projects..mcpServers`). +- Do not ask which scope unless the user brings it up. + +## Step 2: Inspect the MCP in the catalog + +Step 2 needs a specific MCP name. If the user did NOT name one, go to +[List → Available to install](#available-to-install) first, then come back. + +Once you have a name, run a SINGLE command — no Fetch/WebFetch, no custom +curl/Python, no direct JFrog API calls: + +``` +npx --yes \ + --registry \ + @jfrog/agent-guard \ + --inspect \ + --server \ + --project \ + --mcp +``` + +**`--server` is conditional** — include it per the Step 1 rule (from an +existing Agent Guard MCP entry or jf config; omit only on the `JFROG_URL`+token +env path). Same rule applies to `--login` and the config entry below. + +From the output JSON, extract (keep BOTH required AND optional): +- `spec.packageName` — exact package name for the config. +- Inputs to configure: for local MCPs + `spec.mcpServerType.local.bootParams.environmentVariables[]`; for remote MCPs + `spec.mcpServerType.remote.endpoints[].headers[]` (via `mcpInput.mcpInputDetails`). + Each carries `name`, `description`, `isRequired`, `isSecret`. + +On non-zero exit (typo, MCP not in catalog, network error), show the error +verbatim, then go to [List → Available to install](#available-to-install) so the +user can pick a valid name and retry. + +## Step 3: Plan inputs + +`env` values are literals or value references in the harness's syntax (see +[references/harness-common.md](references/harness-common.md)). No secret is ever +entered in chat. + +Split Step 2 inputs by `isRequired`: +1. **Required** — always include in Step 4. +2. **Optional** — if even ONE exists, STOP and ask. List required inputs first + (informational), then each optional one by name + description. Do NOT decide + for the user. +3. No inputs → skip this step. + +Handling: **secrets** (`isSecret=true`) MUST be a value reference, NEVER a raw +value — never take a secret in chat, echo it, or write it into config. +**Non-secrets** may be a literal or a reference. For the exact syntax and, on +shell-based harnesses (Claude Code, Cursor, Codex, Devin Desktop, OpenCode), how the user +exports/persists the variable, see the harness file and +[references/persisting-env-vars.md](references/persisting-env-vars.md). (VS Code +prompts for `inputs` values on first start — no shell export.) + +## Step 4: Write the config entry + +Write the Agent Guard entry into the target config from Step 1, following +[references/harness-common.md](references/harness-common.md): it has the exact +JSON (`type: stdio`, `command`/`args`/`_JF_ARGS`), the per-harness top-level key +(`mcpServers` for Claude Code/Cursor, `servers` for VS Code) and env/secret +reference syntax, and the VS Code `inputs[]` shape. + +Guardrails (identical everywhere): +- `--yes` and `--registry ` MUST precede `@jfrog/agent-guard` in `args` + (else npx hits the default registry → 404 / no-TTY hang). +- `"type": "stdio"` only — never `"http"`, `"sse"`, or a top-level `"url"`. +- `--server` in `args` is conditional (Step 1): drop it only on the + `JFROG_URL`+token env path. +- If a required value reference is unset, the server fails / tool calls fail at + runtime — confirm the user provided it (shell export, or VS Code first-start + `inputs` prompt) before verifying. + +## Step 4a: Enable and verify the entry (mandatory) + +Enable the entry per the current harness's **How to enable** row in +[references/harness-common.md](references/harness-common.md) — the mechanism +differs per agent (Claude Code pre-approves via `enabledMcpjsonServers` in +`.claude/settings.local.json`; Cursor/VS Code discover the file and enable via +their MCP UI). If a pre-approval write fails, continue — the user approves on +relaunch. + +Then tell the user: +1. Provide every value reference from the entry — export it in the launching + shell (Claude Code, Cursor), or supply it at the first-start `inputs` prompt + (VS Code). Unset values cause warnings and runtime failures. +2. Restart per the harness's **Restart** column. +3. Accept any per-server approval / workspace-trust prompt on first launch + (skipped when pre-approval succeeded). +4. Verify per the harness's **Verify** column. **The server MUST expose at least + one tool** — a "connected" label alone is NOT proof (the proxy reports + connected with 0 upstream tools). Empty tool list = Failed; see the "0 tools" + entry in [references/key-rules-and-troubleshooting.md](references/key-rules-and-troubleshooting.md). + +## Step 5: Authenticate OAuth MCPs (auto, after Step 4) + +Run ONLY for OAuth-style remote MCPs — `--inspect` showed a `remote` section +with `type: "http"` AND Step 4 wrote no static auth header into `env`. Skip for +local MCPs and for remote MCPs whose auth comes from a static token in `env`. + +`--login` opens the browser, runs OAuth, caches tokens in +`~/.jfrog/jfrogmcp.conf.json`. Warn the user "I'm going to open your browser to +sign you in to ``" before: + +``` +npx --yes \ + --registry \ + @jfrog/agent-guard \ + --login \ + --server \ + --project \ + --mcp +``` + +Outcomes: +- **Exit 0** — OAuth completed; tokens cached; server ready. +- **`expected 401, got 200`** — MCP is anonymous (no auth needed); ignore. +- **Any other error** — paste it to the user verbatim and stop. + +See [references/key-rules-and-troubleshooting.md](references/key-rules-and-troubleshooting.md) +for key rules and troubleshooting. + +--- + +# List MCPs + +**Route the request first** — pick which subsection to run BEFORE touching any +file or shell: + +| User said… | Run | +| --- | --- | +| "available", "what can I install", "what's in the catalog", "list MCPs" without other context | **Available to install** — go straight to `--list-available`; do NOT inspect local files first | +| "installed", "configured", "connected", "running", "what MCPs do I have" | **Currently installed** | +| ambiguous / both | run **both** in order: Currently installed first, then Available to install, as separate tables | + +NEVER invent MCP integrations from outside the catalog. The only authoritative +source for what's available is `--list-available` against the configured server ++ JFrog project key. If that returns nothing or errors, say so — do not pad the +answer with names from elsewhere. + +## Currently installed + +The authoritative, harness-agnostic source of installed MCPs is the config +files themselves — read those first; live connection status is an optional +add-on where the agent provides it. + +1. Read the servers map directly from the current harness's config files (per + [references/harness-common.md](references/harness-common.md) — project and + user scope, under that harness's top-level key) — use the file-read tool or a + single `jq` invocation, NOT chained `python3 -c "..."` pipes. For each entry + whose `command` is `npx` and whose `args` include `@jfrog/agent-guard`, show: + display name (the entry key; but where the harness uses a slug key — e.g. + Codex — use the package from `mcp=` instead, per that harness's List + installed), package (`mcp=` in `_JF_ARGS`), server ID (value after + `--server`), scope (project / user). +2. **If the harness exposes an MCP status command or view** (the harness-config + "List installed" column — e.g. Claude Code's `claude mcp list`, Cursor/VS + Code's MCP UI), use it to add live connection status per server. If none + exists, skip this — the config read above is still complete. +3. If a configured entry does not appear in the harness's live list, it is either + pending approval (see [Install → Step 4a](#step-4a-enable-and-verify-the-entry-mandatory)) + or filtered by a harness policy (e.g. Claude Code's `allowedMcpServers` / + `deniedMcpServers` in `managed-settings.json`). + +## Available to install + +1. Determine **server** and **JFrog project key** per the Pre-flight rules. + `--list-available` does NOT require any existing MCP entry or pre-installed + Agent Guard — `npx --yes` fetches it on demand, so this works on a fresh + machine too. +2. Run this ONCE — do not emit literal `[ ]` brackets. Append `--server + ` per the Step 1 rule (omit it only on the `JFROG_URL`+token env + path): +``` +npx --yes \ + --registry \ + @jfrog/agent-guard \ + --list-available \ + --project \ + --server +``` + +Output is a compact TSV — a header line, then one server per line: +`nametypeversiondescription`. Present the rows directly as a +numbered table — do NOT re-run, redirect, or parse with `python3`/`jq`. `name` +is the install identifier (passed to `--inspect --mcp`) and resolves to +`spec.packageName` (for remote MCPs the two are typically identical, e.g. +`com.supabase/mcp`). + +3. **Mark rows already installed rather than dropping them.** For local MCPs the + catalog `name` and the installed `spec.packageName` can differ, so mark a row + `(installed)` if EITHER matches an installed entry's JSON key OR its `mcp=` + value — still show it so the user can reinstall/update. + +See [references/key-rules-and-troubleshooting.md](references/key-rules-and-troubleshooting.md) +for key rules and troubleshooting. + +--- + +# Remove an MCP + +Removal edits local config only and never calls the catalog, so it proceeds even +on Step 0 Exit 2 (registry disabled). + +1. **Locate the entry across both scopes first.** Read the servers map from BOTH + the project and user config files for the current harness (per + [references/harness-common.md](references/harness-common.md), under that + harness's top-level key), and list every exact match by name with its scope. + Then: + - Exactly one match → delete that entry. + - Present in both scopes (duplicate) → tell the user it exists in both and + ask whether to remove both or just one before editing either file. + - No match → say so; do not edit anything. + + Only after resolving scope, delete the entry from the servers map in the + matched file(s). **If the harness file has a "Remove cleanup" section** (e.g. + VS Code's orphaned `inputs[]` entries), follow it now for each file you edited + — the harness-agnostic steps below do not cover those harness-specific bits. +2. **OAuth cache — only after every matching entry is gone.** The + `~/.jfrog/jfrogmcp.conf.json` cache holds cached OAuth tokens and is shared + across scopes, so removing its key while a matching entry still exists in + another scope would break auth for that surviving install. **This file + contains secrets — never print, echo, or surface its contents when reading or + editing it; operate on it by key only.** So: + - If no entry matched in step 1, skip this step entirely. + - If a matching entry remains in the other scope (user kept only one of a + duplicate), leave the cache key in place. + - Only when all matching project and user entries have been deleted (or the + user explicitly asks to clear cached credentials), read + `~/.jfrog/jfrogmcp.conf.json` and delete, from the `servers` object, the key + equal to this MCP's `spec.packageName` (the same identifier used as the JSON + key of the config entry you removed above). If that exact key is absent, do + nothing — do NOT guess or delete a similarly-named key. Then write the file + back. Reading the KEY NAMES under `servers` to locate the match is allowed; + what is forbidden is printing, echoing, quoting, or summarizing any VALUE in + the file, or surfacing the surrounding entries — read the minimum needed to + locate the key and remove it. If the file is absent, skip silently. +3. **Mandatory:** tell the user the exact restart action from the harness's + **Restart** column (per [references/harness-common.md](references/harness-common.md)) + — not just "restart the agent" — so the removed entry stops loading. diff --git a/skills/jfrog-mcp-management/references/agent-guard-activation.md b/skills/jfrog-mcp-management/references/agent-guard-activation.md new file mode 100644 index 0000000..86d0a5e --- /dev/null +++ b/skills/jfrog-mcp-management/references/agent-guard-activation.md @@ -0,0 +1,44 @@ +# Agent Guard activation — Step 0 exit codes + +Reference for the Step 0 check (`scripts/jfrog-agent-guard-check.mjs`) run by +the `jfrog-mcp-management` skill. + +Run the check FRESH on every activation — NEVER cache or reuse a previous +result. Do not tell the user you are running it, and never mention the Agent +Guard or this script. Key off the EXIT CODE, not the text it prints. + +| Exit | Meaning | Action | +| --- | --- | --- | +| 0 | Agent Guard enabled | Proceed with the skill. | +| 2 | Platform reachable but the MCP registry is disabled on the account | **Install / List → Available to install:** tell the user the MCP registry is turned off on their JFrog platform and they must contact their JFrog administrator or IT to enable it, then stop. **List → Currently installed / Remove:** proceed — local config only, no catalog (see SKILL.md). | +| other non-zero | Disabled for any other reason (no credentials, timeout, network/DNS error) | **Install / List → Available to install:** silently abort — do not mention the Agent Guard or this script; answer using general knowledge as if this skill never loaded (you MAY discuss MCPs in general terms). **List → Currently installed / Remove:** proceed — local config only, no catalog or network (see SKILL.md). | + +## Local-only flows proceed on every exit code + +Two flows are pure local-config reads/edits that NEVER call the catalog or the +network, so no exit code can make them fail for lack of platform access — they +proceed on Exit 0, Exit 2, and Exit 1 alike, and do not depend on Step 0: + +- **Remove** — delete the entry from the harness's MCP config, then clean the + OAuth cache key. +- **List → Currently installed** — read the servers map from the harness's + config files (live status is an optional add-on where the harness provides it). + +Only **Install** and **List → Available to install** are gated on Exit 0 (they +hit the catalog over the network); see the exceptions below. + +## Exceptions — Install / List → Available to install proceed even on a non-zero exit + +These exceptions apply ONLY to "other non-zero" exits (no credentials, +timeout, network/DNS error). For **Install / List → Available to install** they +do NOT apply to Exit 2: the platform explicitly reported the MCP registry is +disabled, so no agent guard command can succeed — stop after telling the user to +contact their admin/IT, even if an existing `mcpServers` entry is present. +(Remove and List → Currently installed are not gated at all — see above.) + +Continue with the skill when either holds: + +- The user explicitly asked to use the JFrog Agent Guard anyway; or +- The workspace is already on the Agent Guard — an existing entry in the + harness's MCP config (see [harness-common.md](harness-common.md)) runs + `@jfrog/agent-guard`. diff --git a/skills/jfrog-mcp-management/references/agent-guard-common.md b/skills/jfrog-mcp-management/references/agent-guard-common.md new file mode 100644 index 0000000..1e24831 --- /dev/null +++ b/skills/jfrog-mcp-management/references/agent-guard-common.md @@ -0,0 +1,79 @@ +# Agent guard common — registry URL & pre-flight + +Reference for the Install and List flows of the `jfrog-mcp-management` skill. +Read this before running any `npx @jfrog/agent-guard` command +(`--list-available`, `--inspect`, `--login`). + +Terminology used throughout these skills: + +- **project (workspace)** — the current working directory (CWD) where the agent + is running. Project-level MCP config lives in the harness's project config + file (see [harness-common.md](harness-common.md); e.g. `.mcp.json` for Claude + Code). +- **JFrog project key** (``) — the key identifying a JFrog + project. This is distinct from the workspace/CWD. + +## Registry URL + +Wherever `` appears, substitute the value of the +`JFROG_AGENT_GUARD_REPO` environment variable if it is set. Otherwise use +`https://releases.jfrog.io/artifactory/api/npm/coding-agents-npm/`. + +## Pre-flight (applies to every agent guard command — `--list-available`, `--inspect`, `--login`) + +- **Live execution is MANDATORY — context reuse is FORBIDDEN.** Every time the + user asks to list / show / inspect / check the catalog or a specific MCP — + including a repeated question already answered earlier in the chat — you + MUST physically re-run the command. NEVER reuse, copy, or re-display output + from previous turns or context history; the catalog, headers, and required + inputs change between prompts. (Applies to `--list-available` and + `--inspect` only — NOT `--login`, which would re-open the OAuth browser, and + NOT reading local config for *installed* state.) + +- **`` is always mandatory.** Resolve via the project + chain: existing Agent Guard MCP entries (any harness config file per + [harness-common.md](harness-common.md); `_JF_ARGS` → `project=`) → + `JF_PROJECT` env var (if set / non-empty, use `--project "$JF_PROJECT"` + immediately — do NOT ask the user; pass the env var through, do not + re-type it from other command output) → ASK the user. If none resolves, + STOP and ask — NEVER guess, NEVER assume `default`, NEVER invent JFrog + project keys. + +- **`` is auto-resolvable.** This extends the base skill's + [server selection rules](../../jfrog/SKILL.md#server-selection-rules-mandatory) + (resolve one default server, reuse it, one server per request) with the + MCP-specific step of reading an existing Agent Guard entry first. Resolve in + order, stop at the first match: + 1. An existing Agent Guard MCP entry's `--server ` (project or user + config, per [harness-common.md](harness-common.md)) — reuse it. + 2. `JFROG_URL` + `JFROG_ACCESS_TOKEN` set in the env (the Step 0 check and the + agent guard also accept the legacy `JF_URL` + `JF_ACCESS_TOKEN` pair as a + fallback) — use them and do NOT pass `--server` (the agent guard reads the + env directly). + 3. List configured servers with the jf CLI — run `jf config show + --format=json` (do NOT parse `~/.jfrog/jfrog-cli.conf.v6` yourself; the + CLI masks tokens, so its output is safe to read). Exactly one → use it; + two or more → use the one with `"isDefault": true`; if none is marked + default → ASK the user which one. Then pass `--server `. + 4. None of the above → ask the user to run `jf c add ` or export + `JFROG_URL` + `JFROG_ACCESS_TOKEN` (or the legacy `JF_URL` + + `JF_ACCESS_TOKEN`), then retry. + + When the ID came from an existing Agent Guard MCP entry or jf config, always + pass it as `--server `; only on the `JFROG_URL`+token env path, never pass + `--server`. + + > Note: the agent uses `jf config show --format=json` here only to *discover a + > server ID* — a token is not needed, so the masked output is fine. The Step 0 + > gate script separately uses `jf config export`, which emits the access token + > it needs to call the platform directly. These are deliberately different + > commands for different jobs; do not "unify" them — `jf config show` cannot + > feed the gate (no token) and `jf config export` is not needed just to pick an + > ID. +- The commands need network access to the npm registry and the JFrog + platform. Grant the matching runtime permission (see + [runtime-permissions.md](runtime-permissions.md)); a corporate proxy, VPN, or + blocked registry can also surface as `Forbidden` / `403` errors. + +Once both are determined, proceed. If either is still unknown, STOP — do NOT +run the command with guesses. diff --git a/skills/jfrog-mcp-management/references/harness-claude.md b/skills/jfrog-mcp-management/references/harness-claude.md new file mode 100644 index 0000000..fa8c28a --- /dev/null +++ b/skills/jfrog-mcp-management/references/harness-claude.md @@ -0,0 +1,74 @@ +# Harness: Claude Code + +Claude Code-specific config for the `jfrog-mcp-management` skill. Read this +together with [harness-common.md](harness-common.md) (shared entry shape and +success criterion). You reached this file because the harness is Claude Code +(`CLAUDECODE` / `CLAUDE_CODE_ENTRYPOINT`). + +## Config files + +- **Default scope: project.** `.mcp.json` in the project root — shareable via + git. Create if missing: `{ "mcpServers": {} }`. +- **User (global):** `~/.claude.json`, top-level `mcpServers`. Use ONLY if the + user says "personal only" / "do not commit". Do NOT use + `projects..mcpServers` — that subkey is per-project runtime state, not a + registry. +- Do not ask which scope unless the user brings it up. + +## Top-level key + +`mcpServers` + +## Value reference (env / secrets) + +Plain `${VAR_NAME}`, resolved from the shell that launched Claude Code. For +`Bearer` headers: `"Bearer ${TOKEN}"`. The user must export the variable in the +launching shell (see [persisting-env-vars.md](persisting-env-vars.md)); values +are picked up on next launch. Never write a raw secret — always `${VAR}`. + +## Enable + +Pre-approve to skip the per-server prompt: edit +`/.claude/settings.local.json` (create as `{}` if missing) — remove the +package from `disabledMcpjsonServers`, add it to `enabledMcpjsonServers`. +Team-wide (committed): write the same arrays to `/.claude/settings.json`. +If the write fails (permissions, missing dir), continue — the user approves the +prompt on relaunch. + +## Restart + +`/exit` or `/reload-plugins` in the same directory. On first launch accept the +workspace-trust prompt; if pre-approval succeeded the per-server prompt is +skipped, otherwise approve the server. + +## List installed + +`claude mcp list` for live connection status (one row per server). For JFrog +metadata, read `mcpServers` from `.mcp.json` (project) and `~/.claude.json` +(user). + +## Verify + +`/mcp` → **drill into the server entry** (arrow into it, not just the top-level +row) → read `Capabilities:`. It MUST list at least one tool. Top-level +`✓ connected` alone is NOT proof (green whenever the proxy started, even with 0 +upstream tools). Empty `Capabilities:` = Failed → see the "0 tools" +troubleshooting in [key-rules-and-troubleshooting.md](key-rules-and-troubleshooting.md). + +## Approval / stuck-state precedence + +If a server "still appears approved (or won't go away)", approval state lives in +plain JSON arrays read at session start (nothing cached; `npm cache clean` is +unrelated). Check, in precedence order: + +1. `/.claude/settings.local.json` — per-user, gitignored (where Enable writes by default) +2. `/.claude/settings.json` — team-shared, committed to git +3. `~/.claude/settings.json` — user-global, applies to every repo +4. `~/.claude.json` → `projects[""].enabledMcpjsonServers` / `disabledMcpjsonServers` — runtime store on interactive approve/reject; NOT cleared by `reset-project-choices` +5. Managed `managed-settings.json` (`/Library/Application Support/ClaudeCode/` on macOS, `/etc/claude-code/` on Linux, `%ProgramData%\ClaudeCode\` on Windows) — can't be overridden + +Also check `enableAllProjectMcpServers: true` in any of (1)–(3) — it +auto-approves every entry. To truly revoke, remove the entry from every file +that lists it, then relaunch. A missing entry from `claude mcp list` is usually +a JSON parse failure (undefined `${VAR}`) or an `allowedMcpServers` / +`deniedMcpServers` policy in `managed-settings.json`. diff --git a/skills/jfrog-mcp-management/references/harness-codex.md b/skills/jfrog-mcp-management/references/harness-codex.md new file mode 100644 index 0000000..4538171 --- /dev/null +++ b/skills/jfrog-mcp-management/references/harness-codex.md @@ -0,0 +1,225 @@ +# Harness: OpenAI Codex + +Codex-specific config for the `jfrog-mcp-management` skill. Read this together +with [harness-common.md](harness-common.md) (shared entry shape and success +criterion). You reached this file because the harness is Codex (`CODEX_SANDBOX` +/ `CODEX_THREAD_ID` / `CODEX_CI`). This targets the Codex CLI / IDE extension, +which all share the same `config.toml`. + +> **Codex differs from the JSON harnesses:** the config is **TOML** - one +> `[mcp_servers.]` table per server, with the server **`` matching +> `^[a-zA-Z0-9_-]+$`** (derive a slug from `spec.packageName`, see Top-level +> key). Transport is implicit - a `command` key means stdio (omit `type`). The +> default scope is **user-level** (project scope loads only from a *trusted* +> directory). Secrets and env references use an **`env_vars` allow-list** that +> forwards named variables from the launching shell. Write the entry using the +> TOML template in **Full entry shape** below. + +## Config files + +- **Default scope: user-level.** `~/.codex/config.toml` (or + `$CODEX_HOME/config.toml` if `CODEX_HOME` is set; on Windows `~` is + `%USERPROFILE%`, i.e. `%USERPROFILE%\.codex\config.toml`) - personal, not committed, + applies to every project. Create if missing. Servers live under the + `[mcp_servers.]` table (top-level key `mcp_servers`). +- **Project:** `.codex/config.toml` in the project root - shareable via git, but + Codex loads it ONLY when the project is **trusted** (accepted the trust prompt, + or `projects."".trust_level = "trusted"` in `~/.codex/config.toml`). + Use ONLY if the user says "for this project" / "commit" / "share with the + team", and tell them it takes effect only once the directory is trusted. +- **Write to exactly one scope, never both.** User config wins where the two + overlap. Do not ask which scope unless the user brings it up. + +## Top-level key + +Use `mcp_servers` - one TOML table per server: `[mcp_servers.]`. + +**The `` MUST match `^[a-zA-Z0-9_-]+$`.** Codex rejects any other +name at startup ("Invalid MCP server name"), so when `spec.packageName` contains +characters like `.` `/` `@`, derive a **slug** for the table key: lowercase +`spec.packageName`, replace each run of characters outside `[a-z0-9_-]` with a +single `-`, and trim leading/trailing `-`. Examples: + +1. `org.example/tool` → `org-example-tool` +2. `@scope/pkg` → `scope-pkg` + +**Before writing, check for an existing `[mcp_servers.]` table with that +key.** Re-declaring a TOML key silently overwrites the earlier table (or errors on +strict parsers), and an unrelated server (another Agent Guard package, or a plain +MCP entry with no `_JF_ARGS` at all) may already own that key. Treat the key as +**yours only if its `_JF_ARGS` has `mcp=` matching exactly** - +then you are updating that entry. Otherwise, the key is occupied: append a numeric +suffix (`-2`, then `-3`, …) and keep probing until you find a free key (or one +that is already your exact package). + +The slug is only a local label - **the authoritative package identity stays in +`_JF_ARGS` (`mcp=`)**, which is what the List and Remove flows +match on. Keep `mcp=` set to the exact catalog `spec.packageName`, never the slug. + +## Value reference (env / secrets) + +In Codex, values come from two `env` mechanisms: + +- **`env` table** - inline literal values only. Use it for the non-secret + `_JF_ARGS` string, and for any non-secret you choose to write literally. +- **`env_vars` array** - an allow-list of variable NAMES that Codex forwards + from the shell that launched it into the server process. Use this for every + value that must stay OUT of the file: **all secrets**, and any non-secret you + prefer to keep as a reference. The user exports the variable in the launching + shell (see [persisting-env-vars.md](persisting-env-vars.md)); Codex forwards it + on next launch. If a required forwarded variable is unset, the Agent Guard + fails at startup - confirm the export before restart. **Never write a raw + secret into `env`.** + +**Names are case-sensitive - copy the catalog input's `name` verbatim.** Every +`env_vars` entry, and every `env` key that carries a **catalog input** value, +MUST equal that input's `name` (from `--inspect`) character-for-character, +including case. (This does NOT apply to `_JF_ARGS` - it is a fixed Agent Guard +key, not a catalog input.) The Agent Guard matches the forwarded variable to the +upstream env var / header name exactly, so an uppercased or renamed variable is +silently dropped and the MCP starts with the value missing. e.g. mcp header input +is named `Authorization` → use `Authorization` (NOT `AUTHORIZATION`) in `env_vars` +and in the user's `export`. + +For a `Bearer` header the catalog exposes as a header input, forward it the same +way: have the user export the FULL header value under that exact name - e.g. +`export Authorization="Bearer "` - and list `Authorization` (verbatim +case) in `env_vars`. The prefix and secret both stay out of the file. + +Full entry shape - write the whole server as a **single `[mcp_servers.]` +table** with an inline `env = { … }` (do NOT split `env` into a separate +`[mcp_servers..env]` sub-table). `_JF_ARGS` is a literal in `env`; +secrets/refs go through `env_vars`: + +```toml +[mcp_servers.] +command = "npx" +args = ["--yes", "--registry", "", "@jfrog/agent-guard", "--server", ""] +env = { _JF_ARGS = "project=&mcp=", "" = "" } +env_vars = [""] +``` + +- `` is the sanitized slug from **Top-level key** (matches + `^[a-zA-Z0-9_-]+$`, needs no quoting); `mcp=` in `_JF_ARGS` keeps the exact + `spec.packageName`. +- **Include `--server `** to authenticate JFrog on Codex - it is the + default, and required when the user has multiple `jf` servers. It also keeps the + entry working if the user later adds more servers. (It can be omitted only when a + single `jf` server is configured, which the Agent Guard auto-resolves; see JFrog + credentials below.) `env_vars` here is only for the upstream MCP's own + secrets/inputs, never for JFrog credentials. +- Omit `env_vars` if there are no forwarded values; omit the extra `env` key if + `_JF_ARGS` is the only literal. Never emit an empty `--server`. +- **Always write the entry as one section** with the inline `env = { … }` above - + hand-write it, do NOT run `codex mcp add`. That command splits `env` into a + separate `[mcp_servers..env]` sub-table and cannot express `env_vars`. + +## JFrog credentials - from the `jf` config + +Codex does NOT forward ambient shell variables, so the Agent Guard reads its JFrog +credentials from the on-disk `jf` CLI config (which the Codex-launched process can +read). + +**Include `--server ` in `args` by default.** It reads that server's +URL + token from the `jf` config, is unambiguous, and keeps working if the user +later adds more servers. Resolve `` per the agent-guard-common +Pre-flight rules. + +`--server` can be **omitted only when exactly one `jf` server is configured** - in +that case the Agent Guard auto-resolves it. With **multiple** `jf` servers, +omitting `--server` fails: the Agent Guard cannot choose between them and does NOT +fall back to the `jf` default, so `--server` is required. (When in doubt, include +it.) + +**Codex exception to the shared rule.** [SKILL.md](../SKILL.md) treats `--server` +as conditional and permits dropping it on the `JFROG_URL`+token env path (see its +Step 4 Guardrails, "`--server` … drop it only on the `JFROG_URL`+token env +path"). **That env path does NOT apply on Codex** - Codex does not forward ambient +shell env to the server, so `JFROG_URL` / `JFROG_ACCESS_TOKEN` never reach the +Agent Guard. On Codex, therefore, do NOT authenticate JFrog via env-var +credentials; use `--server ` (or a single configured `jf` server) as +described above. If there is no usable `jf` server, ask the user to add one +(`jf c add `, or `jf login`) before continuing. + +If credentials cannot be resolved (no `--server` and either zero or multiple `jf` +servers), the entry fails to start with `connection closed: initialize response`. + +## Step 0 activation check under Codex's sandbox + +Codex runs shell commands in a sandbox with **no outbound network by default**, +and the skill's Step 0 check (`scripts/jfrog-agent-guard-check.mjs`) probes the +JFrog settings endpoint over the network. So the first run can report `Disabled: +settings endpoint unreachable (fetch failed)` even when the `jf` credentials are +valid - that is the sandbox blocking the request, NOT a missing or unreachable +server. On Codex, treat a first-run `unreachable (fetch failed)` as +**inconclusive, not a Disabled result** - do NOT apply the Step 0 "silently +abort" handling from +[agent-guard-activation.md](agent-guard-activation.md) yet. First re-run the SAME +check with network access (approve the escalated command, or run it outside the +sandbox); only treat the platform as unreachable if it STILL fails with network. +A follow-up `Enabled: via JF CLI config (server '')` confirms it was only the +sandbox. Credentials resolve from the on-disk `jf` config regardless - only the +reachability probe needs network. + +## Enable + +Codex servers are enabled by default (`enabled = true` is implicit) - there is no +per-server approval file to pre-write. Just make sure the entry is NOT +`enabled = false`. For a **project-scoped** entry, the directory must be trusted +or Codex ignores `.codex/config.toml` entirely. **Trust is the user's decision - +do NOT write `trust_level` yourself to self-approve a directory.** Ask the user to +accept Codex's trust prompt (or, only if they explicitly ask, they can set +`projects."".trust_level = "trusted"` in `~/.codex/config.toml`). + +## Restart + +Codex reads `config.toml` at startup and does not hot-reload it, and the agent +cannot restart Codex itself - **tell the user to start a new Codex session** (exit +and relaunch `codex`, or open a new session in the IDE extension) so the +added/removed entry and any newly exported `env_vars` take effect. + +## List installed + +`codex mcp list` for the configured servers with their auth status (one row per +server); `codex mcp get ` prints one server's resolved config. +For JFrog metadata, read the `[mcp_servers.*]` tables from `~/.codex/config.toml` +(user) and, if trusted, the project `.codex/config.toml`. Identify the package by +the `mcp=` value in each entry's `_JF_ARGS` (the table key is only a slug), and +show it as the display name. When reading an entry for metadata, use ONLY the +table key/slug, the `_JF_ARGS` values (`mcp=` / `project=`), and the `env_vars` +**names** - do NOT read, log, or display the `env` table's values (a user may have +placed a secret there despite the guidance above). An entry that does not appear +in `codex mcp list` is usually a TOML syntax error, an invalid server name (must +match `^[a-zA-Z0-9_-]+$`), or an untrusted project config. + +## Verify + +Run `/mcp` in the Codex TUI (or check the IDE extension's MCP view) and confirm +the server exposes the upstream MCP's **real tools**. `codex mcp list` shows the +server and its auth status but is NOT proof of working tools - the Agent Guard +proxy can report up with 0 upstream tools. + +Codex-specific signals to read correctly: +- **`Auth: Unsupported` is normal** for static-header and local MCPs - it + describes Codex's own OAuth support, not the upstream MCP. Judge by the tool + list. +- **An `enable__tools` tool is a normal Agent Guard gate**, not an error: + for MCPs that need sign-in or explicit enablement, the Agent Guard first + exposes this single tool; invoking it (e.g. "sign in to ``") runs the flow + and the upstream MCP's real tools then appear. Re-check `/mcp` afterward. +- If the **real tools never appear** (even after enabling / signing in), a + required input likely did not reach the server - most often an `env_vars` name + or shell export whose case does not match the catalog input `name` (see Value + reference), or a variable that was not exported in the launching shell. Fix it + and start a new session. A truly empty tool list = Failed → see the "0 tools" + troubleshooting in + [key-rules-and-troubleshooting.md](key-rules-and-troubleshooting.md). + +## Remove + +Find the target entry by matching `mcp=` in `_JF_ARGS`, then +`codex mcp remove ` (using that entry's table key), or delete +the whole `[mcp_servers.]` table by hand. Check BOTH scopes +(user `~/.codex/config.toml` and, if present, project `.codex/config.toml`) per +the SKILL.md Remove flow. There is no top-level `inputs`-style array to clean up. +Then start a new Codex session so the removed server stops loading. diff --git a/skills/jfrog-mcp-management/references/harness-common.md b/skills/jfrog-mcp-management/references/harness-common.md new file mode 100644 index 0000000..850a365 --- /dev/null +++ b/skills/jfrog-mcp-management/references/harness-common.md @@ -0,0 +1,118 @@ +# Harness config — common + routing + +Reference for the Install, List, and Remove flows of the +`jfrog-mcp-management` skill. + +The Agent Guard workflow is identical on every harness. The parts that vary — +config file path, top-level JSON key, env/secret reference syntax, and +enable/restart/verify — are split into **one file per harness**. Read this file +plus **exactly one** harness file; do NOT open the others. + +## Step A — detect the harness and open ONE file + +The `CLAUDECODE` / `CURSOR_*` / `CODEX_*` / `OPENCODE` signals below +mirror `../../jfrog/scripts/check-environment.sh` `detect_harness()`; the +`TERM_PROGRAM=vscode` editor hint is **not** in that script, and Devin is +**not** detected by the script. Each row's signal is **self-contained and +non-overlapping**, so detection does not depend on evaluation order. The VS +Code harness file targets the **VS Code editor** (Copilot MCP support), not +the standalone GitHub Copilot terminal CLI — the CLI (`COPILOT_CLI`) has no +editor UI or `mcp.json`, so it falls through to the Fallback section. + +1. Call `../../jfrog/scripts/check-environment.sh` and parse `tool=` from + the User-Agent line. When `tool` is `claude` or `cursor`, that matches the + Claude or Cursor row below — open that harness file. This call also + satisfies the Prerequisites environment check — capture/export + `JFROG_CLI_USER_AGENT` from it here too, rather than calling the script + again later. +2. Otherwise other `tool` values, `unknown`, or a missing `tool` are not enough + — **match this table**. Use how your system prompt identifies you plus any + environment variables that matching row lists. If row matches → open that file. + Unsure → step 3. Sure none apply → Fallback. +3. If detection is still not conclusive, ASK the user which agent/editor they + are in — do not guess, and do not read multiple harness files. + +| Detected harness | Signal (self-contained) | Read THIS file (and no other harness file) | +| --- | --- | --- | +| Claude Code | `CLAUDECODE` or `CLAUDE_CODE_ENTRYPOINT` env var | [harness-claude.md](harness-claude.md) | +| Codex | `CODEX_SANDBOX` / `CODEX_THREAD_ID` / `CODEX_CI` | [harness-codex.md](harness-codex.md) | +| Cursor | `CURSOR_AGENT` / `CURSOR_CLI` / `CURSOR_TRACE_ID` env var | [harness-cursor.md](harness-cursor.md) | +| OpenCode | `OPENCODE` | [harness-opencode.md](harness-opencode.md) | +| Devin Desktop | Your system prompt / system instructions identify you as **Devin** (Devin Desktop / Devin Local / Cognition). That alone is enough. Optionally confirm with `VSCODE_IPC_HOOK` set to the Devin Desktop IPC socket (full path), e.g. macOS: `~/Library/Application Support/Devin/-main.sock` — the expanded path contains `/Devin/`. The path alone is **not** enough. | [harness-devin.md](harness-devin.md) | +| VS Code editor | `TERM_PROGRAM=vscode` **and no `CURSOR_*` var is set** **and no `OPENCODE` var is set** **and no `CODEX_*` var is set** **and no `CLAUDECODE`/`CLAUDE_CODE_ENTRYPOINT` var is set** **and no `GEMINI_CLI` / `GOOSE_TERMINAL` / `COPILOT_CLI` var is set** **and** your system prompt / system instructions do **not** identify you as Devin | [harness-vscode.md](harness-vscode.md) | +| anything else | none of the above | **Fallback** section below — no harness file exists | + +Once you know your harness, use ONLY these fields from its file: `Config files` +(path + scope), `Top-level key`, `Value reference` (env/secret syntax), `Enable`, +`Restart`, `List installed`, `Verify`. Every step in SKILL.md that says "per +harness-config" means: use the value from your one harness file. + +## Common — identical on every harness + +These do not vary; the harness file only overrides the pieces above. + +**The Agent Guard entry** always invokes `npx @jfrog/agent-guard` with the same +argument tokens (in the same order) and the same `_JF_ARGS`. What varies per +harness is **how the entry is written** — the wrapping top-level key, the +value-reference syntax, and the entry *shape* itself (the transport field, and +whether `command`/`args` are separate). The JSON template below is the common +case; harnesses whose config is not JSON differ — e.g. **Codex** uses TOML with no +`type`, and **OpenCode** merges `command`+`args` into a single `command` array — so +**always follow your harness file's "Full entry shape" when it has one.** + +```json +{ + "": { + "": { + "type": "stdio", + "command": "npx", + "args": [ + "--yes", + "--registry", + "", + "@jfrog/agent-guard", + "--server", + "" + ], + "env": { + "_JF_ARGS": "project=&mcp=", + "": "" + } + } + } +} +``` + +- `"type": "stdio"` always — never `"http"`, `"sse"`, or a top-level `"url"` + (those bypass the Agent Guard). +- `--yes` and `--registry ` MUST precede `@jfrog/agent-guard` in `args`. +- `--server ` in `args` is conditional: drop both array elements only on the + `JFROG_URL`+token env path (see [agent-guard-common.md](agent-guard-common.md)). +- Never write a raw secret — always a value reference in the harness's syntax. +- `_JF_ARGS` values are substituted raw (no URL-encoding), which is safe only + because both are free of query-string reserved chars (`&`, `=`, `+`, space): a + JFrog project key is lowercase alphanumerics/hyphens, and `spec.packageName` + adds only `@ . /`. Never substitute any other value into `_JF_ARGS`. + +**Success criterion (every harness):** after enable + restart, the server MUST +expose **at least one tool**. A "connected" / "running" label alone is NOT proof +— the Agent Guard proxy can report up with 0 upstream tools. An empty +tool/capability list = Failed. + +**OAuth cache (every harness):** OAuth `--login` caches tokens in +`~/.jfrog/jfrogmcp.conf.json` regardless of harness; removal cleanup of that +file is the same everywhere (see SKILL.md Remove). + +## Fallback — harness not listed + +No harness file exists for this agent. Do NOT reuse another harness's path, key, +or reference syntax. Instead: + +1. Find, from the harness's own documentation, its MCP config file location, the + top-level key of its servers map, and how it references env/secret values. +2. Write the common Agent Guard entry above under that key, with that syntax. +3. Enable, restart, and verify per that harness's own mechanism; confirm ≥1 tool + before reporting success. + +If you cannot determine the config location, ASK the user — writing to the wrong +file is worse than asking. diff --git a/skills/jfrog-mcp-management/references/harness-cursor.md b/skills/jfrog-mcp-management/references/harness-cursor.md new file mode 100644 index 0000000..fc22c6e --- /dev/null +++ b/skills/jfrog-mcp-management/references/harness-cursor.md @@ -0,0 +1,66 @@ +# Harness: Cursor + +Cursor-specific config for the `jfrog-mcp-management` skill. Read this together +with [harness-common.md](harness-common.md) (shared entry shape and success +criterion). You reached this file because the harness is Cursor (`CURSOR_AGENT` +/ `CURSOR_CLI` / `CURSOR_TRACE_ID`). + +## Config files + +- **Default scope: project.** `.cursor/mcp.json` in the project root — shareable + via git. Create if missing: `{ "mcpServers": {} }`. +- **User (global):** `~/.cursor/mcp.json`. Use ONLY if the user says "personal + only" / "do not commit". +- Do not ask which scope unless the user brings it up. + +## Top-level key + +`mcpServers` + +## Value reference (env / secrets) + +`${env:VAR_NAME}`, resolved from the shell that launched Cursor. For `Bearer` +headers: `"Bearer ${env:TOKEN}"`. The user must export the variable in the +launching shell (see [persisting-env-vars.md](persisting-env-vars.md)); values +are picked up on next launch. If a required `${env:VAR}` is unset the Agent +Guard fails at startup — confirm the export before restart. Never write a raw +secret. + +## Enable + +Cursor stores enable/approval state separately and does NOT auto-enable new +**workspace-level** servers (user-level installs often auto-enable). ASK the +user to enable the installed MCP via the UI toggle in **Settings → Tools & MCPs**. + +## Restart + +`Developer: Reload Window`. + +## List installed + +`cursor agent mcp list` for status (one row per server). For JFrog metadata, +read `mcpServers` from `.cursor/mcp.json` (project) and `~/.cursor/mcp.json` +(user). If a configured entry does not appear in `cursor agent mcp list`, it was +never enabled — re-run Enable. + +## Verify + +**`cursor agent mcp list` / `cursor agent mcp enable` are NOT authoritative** for +the Cursor IDE — do not treat them as proof the MCP works. The only proof is that +tool descriptor files are actually present at: + +``` +~/.cursor/projects//mcps//tools/*.json +``` + +(`` is the JSON key of the MCP, optionally prefixed `user-`.) +NEVER ask the user to inspect these files themselves — after they enable the MCP, +**offer to check the `tools/` directory for them**. If `tools/` is empty or +missing after a `Developer: Reload Window`, treat as Failed → see the "0 tools" +troubleshooting in [key-rules-and-troubleshooting.md](key-rules-and-troubleshooting.md). + +## Notes + +Cursor has no `enabledMcpjsonServers`-style precedence files — enable/disable is +the UI toggle above. OAuth `--login` in a sandbox must run with `all` +permissions (see [runtime-permissions.md](runtime-permissions.md)). diff --git a/skills/jfrog-mcp-management/references/harness-devin.md b/skills/jfrog-mcp-management/references/harness-devin.md new file mode 100644 index 0000000..40f04c7 --- /dev/null +++ b/skills/jfrog-mcp-management/references/harness-devin.md @@ -0,0 +1,114 @@ +# Harness: Devin **Desktop** + +Devin Desktop-specific config for the `jfrog-mcp-management` skill. Read this +together with [harness-common.md](harness-common.md) (shared entry shape and +success criterion). You reached this file because Step A matched **Devin**: +your system prompt / system instructions identify you as Devin. You may +optionally confirm with `VSCODE_IPC_HOOK` under the Devin user-data dir (e.g. +`~/Library/Application Support/Devin/-main.sock`). The environment +script does not detect Devin. + +Devin Desktop is a VS Code-family Electron shell that runs the Cascade / Devin +Local agent. It stores MCP configuration in the Windsurf config file used by +the underlying platform. + +## Config files + +- **Default scope: user-level.** Personal, not committed, available across all + workspaces. **Prefer Windsurf** — the same file Cascade uses and that the + JFrog Desktop extension writes the `jfrog` MCP into: + - macOS/Linux: `~/.codeium/windsurf/mcp_config.json` + - Windows: `%APPDATA%\.codeium\windsurf\mcp_config.json` + + Create the parent directory first (`mkdir -p` / platform equivalent), then + create the file if missing: `{ "mcpServers": {} }`. Devin Local imports this + file when `read_config_from.windsurf` is not `false` in + `~/.config/devin/config.json` (default) — so one write serves Cascade and Local. +- **Exception — migrated native store:** If `~/.config/devin/mcp_config.json` + **already exists** (user accepted **Migrate MCP config** / Copy), Devin Local + uses that file instead of Windsurf import. For Local, merge entries **there** + and do **not** require `read_config_from.windsurf`. Cascade never reads the + native file — if the entry must also appear in Cascade, merge into Windsurf + as well. Prefer **Cancel** on migrate so both agents stay on Windsurf. +- **Project scope:** Not supported by Devin Desktop's Cascade / Windsurf config. +- Do not ask which scope unless the user brings it up. + +## Top-level key + +`mcpServers` + +## Value reference (env / secrets) + +`${env:VAR_NAME}`, resolved from the environment that launched Devin Desktop. +For `Bearer` headers: `"Bearer ${env:TOKEN}"`. Devin Desktop also supports +`${file:~/path/to/file}` to inline a file's trimmed contents. The user must +export the variable in the environment that launches Devin Desktop (see +[persisting-env-vars.md](persisting-env-vars.md)); values are picked up on +next launch. If a required `${env:VAR}` is unset the Agent Guard fails at +startup — confirm the export before restart. Never write a raw secret. + +## Enable + +Devin Desktop loads every non-disabled entry in `mcpServers` automatically on +window load; there is no per-server approval prompt to pre-approve. If the +entry carries `"disabled": true`, remove it so the server runs. Otherwise +nothing to do here. + +## Restart + +`Developer: Reload Window` (or fully quit and reopen Devin Desktop). Devin +Desktop re-reads `mcp_config.json` on window load and reconnects each server. + +## List installed + +Open the **MCP servers** panel (Cascade panel toolbar, or +`Devin Settings → Cascade → MCP Servers`), or **Open customizations** on a +Devin Local session — each configured server is listed with its live +connection state. Servers and their tools are also reachable via `@` in the +chat input. Do **not** use `/mcp` here: that slash command is Devin CLI only; +in Desktop `/` lists workflows, so `/mcp` can fuzzy-match a skill and mislead. +Confirm via the MCP servers panel / Open customizations, or by checking that +`` exists under `mcpServers` in the active store (Windsurf by default; +native `~/.config/devin/mcp_config.json` only when that file already exists — +see Config files). When reading the file, do not report secret values — env +**key names** only; never display resolved `${env:…}` or `${file:…}` contents. + +## Verify + +Before treating a missing server as Failed: confirm the entry is in the active +store (Windsurf by default; native only when that file already exists). For +Devin Local on Windsurf, also confirm `read_config_from.windsurf` is not +`false`. Skip that flag check when Local is on the native file. + +Ask which MCP servers are available, or open the MCP servers panel / Open +customizations, and confirm `` is listed and connected. Then ask the +agent to list that server's tools (or reach it via `@`); the server MUST +expose **at least one tool**. A connected indicator alone is NOT proof — the +Agent Guard proxy can report connected with 0 upstream tools. Empty tool +list = Failed → see the "0 tools" troubleshooting in +[key-rules-and-troubleshooting.md](key-rules-and-troubleshooting.md). + +On first connect without cached OAuth, Devin opens a browser to sign in; later +runs reuse stored credentials. Treat **Output → MCP** as authentication / +connection status only — never as a source of token values. Devin Local may +also prompt to approve each MCP tool call by default — grant the prompt before +treating an empty list as a failure. + +## Notes + +- Cascade always reads `~/.codeium/windsurf/mcp_config.json`. Devin Local + imports that same file when `read_config_from.windsurf` is enabled in + `~/.config/devin/config.json` (default). If Local is on Windsurf and that + flag is `false`, Local will not see Windsurf entries even though the file on + disk is unchanged. +- Some Devin Desktop builds prompt to copy Windsurf MCP config to + `~/.config/devin/mcp_config.json` (**Migrate MCP config**). Prefer **Cancel** + unless the user wants to migrate: once the native file exists, Local uses it + as its store (no Windsurf-import requirement) while Cascade continues to use + Windsurf only — installs then diverge unless you write both. +- OAuth `--login` caches tokens in `~/.jfrog/jfrogmcp.conf.json` (same as all + harnesses); removal cleanup of that file is the same everywhere. +- Devin Desktop is distinct from **Devin CLI** (the `devin` terminal agent): + the CLI has its own config at `.devin/config.json` / `.devin/config.local.json` + and is not covered by this harness file. CLI-only surfaces such as `/mcp` + do not apply here. diff --git a/skills/jfrog-mcp-management/references/harness-opencode.md b/skills/jfrog-mcp-management/references/harness-opencode.md new file mode 100644 index 0000000..816b810 --- /dev/null +++ b/skills/jfrog-mcp-management/references/harness-opencode.md @@ -0,0 +1,164 @@ +# Harness: OpenCode + +OpenCode-specific config for the `jfrog-mcp-management` skill. Read this together +with [harness-common.md](harness-common.md) (shared entry shape and success +criterion). You reached this file because the harness is OpenCode (`OPENCODE`, +set in the environment at startup). This targets all OpenCode surfaces (TUI, CLI, +Desktop, IDE, web) - they share one backend and the same `opencode.json`. + +> **How OpenCode stores the entry:** config is **JSON / JSONC** under the +> top-level **`mcp`** key; each server is a **`type: "local"`** entry whose +> **`command` is a single ARRAY** (executable + args combined - there is NO +> separate `args`); env vars go in an **`environment`** object; and value +> references use **`{env:VAR}`** (or `{file:/path}`). Write the entry using the +> JSON template in **Full entry shape** below. + +## Config files + +- **Default scope: user-level (global).** `~/.config/opencode/opencode.json` + (`.jsonc` also works) - personal, not committed, applies to every project. + Create if missing: `{ "mcp": {} }`. (`$OPENCODE_CONFIG`, if set, adds a custom + config file - merged after the global file and before project config - it does + NOT replace the global file; `$OPENCODE_CONFIG_DIR`, if set, adds a custom + config directory whose `opencode.json` / `.jsonc` is also loaded.) +- **Project:** `opencode.json` (or `.jsonc`) in the project root - shareable via + git. Use ONLY if the user says "for this project" / "commit" / "share with the + team". +- **Write to exactly one scope, never both.** Config files are merged; project + overrides global on conflicts. Do not ask which scope unless the user brings it + up. + +## Top-level key + +`mcp` - one entry per server: `mcp.`. Use `spec.packageName` +directly as the key; special characters (`.` `/` `@`) are fine because OpenCode +sanitizes the name (`[^a-zA-Z0-9_-]` → `_`) when it exposes tools as +`_`. + +## Value reference (env / secrets) + +`{env:VAR_NAME}` inside the `environment` object, substituted from OpenCode's +environment when it loads `opencode.json` (use `{file:/path}` to read a value +from a file instead). For `Bearer` headers: `"Bearer {env:TOKEN}"`. The user must +export the variable in the shell that launched OpenCode (see +[persisting-env-vars.md](persisting-env-vars.md)); values are picked up on next +launch. **Names are case-sensitive** - each `environment` key that carries a +catalog input MUST equal that input's `name` (from `--inspect`) +character-for-character, or the Agent Guard drops it and the MCP starts with the +value missing. Never write a raw secret - always a `{env:...}` / `{file:...}` +reference. + +Full entry shape (`command` is one array; `_JF_ARGS` is a literal in +`environment`; secrets/refs use `{env:...}`): + +```json +{ + "mcp": { + "": { + "type": "local", + "command": ["npx", "--yes", "--registry", "", "@jfrog/agent-guard", "--server", ""], + "enabled": true, + "environment": { + "_JF_ARGS": "project=&mcp=", + "": "{env:}" + } + } + } +} +``` + +- `"type": "local"` always - never `"remote"` or a top-level `"url"` (those + bypass the Agent Guard). +- `command` merges the common entry's `command` + `args` into ONE array, same + tokens in the same order; `--yes` and `--registry ` MUST precede + `@jfrog/agent-guard`. +- **Include `--server `** to authenticate JFrog - it is the default, + and required when the user has multiple `jf` servers; it also keeps the entry + working if the user later adds more servers. (It can be omitted only when a + single `jf` server is configured, which the Agent Guard auto-resolves; see JFrog + credentials below.) The `environment` block is only for the upstream MCP's own + secrets/inputs, never for JFrog credentials. +- **Always keep `environment` with `_JF_ARGS`** - it carries the project + + package identity the Agent Guard needs to route the request. Omit only optional + input keys; never drop `_JF_ARGS` or the whole `environment` object. + +## JFrog credentials - from the `jf` config + +**Include `--server ` by default.** It reads that server's URL + token +from the on-disk `jf` CLI config, is unambiguous, and keeps working if the user +later adds more servers. Resolve `` per the agent-guard-common +Pre-flight rules; never emit an empty `--server`. + +`--server` can be **omitted only when exactly one `jf` server is configured** - in +that case the Agent Guard auto-resolves it. With **multiple** `jf` servers, +omitting `--server` fails: the Agent Guard cannot choose between them and does NOT +fall back to the `jf` default, so `--server` is required. (When in doubt, include +it.) + +**OpenCode exception to the shared rule.** [SKILL.md](../SKILL.md) treats `--server` +as conditional and permits dropping it on the `JFROG_URL`+token env path (see its +Step 4 Guardrails, "`--server` … drop it only on the `JFROG_URL`+token env +path"). **That env path does NOT apply on OpenCode** - do NOT authenticate JFrog via env-var credentials, even though OpenCode would forward `JFROG_URL` / `JFROG_ACCESS_TOKEN` to the server. Use +`--server ` (or a single configured `jf` server) as described above. If +there is no usable `jf` server, ask the user to add one (`jf c add `, or +`jf login`) before continuing. + +If credentials cannot be resolved (no `--server` and either zero or multiple `jf` +servers), the entry fails to start and the server connects with no tools. + +## Enable + +Servers are enabled by default (`enabled: true` is implicit; only +`enabled: false` disables) - writing the entry is enough, there is no separate +approval file. To disable without deleting, set `enabled: false` in the entry and +edit the config file directly. + +## Restart + +OpenCode reads config and connects MCP servers at startup and does not hot-reload +edits - **tell the user to start a new OpenCode session** (exit and relaunch +`opencode`) so the added/removed entry and any newly exported `environment` +values take effect. + +## List installed + +`opencode mcp list` (alias `ls`) shows the configured servers with their +connection status. For JFrog metadata, read the `mcp` object from every config +scope listed under **Config files** above (global, `$OPENCODE_CONFIG`, +`$OPENCODE_CONFIG_DIR`, and project). Identify the package by the `mcp=` value in +each entry's +`environment._JF_ARGS`; the entry key is the display name. Parse only the `mcp` +section - do NOT print, log, or return the whole file or unrelated config values +(it may hold provider keys and personal settings). + +## Verify + +Confirm the server exposes the upstream MCP's **real tools** (they appear to the +agent as `_`). `opencode mcp list` shows connection +status, but a "connected" row is NOT proof - the Agent Guard proxy can report up +with 0 upstream tools. + +- **An `enable__tools` tool is a normal Agent Guard gate**, not an error: + for MCPs that need sign-in or explicit enablement, the Agent Guard first + exposes this single tool; invoking it (e.g. "sign in to ``") runs the flow + and the upstream MCP's real tools then appear. Re-check afterward. (OpenCode's + own `opencode mcp auth` is for `type: "remote"` OAuth servers only and does NOT + apply to this local Agent Guard entry.) +- If the **real tools never appear** (even after enabling / signing in), a + required input likely did not reach the server - most often an `environment` + name or shell export whose case does not match the catalog input `name` (see + Value reference), or a variable that was not exported in the launching shell. + Fix it and start a new session. A truly empty tool list = Failed → see the + "0 tools" troubleshooting in + [key-rules-and-troubleshooting.md](key-rules-and-troubleshooting.md). + +## Remove + +Find the target entry by matching `mcp=` in +`environment._JF_ARGS`, then delete the `mcp.` entry from whichever +config holds it - check every scope listed under **Config files** above (global, +`$OPENCODE_CONFIG`, `$OPENCODE_CONFIG_DIR`, and project). Hand-edit the file +directly (current builds have no `opencode mcp remove`), +touching only the target `mcp.` entry and leaving other config +values untouched and unprinted. There is no separate `inputs`-style array to +clean up. Then start a new OpenCode session so the removed server stops loading. diff --git a/skills/jfrog-mcp-management/references/harness-vscode.md b/skills/jfrog-mcp-management/references/harness-vscode.md new file mode 100644 index 0000000..2c0b61f --- /dev/null +++ b/skills/jfrog-mcp-management/references/harness-vscode.md @@ -0,0 +1,127 @@ +# Harness: VS Code (GitHub Copilot) + +VS Code-specific config for the `jfrog-mcp-management` skill. Read this together +with [harness-common.md](harness-common.md) (shared entry shape and success +criterion). You reached this file because the harness is the VS Code editor +(`TERM_PROGRAM=vscode`, no `CURSOR_*` set). This targets the VS Code **editor** +with Copilot MCP support — not the standalone GitHub Copilot terminal CLI, which +has no `mcp.json` or editor UI and uses the Fallback path instead. + +> **VS Code differs from the others in three ways:** the top-level key is +> **`servers`** (not `mcpServers`); the default scope is **user-level** (not +> project); and secrets use a top-level **`inputs` array** with `${input:}`, +> not shell env vars. + +## Config files + +- **Default scope: user-level.** Personal, not committed, available across all + workspaces. Open with `MCP: Open User Configuration`; on disk: + - macOS: `~/Library/Application Support/Code/User/mcp.json` + - Linux: `~/.config/Code/User/mcp.json` + - Windows: `%APPDATA%\Code\User\mcp.json` + + Create if missing: `{ "servers": {}, "inputs": [] }`. +- **Workspace:** `.vscode/mcp.json`. Use ONLY if the user says "for this + project" / "commit" / "share with the team" (shareable via git). +- **Write to exactly one scope, never both.** In the default case write only the + user-level file; when the user opts into workspace scope write only + `.vscode/mcp.json` and do NOT touch the user-level config. +- Do not ask which scope unless the user brings it up. + +## Top-level key + +`servers` (NOT `mcpServers`). Writing `mcpServers` produces a file VS Code +silently ignores. + +## Value reference (env / secrets) + +A top-level **`inputs` array**, referenced from `env` as `"${input:}"`. VS +Code prompts for each value on first start and stores it (OS keychain) — there +is no shell export, so [persisting-env-vars.md](persisting-env-vars.md) does not +apply here. + +Full entry shape (note the sibling `inputs` array alongside `servers`): + +```json +{ + "inputs": [ + { + "type": "promptString", + "id": "-", + "description": "", + "password": true + } + ], + "servers": { + "": { + "type": "stdio", + "command": "npx", + "args": ["--yes", "--registry", "", "@jfrog/agent-guard", "--server", ""], + "env": { + "_JF_ARGS": "project=&mcp=", + "": "${input:-}" + } + } + } +} +``` + +Rules for the `inputs` block: + +- One entry per env var / header you configure from Step 3. +- `id`: `-`, all lowercase, hyphenated; unique within the + file. Reference from `env` as `"${input:}"`. +- `type`: always `"promptString"`. +- `password: true` for catalog `isSecret=true`. **OMIT the `password` key + entirely** (never set it to `false`) for non-secrets like URLs/flags. +- `description`: use the catalog `description`; if empty, construct a brief one. +- `Bearer` headers: use `"Bearer ${input:}"` and ask only for the token. + +## Enable + +Writing the entry is not enough — the server must be started via the UI. If it +is not already running, ask the user to **Start** it: the **Start** CodeLens +above the `mcp.json` entry, or `MCP: List Servers` → select it → **Start +Server**. On first start VS Code prompts for each `${input:...}` value; required +ones must be supplied or the server fails to start. + +## Restart + +`Developer: Reload Window`, or `MCP: List Servers` → Restart the server. + +## List installed + +Read `servers` from BOTH the workspace `.vscode/mcp.json` and the user-level +`mcp.json` (paths above). Live status (Running / Stopped / Failed) is UI-only — +the agent cannot read it. Only when the user explicitly asks whether a server is +running, or while troubleshooting, ask them to open `MCP: List Servers` and +report each server's status. An entry that does not appear there was never +started — re-run Enable. + +## Verify + +Ask the user to confirm in `MCP: List Servers` that the server is **Running with +at least one tool**. "Discovered 0 tools" is NOT healthy — the Agent Guard +started but the upstream MCP didn't. Treat 0 tools as Failed → see the "0 tools" +troubleshooting in [key-rules-and-troubleshooting.md](key-rules-and-troubleshooting.md). + +## Remove cleanup + +VS Code is the only harness with a top-level `inputs` array, so removal has an +extra step the harness-agnostic flow does not: after deleting the server's entry +from `servers`, also delete from the top-level `inputs` array every entry whose +`id` was referenced (as `"${input:}"`) ONLY by that server's `env` — i.e. +every `inputs` entry now orphaned. Leave NO orphaned `inputs` entries for the +removed server; a dangling `${input:}` declaration keeps its keychain-stored +value alive after the server is gone. Do NOT delete an `id` still referenced by +another surviving server. If removing the server empties `inputs`, an empty +`inputs: []` (or dropping the key) is fine. Operate by `id` only — never print or +echo any stored value. + +## Notes + +A wrong stored secret is cleared via the **Clear** CodeLens above the matching +`inputs` entry in `mcp.json`; then restart the server and VS Code re-prompts. +Several steps here (Start, entering inputs, checking `MCP: List Servers`) are +UI-only **user** actions — ask the user to do them; editing `mcp.json` and +running the agent guard commands are your steps. diff --git a/skills/jfrog-mcp-management/references/key-rules-and-troubleshooting.md b/skills/jfrog-mcp-management/references/key-rules-and-troubleshooting.md new file mode 100644 index 0000000..40cba92 --- /dev/null +++ b/skills/jfrog-mcp-management/references/key-rules-and-troubleshooting.md @@ -0,0 +1,81 @@ +# Key rules & troubleshooting + +Reference for the Install and List flows of the `jfrog-mcp-management` skill. + +## Key Rules + +- **Package scope is case-sensitive — ALWAYS write it lowercase as + `@jfrog/agent-guard`, NEVER `@JFrog/agent-guard`.** npm scopes are + case-sensitive; the published package is the lowercase `@jfrog/agent-guard`. + Capitalizing the brand (`@JFrog`) points at a different/nonexistent scope and + breaks the command. Use the exact lowercase string in every command and config + entry. +- **`npx` arg order:** `--yes`, `--registry `, `@jfrog/agent-guard`, then + agent guard flags. Both `--yes` and `--registry` MUST precede the package + name or `npx` falls back to the default registry (404) and may block on a + no-TTY prompt. +- **Always `"type": "stdio"`** pointing at `npx @jfrog/agent-guard`, even for + remote-only catalog MCPs (the agent guard proxies them). `"http"`, `"sse"`, + or a top-level `"url"` bypass the agent guard. +- `_JF_ARGS` is **only** for the config entry the agent launches at session + start (the `env` of the entry written when adding an MCP); MUST contain + `project=&mcp=`. NEVER pass `_JF_ARGS` to + `--list-available`, `--inspect`, or `--login` — those take `--server` / + `--project` as CLI flags only. +- NEVER assume `default` as a JFrog project key. If the project key is unknown + after the project chain (existing `mcpServers` entries → `JF_PROJECT` env + var), STOP and ask the user. Same for server ID if used. NEVER invent or + guess JFrog project keys or server IDs. +- Package name MUST come from the catalog (`--inspect` / `--list-available`). + NEVER guess. NEVER install MCPs outside the agent guard. NEVER use + Fetch/WebFetch for catalog calls. +- NEVER pipe a catalog command through `python3`, and NEVER capture it with + `2>&1` — `npx`/`npm` writes progress to stderr, which corrupts the output + stream. For `--list-available` present the compact TSV it prints; for + `--inspect` read the JSON it prints on stdout directly (or with a single `jq` + filter), never via `python3`. +- NEVER write a raw secret into any MCP config file (see + [harness-common.md](harness-common.md) for each harness's file) — always use + `${VAR_NAME}`. NEVER show tokens / API keys. +- NEVER try multiple servers — ask the user to pick one. + +## Troubleshooting + +Items below are harness-agnostic unless they point into the current harness's +row in [harness-common.md](harness-common.md). + +- **"connected" but 0 tools** (empty tool/capability list in the harness's + verify view — e.g. Claude Code's `/mcp` `Capabilities:`) — agent guard proxy + started, upstream MCP did not. A "connected" label is misleading here. NEVER + report success when there are 0 tools. + 1. Relaunch in the harness's debug mode if it has one (e.g. Claude Code: + `claude --debug`) and read the agent guard stderr; diagnose by MCP type: + - **OAuth (remote)** — re-run the OAuth login (`--login`); refresh token + likely expired. + - **Static-token (remote)** — confirm every `${VAR}` in `env` is exported + in the launching shell and the token is still valid. + - **Local (stdio)** — check that the bundled binary actually launched + (agent guard stderr will show the spawn error). + 2. Verify that the MCP server is still allowed. See the skill's "Available to + install" flow. +- **Configured server missing from the harness's list/verify view** — + rejected/pending. Re-run the enable/verify step (Install → Step 4a). +- **MCP still appears as approved (or won't go away) after editing the config** + — on harnesses that pre-approve via files (e.g. Claude Code), approval state + lives in plain JSON arrays read at session start (nothing cached, so `npm + cache clean` is unrelated). Check that harness's approval-precedence list in + [harness-common.md](harness-common.md) and remove the entry from every file + that lists it, then restart. On UI-toggle harnesses (Cursor, VS Code) there is + no such file — disable/stop the server in the harness's MCP view instead. +- **Agent Guard: `multiple/no JFrog server configured`** (the agent guard + cannot pick a JFrog server) — pass `--server ` (after `jf c add `) OR + export both `JFROG_URL` and `JFROG_ACCESS_TOKEN` in the launching shell, then + restart the agent. +- **OAuth MCP failing** — refresh token expired; re-run the OAuth login step. +- **401/403 with `${VAR}`** — env var unset/wrong; re-export in the launching + shell and restart the agent. +- **Network / proxy / DNS error** — outside the agent guard's scope; tell the + user and stop. +- **npx package fetch returns 403** — usually a corporate proxy/VPN, a blocked + or wrong registry, or a curation policy. Confirm `--registry + ` resolves and the access token is valid for that repo. diff --git a/skills/jfrog-mcp-management/references/persisting-env-vars.md b/skills/jfrog-mcp-management/references/persisting-env-vars.md new file mode 100644 index 0000000..1460227 --- /dev/null +++ b/skills/jfrog-mcp-management/references/persisting-env-vars.md @@ -0,0 +1,85 @@ +# Persisting environment variables + +Read this for **shell-based harnesses** when a Step 3 input needs to be exported +so its value takes effect. How each harness picks up the exported variable: + +- **Claude Code** — a `${VAR}` reference in the config. +- **Cursor** — a `${env:VAR}` reference in the config. +- **Devin Desktop** — a `${env:VAR}` reference in the config. +- **Codex** — a variable name listed in the `env_vars` allow-list; Codex forwards + that named variable's value from the launching shell to the server (e.g. an env + var like `Authorization`). +- **OpenCode** — a `{env:VAR}` reference in the config `environment` (OpenCode + also forwards its ambient environment to local MCP servers). + +This applies to any secret, or a non-secret you chose to keep out of the config as +a reference. (VS Code does not use shell env for this — it prompts for `inputs` +values and stores them itself; skip this file.) + +These references resolve from the shell that launched the agent, so the variable +has to be exported in that shell and persisted across relaunches. Don't rely on +a fixed list of shells/rc files — detect the syntax family and the actual +startup file the running shell uses, and fall back to asking the user whenever +either is ambiguous. + +## 1. Determine the syntax family + +```bash +echo "$SHELL" +``` + +`$SHELL` reports the user's default *login* shell, which is not necessarily the +shell that launched the agent (e.g. a bash session started from a zsh login +shell). Prefer detecting the actual running/parent shell when you can (e.g. the +process that started Claude); use `$SHELL` only as a fallback, and **ask the +user** whenever the running shell — or its startup file — can't be determined +unambiguously. + +- Basename ends in `sh` (`bash`, `zsh`, `ksh`, `dash`, `ash`, `sh`, ...) or any + other POSIX-compatible shell → **POSIX family**: `export VAR_NAME=""`. + This covers virtually every Unix shell except fish, so don't special-case + bash vs. zsh vs. anything else in this family — the export syntax is + identical. +- Basename is `fish` → **fish family**: `set -gx VAR_NAME ""`. +- No `$SHELL` (native Windows session, PowerShell/CMD) → **Windows**: for + **non-secret** values persist with `setx VAR_NAME ""` (sets it for + future sessions; the current one still needs the in-session equivalent, + `$env:VAR_NAME` / `set VAR_NAME`). Do **not** use `setx` for secrets — it + puts the value on the command line (visible in process listings / command + history). For secret values, direct the user to set it via the Windows + environment-variable UI (System Properties → Environment Variables) or a + secret manager, and keep the in-session example session-scoped. +- Anything that doesn't clearly match one of the above → ask the user which + family applies rather than guessing. + +## 2. Find the startup file to persist it in + +Start from the family's canonical default, then verify it's actually the file +in play before writing to it: + +| Family | Canonical default | +|--------|-------------------| +| POSIX (bash) | `~/.bashrc` (macOS login shells, e.g. Terminal.app, instead read `~/.bash_profile`, which usually sources `~/.bashrc`) | +| POSIX (zsh) | `~/.zshrc` | +| POSIX (other: ksh, dash, ash, sh, ...) | ask the user — these don't have one universal convention | +| fish | `~/.config/fish/config.fish` | +| Windows | persistent user env (`setx`), no file to edit | + +- **Verify before writing**, don't assume the default is correct: `test -f + && echo exists`. If it's missing, or a dotfiles manager / + framework (oh-my-zsh, starship, chezmoi, etc.) is in play — which often + generates or `source`s rc files from elsewhere — a hardcoded guess can + silently miss the file the shell actually reads. Ask the user to confirm or + name the right file rather than writing blind. +- **If in doubt at any point, ask the user directly** which file to edit — do + not silently pick one from memory of "common" shells. + +## Rules + +- **Security:** NEVER take secrets in the chat, echo them back, or write raw + secret values into a config file. For secret values, instruct the user to add + the line themselves (e.g. via `read -rs VAR_NAME && export VAR_NAME` for the + current session) — you never see or type the value. +- After exporting, the user must **relaunch the agent** so the exported value + takes effect — the harness picks it up on next launch (resolving `${VAR}` / + `${env:VAR}`, or forwarding the `env_vars`-listed variable on Codex). diff --git a/skills/jfrog-mcp-management/references/runtime-permissions.md b/skills/jfrog-mcp-management/references/runtime-permissions.md new file mode 100644 index 0000000..39a2570 --- /dev/null +++ b/skills/jfrog-mcp-management/references/runtime-permissions.md @@ -0,0 +1,19 @@ +# Runtime permissions + +The Step 0 Agent Guard check and the agent guard commands make outbound HTTPS +calls, and some operations also write under `~/.jfrog/`. Grant the matching +runtime access, or the commands fail (`Forbidden`, empty output) or the Step 0 +check returns a false "disabled" result. + +| Operation | What it needs | +| --- | --- | +| Step 0 check, `--inspect`, `--list-available` | Network: outbound HTTPS to the npm registry and the JFrog platform | +| OAuth `--login`, removing a cached entry | Network + write access to `~/.jfrog/` (`jfrogmcp.conf.json`) | + +How that access is granted depends on the agent. Some agents (e.g. Claude Code) +read the skill's optional `allowed-tools` frontmatter to pre-approve the +specific commands the skill runs, so the user is not prompted per call; others +prompt for approval or use their own permission model. Either way the skill +works — an agent that does not honor `allowed-tools` just asks the user to +approve the command. Do NOT treat `allowed-tools` as the permission mechanism; +it is only a convenience where supported. diff --git a/skills/jfrog-mcp-management/scripts/jfrog-agent-guard-check.mjs b/skills/jfrog-mcp-management/scripts/jfrog-agent-guard-check.mjs new file mode 100644 index 0000000..0024321 --- /dev/null +++ b/skills/jfrog-mcp-management/scripts/jfrog-agent-guard-check.mjs @@ -0,0 +1,270 @@ +#!/usr/bin/env node +// Copyright (c) JFrog Ltd. 2026 +// Licensed under the Apache License, Version 2.0 +// https://www.apache.org/licenses/LICENSE-2.0 +// +// JFrog Agent Guard activation check. +// +// Silent gate used by Step 0 of the jfrog-mcp-management skill. Determines +// whether the JFrog Agent Guard is enabled for the current environment. +// +// Contract: +// - exit 0 -> Agent Guard ENABLED (skill may proceed) +// - exit 2 -> reachable but the platform has the MCP registry DISABLED +// (skill should tell the user to contact their admin / IT) +// - exit 1 -> DISABLED for any other reason: no credentials, timeout, +// network/DNS error (skill must silently abort) +// - a single human-readable reason line is written to stdout for +// diagnostics only; Step 0 keys off the EXIT CODE, not the text. +// +// Set JF_AGENT_GUARD_DEBUG=true for verbose tracing on stderr. + +import { execFileSync } from "node:child_process"; +import process from "node:process"; + +const SETTINGS_PATH = + "/ml/core/api/v1/administration/account-settings/mcp_gateway_plugin_enabled"; +const REQUEST_TIMEOUT_MS = 5000; + +const debugEnabled = process.env.JF_AGENT_GUARD_DEBUG === "true"; +const debug = (message) => { + if (debugEnabled) console.error(`[jfrog-agent-guard] ${message}`); +}; + +// New JFROG_* env vars take precedence over the legacy JF_* names. +const env = (newName, oldName) => + process.env[newName] ?? (oldName ? process.env[oldName] : undefined); + +const enabled = (reason) => { + process.stdout.write(`Enabled: ${reason}\n`); + process.exit(0); +}; + +const disabled = (reason) => { + process.stdout.write(`Disabled: ${reason}\n`); + process.exit(1); +}; + +// Reachable platform that reports the MCP registry turned off. Distinct exit +// code so the skill can tell the user to contact their admin / IT. +const registryDisabled = (reason) => { + process.stdout.write(`RegistryDisabled: ${reason}\n`); + process.exit(2); +}; + +// Resolve credentials from Path A (environment variables) or Path B +// (the default JFrog CLI configuration). Returns { baseUrl, token, source } +// or null when neither path yields a usable URL + access token. +function resolveCredentials() { + const explicitServerId = process.argv[2]; + // With an explicit server ID, try the named jf-config server FIRST so the + // gate checks THAT JPD, not the ambient default. But if it does not resolve + // (server not in jf config, jf absent/old), fall back to env credentials + // rather than reporting a false "disabled" — the platform may be fully + // reachable via exported JFROG_URL + token even with no matching jf server. + if (explicitServerId) { + const fromCli = resolveFromCliConfig(); + if (fromCli) return fromCli; + debug( + "Explicit server ID did not resolve via jf config; falling back to env credentials.", + ); + } + + // Path A — environment variables. + const envUrl = env("JFROG_URL", "JF_URL"); + const envToken = env("JFROG_ACCESS_TOKEN", "JF_ACCESS_TOKEN"); + if (envUrl && envToken) { + debug("Using credentials from environment variables (Path A)."); + return { baseUrl: envUrl, token: envToken, source: "environment variables" }; + } + debug( + "Environment credentials incomplete; trying JFrog CLI config (Path B).", + ); + + // Path B — default server from the local JFrog CLI configuration. If an + // explicit ID was given we already tried the CLI above (and env fell through), + // so there is nothing left to resolve. + if (explicitServerId) return null; + return resolveFromCliConfig(); +} + +function resolveFromCliConfig() { + // `jf config export [server ID]` emits the server as a base64-encoded JSON + // blob containing url, accessToken, and serverId. An optional server ID may + // be passed as argv[2]; without it the CLI's default server is used. We use + // the CLI rather than reading ~/.jfrog/jfrog-cli.conf.v6 directly because + // newer CLIs do not persist the access token in that file (and the platform + // URL may be stored only as an /artifactory-suffixed URL there, which is + // wrong for /ml/core). + const serverId = process.argv[2]; + const exportArgs = serverId ? ["config", "export", serverId] : ["config", "export"]; + let exported; + try { + exported = execFileSync("jf", exportArgs, { + encoding: "utf8", + stdio: ["ignore", "pipe", "ignore"], + timeout: 2000, + }).trim(); + } catch (error) { + debug( + `'jf config export' failed (jf not on PATH or no server configured): ${error?.message}`, + ); + return null; + } + + let cfg; + try { + cfg = JSON.parse(Buffer.from(exported, "base64").toString("utf8")); + } catch (error) { + debug(`Could not decode the jf config export token: ${error?.message}`); + return null; + } + + // `url` is the platform/JPD root — the base the /ml/core settings path needs. + const baseUrl = cfg?.url; + const token = cfg?.accessToken; + if (!baseUrl) { + debug("Exported JFrog CLI config has no platform URL."); + return null; + } + if (!token) { + debug("Exported JFrog CLI config has no access token (bearer auth needed)."); + return null; + } + + const id = cfg?.serverId ?? "default"; + return { baseUrl, token, source: `JF CLI config (server '${id}')` }; +} + +async function isGatewayPluginEnabled(baseUrl, token) { + // Normalize to the platform root: drop trailing slashes and a trailing + // `/artifactory` segment. Users commonly export JFROG_URL as + // `https://myco.jfrog.io/artifactory`, but the settings path lives under + // `/ml/core` off the platform root — without this, Path A would build + // `.../artifactory/ml/core/...` and 404 into a false "disabled" (exit 1). + const root = baseUrl.replace(/\/+$/, "").replace(/\/artifactory$/, ""); + const url = root + SETTINGS_PATH; + debug(`Fetching gateway plugin setting from ${url}`); + + // Trade-off: we use a direct fetch() rather than `jf api` (the pattern other + // scripts in this repo use for authenticated JFrog REST calls) because this + // gate keys off exact HTTP status codes — 200+value:false vs 401/403 vs + // unreachable each map to a different exit code — and parsing `jf api`'s + // "[Warn] ... returned NNN" / "Http Status: NNN" stderr convention for that + // is brittle. The cost: this call does NOT inherit any corporate-proxy or + // custom-CA settings baked into the user's `jf` config, so an env that only + // works through jf's transport can surface here as an unreachable/timeout + // (exit 1). If that becomes common, switch to `jf api` and parse its status. + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS); + try { + const response = await fetch(url, { + method: "GET", + headers: { + Accept: "application/json", + Authorization: `Bearer ${token}`, + }, + signal: controller.signal, + }); + if (!response.ok) { + debug(`Settings request returned HTTP ${response.status}.`); + // Non-OK (incl. 401/403) means an auth/permission/transport problem, NOT + // a deliberately-disabled registry — stay silent (exit 1) rather than + // sending the user to IT. Only HTTP 200 + value:false is "disabled". + return { + ok: false, + reason: `settings endpoint returned HTTP ${response.status}`, + }; + } + const data = await response.json(); + // Be tolerant about where and how the flag is carried, so a shape/casing + // change on the platform side can't turn a genuinely-enabled registry into + // a false "disabled" (exit 1). The endpoint URL already names the setting + // (`.../mcp_gateway_plugin_enabled`), so the body may arrive as any of: + // - `{ settings: { mcpGatewayPluginEnabled: } }` (wrapped); + // - the same at the top level, un-wrapped; + // - `{ value: }` (bare wrapper, key implied by the URL); + // - a bare boolean `true` / `false`. + // Casing: the path segment is snake_case while JFrog JSON bodies are + // typically camelCase — accept either. + const unwrap = (v) => + v !== null && typeof v === "object" ? v?.value : v; + const container = data?.settings ?? data; + const named = + container?.mcpGatewayPluginEnabled ?? + container?.mcp_gateway_plugin_enabled; + // `named` first (explicit key), then the bare wrapper / bare boolean forms. + const value = + typeof data === "boolean" + ? data + : named !== undefined + ? unwrap(named) + : unwrap(container); + debug(`Settings response indicates gateway plugin enabled=${value}.`); + if (value === true) return { ok: true }; + if (value === false) { + return { + ok: false, + registryOff: true, + reason: "mcp gateway plugin setting returned false", + }; + } + return { + ok: false, + reason: "settings endpoint returned an invalid gateway-plugin setting", + }; + } catch (error) { + const reason = + error?.name === "AbortError" ? "timeout" : error?.message ?? "unknown error"; + debug(`Settings request failed: ${reason}`); + return { ok: false, reason: `settings endpoint unreachable (${reason})` }; + } finally { + clearTimeout(timeout); + } +} + +async function main() { + // Manual overrides bypass credential resolution and the network call + // entirely. Checked first, in this order, so a conflicting config fails + // safe (disabled) rather than silently favoring enablement. + const forceDisabled = + env("_JF_AGENT_GUARD_FORCE_DISABLE") === "true"; + const forceEnabled = + env("JF_AGENT_GUARD_FORCE_ENABLE") === "true"; + if (forceDisabled) { + disabled("forced via _JF_AGENT_GUARD_FORCE_DISABLE"); + return; + } + if (forceEnabled) { + enabled("forced via JF_AGENT_GUARD_FORCE_ENABLE"); + return; + } + + const creds = resolveCredentials(); + if (!creds) { + disabled( + "JFROG_URL/JF_URL + access token not set and no default JF CLI config found", + ); + return; + } + + const result = await isGatewayPluginEnabled(creds.baseUrl, creds.token); + if (result.ok) { + enabled(`via ${creds.source}`); + return; + } + if (result.registryOff) { + registryDisabled(result.reason); + return; + } + disabled(result.reason); +} + +try { + await main(); +} catch (error) { + // Last-resort guard: any unexpected throw must NOT leak a stack trace to the + // user (the skill's Step 0 is silent). Downgrade to the safe "disabled" exit. + debug(`Unexpected error: ${error?.stack ?? error?.message ?? error}`); + disabled("unexpected error"); +} diff --git a/skills/jfrog-reference-architecture/SKILL.md b/skills/jfrog-reference-architecture/SKILL.md new file mode 100644 index 0000000..394d000 --- /dev/null +++ b/skills/jfrog-reference-architecture/SKILL.md @@ -0,0 +1,155 @@ +--- +name: jfrog-reference-architecture +description: >- + Guides JFrog Platform topology, sizing (RPM, t-shirt templates), deployment, + multi-site use cases, SaaS vs self-managed, HA, air-gapped, and disaster + recovery using the official Reference Architecture site as the sole source of + facts. Use this skill when the user asks how to size Artifactory or Xray, + which deployment pattern to choose, single vs multi site, active-active, + active-passive, CI/CD separation, Helm/Kubernetes install planning, or + reference architecture (live WebFetch from jfrog.com/reference-architecture). + Do NOT use for artifact search or download, repository or permission admin, + CVE or vulnerability lookups, live jf CLI operations against their instance, + or package curation — use the jfrog base skill or + jfrog-package-safety-and-download instead. +compatibility: >- + Requires outbound HTTPS (WebFetch or equivalent). Request full_network when + the runtime blocks fetches. No jf CLI or configured JFrog instance required + for planning-only questions. +metadata: + role: workflow +--- + +# JFrog Reference Architecture + +Planning skill for topology, sizing, and deployment. Answers must come from +**live fetches** of the [JFrog Platform Reference Architecture](https://jfrog.com/reference-architecture/) — not from training data or duplicated tables in this repo. + +## Prerequisites + +- Read `../jfrog/SKILL.md` for JFrog Platform concepts, product vocabulary, and routing to other workflows. +- **No `jf` CLI required** for planning-only questions (no live instance needed). + +## Source of truth + +| Allowed in this skill | Not allowed | +|-----------------------|-------------| +| Fetch procedures, workflows, output templates | Sizing RPM tables, use-case narratives, deployment checklists copied from the site | +| Helm chart **preference** (jfrog-platform on Kubernetes) | Hardcoded slug lists or criteria | + +**Every factual claim** (numbers, template names, limitations, infrastructure guidance) must come from a **`WebFetch` in the current session**. If fetch fails, retry or ask the user to open the URL — do not guess from memory. + +**Citations:** Use the `URL:` line from the relevant section in the fetched content (public HTML URL). You may note content was read from `llms-full.txt`. + +For fetch URLs, size thresholds, and the fallback ladder, see [references/doc-access.md](references/doc-access.md). + +## Gotchas + +| Symptom | Mitigation | +|---------|------------| +| Sizing numbers or use-case names not on the official site | `WebFetch` ref-arch first; cite `URL:` from the fetch — not training data | +| `small` template recommended for production | Re-read production warnings in the fetched Artifactory/Xray sizing sections | +| SaaS section missing or 404 | SaaS paths use prefix **`jfrog-saas`**, not `saas` | +| HA storage guidance wrong | Per ref arch: **`cluster-file-system`** or object storage — not `file-system` for HA | +| `WebFetch` blocked, truncated, or over size limits | Request `full_network`; downgrade per [references/doc-access.md](references/doc-access.md) | + +## Session bootstrap + +Before answering a reference-architecture question: + +1. **`WebFetch`** `https://jfrog.com/reference-architecture/llms-full.txt` (primary). +2. Keep the response in context for follow-ups in the same thread. +3. Note approximate response size. If over **1 MB**, or truncated, follow the downgrade path in `references/doc-access.md` (sitemap + targeted `index.md`). +4. Re-bootstrap when uncertain after long unrelated conversation. + +Request **`full_network`** (or the runtime equivalent) when `WebFetch` is blocked. + +### Parsing llms-full.txt + +Sections are separated by `---` and typically include: + +- `# ` +- `URL: https://jfrog.com/reference-architecture/...` +- Optional `> <summary>` +- Body text (may be condensed vs the HTML page) + +Use only text from the fetch. For recommendations, cite the section’s `URL:` line. + +## Intent routing + +| User intent | Where to look in llms-full | Fallback | +|-------------|---------------------------|----------| +| Sizing | `# Sizing`, `# AWS Sizing`, Azure, GCP sections | `.../self-managed/deployment/sizing/index.md` | +| Topology / use case | Matching title + `URL:` for SaaS (`jfrog-saas`) or self-managed | That path’s `index.md` | +| List use cases | All `URL:` lines containing `/use-cases/` | `sitemap.xml` | +| Deployment / install | Deployment and considerations sections | `.../deployment/index.md` | +| Disaster recovery | DR playbook / tiers sections | Matching `index.md` | + +SaaS paths use prefix **`jfrog-saas`**, not `saas`. + +## Workflow: Sizing + +1. Bootstrap `llms-full.txt` (unless downgraded to single-page fetch). +2. Find the **Sizing** section. Build follow-up questions from **Artifactory Sizing Templates Criteria** in the fetch (peak **Requests Per Minute** and **Concurrent Connections** per template). +3. Use **`AskQuestion`** when available; otherwise numbered options using labels from the fetched table only. +4. If the user mentions Xray or production, use **Xray Sizing Templates Criteria** and production warnings from the same fetch (e.g. small is not for production). +5. If the user names a cloud, use **AWS Sizing** / **Azure** / **GCP** sections from the fetch. +6. Recommend a template (`small` through `2xlarge`) and cite the sizing page `URL:` from the dump. +7. **Helm:** Recommend the [jfrog-platform](https://github.com/jfrog/charts/tree/master/stable/jfrog-platform) chart with `-f sizing/platform-<template>.yaml`. `WebFetch` the chart README if the user wants exact install commands. + +### Sizing output template + +```markdown +## Recommended sizing + +- **Template**: <from fetched table> +- **Artifactory**: <RPM and concurrent connections from fetched table> +- **Xray** (if applicable): <from fetched Xray table> +- **Source**: <URL: line from Sizing section> +- **Helm**: `helm upgrade --install` with `-f sizing/platform-<template>.yaml` on chart `jfrog/jfrog-platform` +- **Caveats**: <Notes / additional factors from fetched Sizing section> +``` + +## Workflow: Topology and use cases + +1. Bootstrap `llms-full.txt`. +2. Ask **1–2 follow-ups per turn** until hosting model and site count are clear: + - SaaS vs self-managed (if unsure, fetch home/overview from dump and mention SaaS value proposition from site text). + - Single site vs multi site. + - For multi-site: DR/failover, geo performance, CI/CD separation, edges, hybrid variants, IoT, subsidiaries/vendors, air-gapped (self-managed only), archiving. +3. **List documented use cases:** Filter all `URL:` lines containing `/use-cases/` from the dump; group under **JFrog SaaS** vs **Self-managed**. Fallback: `sitemap.xml` if the user wants sitemap-complete listing. +4. **Recommend a use case:** Match the user’s answers to sections in the dump; cite each `URL:`. If the dump is insufficient for one page, `WebFetch` `https://jfrog.com/reference-architecture/<path>/index.md` for that `URL:` path. +5. **No exact match:** Suggest combining documented patterns (e.g. active-passive for DR + main-site-with-edges); fetch each component section before describing how they combine. Remind that the ref arch is a starting point for emerging cases. + +## Workflow: Deployment + +1. Bootstrap `llms-full.txt`. +2. Use deployment, considerations, HA, database, storage, and cloud sections from the fetch. +3. **Default policy (not a substitute for ref-arch facts):** Deploy on **Kubernetes** with the **jfrog-platform** Helm chart even when the user only wants Artifactory — enable Artifactory, disable other products in values. Do not steer to legacy standalone Artifactory charts unless the user explicitly requires non-Kubernetes deployment. +4. Production reminders from fetched content where applicable: external managed PostgreSQL (not bundled chart DB for production), object storage / `cluster-file-system` for HA, Enterprise license for `replicaCount > 1`. +5. `WebFetch` the [chart README](https://github.com/jfrog/charts/tree/master/stable/jfrog-platform) when the user needs install snippets, OpenShift (`openshift-values.yaml` last), or RabbitMQ quorum files. + +### Deployment output template + +```markdown +## Deployment recommendation + +- **Runtime**: Kubernetes + jfrog-platform Helm chart +- **Reference**: <URL: from deployment-related sections> +- **Key considerations**: <bullets from fetched considerations sections> +- **Helm** (if requested): <commands from chart README fetch> +``` + +## When to read reference files + +- **Fetch ladder, Markdown URL rule, size governance:** [references/doc-access.md](references/doc-access.md) + +## Examples + +**Sizing:** User asks what sizing to set for Artifactory → bootstrap llms-full → ask peak RPM using fetched table options → recommend template and Helm sizing file. + +**List use cases:** User asks for all documented use cases → bootstrap llms-full → list grouped by SaaS vs self-managed from `URL:` lines in dump. + +**Topology:** User needs DR across two regions → clarify SaaS vs self-managed → recommend active-passive (or related) section from dump with `URL:` citations. + +**Deploy:** User wants Artifactory on EKS → deployment + AWS sizing sections from dump → jfrog-platform chart with external DB and sizing values file. diff --git a/skills/jfrog-reference-architecture/references/doc-access.md b/skills/jfrog-reference-architecture/references/doc-access.md new file mode 100644 index 0000000..3f576b7 --- /dev/null +++ b/skills/jfrog-reference-architecture/references/doc-access.md @@ -0,0 +1,32 @@ +# Reference Architecture — documentation access + +Fetch official content in-session. **Do not copy page bodies into this repo.** + +## Bootstrap and fallback + +| Step | URL | When | +|------|-----|------| +| Primary | https://jfrog.com/reference-architecture/llms-full.txt | Start of every ref-arch session | +| 1 | https://jfrog.com/reference-architecture/llms.txt | llms-full fails or for `index.md` URL pattern | +| 2 | `https://jfrog.com/reference-architecture/<path>/index.md` | One section; append `index.md` to HTML path | +| 3 | https://jfrog.com/reference-architecture/sitemap.xml | Exhaustive URL list | +| 4 | HTML URL (no `index.md`) | If `index.md` fails | + +Parse llms-full by `---`, `# <Title>`, and `URL: https://jfrog.com/reference-architecture/...`. +Base path: `https://jfrog.com/reference-architecture/`. SaaS prefix: **`jfrog-saas`**, not `saas`. + +## Size governance + +| Fetched size | Action | +|--------------|--------| +| Under ~1 MB, not truncated | One llms-full bootstrap per ref-arch thread | +| ~1–2 MB or ref-arch is side context | Prefer sitemap + targeted `index.md` | +| Truncated or over ~2 MB | Skip mandatory llms-full; use fallback ladder only | + +Downgrade early for narrow questions (e.g. sizing only → `.../deployment/sizing/index.md`). +Tell the user when targeted fetches replace a full bootstrap. + +## Citations and Helm + +- User-facing link: the section `URL:` line (HTML). +- Chart details: https://github.com/jfrog/charts/tree/master/stable/jfrog-platform — `WebFetch` README when install commands are needed. diff --git a/skills/jfrog-setup-package-managers/SKILL.md b/skills/jfrog-setup-package-managers/SKILL.md new file mode 100644 index 0000000..160086f --- /dev/null +++ b/skills/jfrog-setup-package-managers/SKILL.md @@ -0,0 +1,250 @@ +--- +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, 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. 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. + NOT for installing packages, general Artifactory repo operations (use the base + jfrog skill), or MCP server setup (use jfrog-mcp-management). +metadata: + role: workflow +--- + +# JFrog — Setup Package Managers for Artifactory + +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 package-manager-native config (`.npmrc`, `pip.conf`, `uv.toml`, …); the binding +lets the hook re-apply on later sessions. + +## At a glance (always-read core) + +Every `jf setup` this session: + +- **Cover base [`../jfrog/SKILL.md`](../jfrog/SKILL.md) At-a-glance / Tier A** + (Step 0.1) → `<UA>`, `--server-id` placement, single-server, stop-don't-switch. + Prefer full base SKILL.md when you can; Tier B (`cli-gotchas` / `jf-api` / …) + only if the next action needs `jf api` / advanced CLI +- **Always `--repo` + `--server-id`.** `<repoKey>` ← [Step 2](#step-2--get-the-resolved-repo) + (table / binding / global-cache) or user override / unresolved AskQuestion; + never self-discover. `<SID>` ← resolver only (never user-selected) +- **Confirm** before first `jf setup` unless user asked silent / non-interactive +- **Exit 0 → merge binding**; non-zero → stop, surface CLI verbatim, offer + alternate repo or `abort` (2-answer cap) +- **Binding = decisions, not creds** — never write tokens into + `.jfrog/local/package-resolution.json` +- **Unresolved / failed:** ask with failure verbatim — never switch servers +- **Never skip** [Gotchas](#gotchas--hard-rules-never-skip) + base Tier A hard + rules (`../jfrog/SKILL.md` Cautious execution / Server selection / Tier A + gotcha floor). Full `cli-gotchas.md` is Tier B — not required for `jf setup` + +Steps: [0](#step-0--read-the-base-skill-then-ensure-jf-is-ready) → +[1](#step-1--identify-package-managers-to-bind) → +[2](#step-2--get-the-resolved-repo) → +[3](#step-3--confirm-run-jf-setup-persist-binding) → +[4](#step-4--load-the-routing-policy) + +## Scope (this skill vs session hook) + +**Session-start hook:** resolves repo keys per package type, injects the +"Resolved URLs for this session" table, refreshes the global cache. The same +renderer is available on demand via `modules/package-resolution/scripts/print-policy.mjs` (the enforce +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 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 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 +package manager still works (Step 1's user-mention signal and Step 2's AskQuestion for an +unlisted package manager apply as usual). + +## Prerequisites + +- `jf setup` **mutates user state** (`~/.npmrc`, `~/.docker/config.json`, …). + Confirm before the first `jf setup` in a session unless the user explicitly + requests silent/non-interactive setup. +- Covering base At-a-glance / Tier A is required — done as Step 0.1 below. + +**Out of scope:** CLI install/login (`../jfrog/references/…`). + +## Gotchas — hard rules (never skip) + +**Not tips.** Do/don'ts and known traps for `jf setup` — follow every bullet +before binding. Also honor base **Tier A** hard rules from +[`../jfrog/SKILL.md`](../jfrog/SKILL.md) (Cautious execution, Server selection, +Tier A gotcha floor). Full `cli-gotchas.md` is Tier B — load only if this +session also needs `jf api` / advanced CLI. + +- **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 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 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, package-manager → type map, merge semantics | + +## Step 0 — Read the base skill, then ensure `jf` is ready + +1. **Cover base skill At-a-glance / Tier A before the first non-exempt `jf` + (even when `jf` is already configured).** Prefer reading + [`../jfrog/SKILL.md`](../jfrog/SKILL.md) in full when you can; the At-a-glance + Tier A floor is enough for `jf setup` / package-manager binding. Load Tier B + (`cli-gotchas.md`, `jf-api.md`, …) only if the next action needs `jf api` / + advanced CLI. Then run that skill's *Environment check* (and export + `JFROG_CLI_USER_AGENT`) before the first `jf` call. +2. Ensure `jf` + a configured server (`<SID>`). If `jf config show` already + succeeds, skip to Step 1; otherwise: + - **`jf --version`** missing → install per + [`../jfrog/references/jfrog-cli-install-upgrade.md`](../jfrog/references/jfrog-cli-install-upgrade.md). + - **`jf config show`** empty → login per + [`../jfrog/references/jfrog-login-flow.md`](../jfrog/references/jfrog-login-flow.md) + or `jf config add` with access-token (Bearer-only). +3. Do not run `jf setup` until both succeed. Confirm before install/login. + +## Step 1 — Identify package managers to bind + +Combine four signals, in order; intersect with `jf setup --help` supported list: + +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` | `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` (bind under type **`gradle`**) | + | `go.mod` | `go` | + | `Dockerfile`, `compose.yaml`, `docker-compose.yml` | `docker` / `podman` | + | `*.csproj`, `NuGet.Config` | `nuget` / `dotnet` | + | `Chart.yaml` | `helm` | + + **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 `<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>` + (`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). + +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=<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 (include the resolver/setup failure text verbatim): + +> 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 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 package manager: + + ```text + <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 package manager at a time: + + ```bash + jf setup <package-manager> --server-id <SID> --repo <repoKey> [--project <key>] + ``` + +5. **Exit code `0` = success** — merge binding (step 6). On non-zero, **stop**, + surface CLI output verbatim, offer alternate repo or `abort` (2-answer cap). + +6. On success, merge into `.jfrog/local/package-resolution.json` per + [`workspace-binding.md`](references/workspace-binding.md): + + ```json + { "repositories": { "<pkgType>": "<repoKey>" } } + ``` + + Map package manager → type via the reference table (`gradle` → `gradle`). + Merge atomically. + +## Step 4 — Load the routing policy + +If this session started with the "routing NOT READY" (enforce) notice, that +notice includes a refresh command (`node <plugin>/modules/package-resolution/scripts/print-policy.mjs`). +After Step 3 succeeds, run that exact command and treat its stdout as the +authoritative, now-current policy — it prints the resolved Artifactory URLs and +hard rules. Continue the original request using those URLs. + +If the command prints nothing, routing is off by config +(`packageResolution.enabled` is not `true`) — an admin opt-in. Report that to +the user and let them decide whether to enable it. + +## Before you run `jf setup` — checklist + +[At a glance](#at-a-glance-always-read-core) invariants: + +- [ ] base At-a-glance / Tier A covered; `<UA>` exported +- [ ] `<repoKey>` ← Step 2 or user override; `<SID>` ← resolver only +- [ ] confirmed (or explicit silent-setup) +- [ ] `jf setup <pm> --server-id <SID> --repo <repoKey>` +- [ ] exit 0 → merge binding (no creds); non-zero → stop + report verbatim; + never switch servers +- [ ] **never skip** Gotchas (this skill) + base Tier A hard rules (full + `cli-gotchas.md` only if Tier B path) diff --git a/skills/jfrog-setup-package-managers/references/global-cache-file.md b/skills/jfrog-setup-package-managers/references/global-cache-file.md new file mode 100644 index 0000000..7cb7869 --- /dev/null +++ b/skills/jfrog-setup-package-managers/references/global-cache-file.md @@ -0,0 +1,117 @@ +# `package-resolution.json` — Global Resolver Cache + +The session-start hook runs a small resolver that picks the Artifactory +repository key per package type for the current JFrog server and caches +the result in: + +``` +~/.jfrog/skills-cache/package-resolution.json +``` + +This skill **reads** that file in Step 2 to recover the repo key per PM +without re-doing discovery. The file is the canonical, machine-readable +mirror of the "Resolved URLs for this session" table that the hook +injects into agent context — the latter can be trimmed by long-context +pruning, the file cannot. + +> This is a **read-only contract** for this skill. The cache is written by +> the session-start hook; never write or hand-edit it. + +> **Not** the workspace binding file — that lives at +> `.jfrog/local/package-resolution.json` (see [`workspace-binding.md`](workspace-binding.md)). + +## Shape + +```json +{ + "schemaVersion": 1, + "servers": { + "<serverId>": { + "repositories": { + "npm": "npm-virtual", + "pypi": "pypi-virtual", + "maven": "libs-release", + "gradle":"gradle-virtual", + "go": "go-virtual", + "docker":"docker-virtual", + "helm": "helm-virtual", + "nuget": "nuget-virtual" + }, + "cached_at": "2026-05-27T09:30:00Z", + "source": "verified", + "agentsConfigMtimeMs": 1719158400000 + } + } +} +``` + +Each `servers.<serverId>` entry holds `repositories`, `cached_at`, `source`, and +`agentsConfigMtimeMs` (mtime of `~/.jfrog/agents-conf.json` at last refresh). +The workspace binding file at +[`.jfrog/local/package-resolution.json`](workspace-binding.md) holds +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 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`, `gradle`, `go`, +`docker`, `helm`, `nuget`. Note `pypi` (not `pip`) — same convention the +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 package manager + +When you look up a package manager in this file, you get one of: + +| Class | Detect | What the resolver did | HTTP-verified? | +|---|---|---|---| +| **resolved (verified)** | `repositories.<pkg>` present, `source` is `verified` | Key from `~/.jfrog/agents-conf.json` `defaultGlobalRepos`, checked via `GET /api/repositories/<key>` | **Yes** (at last refresh) | +| **resolved (trusted)** | `repositories.<pkg>` present, `source` is `agents-config` | Key from agents-conf.json with `verifyRepos: false` | **No** | +| **unresolved** | `repositories.<pkg>` is missing | No mapping in agents-conf.json, verify failed, or type not configured | n/a | + +The skill relies on `jf setup --repo` to validate the repo key at apply +time (`GET /api/repositories/<repoKey>` inside the CLI). + +## Reading the cache from the skill + +The current `serverId` for this session comes from `jf config export` +(the default server). Read the cache with: + +```bash +SID="$(jf c show --server-id 2>/dev/null | awk '/Server ID/ {print $3; exit}')" +CACHE="$HOME/.jfrog/skills-cache/package-resolution.json" + +# Get a repo key for a package type (empty if unresolved): +jq -r --arg sid "$SID" --arg type "<pkgType>" '.servers[$sid].repositories[$type] // ""' "$CACHE" + +# Dump every resolved (pkgType, repoKey) pair for this SID: +jq -r --arg sid "$SID" '.servers[$sid].repositories | to_entries[] | "\(.key)\t\(.value)"' "$CACHE" + +# Inspect resolution source: +jq -r --arg sid "$SID" '.servers[$sid].source' "$CACHE" +``` + +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 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. + +## Not in this file + +These belong elsewhere and the skill must not look for them here: + +- Tokens, credentials, refresh tokens. (Stored by `jf config`.) +- Per-workspace bindings. (Stored in + [`.jfrog/local/package-resolution.json`](workspace-binding.md).) diff --git a/skills/jfrog-setup-package-managers/references/jf-setup-command.md b/skills/jfrog-setup-package-managers/references/jf-setup-command.md new file mode 100644 index 0000000..da87292 --- /dev/null +++ b/skills/jfrog-setup-package-managers/references/jf-setup-command.md @@ -0,0 +1,68 @@ +# `jf setup` Command Reference + +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 <package-manager> --server-id <SID> --repo <repo-key> [--project <project-key>] +``` + +Always pass `--server-id` and `--repo`. Without `--repo`, multiple matching +repos trigger an interactive prompt or error (`Please provide the repository +name using '--repo' flag`). + +`docker` / `podman` use the same shape — CLI validates the repo via +`GET /artifactory/api/repositories/<key>` before configuring. Record +`repositories.docker` in the workspace marker for pull URL composition. + +## Supported package-manager list + +Drifts across CLI versions — always parse from the installed binary: + +```bash +jf setup --help +``` + +Look for the "Supported package managers are:" line. Never hardcode. + +## Success and failure + +| Signal | Meaning | Action | +|---|---|---| +| Exit `0` | Success | Merge marker, continue | +| Non-zero | Failure | Stop; surface stdout+stderr verbatim | +| `repository <key> not found` | Bad key, wrong type, or permissions | AskQuestion for alternate repo | +| `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 package manager after a failure. + +## Agent notes + +### 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 new file mode 100644 index 0000000..24f135c --- /dev/null +++ b/skills/jfrog-setup-package-managers/references/workspace-binding.md @@ -0,0 +1,97 @@ +# `.jfrog/local/package-resolution.json` — Workspace Binding File + +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 package-manager-native files written by `jf setup` itself. + +## Location + +``` +<workspace-root>/.jfrog/local/package-resolution.json +``` + +`<workspace-root>` is the directory the user opened in the IDE — **not** +`$HOME`. Workspace-scoped on purpose: different projects can override +different Artifactory repos. + +## Schema + +```json +{ + "repositories": { + "npm": "<repository-key>", + "pypi": "<repository-key>", + "maven": "<repository-key>", + "gradle": "<repository-key>", + "go": "<repository-key>", + "docker": "<repository-key>", + "helm": "<repository-key>", + "nuget": "<repository-key>" + } +} +``` + +| Field | Required | Description | +|---|---|---| +| `repositories` | yes | Map keyed by **package type** — same keys as `servers.<serverId>.repositories` in the global resolver cache. Omit package types you do not override. | + +### Package-manager name → package type (when merging after `jf setup`) + +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`, `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` | +| `nuget`, `dotnet` | `nuget` | + +## Operations + +### 1. Load + +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.<type>` against what the resolver chose in Step 2: + +| Case | Action | +|---|---| +| Missing type in `repositories` | Run `jf setup` and merge in Step 6. | +| Same repo key | **Skip** `jf setup` — hook already applies overrides on session start. | +| Different repo key | Show diff and confirm via AskQuestion before overwriting. | + +### 2. Write / merge + +After each successful `jf setup`: + +1. Read the current file (treat ENOENT as `{ "repositories": {} }`). +2. Set `repositories[<pkgType>] = <repoKey>` using the package-manager → type table above. +3. Atomically write `{ "repositories": { ... } }` — preserve other package + types already in the map. + +JSON must use 2-space indent. + +### 3. Never write + +- Credentials (`accessToken`, passwords, …). +- Package-manager-native config paths — those are owned by `jf setup`. + +## Integration contract + +| Consumer | What it reads | +|---|---| +| Session-start hook | `repositories` — first workspace root with this file (multi-root) | +| This skill | Round-trip load → diff → confirm → write | +| `opencode-jfrog-plugin` | **Not updated** — out of scope until it reads this file | + +Changing the `repositories` key semantics is a breaking change; coordinate +with the hook before altering them. diff --git a/skills/jfrog/SKILL.md b/skills/jfrog/SKILL.md index 973ccf4..6ebbdb3 100644 --- a/skills/jfrog/SKILL.md +++ b/skills/jfrog/SKILL.md @@ -15,21 +15,78 @@ description: >- advanced security, exposures, or any JFrog product name. Do NOT use this skill to install, add, remove, list, or manage MCP servers. compatibility: >- - Requires jq on PATH. + Requires bash, jq, and awk on PATH. The JFrog CLI (jf) is required for Tier 2 + (CLI) and Tier 3 (jf api) operations; without it, only MCP (Tier 1) is available. metadata: role: base - version: "0.16.0" + version: "0.23.1" --- # JFrog Skill The foundational skill for all JFrog agent interactions. Covers JFrog Platform concepts, `jf` CLI setup and authentication, and intent routing to workflow skills. +## At a glance (always-read core) + +Network-facing `jf` this session. Exempt until `<SID>`: `jf --version`, +`jf config show`. + +**Tier A — always-read floor** (before first *non-exempt* `jf`): + +- **UA:** [Environment check](#environment-check) once → + `export JFROG_CLI_USER_AGENT='<UA>'` atop every bash that runs `jf` +- **Server:** resolve default once → `--server-id <SID>` **after** subcommand + (`jf api --server-id …`, never `jf --server-id … api`). One request → one + server (unless user names servers, e.g. `compare <a> and <b>`) +- **Error (401/403/404/timeout):** stop — never retry another server / never + infer multi-server. Override only if user names a server +- **No prep mutations:** missing repo/artifact/build → stop + report; no + create/copy/upload unless asked +- **Never guess** tools / `jf api` paths → tool list / `--help` / `references/`. + 404 → stop (no guessed retry). `jf api` needs product prefix + (`/artifactory`, `/xray`, …) +- **Hard-rule signals:** [Cautious execution](#cautious-execution), + [Server selection rules](#server-selection-rules-mandatory), + [Gotchas](#gotchas--hard-rules-never-skip) Tier A bullets below — not tips +- **Gotcha floor (Tier A):** never interactive (`jf config add`, `jf login`, + template wizards, …); if a call fails **with** `--server-id`, do **not** + retry without it; 401/403/404/timeout → stop, never hop servers; `--quiet` + is not global — check `--help` before adding it + +**Tier B — path-gated MUST** (before `jf api` / AQL / advanced CLI I/O / +MCP-result-via-shell anti-patterns): full +[`references/cli-gotchas.md`](references/cli-gotchas.md), +[`references/jf-api.md`](references/jf-api.md), +[`references/preserving-command-output.md`](references/preserving-command-output.md), +[`references/cli-command-discovery.md`](references/cli-command-discovery.md). +Setup / `jf setup` / ordinary CLI do **not** require Tier B. + +**Tier C — on-demand:** [`references/INDEX.md`](references/INDEX.md) domain +refs; login / CLI install when needed. + +Contents (prefer full SKILL.md; At a glance = Tier A floor if you only see the +top): + +| Section | Topic | +|---------|-------| +| [Tool selection strategy](#tool-selection-strategy) | MCP vs CLI vs `jf api` | +| [Prerequisites](#prerequisites), [Environment check](#environment-check) | before first non-exempt `jf` | +| [Cautious execution](#cautious-execution), [Server selection rules](#server-selection-rules-mandatory) | **Tier A hard rules** | +| [Gotchas — hard rules](#gotchas--hard-rules-never-skip) | Tier A reminders; full `cli-gotchas.md` = **Tier B** | +| [Path-gated base references](#path-gated-base-references-must-before-jf-api--advanced-cli) | **Tier B MUST** before `jf api` / advanced CLI | +| [When to read reference files](#when-to-read-reference-files) → [`references/INDEX.md`](references/INDEX.md) | Tier C domain refs | +| [Command discovery](#command-discovery) / [jf api](#invoking-platform-apis-with-jf-api) | Tier B when those paths apply | +| [Structured inputs](#structured-inputs) / [Batch](#batch-and-parallel-execution) / [Preserving output](#preserving-command-output) | templates / parallel / temp files | + +> **Floor for partial reads:** Tier A (this section) before first non-exempt +> `jf`. Prefer the full SKILL.md when you can. Load **Tier B** only when the +> next action needs `jf api` / AQL / advanced CLI I/O (checklist). Domain +> detail → Tier C [`references/INDEX.md`](references/INDEX.md). + Interact with the JFrog Platform through three tool tiers — see -[Tool selection strategy](#tool-selection-strategy). In code examples below, -`<skill_path>` refers to this skill's directory and is resolved automatically -by the agent. If the agent does not resolve it, determine the path by locating -this SKILL.md file and using its parent directory. +[Tool selection strategy](#tool-selection-strategy). In code examples, +`<skill_path>` is this skill's directory, resolved automatically by the agent. +If unresolved, locate this SKILL.md file and use its parent directory. > **Out of scope: MCP server management.** Installing, listing, removing, or > configuring MCP servers (e.g. "install an MCP", "what MCPs can I install", @@ -51,8 +108,8 @@ cover the operation or fails: subcommand. Validate the path first — see rule 6 in [Cautious execution](#cautious-execution). -MCP and the CLI may use different token scopes. If one tier returns 403, -try the alternate tier before reporting the operation blocked. +MCP and CLI may use different token scopes. One tier returns 403 → try the +other tier before reporting the operation blocked. ## Prerequisites @@ -82,23 +139,25 @@ thing to check — re-run with the appropriate escalation above. ## Environment check -MCP (Tier 1) operations do not require this check and can proceed immediately. -Before your first Tier 2 or Tier 3 (`jf`) operation in a session, run the -environment check and **remember its stdout** as `<UA>` for the rest of the -session: +MCP (Tier 1) skips this check — proceed immediately. Before your first Tier 2 +or Tier 3 (`jf`) operation this session, run the environment check and +**remember its stdout** as `<UA>` for the rest of the session: ```bash bash <skill_path>/scripts/check-environment.sh <model-slug> -# stdout (one line): jfrog-skills/<version> [(tool=<harness>; model=<model-slug>)] jfrog-cli-go/<cli-version> +# stdout (one line): jfrog-skills/<version> (trigger=skill; tool=<harness>; client=<app>; model=<model-slug>) jfrog-cli-go/<cli-version> +# Stack: trigger (skill vs hook) → client (app) → agent/tool (harness) → model. +# Keys after trigger= are present when known. On jf >= 2.120.0 the CLI emits +# ai-agent/ and ai-client/ itself, so tool=/client= are omitted here. +# `trigger=skill` always stays (APR hooks set `trigger=hook` when they spawn jf). # stderr: JSON state (cached 24h at ${JFROG_CLI_HOME_DIR:-$HOME/.jfrog}/skills-cache/jfrog-skill-state.json) ``` -Pass the precise underlying-model slug with version: `opus-4.7`, -`sonnet-4.5`, `gpt-5-codex`, `gemini-2.5-pro`, `composer-2-fast`. Cursor's -Composer product slug **is** the canonical id — use it as-is. Do **not** -pass harness/role names (`subagent`, `agent`, `assistant`) or bare family -names (`claude`, `gpt`); subagents inherit the parent's slug. If genuinely -unknown, pass `unknown`. +Pass your own model slug, lowercased, with version (e.g. `opus-4.7`, +`gpt-5.6-sol`, `gemini-2.5-pro`, `composer-2-fast`). Examples, not an +allowlist — emit a new/unlisted name verbatim, not `unknown`. Not +harness/role (`subagent`, `agent`) or bare family (`claude`, `gpt`); +subagents inherit the parent's slug. `unknown` only if truly unidentifiable. ### Export `JFROG_CLI_USER_AGENT` once per bash invocation @@ -107,14 +166,22 @@ all `jf` calls in that invocation pick it up: ```bash export JFROG_CLI_USER_AGENT='<UA>' +export JFROG_CLI_AI_MODEL='<model-slug>' # jf >= 2.120.0 emits ai-model/<slug> from this jf config show jf api /artifactory/api/system/version ``` +`JFROG_CLI_AI_MODEL` carries the model the CLI cannot infer from the environment; +export it alongside `<UA>` (same `<model-slug>` you passed the script). Older CLIs +ignore it and rely on the `model=` key already inside `<UA>`. + Do **not** repeat the assignment per `jf` call (`JFROG_CLI_USER_AGENT='<UA>' jf …` -on every line). Examples elsewhere in this skill and in `references/*.md` -omit the export for readability — the rule is global. When launching a -subagent, pass `<UA>` in its prompt; subagents do not re-run the script. +on every line). This is a **session-global invariant**: it applies to *every* +`jf` invocation in the session, including `jf` calls you make while following +any workflow skill that builds on this base skill. Examples elsewhere in this +skill and in `references/*.md` omit the export for readability — the rule is +global. When launching a subagent, pass `<UA>` in its prompt; subagents do not +re-run the script. | Exit | Meaning | |------|---------| @@ -151,8 +218,8 @@ command output* below. ## Cautious execution -Do not run commands speculatively. Before executing any JFrog CLI command, -MCP tool call, or API call: +**HARD RULES — never skip.** Speculative / preparatory / guessed ops are +forbidden. Before any JFrog CLI command, MCP tool call, or API call: 1. Confirm the operation is needed to fulfill the user's request. If the request is ambiguous or could refer to multiple systems (e.g. @@ -183,9 +250,15 @@ MCP tool call, or API call: ## Server selection rules (mandatory) -**Single-server invariant.** Every `jf` call MUST pass `--server-id <SID>` -(default resolved below); for one user request, all `jf` calls use **exactly -one** server-id. A wrong answer from the wrong server is worse than a stop-and-ask. +**HARD RULES — never skip or soften.** Wrong-server answers and silent +server-switching are worse than stopping to ask. + +**Single-server invariant.** After `<SID>` is resolved, every subsequent +network-facing `jf` call MUST pass `--server-id <SID>` (default resolved below); +bootstrap `jf --version` / `jf config show` stay exempt until then. For one user +request, all network `jf` calls use **exactly one** server-id — unless the user +names servers to compare (e.g. `compare <a> and <b>`), where each call passes +its own target's `--server-id`. **JFrog MCP and CLI use independent auth.** MCP tools authenticate through the MCP server session (not `jf config`); CLI commands authenticate through @@ -234,303 +307,135 @@ failure, **stop with no further `jf` calls** and respond: > configured server(s): `<list>` — I won't query them without your explicit > instruction. How would you like to proceed? -## When to read reference files - -Load the most specific file for the task at hand. Avoid loading more than 2-3 -reference files for a single operation — start with the most relevant one and -only load additional files if the first doesn't cover the need. File sizes -vary (~25–640 lines); larger files are noted with approximate line counts -below. - -### Cross-domain - -- **Disambiguating a JFrog entity, understanding entity types, or planning operations that span multiple products**: read `references/jfrog-entity-index.md`, then follow pointers to the relevant domain file -- **Looking up documentation URLs**: read `references/jfrog-url-references.md` - -### Artifactory - -- **Repository types, artifacts, builds, properties, or permission targets (concepts)**: read `references/artifactory-entities.md` (~220 lines) -- **Stored packages, package versions, version locations, or the metadata layer over Artifactory (concepts)**: read `references/stored-packages-entities.md` (~165 lines) -- **Repo, file, build, permission, user/group, or replication operations**: if the JFrog MCP server exposes a tool for the operation, prefer it. For CLI/API fallback, read `references/artifactory-operations.md` (for **listing builds** use AQL with `limit`/`offset` — see § *Listing build names*; for **full build detail** use `GET /api/build/<name>/<number>?project=` — see § *Retrieving full build info*) -- **AQL queries**: read `references/artifactory-aql-syntax.md` (~585 lines) -- **Artifactory REST beyond the CLI, structured JSON templates (replacing interactive wizards), or any Artifactory API gap**: read `references/artifactory-api-gaps.md` (~220 lines) - -### Xray & security - -- **Watches, policies, violations, components, or vulnerability scanning (concepts)**: read `references/xray-entities.md` (~290 lines) -- **Exposures scanning results (secrets, IaC, service misconfigurations, application security risks)**: read `references/xray-entities.md` § Exposures (Advanced Security) -- **Curation audit events (approved/blocked packages, dry-run policy evaluations, curation export)**: read `references/xray-entities.md` § Curation audit events +## Path-gated base references (MUST before `jf api` / advanced CLI) -### Release lifecycle & distribution +These four files **are Tier B of the base skill** — content that used to live +in this SKILL.md. They are **not** optional INDEX domain lookups, and they are +**not** required before every CLI / setup path. -- **Release bundles, lifecycle stages, distribution, or evidence (concepts)**: read `references/release-lifecycle-entities.md` (~180 lines) -- **Applications, application versions, releasables, promotions, or AppTrust (concepts)**: read `references/apptrust-entities.md` (~155 lines) +**MUST read every one in full before** `jf api`, AQL via `jf api`, advanced +CLI I/O (temp-file / stdout-stderr patterns), or acting on MCP results via +shell/`jq`. Ordinary `jf` (e.g. `jf setup`, `jf rt …` with known flags) needs +**Tier A only** ([At a glance](#at-a-glance-always-read-core)). -### Catalog +The short [Gotchas](#gotchas--hard-rules-never-skip) Tier A bullets are the +session floor — **they do not replace** full +[`references/cli-gotchas.md`](references/cli-gotchas.md) when you enter Tier B. -- **Public or custom catalog, package metadata, vulnerability advisories, licenses, OpenSSF, or MCP services (concepts)**: if the JFrog MCP server exposes a catalog tool, prefer it for single-package lookups. For deeper queries, read `references/catalog-entities.md` (~190 lines) -- **CVE details, vulnerability lookup by CVE ID, or severity/affected-packages/fix-versions for a specific CVE**: prefer an MCP vulnerability-lookup tool if the JFrog MCP server exposes one. Otherwise read `references/onemodel-query-examples.md` § *Public security domain* for the `searchVulnerabilities` query shape — this is self-contained; do not load the `jfrog-package-safety-and-download` skill for pure CVE lookups +| Tier B — MUST read in full (path-gated) | Covers | +|-----------------------------------------|--------| +| [`references/cli-gotchas.md`](references/cli-gotchas.md) | gotchas, caveats, known issues, do/don't, I/O & auth traps | +| [`references/jf-api.md`](references/jf-api.md) | product-prefix table, flags, examples, GraphQL payload | +| [`references/preserving-command-output.md`](references/preserving-command-output.md) | temp files, `$$` paths, no re-fetch for `jq` | +| [`references/cli-command-discovery.md`](references/cli-command-discovery.md) | namespaces, top-level cmds, Pipelines sunset | -### OneModel (GraphQL) +Skipping any of these **on a Tier B path** = incomplete base-skill load / +hard-rule violation. Skipping them on a Tier A-only path (setup / simple CLI) +is **not** a violation. -- **GraphQL queries** (applications, packages, evidence, release bundles, catalog, cross-domain, or "list/search my" platform entities): read `references/onemodel-graphql.md` (~325 lines) -- **Query templates and domain-specific examples**: read `references/onemodel-query-examples.md` (~555 lines) -- **Pagination, filtering, GraphQL variables, or date formatting**: read `references/onemodel-common-patterns.md` (~280 lines) - -### Platform administration - -- **Platform structure, project/repo membership, or project roles vs environments (concepts)**: read `references/platform-access-entities.md` -- **Access tokens, stats, projects, or system health**: read `references/platform-admin-operations.md` -- **Managing JFrog Projects, members, or environments**: read `references/projects-api.md` (~260 lines) -- **Platform REST beyond the CLI, or any platform-level API gap**: read `references/platform-admin-api-gaps.md` (~180 lines) - -### CLI setup & authentication - -- **Adding a server or logging in**: read `references/jfrog-login-flow.md` (~130 lines) -- **CLI not installed, upgrade needed, or `jq` unavailable**: read `references/jfrog-cli-install-upgrade.md` - -### General patterns +## When to read reference files -- **Batching, parallel Shell calls, or launching subagents**: read `references/general-parallel-execution.md` (~135 lines) -- **Large or parallel data gathering, list-vs-detail APIs, cache hygiene**: read `references/general-bulk-operations-and-agent-patterns.md` -- **Standalone HTML report with JFrog-aligned styling**: read `references/jfrog-brand-html-report.md` -- **Reusable gotchas from past tasks**: read or extend `references/general-use-case-hints.md` +Prefer reading this SKILL.md in full. [At a glance](#at-a-glance-always-read-core) +is the **Tier A** floor for partial readers. **Path-gated base references** +above are **Tier B** (mandatory on those paths, not every session). Everything +else under [`references/INDEX.md`](references/INDEX.md) is **Tier C** domain +detail — load ≤2–3 most specific files for the task; skip unused domains. +`references/INDEX.md` lists every `references/*.md` file (Tier B + Tier C). +Add/rename/remove a file → update INDEX in the same change — CI +(`tests/jfrog/test_reference_index_contract.py`) fails if they diverge. ## Command discovery -Use the commands listed below as your primary reference. Run `--help` to -verify options you are unsure about or to discover commands not listed here — -do not rely on memorized commands outside this skill, as they may be outdated. - -1. `jf --help` — list all namespaces and top-level commands -2. `jf <namespace> --help` — list subcommands in a namespace -3. `jf <command> --help` — show usage, arguments, and options - -### CLI namespaces - -| Namespace | Alias | Product | -|-----------|-------|---------| -| `rt` | | Artifactory | -| `xr` | | Xray | -| `ds` | | Distribution V1 | -| `at` | `apptrust` | AppTrust | -| `evd` | | Evidence | -| `mc` | | Mission Control | -| `worker` | | Workers | -| `config` | `c` | CLI server configuration | -| `plugin` | | CLI plugin management | -| `ide` | | IDE integration | - -> **Sunset notice:** JFrog Pipelines has been sunset and is no longer supported. -> Do not use the `pl` CLI namespace or the Pipelines REST API -> (`/pipelines/api/...`). If a user asks about Pipelines, inform them the -> product has been sunset. +Run `--help` to verify options — do not rely on memorized commands. -Top-level lifecycle commands (no namespace): `rbc`, `rbp`, `rbd`, `rba`, -`rbf`, `rbe`, `rbi`, `rbs`, `rbu`, `rbdell`, `rbdelr`. +1. `jf --help` → 2. `jf <namespace> --help` → 3. `jf <command> --help` -Top-level security commands: `audit`, `scan`, `build-scan`, `curation-audit`, -`sbom-enrich`. - -Top-level other: `access-token-create` (`atc`), `login`, `how`, `stats`, -`generate-summary-markdown`, `exchange-oidc-token`, `completion`. +**Tier B — MUST read in full before relying on discovery beyond `--help`:** +[`references/cli-command-discovery.md`](references/cli-command-discovery.md) +(namespaces, top-level lifecycle/security commands, Pipelines sunset). ## Invoking platform APIs with `jf api` -`jf api` is the Tier 3 entry point for JFrog Platform REST and GraphQL -endpoints, auto-authenticated against the resolved server. **Do not use -`jf rt curl` or `jf xr curl`**; they are superseded by `jf api`. - -### Product-prefix table - -`jf api` requires the **full** path including the product prefix; omitting it -returns 404. - -| Product | Path prefix | -|---------|-------------| -| Artifactory | `/artifactory/api/...` | -| Xray | `/xray/api/...` | -| Access (users, groups, tokens, permissions, projects) | `/access/api/...` | -| Evidence | `/evidence/api/...` | -| Release Lifecycle | `/lifecycle/api/...` | -| AppTrust | `/apptrust/api/...` | -| Distribution | `/distribution/api/...` | -| OneModel (GraphQL) | `/onemodel/api/v1/graphql`, `/onemodel/api/v1/supergraph/schema` | -| Mission Control | `/mc/api/...` | -| Curation | `/xray/api/v1/curation/...` (lives under Xray) | - -### Examples - -```bash -jf api /artifactory/api/repositories -jf api --server-id <SID> /artifactory/api/system/version - -# AQL (POST with text/plain body) -jf api /artifactory/api/search/aql \ - -X POST -H "Content-Type: text/plain" -d '<aql-query>' -``` - -Common flags: `-X/--method`, `-H/--header`, `-d/--data`, `--input <file>`, -`--server-id`, `--timeout`. Body on stdout, status on stderr — see -[Gotchas](#gotchas). - -### GraphQL (OneModel) - -OneModel is the unified GraphQL API. **Do not** embed the query inside a JSON -literal (`-d '{"query":"..."}'`) — escaping breaks requests. Build the payload -with `jq -n --arg`, pass it via `--input`, and save the response to a file -before running `jq` on it. - -```bash -QUERY='{ evidence { searchEvidence(first: 5, where: { hasSubjectWith: { repositoryKey: "my-repo-local" } }) { totalCount } } }' -PAYLOAD=/tmp/onemodel-payload-$$.json RESPONSE=/tmp/onemodel-$$.json -jq -n --arg q "$QUERY" '{query:$q}' > "$PAYLOAD" -jf api /onemodel/api/v1/graphql -X POST \ - -H "Content-Type: application/json" --input "$PAYLOAD" > "$RESPONSE" -jq . "$RESPONSE" -``` - -Schema discovery: `jf api /onemodel/api/v1/supergraph/schema > "$SCHEMA_FILE"` -(store only under `~/.jfrog/skills-cache/`, never query responses). Read -`references/onemodel-graphql.md` for the full workflow (schema fetch, -validation, pagination, errors), plus `references/onemodel-query-examples.md` -and `references/onemodel-common-patterns.md` for query shapes, pagination, -variables, and dates. +Tier 3 for Platform REST/GraphQL, auto-authenticated. **Do not use +`jf rt curl` / `jf xr curl`.** Always include the **product prefix** +(`/artifactory`, `/xray`, `/access`, …) — omit → 404. +**Tier B — MUST read in full before `jf api`:** +[`references/jf-api.md`](references/jf-api.md) +(prefixes, flags, examples, OneModel GraphQL payload). Body on stdout / status +on stderr — see [Gotchas](#gotchas--hard-rules-never-skip) + full +`cli-gotchas.md` (Tier B). ## Structured inputs -Several CLI commands require JSON template files. The templates are normally -created by interactive wizard commands (`jf rt rpt`, `jf rt ptt`, `jf rt rplt`) -which agents cannot use. Instead, retrieve an existing config via REST API as a -starting point and modify it: +Interactive wizards (`jf rt rpt` / `ptt` / `rplt`) are unusable for agents. +Fetch an existing config via REST and edit: ```bash jf api /artifactory/api/repositories/<repo-key> ``` -For other Artifactory or platform REST patterns, or when you need more than -this repo GET, see **Any API gap** under [When to read reference files](#when-to-read-reference-files). - -## Gotchas - -### MCP tools - -- MCP tools return structured data in the tool result. Read response fields - directly; do not pipe MCP output through shell commands or `jq`. - -### CLI and `jf api` - -- `jf api` requires the **product prefix** in the path. Omitting it returns - 404. See the [product-prefix table](#product-prefix-table) for the full list. -- `jf api` writes the body (success or error JSON) to **stdout** and - `[Info] Http Status: NNN` to **stderr** on every call; non-2xx also exits - 1 and adds `[Warn] jf api: <method> <url> returned NNN`. Pipe stdout to - `jq` directly; **never `2>&1 | jq`** — stderr corrupts the JSON. To keep - diagnostics: `jf api <path> 2>/tmp/err-$$.log | jq .`. -- `jf api` has **no `-L`** (follow redirects) and **no `-o`** (output file). - Save bodies with shell redirection - (`jf api ... > /tmp/out-$$.json`); for - binary downloads through the Artifactory remote proxy prefer `jf rt dl`, - which handles the cache and redirect semantics natively. -- Remote repository content is stored in a `-cache` suffixed repo. Properties - and AQL queries for remote repo artifacts must target the cache repo. - Conversely, `/api/repositories/<key>` only accepts the parent remote key - (without `-cache`) — strip the suffix for configuration lookups. -- **Do not use `jf rt search`** — always use a direct AQL query via - `jf api /artifactory/api/search/aql -X POST -H "Content-Type: text/plain" -d '<aql>'`. - See `references/artifactory-aql-syntax.md`. -- Use `--quiet` flag for non-interactive execution (suppresses confirmation - prompts). **Caution:** `--quiet` is not a global flag — commands that do not - support it (e.g. `jf rt s`, `jf rt ping`) will fail with misleading errors - like "Wrong number of arguments" or "flag provided but not defined". Check - `--help` for a command before adding `--quiet`. -- Use `--server-id` when targeting a non-default server. If a command fails - with `--server-id`, do not retry without it — that silently targets the - default server instead. See [Server selection rules](#server-selection-rules-mandatory). -- Never use interactive commands. All JFrog CLI operations must be performed - non-interactively. Known interactive commands to avoid: `jf config add`, - `jf login`, `jf rt repo-template`, `jf rt permission-target-template`, and - `jf rt replication-template`. For server setup, follow `references/jfrog-login-flow.md`. - For templates, use JSON schemas or REST API. If a command prompts for input - unexpectedly, find the non-interactive alternative via `--help` or REST API. -- `jf config export` output is base64-encoded JSON. Decode with - `base64 -d | jq` to extract fields. -- Build info lookups require a scope (`?buildRepo=` or `?project=`) — - resolve it before calling the API. See `references/artifactory-operations.md` - §Retrieving build info for the full workflow. -- If a `jf api` call returns 401, the configured token may have expired or - been rotated — ask the user to re-run the login flow (see - `references/jfrog-login-flow.md`) for the **same** server. If 403, the - token lacks required permissions. If 404, verify the endpoint path - (especially the product prefix) and target server version. On any of - these errors, do not try a different configured server as a workaround — - that targets a different environment. Report the error and ask the user. -- **Xray contextual analysis:** the summary artifact response has two - applicability fields — `applicability` (top-level, often null) and - `applicability_details` (always present with a `result` string). **Use - `applicability_details[].result` for counts and summaries.** Using the - top-level `applicability` field for aggregation produces wrong counts because - it is null when no scanner exists. See `references/xray-entities.md` - §Contextual analysis for the eight possible result values and jq snippets. -- **OneModel GraphQL:** always fetch the supergraph schema from the **same** - server you query before building operations (schemas differ by deployment); - cache, validate, and execute per `references/onemodel-graphql.md`. -- Never duplicate a network-fetching command to retry `jq` parsing — save the - response to a temp file first (see [Preserving command output](#preserving-command-output)). -- When collecting detail responses in a loop (e.g. per-repo GETs), validate - each body with `jq -e .` before appending to a results file. One non-JSON - or empty response corrupts a downstream `jq -s` slurp. Write validated - lines to an NDJSON file, then `jq -s '.' file.ndjson` to produce the final - array. See `references/general-bulk-operations-and-agent-patterns.md`. -- Accumulated edge cases from real tasks live in `references/general-use-case-hints.md` - — read when debugging odd failures; **append** a short entry when you confirm - a new, reusable gotcha. - +More REST/template patterns → `references/artifactory-api-gaps.md` or +`references/platform-admin-api-gaps.md` via [`references/INDEX.md`](references/INDEX.md). + +## Gotchas — hard rules (never skip) + +**Not tips.** Tier A bullets below are the always-read floor. Full +[`references/cli-gotchas.md`](references/cli-gotchas.md) is **Tier B** — +**MUST** before `jf api` / AQL / advanced CLI I/O / MCP-via-shell; **not** +required before every CLI or `jf setup`. Short bullets do **not** replace the +full file on Tier B paths. + +**Tier A floor (every non-exempt `jf` session):** + +- **`--quiet`** is not global — check `--help` before adding it +- **`--server-id`:** if a call fails with it, do not retry without it (silent + default-server switch). See [Server selection rules](#server-selection-rules-mandatory) +- **Non-interactive only** — avoid `jf config add`, `jf login`, `*template` + wizards; use `references/jfrog-login-flow.md` / REST +- **Auth errors:** 401 → re-login **same** server; 403 → permissions; 404 → + path/prefix/version. Never switch configured servers as a workaround + +**Tier B reminders (load full `cli-gotchas.md` + sibling Tier B refs before +these paths):** + +- **MCP:** read structured tool results directly — do not pipe through shell/`jq` +- **`jf api` I/O:** body → stdout, status → stderr; pipe stdout to `jq`; + **never `2>&1 | jq`**. No `-L` / `-o` — redirect: `jf api … > /tmp/out-$$.json` +- **Product prefix** required on every `jf api` path (see Tier B `jf-api.md`) +- **Never re-fetch to retry `jq`** — save output first + ([Preserving command output](#preserving-command-output) + Tier B + `preserving-command-output.md`) ## Batch and parallel execution -When a task requires multiple independent operations, use the lightest -parallelism mechanism that fits. Three tiers: (1) batch commands in a single -Shell call using loops or `&`, (2) issue parallel Shell tool calls, (3) launch -parallel subagents for large fan-out. Read `references/general-parallel-execution.md` -(~135 lines) for tier selection, examples, and subagent prompt structuring. +Independent ops → lightest parallelism: (1) loops/`&` in one Shell, (2) parallel +Shell calls, (3) subagents for large fan-out. Details → +`references/general-parallel-execution.md`. ## Preserving command output -When a CLI command or API call returns data, redirect the output to a temporary -file so you can re-read it without re-executing the call: - -```bash -OUT=/tmp/jf-repos-$$.json -jf api /artifactory/api/repositories > "$OUT" -echo "$OUT" -``` - -Use `$$` (the shell PID) in the filename to prevent collisions across -concurrent sessions or processes. - -**Cross-call gotcha:** each Shell tool invocation runs in a new process with a -different PID, so `$$` expands to a different value in each call. Always -**echo the expanded filename** so the agent can read it from the output and -reuse the literal path in subsequent calls. Three patterns, in priority order: - -1. **`$$` + echo** (preferred): use `$$` for collision safety, echo the path - as shown above. The agent reads `/tmp/jf-repos-12345.json` from the output - and passes that literal value to the next Shell call. -2. **Session ID**: when many files share a prefix across calls, generate an ID - once (`SID=$(date +%s)-$$`), echo it, and reuse in later calls. -3. **Hardcoded names**: last resort — risks collisions when parallel calls or - subagents write to the same path. - -This protects against wasted round-trips when you need to retry parsing — for -example, if a `jq` filter fails or you extract the wrong field on the first -attempt. Re-read the file instead of hitting the server again. - -Do **not** duplicate the same **network** request in a shell pipeline (e.g. with -`||`) only to re-run `jq` or to reveal jq diagnostics—the duplicate call -adds load on JFrog without fetching new data. Run -`jq '<filter>' /tmp/jf-*-$$.json` (or redirect stdin from the file) instead -of re-running the same `jf api` or other identical network-backed command. - -Do **not** reuse saved output across unrelated steps or changed contexts (different -server, user, or intent). The file is only valid for the immediate sequence of -operations that motivated the original call. +Save network responses to a temp file; echo the path; re-read for `jq` — never +re-run the same network call to fix parsing. + +**Tier B — MUST read in full before advanced I/O / re-parse patterns:** +[`references/preserving-command-output.md`](references/preserving-command-output.md) +(`$$` + echo, session id, no re-fetch / no cross-context reuse). + +## Before you run `jf` — quick checklist + +[At a glance](#at-a-glance-always-read-core) **Tier A** floor; add **Tier B** +only when the next action needs `jf api` / advanced CLI: + +- [ ] `export JFROG_CLI_USER_AGENT='<UA>'` in this bash +- [ ] network `jf`: `--server-id <SID>` after subcommand (not `jf --version` / + `jf config show` pre-SID) +- [ ] one server; error → stop, don't switch (multi only if user names / + `compare`) +- [ ] no prep create/copy/upload unless asked +- [ ] never guess tools/paths → list / `--help` / `references/`; 404 → stop; + `jf api` product prefix (`/artifactory`, `/xray`, …) +- [ ] **Tier A** hard rules: Cautious execution + Server selection + Gotchas + Tier A floor (interactive / `--server-id` retry / stop-on-error / + `--quiet`) +- [ ] **Tier B** (only if next action is `jf api` / AQL / advanced CLI I/O): + full `cli-gotchas.md`, `jf-api.md`, `preserving-command-output.md`, + `cli-command-discovery.md` diff --git a/skills/jfrog/references/INDEX.md b/skills/jfrog/references/INDEX.md new file mode 100644 index 0000000..c005c53 --- /dev/null +++ b/skills/jfrog/references/INDEX.md @@ -0,0 +1,83 @@ +# Reference index — when to read which file + +**Tier A** = `SKILL.md` At-a-glance floor (before first non-exempt `jf`). +**Tier B** = four files below — **MUST** before `jf api` / AQL / advanced CLI +I/O / MCP-via-shell; **not** before every CLI or `jf setup`. +**Tier C** = domain entries — ≤2–3 most specific; skip unused. Login / CLI +install when needed. + +Paths relative to skill root. List **every** `references/` file (except this +one). CI: `tests/jfrog/test_reference_index_contract.py`. + +--- + +## Tier B — path-gated (MUST before `jf api` / advanced CLI) + +Ordinary CLI / `jf setup` → Tier A only. Skipping any below on a Tier B path = +hard-rule violation. + +- **Gotchas / caveats / do-don'ts**: **MUST** `references/cli-gotchas.md` on Tier B — not replaceable by SKILL.md Tier A floor +- **`jf api` prefixes / flags / GraphQL**: **MUST** `references/jf-api.md` before `jf api` +- **Temp files / `$$` / no re-fetch**: **MUST** `references/preserving-command-output.md` before advanced I/O +- **Namespaces / top-level cmds / Pipelines sunset**: **MUST** `references/cli-command-discovery.md` when discovery beyond `--help` + +Tier C (when needed — not Tier B): + +- **Login / add server**: `references/jfrog-login-flow.md` +- **CLI install / upgrade / `jq` missing**: `references/jfrog-cli-install-upgrade.md` + +--- + +## Domain / on-demand (INDEX navigation) + +Load the most specific file for the task. Avoid more than 2–3 reference files +for one operation. + +## Cross-domain + +- **Disambiguating a JFrog entity, understanding entity types, or planning operations that span multiple products**: read `references/jfrog-entity-index.md`, then follow pointers to the relevant domain file +- **Looking up documentation URLs**: read `references/jfrog-url-references.md` + +## Artifactory + +- **Repository types, artifacts, builds, properties, or permission targets (concepts)**: read `references/artifactory-entities.md` +- **Stored packages, package versions, version locations, or the metadata layer over Artifactory (concepts)**: read `references/stored-packages-entities.md` +- **Repo, file, build, permission, user/group, or replication operations**: if the JFrog MCP server exposes a tool for the operation, prefer it. For CLI/API fallback, read `references/artifactory-operations.md` (for **listing builds** use AQL with `limit`/`offset` — see § *Listing build names*; for **full build detail** use `GET /api/build/<name>/<number>?project=` — see § *Retrieving full build info*) +- **AQL queries**: read `references/artifactory-aql-syntax.md` +- **Artifactory REST beyond the CLI, structured JSON templates (replacing interactive wizards), or any Artifactory API gap**: read `references/artifactory-api-gaps.md` + +## Xray & security + +- **Watches, policies, violations, components, or vulnerability scanning (concepts)**: read `references/xray-entities.md` +- **Exposures scanning results (secrets, IaC, service misconfigurations, application security risks)**: read `references/xray-entities.md` § Exposures (Advanced Security) +- **Curation audit events (approved/blocked packages, dry-run policy evaluations, curation export)**: read `references/xray-entities.md` § Curation audit events + +## Release lifecycle & distribution + +- **Release bundles, lifecycle stages, distribution, or evidence (concepts)**: read `references/release-lifecycle-entities.md` +- **Applications, application versions, releasables, promotions, or AppTrust (concepts)**: read `references/apptrust-entities.md` + +## Catalog + +- **Public or custom catalog, package metadata, vulnerability advisories, licenses, OpenSSF, or MCP services (concepts)**: if the JFrog MCP server exposes a catalog tool, prefer it for single-package lookups. For deeper queries, read `references/catalog-entities.md` +- **CVE details, vulnerability lookup by CVE ID, or severity/affected-packages/fix-versions for a specific CVE**: prefer an MCP vulnerability-lookup tool if the JFrog MCP server exposes one. Otherwise read `references/onemodel-query-examples.md` § *Public security domain* for the `searchVulnerabilities` query shape — this is self-contained; do not load the `jfrog-package-safety-and-download` skill for pure CVE lookups + +## OneModel (GraphQL) + +- **GraphQL queries** (applications, packages, evidence, release bundles, catalog, cross-domain, or "list/search my" platform entities): read `references/onemodel-graphql.md` +- **Query templates and domain-specific examples**: read `references/onemodel-query-examples.md` +- **Pagination, filtering, GraphQL variables, or date formatting**: read `references/onemodel-common-patterns.md` + +## Platform administration + +- **Platform structure, project/repo membership, or project roles vs environments (concepts)**: read `references/platform-access-entities.md` +- **Access tokens, stats, projects, or system health**: read `references/platform-admin-operations.md` +- **Managing JFrog Projects, members, or environments**: read `references/projects-api.md` +- **Platform REST beyond the CLI, or any platform-level API gap**: read `references/platform-admin-api-gaps.md` + +## General patterns + +- **Batching, parallel Shell calls, or launching subagents**: read `references/general-parallel-execution.md` +- **Large or parallel data gathering, list-vs-detail APIs, cache hygiene**: read `references/general-bulk-operations-and-agent-patterns.md` +- **Standalone HTML report with JFrog-aligned styling**: read `references/jfrog-brand-html-report.md` +- **Reusable gotchas from past tasks**: read or extend `references/general-use-case-hints.md` diff --git a/skills/jfrog/references/apptrust-entities.md b/skills/jfrog/references/apptrust-entities.md index 6ca5088..d1e014f 100644 --- a/skills/jfrog/references/apptrust-entities.md +++ b/skills/jfrog/references/apptrust-entities.md @@ -4,14 +4,13 @@ When to read this file: - Working with **applications**, **application versions**, or **releasables**. - Querying or managing **application version promotions** through stages. -- Understanding what **sources** (builds, release bundles, other app versions) feed into an application version. -- Using the OneModel GraphQL API with the `applications` query root. +- Understanding **sources** (builds, release bundles, other app versions) feeding an application version. +- OneModel GraphQL with `applications` query root. -AppTrust entities are accessed exclusively via the **OneModel GraphQL API** -(`/onemodel/api/v1/graphql`). There are no CLI commands for this domain. +AppTrust via **OneModel GraphQL API** (`/onemodel/api/v1/graphql`). No CLI. -For the OneModel query workflow (credentials, schema fetch, validation, -execution), read `references/onemodel-graphql.md`. +OneModel workflow (credentials, schema fetch, validation, execution): +`references/onemodel-graphql.md`. ## Entity relationship overview @@ -31,28 +30,26 @@ erDiagram ## Application -The top-level entity representing a software application registered in -AppTrust. Applications belong to a JFrog Project and serve as the -organizational container for tracking versions, ownership, and criticality. +Top-level software application in AppTrust. Belongs to a JFrog Project; +container for versions, ownership, criticality. | Field | Description | |-------|-------------| -| `key` | Unique identifier (referenced as `applicationKey` or `appKey` elsewhere) | -| `projectKey` | JFrog Project this application belongs to | +| `key` | Unique ID (`applicationKey` / `appKey` elsewhere) | +| `projectKey` | JFrog Project | | `displayName` | Human-readable name | | `criticality` | `unspecified`, `low`, `medium`, `high`, `critical` | | `maturityLevel` | `unspecified`, `experimental`, `production`, `end_of_life` | -| `owners` | List of users or groups that own the application | -| `labels` | Key-value pairs for custom categorization | +| `owners` | Owning users/groups | +| `labels` | Key-value categorization | Query: `applications.getApplication(key: "...")` or `applications.searchApplications(where: {...})`. ## Application version -A versioned instance of an application. Each version captures a specific set -of releasable artifacts, their sources, and a promotion history through -lifecycle stages. +Versioned instance of an application — releasable artifacts, sources, promotion +history through lifecycle stages. | Field | Description | |-------|-------------| @@ -61,20 +58,18 @@ lifecycle stages. | `tag` | Optional tag | | `status` | Processing status: `STARTED`, `FAILED`, `COMPLETED`, `DELETING` | | `releaseStatus` | Release maturity: `PRE_RELEASE`, `RELEASED`, `TRUSTED_RELEASE` | -| `currentStageName` | Most recent stage the version has been promoted to (null if never promoted) | +| `currentStageName` | Latest promoted stage (null if never promoted) | | `createdBy`, `createdAt` | Audit fields | | `evidenceSubject` | Evidence attestation anchor (shared across domains) | -The `releaseStatus` field is distinct from `status`: `status` tracks the -version creation process, while `releaseStatus` tracks its release maturity. +`releaseStatus` ≠ `status`: `status` = creation process; `releaseStatus` = release maturity. Query: `applications.getApplicationVersion(applicationKey: "...", version: "...")` or `applications.searchApplicationVersions(where: {...})`. ## Releasable -A deployable unit within an application version — either a **package version** -or an individual **artifact**. +Deployable unit within an application version — **package version** or individual **artifact**. | Field | Description | |-------|-------------| @@ -89,14 +84,12 @@ or an individual **artifact**. | `packageVersionLocation` | Link to `StoredPackageVersionLocation` for package releasables | | `vcsCommit` | VCS commit details (for AppTrust-bound package versions) | -Releasables bridge the application model to the underlying Artifactory -storage. The `packageVersionLocation` field connects to the Stored Packages -domain (see `stored-packages-entities.md`). +Releasables bridge application model to Artifactory storage. `packageVersionLocation` +→ Stored Packages domain (`stored-packages-entities.md`). ## Application version promotion -Records the promotion of an application version from one stage to another. -All promotions are recorded including failed attempts. +Promotion of application version between stages. All attempts recorded including failures. | Field | Description | |-------|-------------| @@ -107,13 +100,12 @@ All promotions are recorded including failed attempts. | `artifacts` | Artifacts included in this promotion (repo + path) | | `messages` | Error messages if the promotion failed | -Promotions use the same environment/stage model as Release Bundle promotions -(see `release-lifecycle-entities.md`) but at the application level. +Same environment/stage model as Release Bundle promotions +(`release-lifecycle-entities.md`), at application level. ## Sources -Sources describe how releasables were assembled into an application version. -Four types exist: +How releasables were assembled into an application version. Four types: | Source type | Fields | Description | |-------------|--------|-------------| @@ -122,8 +114,7 @@ Four types exist: | **ApplicationVersion** | `applicationKey`, `version` | Another application version (composition) | | **Direct** | (none) | Directly included without an associated build or bundle | -Sources appear at both the application version level (all sources) and the -individual releasable level (sources for that specific releasable). +At application version level (all sources) and releasable level (per-releasable sources). ## Artifacts (within application versions) @@ -139,16 +130,12 @@ Individual files within releasables. ## Cross-domain connections -AppTrust entities connect to other domains via the OneModel GraphQL API: +Via OneModel GraphQL: - **Evidence** — `ApplicationVersion.evidenceSubject` and - `ApplicationVersionArtifact.evidenceSubject` link to the Evidence domain - via `EvidenceSubject.fullPath`. This allows querying evidence attached to - app versions and their artifacts. -- **Stored Packages** — `Releasable.packageVersionLocation` links to - `StoredPackageVersionLocation`, connecting the application model to where - packages physically reside in Artifactory. -- **Release Bundles** — source type `ReleaseBundle` references release bundle - name/version from the Release Lifecycle domain. -- **Builds** — source type `Build` references build-info records from - Artifactory. + `ApplicationVersionArtifact.evidenceSubject` → Evidence domain via + `EvidenceSubject.fullPath`. +- **Stored Packages** — `Releasable.packageVersionLocation` → + `StoredPackageVersionLocation` (physical Artifactory location). +- **Release Bundles** — source type `ReleaseBundle` → Release Lifecycle name/version. +- **Builds** — source type `Build` → Artifactory build-info records. diff --git a/skills/jfrog/references/artifactory-api-gaps.md b/skills/jfrog/references/artifactory-api-gaps.md index 1533d8a..c125cf2 100644 --- a/skills/jfrog/references/artifactory-api-gaps.md +++ b/skills/jfrog/references/artifactory-api-gaps.md @@ -1,9 +1,8 @@ # Artifactory API Gaps -Operations available through REST API but not through CLI commands. -Invoke them via `jf api <path> [flags]` (authentication is handled -automatically against the active `jf config` server; see the base skill's -*Invoking platform APIs with `jf api`* section). +REST operations without CLI commands. Invoke via `jf api <path> [flags]` +(auth automatic against active `jf config` server; see base skill +*Invoking platform APIs with `jf api`*). ## Repository management @@ -11,8 +10,7 @@ automatically against the active `jf config` server; see the base skill's ```bash jf api /artifactory/api/repositories/<repo-key> ``` -Returns the full JSON configuration of a repository. Useful as a template -for creating similar repos. +Full JSON repo config. Useful as template for similar repos. ### List all repositories ```bash @@ -89,7 +87,7 @@ jf api /artifactory/api/search/aql \ -d 'items.find({"repo":"my-repo","name":{"$match":"*.jar"}})' ``` -For remote repository content, query the `-cache` suffixed repo: +Remote repo content — query `-cache` suffixed repo: ```bash jf api /artifactory/api/search/aql \ -X POST -H "Content-Type: text/plain" \ @@ -113,8 +111,8 @@ jf api "/artifactory/api/search/gavc?g=com.example&a=mylib&v=1.0" ## User and group management -User and group operations are handled by the Access service. See -`platform-admin-api-gaps.md` (Users / Groups sections) for the full set. +User/group operations via Access service. See +`platform-admin-api-gaps.md` (Users / Groups sections). ## Metadata calculation @@ -172,10 +170,9 @@ jf api "/artifactory/api/federation/fullSyncAll/<repo-key>" -X POST ### List builds (prefer scoped queries) -**Unscoped** `GET /artifactory/api/build` (no query parameters) can **time -out** on busy instances. Prefer **project-scoped** or **repo-scoped** -listing, then detail GETs. Full flow: read `artifactory-operations.md` -§ *Listing builds when the project key is known*. +**Unscoped** `GET /artifactory/api/build` can **time out** on busy instances. +Prefer project- or repo-scoped listing + detail GETs. Flow: `artifactory-operations.md` +§ *Listing build names*. ```bash # Project scope — build names (latest per name) diff --git a/skills/jfrog/references/artifactory-aql-syntax.md b/skills/jfrog/references/artifactory-aql-syntax.md index b120275..c174a63 100644 --- a/skills/jfrog/references/artifactory-aql-syntax.md +++ b/skills/jfrog/references/artifactory-aql-syntax.md @@ -18,14 +18,14 @@ jf api /artifactory/api/search/aql \ .distinct(<boolean>) ``` -Only `.find()` is required. The others are optional and chainable. -**The chain order above is enforced by the server.** `.include()` must come -before `.sort()`, `.sort()` before `.offset()`, etc. Putting them out of -order (e.g. `.sort()` before `.include()`) produces a parse error. +Only `.find()` is required; others optional and chainable. +**Server enforces the chain order above.** `.include()` before `.sort()`, +`.sort()` before `.offset()`, etc. Out of order (e.g. `.sort()` before +`.include()`) → parse error. -**Mandatory include fields:** `items` requires `"repo","path","name"`; -`builds` requires `"name","number","repo"`. Always include these even when -you only need a subset — narrow results with `jq` post-query instead: +**Mandatory include fields:** `items` → `"repo","path","name"`; `builds` → +`"name","number","repo"`. Always include these even when you need a subset — +narrow with `jq` post-query: ``` items.find({"name":"commons-lang3-3.12.0.jar"}) @@ -35,9 +35,7 @@ items.find({"name":"commons-lang3-3.12.0.jar"}) ## Domains -AQL has 13 queryable domains. Each domain represents a different entity type -and has its own set of fields. - +13 queryable domains — each entity type has its own fields. | Domain | Query name | Description | | -------------------- | ------------------- | ---------------------------------------------- | @@ -55,12 +53,10 @@ and has its own set of fields. | Release bundles | `releases` | Release bundle records | | Release bundle files | `release_artifacts` | Files within a release bundle | - ## Domain relationships -Domains connect through the following join paths. Cross-domain queries -traverse these links — fields from related domains can appear in criteria -and include clauses by prefixing the domain path. +Join paths below. Cross-domain queries traverse these links — related-domain +fields in criteria/includes use a prefixed domain path. ```mermaid erDiagram @@ -79,15 +75,12 @@ erDiagram release_artifacts }o--|| releases : "belongs to" ``` - - -**Key:** Items connect to build artifacts and dependencies through SHA-1 -checksum matching, not a direct key. This means a cross-domain query from -items to builds traverses: items → artifacts → modules → builds. +**Key:** Items ↔ build artifacts/dependencies via SHA-1 checksum match (not +a direct key). Path items → builds: items → artifacts → modules → builds. ### Cross-domain field paths -To reference a field from a related domain, use dot-separated domain paths: +Related-domain field → dot-separated domain path: ``` items.find({"artifact.module.build.name":"my-build"}) @@ -108,12 +101,11 @@ From builds: ## Fields by domain -Field types: `string`, `date`, `int`, `long`, `itemType` (`file`, `folder`, -or `any`). Fields marked "default" are returned without explicit `.include()`. +Types: `string`, `date`, `int`, `long`, `itemType` (`file`, `folder`, `any`). +"Default" = returned without explicit `.include()`. ### items - | Field | Type | Default | | --------------- | -------- | ------- | | `repo` | string | yes | @@ -133,23 +125,19 @@ or `any`). Fields marked "default" are returned without explicit `.include()`. | `original_md5` | string | no | | `original_sha1` | string | no | - -Computed field: `virtual_repos` — returns virtual repositories that include -the item's actual repository. Must use `.include("virtual_repos")` explicitly; -requires `repo`, `path`, `name` in the result set. +Computed: `virtual_repos` — virtual repos that include the item's actual +repo. Requires `.include("virtual_repos")` plus `repo`,`path`,`name` in +the result set. ### properties - | Field | Type | Default | | ------- | ------ | ------- | | `key` | string | yes | | `value` | string | yes | - ### stats - | Field | Type | Default | | ---------------------- | ------ | ------- | | `downloads` | int | yes | @@ -161,20 +149,16 @@ requires `repo`, `path`, `name` in the result set. | `remote_origin` | string | yes | | `remote_path` | string | yes | - ### item.infos - | Field | Type | Default | | ------------------- | ------ | ------- | | `props_modified` | date | yes | | `props_modified_by` | string | yes | | `props_md5` | string | yes | - ### builds - | Field | Type | Default | | ------------- | ------ | ------- | | `url` | string | yes | @@ -187,18 +171,14 @@ requires `repo`, `path`, `name` in the result set. | `modified_by` | string | yes | | `repo` | string | no | - ### modules - | Field | Type | Default | | ------ | ------ | ------- | | `name` | string | yes | - ### artifacts - | Field | Type | Default | | ------ | ------ | ------- | | `name` | string | yes | @@ -206,10 +186,8 @@ requires `repo`, `path`, `name` in the result set. | `sha1` | string | yes | | `md5` | string | yes | - ### dependencies - | Field | Type | Default | | ------- | ------ | ------- | | `name` | string | yes | @@ -218,19 +196,15 @@ requires `repo`, `path`, `name` in the result set. | `sha1` | string | yes | | `md5` | string | yes | - ### build.properties - | Field | Type | Default | | ------- | ------ | ------- | | `key` | string | yes | | `value` | string | yes | - ### build.promotions - | Field | Type | Default | | ------------ | ------ | ------- | | `created` | date | yes | @@ -240,19 +214,15 @@ requires `repo`, `path`, `name` in the result set. | `comment` | string | yes | | `user` | string | yes | - ### module.properties - | Field | Type | Default | | ------- | ------ | ------- | | `key` | string | yes | | `value` | string | yes | - ### releases - | Field | Type | Default | | -------------- | --------------------------- | ------- | | `name` | string | yes | @@ -263,18 +233,14 @@ requires `repo`, `path`, `name` in the result set. | `type` | string (`SOURCE`, `TARGET`) | yes | | `storing_repo` | string | yes | - ### release_artifacts - | Field | Type | Default | | ------ | ------ | ------- | | `path` | string | yes | - ## Comparators - | Operator | Meaning | Example | | ---------- | -------------------------------- | ------------------------------------ | | `$eq` | Equals (default if omitted) | `{"type":"file"}` | @@ -288,16 +254,13 @@ requires `repo`, `path`, `name` in the result set. | `$lt` | Less than | `{"size":{"$lt":"5000"}}` | | `$lte` | Less than or equal | `{"modified":{"$lte":"2025-01-01"}}` | - ### Boolean operators - | Operator | Description | | -------- | ---------------------------------------------------------------------- | | `$and` | All conditions must match (implicit when fields are at the same level) | | `$or` | Any condition must match | - ``` items.find({"$and":[ {"repo":"my-repo"}, @@ -310,22 +273,18 @@ items.find({"$and":[ ### Relative date comparators -AQL supports relative date queries with `$last` and `$before`: - +`$last` / `$before` for relative dates: | Operator | Meaning | Example | | --------- | ------------------------------------------------------- | ------------------------------- | | `$last` | Within the last N period (equivalent to `$gt` from now) | `{"modified":{"$last":"7d"}}` | | `$before` | Before the last N period (equivalent to `$lt` from now) | `{"created":{"$before":"3mo"}}` | - -Supported units: `d` (days), `w` (weeks), `mo` (months), `y` (years), -`s` (seconds), `mi` (minutes), `ms` (milliseconds). +Units: `d`, `w`, `mo`, `y`, `s`, `mi`, `ms`. ### Multi-property AND -To match items that have property A=1 **and** property B=2 (different -property rows), use `$and` with `@` shorthand: +Match property A=1 **and** B=2 (different property rows) with `$and` + `@`: ``` items.find({"$and":[ @@ -334,20 +293,18 @@ items.find({"$and":[ ]}) ``` -AQL also documents a `$msp` (multi-set property) operator for this purpose, -but `$msp` is **unreliable in practice** — it returns 0 results on many -server versions even when matching items exist. Prefer `$and` with `@` -shorthand, which is verified to work correctly. +`$msp` (multi-set property) is **unreliable in practice** — often 0 results +even when matches exist. Prefer `$and` + `@` (verified). ## Date queries -Dates use ISO 8601 format for absolute dates: +Absolute dates → ISO 8601: ``` items.find({"modified":{"$gt":"2025-06-01T00:00:00.000Z"}}) ``` -Or use relative dates (preferred — avoids hardcoding timestamps): +Or relative dates (preferred — no hardcoded timestamps): ``` items.find({"modified":{"$last":"30d"}}) @@ -356,9 +313,9 @@ items.find({"created":{"$before":"6mo"}}) ## Property queries -Two equivalent syntaxes for property filtering: +Two equivalent property-filter syntaxes: -**`@key` shorthand** — concise, works for single property conditions: +**`@key` shorthand** — concise, single property conditions: ``` items.find({"repo":"my-repo","@build.name":"my-build","type":"file"}) @@ -374,15 +331,8 @@ items.find({ }) ``` -**Multi-property AND** — use `$and` with `@` shorthand to match across -different property rows: - -``` -items.find({"$and":[ - {"@build.name":"my-build"}, - {"@build.number":"42"} -]}) -``` +**Multi-property AND** — same `$and` + `@` pattern as +[Multi-property AND](#multi-property-and) above (do not re-copy here). > **Note:** The `@key` shorthand works inside `$and`. For `$or`, use the > explicit `property.key`/`property.value` form if the shorthand does not @@ -390,15 +340,12 @@ items.find({"$and":[ ## Include -Select which fields to return. Without `.include()`, AQL returns each -domain's default field set. +Fields to return. No `.include()` → domain defaults. -**When you use `.include()`, you replace the defaults — so you must -explicitly list any required fields:** +**`.include()` replaces defaults — list every required field:** -- `items` domain: always include `"repo","path","name"` (server rejects -the query otherwise) -- `builds` domain: always include `"name","number","repo"` +- `items`: always `"repo","path","name"` (else server rejects) +- `builds`: always `"name","number","repo"` ``` items.find({"repo":"my-repo"}) @@ -421,14 +368,14 @@ items.find({"repo":"my-repo"}) .limit(50) ``` -Sort directions: `$asc`, `$desc`. Sort fields must also appear in the result -set (explicit `.include()` or default fields). See +Sort: `$asc` / `$desc`. Sort fields must appear in the result set +(explicit `.include()` or defaults). See [Before constructing a query](#before-constructing-a-query) for sort performance rules. ## Distinct -Deduplicate result rows: +Deduplicate rows: ``` items.find({"repo":"my-repo"}).distinct(true) @@ -436,41 +383,37 @@ items.find({"repo":"my-repo"}).distinct(true) ## Validation rules -The server enforces these constraints — violating them produces an error: +Server constraints — violations → error: -**Non-admin users:** +**Non-admin:** -- `items` domain queries must include `repo`, `path`, `name` in results -(needed for permission filtering) -- `builds` domain queries must include `name`, `number`, `repo` in results +- `items` results must include `repo`, `path`, `name` (permission filtering) +- `builds` results must include `name`, `number`, `repo` -**Transitive mode** (`.transitive()` for querying through virtual repos): +**Transitive** (`.transitive()` through virtual repos): -- Only works with `items` domain +- `items` domain only - Include subdomains limited to `items` and `properties` -- Repo criteria must use `$eq` (exact match) with a single repository -- No `offset` or `sort` allowed +- Repo criteria: `$eq` only, single repository +- No `offset` or `sort` ## Before constructing a query -Run through these checks before writing any AQL query: - -1. **Never `.sort()` without a `repo` filter** — forces a full table scan - across all repositories. Sort client-side with `jq` instead. Also, - `.sort()` on cross-domain fields (e.g. `stat.downloads` in `items.find()`) - is silently ignored — fetch all rows and sort client-side. -2. **Always set `.limit()`** — no built-in default limit; unbounded queries - can time out or OOM. Broad queries without a `repo` filter are especially - expensive. -3. **`range.total` = returned count, not total matching** — AQL has no - count-only mode. To find the true total, paginate with `.offset()` until - a page returns fewer results than the limit. -4. **AQL has no repo-type field** — to restrict to local repos, either - pre-query `GET /api/repositories?type=local` and add repo names to - criteria (practical when count is small), or query without a repo filter - and exclude `-cache` / `-virtual` suffixed repos client-side with `jq`. +Checks before writing AQL: + +1. **Never `.sort()` without a `repo` filter** — full table scan. Sort + client-side with `jq`. Cross-domain sort fields (e.g. `stat.downloads` in + `items.find()`) are silently ignored — fetch all + sort client-side. +2. **Always `.limit()`** — no default; unbounded queries can time out / OOM. + Broad queries without `repo` are especially expensive. +3. **`range.total` = returned count, not total matching** — no count-only + mode. True total → paginate `.offset()` until a page returns fewer than + the limit. +4. **No repo-type field** — local-only: pre-query + `GET /api/repositories?type=local` and add names to criteria (small lists), + or query without repo filter and drop `-cache`/`-virtual` via `jq`. 5. **Narrow server-side first** — add every applicable filter (`created_by`, - `created`, `type`, `name`) before relying on client-side `jq` filtering. + `created`, `type`, `name`) before client-side `jq`. ## Common query patterns @@ -488,8 +431,8 @@ items.find({"repo":"my-repo","size":{"$gt":"104857600"},"type":"file"}) ### Find Maven SNAPSHOT JARs -Use `*-SNAPSHOT*.jar` (not `*-SNAPSHOT.jar`) to also match classifier -artifacts like `-sources.jar` and `-javadoc.jar`: +Use `*-SNAPSHOT*.jar` (not `*-SNAPSHOT.jar`) to also match classifiers +(`-sources.jar`, `-javadoc.jar`): ``` items.find({"repo":"libs-snapshot","name":{"$match":"*-SNAPSHOT*.jar"},"type":"file"}) @@ -505,9 +448,9 @@ items.find({"repo":"my-repo","modified":{"$last":"7d"},"type":"file"}) ### Docker queries -Use `"name":"manifest.json"` to **list tags** (one per tag). Use -`"name":{"$match":"*manifest.json"}` to **query all manifests** (includes -`list.manifest.json` for multi-arch tags — see [Gotchas](#gotchas)). +`"name":"manifest.json"` → **list tags** (one per tag). +`"name":{"$match":"*manifest.json"}` → **all manifests** (includes +`list.manifest.json` for multi-arch — see [Gotchas](#gotchas)). ``` items.find({"repo":"docker-local","path":{"$match":"my-image/*"},"name":"manifest.json"}) @@ -523,18 +466,16 @@ jf api "/artifactory/api/docker/<repo>/v2/<image>/manifests/<tag>" \ -H "Accept: application/vnd.docker.distribution.manifest.v2+json" ``` -For multi-arch images the response is an image index; fetch each platform -manifest by digest to get its layers. +For multi-arch: response is an image index — fetch each platform manifest +by digest for layers. ### Find artifacts with a specific property -``` -items.find({"repo":"my-repo","@build.name":"my-build","type":"file"}) -``` +See [Property queries](#property-queries) (`@key` shorthand and explicit form). ### Find never-downloaded files (zero download count) -Zero-download items lack a stats row — filter client-side instead +Zero-download items lack a stats row — filter client-side (see [Gotchas](#gotchas)): ```bash @@ -547,8 +488,8 @@ items.find({"repo":"my-repo","type":"file"}) ### Find artifacts not downloaded in 90 days -Only matches previously-downloaded items (see [Gotchas](#gotchas)). -Combine with the never-downloaded pattern above for full coverage. +Only previously-downloaded items (see [Gotchas](#gotchas)). Combine with +never-downloaded pattern above for full coverage. ``` items.find({ @@ -569,8 +510,7 @@ items.find({"artifact.module.build.name":"my-service"}) ### Find builds by name -Non-admin users must include `name`, `number`, `repo` — omitting any -produces an error. +Non-admin must include `name`, `number`, `repo` — omit any → error. ``` builds.find({"name":{"$match":"*my-service*"}}) @@ -595,8 +535,8 @@ dependencies.find({"module.build.name":"my-service","module.build.number":"42"}) ### Remote repository content -Remote repo artifacts are stored in a `-cache` suffixed repo. Always query -the cache repo, not the remote repo itself: +Remote artifacts live in a `-cache` suffixed repo. Query the cache, not the +remote itself: ``` items.find({"repo":"npm-remote-cache","name":{"$match":"*.tgz"}}) diff --git a/skills/jfrog/references/artifactory-entities.md b/skills/jfrog/references/artifactory-entities.md index 1e4d950..59e650b 100644 --- a/skills/jfrog/references/artifactory-entities.md +++ b/skills/jfrog/references/artifactory-entities.md @@ -2,44 +2,39 @@ When to read this file: -- Working with **repositories** and you need to understand the difference between local, remote, virtual, and federated types. +- Working with **repositories** — need local/remote/virtual/federated type differences. - Managing **artifacts**, **properties**, or **package types**. - Working with **builds**, **build promotion**, or **permission targets**. -- Debugging unexpected behavior related to repo types (e.g. upload failures, missing search results). +- Debugging repo-type issues (e.g. upload failures, missing search results). -For CLI commands see `artifactory-operations.md`. For API gaps see -`artifactory-api-gaps.md`. For AQL syntax see `artifactory-aql-syntax.md`. +CLI: `artifactory-operations.md`. API gaps: `artifactory-api-gaps.md`. AQL: `artifactory-aql-syntax.md`. ## Repositories -A repository is the primary storage and resolution unit in Artifactory. Every -repo has a **key** (unique identifier), a **package type** (immutable after -creation), and a **repository class** (`rclass`) that determines its behavior. +Repository = primary storage/resolution unit in Artifactory. Each repo has **key** (unique id), **package type** (immutable after creation), **repository class** (`rclass`) determining behavior. ### Repository types | Type | `rclass` | Behavior | Stores artifacts? | |------|----------|----------|-------------------| | **Local** | `local` | Hosts artifacts deployed directly (upload, promote, copy, move) | Yes | -| **Remote** | `remote` | Proxies an external URL; downloads are cached in a companion `-cache` repo | Only in the `-cache` repo | -| **Virtual** | `virtual` | Aggregates multiple local and remote repos under a single URL for resolution | No (resolves from underlying repos) | -| **Federated** | `federated` | Local repo that bi-directionally synchronizes across Platform Deployments | Yes (replicated across sites) | +| **Remote** | `remote` | Proxies external URL; downloads cached in companion `-cache` repo | Only in `-cache` repo | +| **Virtual** | `virtual` | Aggregates local + remote repos under single URL for resolution | No (resolves from underlying repos) | +| **Federated** | `federated` | Local repo bi-directionally syncs across Platform Deployments | Yes (replicated across sites) | ### Key relationships and fields - `key` — unique repo identifier (e.g. `libs-release-local`) -- `packageType` — determines layout and protocol (see Package types below) +- `packageType` — layout + protocol (see Package types below) - `rclass` — `local`, `remote`, `virtual`, or `federated` -- `url` — (remote only) the external source URL being proxied +- `url` — (remote only) external source URL being proxied - `repositories` — (virtual only) ordered list of local/remote repos to aggregate -- `projectKey` — links repo to a JFrog Project (see `platform-access-entities.md`) -- `environments` — environments the repo is assigned to (used in RBAC and lifecycle) +- `projectKey` — links repo to JFrog Project (see `platform-access-entities.md`) +- `environments` — repo environment assignment (RBAC + lifecycle) ### System repositories -Artifactory and Xray maintain several **system repositories** for internal -platform metadata. These are not user-created and should be excluded when -iterating over repositories for reporting, scanning, or auditing: +Artifactory + Xray maintain **system repositories** for internal platform metadata. Not user-created — exclude when iterating repos for reporting, scanning, or auditing: | Pattern | Purpose | |---------|---------| @@ -51,34 +46,23 @@ iterating over repositories for reporting, scanning, or auditing: | `*-build-info` | Project-scoped build info storage | | `*-application-versions` | AppTrust application version metadata | -Including these in aggregate queries (violation counts, storage reports, etc.) -produces misleading results because they contain platform metadata rather than -user artifacts. +Including these in aggregate queries (violation counts, storage reports, etc.) produces misleading results — platform metadata, not user artifacts. ### Remote repository cache -When Artifactory downloads an artifact through a remote repo, it stores the -cached copy in a **separate local repo** named `<remote-key>-cache`. This is -critical for: +When Artifactory downloads via remote repo, cached copy stored in **separate local repo** named `<remote-key>-cache`. Critical for: -- **AQL queries** — search the `-cache` repo, not the remote repo key -- **Properties** — properties on cached artifacts live on the `-cache` repo -- **Storage calculations** — cached artifacts consume storage under the `-cache` repo +- **AQL queries** — search `-cache` repo, not remote repo key +- **Properties** — cached artifact properties live on `-cache` repo +- **Storage calculations** — cached artifacts consume storage under `-cache` repo -The remote repo key itself is used for **configuration** (URL, credentials, -inclusion/exclusion patterns) but does not directly contain artifacts. +Remote repo key used for **configuration** (URL, credentials, inclusion/exclusion patterns) — does not directly contain artifacts. ### Virtual repository resolution -A virtual repo aggregates **both local and remote repos** under a single URL. -It resolves artifacts by searching its underlying repos in the configured -**order** — when the same artifact exists in multiple underlying repos, the -first match wins. +Virtual repo aggregates **local + remote repos** under single URL. Resolves by searching underlying repos in configured **order** — same artifact in multiple repos → first match wins. -A virtual repo may designate one of its underlying **local** repos as the -**default deployment repository**. Uploads through the virtual URL are routed -to that local repo. Without a default deployment repo, the virtual repo is -read-only. +Virtual repo may designate underlying **local** repo as **default deployment repository**. Uploads through virtual URL routed there. Without default deployment repo → read-only. ```mermaid erDiagram @@ -90,65 +74,56 @@ erDiagram ## Artifacts -An artifact is a file stored in a repository. Each artifact is uniquely -identified by the triple **repo + path + name**. +Artifact = file in repository. Uniquely identified by **repo + path + name**. Key attributes: - `repo`, `path`, `name` — location identifier - `size` — bytes -- `sha256`, `sha1`, `md5` — checksums (sha256 is the primary identifier for cross-referencing with builds and Xray) +- `sha256`, `sha1`, `md5` — checksums (build-info records all three; Xray cross-references by sha256, AQL item↔build joins by sha1) - `created`, `modified`, `created_by`, `modified_by` — audit fields -Artifacts are **content-addressable** — build info and Xray reference them by -checksum, not by path. Moving or copying an artifact changes its path but not -its checksum, so build associations follow the artifact. +Artifacts are **content-addressable** — build info + Xray reference by checksum, not path. Move/copy changes path, not checksum → build associations follow artifact. ## Properties -Key-value metadata pairs attached to artifacts or folders. +Key-value metadata on artifacts or folders. -- Keys are strings; values are strings or arrays of strings -- Set via `jf rt set-props`, queried via AQL or the properties API -- Commonly used for: build metadata, maturity labels, promotion tracking, cleanup policies -- Properties on remote-cached artifacts live on the `-cache` repo +- Keys = strings; values = strings or string arrays +- Set via `jf rt set-props`, query via AQL or properties API +- Common uses: build metadata, maturity labels, promotion tracking, cleanup policies +- Remote-cached artifact properties live on `-cache` repo ## Package types -The `packageType` field on a repository determines how Artifactory interprets -its contents. It controls directory structure conventions, metadata extraction, -and which client protocols are supported (e.g. Docker registry API, npm -registry, Maven layout). +`packageType` on repo determines how Artifactory interprets contents — directory layout, metadata extraction, client protocols (Docker registry API, npm registry, Maven layout). Common types: `maven`, `gradle`, `npm`, `docker`, `pypi`, `nuget`, `go`, `helm`, `rpm`, `debian`, `generic`. -Package type is **immutable** — it cannot be changed after repo creation. Use -`generic` when no specific package type applies. +Package type **immutable** — cannot change after repo creation. Use `generic` when no specific type applies. ## Build info -A build info record captures CI/CD metadata: which artifacts were produced, -which dependencies were consumed, and the build environment. +Build info record captures CI/CD metadata: produced artifacts, consumed dependencies, build environment. | Field | Description | |-------|-------------| -| `name` + `number` | Unique identifier for a build run | -| `modules` | List of modules, each with its own artifacts and dependencies | -| `vcs` | Version control metadata (revision, URL, branch) | +| `name` + `number` | Unique build run identifier | +| `modules` | Modules, each with artifacts + dependencies | +| `vcs` | VCS metadata (revision, URL, branch) | | `buildAgent`, `agent` | CI tool info | | `properties` | Custom build-level properties | -Build info references artifacts **by checksum** (sha256). This means: -- A build can reference artifacts across multiple repositories -- Moving an artifact does not break the build association -- Xray scans build info by resolving checksums to components +Build info references artifacts **by checksum** (AQL item↔build joins by sha1; Xray cross-references by sha256): +- Build can reference artifacts across multiple repos +- Moving artifact does not break build association +- Xray scans build info by resolving checksums → components Lifecycle: collect → publish → (optionally) promote → (optionally) scan. ## Build promotion -Promotion changes a build's **status** and can copy or move its artifacts -from a source repo to a target repo. +Promotion changes build **status**; can copy/move artifacts from source repo → target repo. | Field | Description | |-------|-------------| @@ -157,18 +132,15 @@ from a source repo to a target repo. | `targetRepo` | Where artifacts should be moved/copied | | `copy` | If `true`, copy instead of move | -Promotion records are queryable via AQL (`build.promotions` domain) and the -build promotion API. +Promotion records queryable via AQL (`build.promotions` domain) + build promotion API. ## Permissions -Permissions define RBAC policies mapping **resources** and **principals** -(users and groups) to **actions**. Two models exist: +Permissions = RBAC policies mapping **resources** + **principals** (users, groups) → **actions**. Two models: ### Permissions V2 (Access Permissions) — current model -Managed by the **Access service** (since Artifactory 7.72.0, recommended from -7.77.2). Supports all resource types. +**Access service** (since 7.72.0, recommended 7.77.2+). All resource types. | Component | Description | |-----------|-------------| @@ -179,11 +151,11 @@ Resource types: `artifact` (repositories), `build`, `release_bundle`, `destination` (Edge nodes), `pipeline_source`. Each resource contains: -- `targets` — map of target names/patterns to include/exclude patterns -- `actions.users` — map of username → list of actions -- `actions.groups` — map of group name → list of actions +- `targets` — target names/patterns → include/exclude patterns +- `actions.users` — username → action list +- `actions.groups` — group name → action list -Actions use uppercase: `READ`, `ANNOTATE`, `DEPLOY/CACHE`, `DELETE/OVERWRITE`, +Actions uppercase: `READ`, `ANNOTATE`, `DEPLOY/CACHE`, `DELETE/OVERWRITE`, `MANAGE_XRAY_METADATA`, `MANAGE`. API: `POST/PUT/GET/DELETE /access/api/v2/permissions/{permissionName}`. @@ -192,17 +164,15 @@ Documentation: [Permissions](https://docs.jfrog.com/administration/docs/permissi ### Permission targets (V1) — legacy model -Managed by **Artifactory**. Still functional and backwards compatible, but -V2 is recommended for new implementations. The CLI `jf rt permission-target-*` -commands use this API. +**Artifactory**-managed. Functional + backwards compatible; prefer V2 for new work. CLI: `jf rt permission-target-*`. | Component | Description | |-----------|-------------| -| `repositories` | List of repo keys or patterns | -| `actions.users` | Map of username → list of actions | -| `actions.groups` | Map of group name → list of actions | +| `repositories` | Repo keys or patterns | +| `actions.users` | Username → action list | +| `actions.groups` | Group name → action list | -Actions use lowercase: `read`, `write`, `annotate`, `delete`, `manage`. +Actions lowercase: `read`, `write`, `annotate`, `delete`, `manage`. Does **not** support `destination` or `pipeline_source` resource types. @@ -219,18 +189,15 @@ API: `PUT /artifactory/api/security/permissions/{permissionName}`. | Pattern fields | `includes_pattern` / `excludes_pattern` | `include_patterns` / `exclude_patterns` | | CLI support | `jf rt permission-target-*` | No direct CLI commands (use REST) | -For project-scoped RBAC, see Project roles in `platform-access-entities.md`. +Project-scoped RBAC: see Project roles in `platform-access-entities.md`. ## Replication -Replication synchronizes artifacts and properties between repositories, either -within the same instance or across Platform Deployments. +Replication syncs artifacts + properties between repos — same instance or across Platform Deployments. | Type | Direction | Trigger | |------|-----------|---------| -| **Push** | Source pushes to target | Scheduled or event-based | -| **Pull** | Target pulls from source | Scheduled | +| **Push** | Source → target | Scheduled or event-based | +| **Pull** | Target ← source | Scheduled | -Replication configs are JSON templates applied per repository. Both artifact -content and properties are replicated. For federated repos, replication is -automatic and bi-directional across all member nodes. +Replication configs = JSON templates per repository. Both artifact content + properties replicated. Federated repos → automatic bi-directional replication across member nodes. diff --git a/skills/jfrog/references/artifactory-operations.md b/skills/jfrog/references/artifactory-operations.md index bab2b65..771584b 100644 --- a/skills/jfrog/references/artifactory-operations.md +++ b/skills/jfrog/references/artifactory-operations.md @@ -1,22 +1,20 @@ # Artifactory Operations -CLI commands for managing Artifactory resources. All commands use the `jf rt` -namespace. Run `jf rt --help` to discover subcommands not listed here. +CLI for Artifactory resources — `jf rt` namespace. Run `jf rt --help` for subcommands not listed here. ## Repository management -Repositories are created from JSON templates. The workflow is: +Repos from JSON templates: -1. Get a template: retrieve an existing repo config via +1. Get template: existing config via `jf api /artifactory/api/repositories/<repo-key>` - and modify it, or craft JSON manually. + and modify, or craft JSON manually. Note: `jf rt repo-template` is interactive and cannot be used by agents. 2. Create: `jf rt repo-create <template.json>` 3. Update: `jf rt repo-update <template.json>` 4. Delete: `jf rt repo-delete <repo-pattern> --quiet` -To list repositories, use: -`jf api /artifactory/api/repositories` +List: `jf api /artifactory/api/repositories` ## File operations @@ -31,12 +29,12 @@ To list repositories, use: ### Searching across repositories -`jf rt search` expects a `<repo>/<pattern>` argument. When the repo is unknown, -agents tend to use a leading wildcard (`jf rt search "*/path/..."`), which -generates an unscoped AQL internally and can time out on large instances. +`jf rt search` expects `<repo>/<pattern>`. When repo unknown, agents often use +leading wildcard (`jf rt search "*/path/..."`) → unscoped AQL internally → +timeouts on large instances. -Use a direct AQL query with `name` and `path` criteria instead — omitting the -`repo` field searches all accessible repos via indexed columns: +Use direct AQL with `name` and `path` — omitting `repo` searches all accessible +repos via indexed columns: ```bash jf api /artifactory/api/search/aql \ @@ -47,21 +45,16 @@ jf api /artifactory/api/search/aql \ }).include("repo","path","name","size","sha256")' ``` -Add `"repo":"<repo-name>"` to the criteria when the target repo is known, to -narrow the search further. +Add `"repo":"<repo-name>"` when target repo is known. ## 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). +**Project scoping:** `?project=<key>` on **every** build detail call. User key +→ use it; else `?project=default`. AQL: `"repo":"<project-key>-build-info"` or +`"repo":"artifactory-build-info"` for default. -**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*. +**Server rule:** 404 on `?project=<key>` ≠ try another server. Resolved server +only; on failure report and stop. See `SKILL.md` § *Server selection rules*. ### Publishing builds @@ -73,8 +66,8 @@ report and stop. See `SKILL.md` § *Server selection rules*. ### Listing build names -**Do not use `GET /api/build`** — it has no pagination and times out on large -instances. Always use AQL with `limit` and `offset`. +**Do not use `GET /api/build`** — no pagination; times out on large instances. +Always AQL with `limit` and `offset`. **All builds** (no project scope): @@ -84,9 +77,8 @@ jf api /artifactory/api/search/aql \ -d 'builds.find().include("name","number","repo","created").sort({"$desc":["created"]}).offset(0).limit(100)' ``` -**Project-scoped** — filter by the project's build-info repository -(`<project-key>-build-info`, or `artifactory-build-info` for the default -project): +**Project-scoped** — filter by build-info repo +(`<project-key>-build-info`, or `artifactory-build-info` for default project): ```bash jf api /artifactory/api/search/aql \ @@ -94,19 +86,14 @@ jf api /artifactory/api/search/aql \ -d 'builds.find({"repo":"<project-key>-build-info"}).include("name","number","repo","created").sort({"$desc":["created"]}).offset(0).limit(100)' ``` -**Pagination:** The response includes a `range` object with `total` (total -matching records). If `total` exceeds the `limit`, tell the user: *"Showing +**Pagination:** `range.total` vs `limit` → if exceeded, tell user: *"Showing first 100 of N results (paginated). Ask for the next batch if needed."* -For subsequent pages, increment `offset` by 100. +Increment `offset` by 100 per page. -**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. +**Output rule (mandatory):** AQL = one row per name+number. Extract **unique +build names** client-side (e.g. `jq '[.results[].builds.name] | unique'`). Present +**only deduplicated names** — no numbers, timestamps, run counts, or per-run +details (not even "bonus"/"most recent" table). Run details only if explicitly requested. ### Listing runs of a specific build @@ -116,35 +103,29 @@ jf api /artifactory/api/search/aql \ -d 'builds.find({"name":"<build-name>"}).include("name","number","repo","created").sort({"$desc":["created"]}).offset(0).limit(100)' ``` -Add `"repo":"<project-key>-build-info"` to the criteria when a project key -is known. Apply the same pagination rules as above. +Add `"repo":"<project-key>-build-info"` when project key known. Same pagination rules. ### Retrieving full build info -Use the REST detail endpoint for a **single** build run. Always include -`?project=<key>` (or `?project=default` when no key is provided): +REST detail endpoint for a **single** run. Always include `?project=<key>` +(or `?project=default` when no key): ```bash jf api "/artifactory/api/build/<name>/<number>?project=<key>" ``` -This is the only `/api/build` endpoint that should be used — it returns a -single record and does not need pagination. +Only `/api/build` endpoint to use — single record, no 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. +404 on detail call → build likely in different project. **Ask user for project +key** — do not search across repos or servers. ### 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 -builds exist. +empty list even when project-scoped build info exists (e.g. under `*-build-info`). +Prefer AQL to discover builds; empty repository list ≠ no builds. ## Permissions @@ -164,7 +145,7 @@ Note: `jf rt permission-target-template` is interactive. - Delete group: `jf rt group-delete <name>` - Add users to group: `jf rt group-add-users <group> <users-list>` -To get user details or update users, use `jf api`: +User details/update via `jf api`: ``` jf api /access/api/v2/users/<username> ``` diff --git a/skills/jfrog/references/catalog-entities.md b/skills/jfrog/references/catalog-entities.md index 085bea0..f8e9a87 100644 --- a/skills/jfrog/references/catalog-entities.md +++ b/skills/jfrog/references/catalog-entities.md @@ -3,40 +3,36 @@ When to read this file: - Querying **public package metadata** (descriptions, vulnerabilities, licenses, operational info). -- Working with the **Custom Catalog** (org-specific labels, package views, federation). -- Looking up **vulnerability details** beyond what Xray provides (advisories, EPSS, CWE, known exploits). +- Working with **Custom Catalog** (org-specific labels, package views, federation). +- Looking up **vulnerability details** beyond Xray (advisories, EPSS, CWE, known exploits). - Querying **OpenSSF scorecards**, **ML model metadata**, or **MCP service** registries. -- Using the OneModel GraphQL API with `publicPackages`, `customPackages`, +- Using OneModel GraphQL with `publicPackages`, `customPackages`, `publicSecurityInfo`, `publicLegalInfo`, `publicOperationalInfo`, `publicCatalogLabels`, or `publicRemoteServices` query roots. -Catalog entities are accessed via the **OneModel GraphQL API** -(`/onemodel/api/v1/graphql`). +Catalog entities via **OneModel GraphQL API** (`/onemodel/api/v1/graphql`). -For the OneModel query workflow (credentials, schema fetch, validation, -execution), read `references/onemodel-graphql.md`. +OneModel query workflow (credentials, schema fetch, validation, execution): `references/onemodel-graphql.md`. ## Two catalog layers | Layer | Scope | Description | |-------|-------|-------------| -| **Public Catalog** | Global | JFrog's curated package database — security, legal, and operational metadata for public packages across ecosystems | -| **Custom Catalog** | Organization | Org-specific overlay — custom labels, per-org package views, federation config | +| **Public Catalog** | Global | JFrog global package DB — security, legal, operational metadata across ecosystems | +| **Custom Catalog** | Organization | Org overlay: custom labels, per-org views, federation config | -The Custom Catalog builds on top of the Public Catalog. A public package -can be enriched with org-specific labels and metadata through the Custom -Catalog without altering the underlying public data. +Custom Catalog overlays Public Catalog — org labels/metadata without changing public data. ## Public Catalog entities ### PublicPackage -A package as known to JFrog's global package database. +Package in JFrog global package database. | Field | Description | |-------|-------------| -| `name` | Package name (e.g. `lodash`, `spring-boot-starter-web`) | -| `type` | Package type (e.g. `npm`, `maven`, `pypi`) | +| `name` | Package name (`lodash`, `spring-boot-starter-web`) | +| `type` | Package type (`npm`, `maven`, `pypi`) | | `ecosystem` | Ecosystem identifier | | `description` | Rich-text description | | `homepage`, `vcsUrl` | Package URLs | @@ -53,12 +49,12 @@ Query: `publicPackages.searchPackages(where: {...})`. ### PublicPackageVersion -A specific version with security, legal, and operational analysis. +Specific version with security, legal, operational analysis. | Field | Description | |-------|-------------| | `version` | Version string | -| `isLatest` | Whether this is the latest version | +| `isLatest` | Whether latest version | | `isListedVersion` | Whether visible in Catalog UI | | `publishedAt`, `modifiedAt` | Timestamps | | `trendingScore` | Version-level popularity | @@ -72,76 +68,71 @@ Each version carries three info blocks: ### PublicVulnerability -Vulnerability data richer than what Xray violations expose. Useful for -deep-dive security analysis and advisory lookups. +Richer vulnerability data than Xray violations — deep-dive analysis + advisory lookups. | Field | Description | |-------|-------------| -| `name` | CVE identifier (e.g. `CVE-2021-44228`) | +| `name` | CVE id (`CVE-2021-44228`) | | `ecosystem` | Affected ecosystem | | `severity` | `CRITICAL`, `HIGH`, `MEDIUM`, `LOW` | | `description` | Detailed impact description | | `cvss` | CVSS scores — v2, v3, **and v4** | -| `epss` | EPSS (Exploit Prediction Scoring System) — exploit likelihood | -| `knownExploit` | Known exploit information | -| `withdrawn` | Whether the CVE has been retracted | +| `epss` | EPSS exploit likelihood | +| `knownExploit` | Known exploit info | +| `withdrawn` | CVE retracted | | `aliases` | Alternative identifiers | | `references` | Advisory URLs | | `publishedAt`, `modifiedAt` | Timestamps | -Advisory sources (via `advisories` connection): -- **NVD** — NIST National Vulnerability Database +Advisory sources (`advisories` connection): +- **NVD** — NIST vulnerability DB - **GHSA** — GitHub Security Advisory -- **JFrog Advisory** — JFrog's own research (includes impact reasons) +- **JFrog Advisory** — JFrog research (impact reasons) - **Debian Security Tracker** - **RedHat OVAL** Additional connections: `cwesConnection` (CWE entries), `cpesConnection` -(CPE entries), `publicPackageInfo` (affected packages and versions). +(CPE entries), `publicPackageInfo` (affected packages + versions). Query: `publicSecurityInfo.searchVulnerabilities(where: {...})`. #### Filtering limitations -`searchVulnerabilities` can filter by CVE name, ecosystem, severity, CVSS, -EPSS, known exploit status, and publication date — but **not** by affected -package name. There is no `hasPublicPackageInfoWith` or similar filter on -`PublicVulnerabilityWhereInput`. To find vulnerabilities affecting a specific -package, use one of these alternatives: +`searchVulnerabilities` filters by CVE name, ecosystem, severity, CVSS, +EPSS, known exploit status, publication date — but **not** by affected +package name. No `hasPublicPackageInfoWith` or similar filter on +`PublicVulnerabilityWhereInput`. To find vulnerabilities affecting specific +package, use alternatives: - **Version-level security info** (GraphQL): query - `publicPackages.getPackage(type, name)` and navigate to - `versionsConnection → securityInfo → vulnerabilitiesConnection` to get + `publicPackages.getPackage(type, name)` → + `versionsConnection → securityInfo → vulnerabilitiesConnection` for CVEs affecting specific versions. -- **Individual CVE lookup**: use `searchVulnerabilities(where: { name: "<CVE>" })` - and inspect `publicPackageInfo.vulnerablePublicPackagesConnection` on the +- **Individual CVE lookup**: `searchVulnerabilities(where: { name: "<CVE>" })` + → inspect `publicPackageInfo.vulnerablePublicPackagesConnection` on `generic` ecosystem entry. #### Ecosystem multiplicity -A single CVE appears as multiple `PublicVulnerability` entries — one per -ecosystem. The `ecosystem` field determines which entry you see: +Single CVE → multiple `PublicVulnerability` entries (one per ecosystem). `ecosystem` field determines entry: | Ecosystem | Contains | |-----------|----------| -| `generic` | Non-OS package-level data (npm, maven, pypi, go, etc.) — includes `publicPackageInfo` with vulnerable versions and fix versions | -| `debian`, `redhat`, `ubuntu`, etc. | OS-specific advisory data — severity may differ from NVD; `publicPackageInfo` is typically empty (OS packages are tracked separately) | +| `generic` | Non-OS package-level data (npm, maven, pypi, go, etc.) — includes `publicPackageInfo` with vulnerable + fix versions | +| `debian`, `redhat`, `ubuntu`, etc. | OS-specific advisory data — severity may differ from NVD; `publicPackageInfo` typically empty (OS packages tracked separately) | -When looking up a CVE by name, `searchVulnerabilities(where: { name: "<CVE>" })` -returns all ecosystem entries. To get affected packages and fix versions for -libraries like npm or maven, filter for or focus on the `generic` ecosystem -entry. `getVulnerability` requires both `name` and `ecosystem` — use -`searchVulnerabilities` when the ecosystem is unknown. +CVE lookup by name: `searchVulnerabilities(where: { name: "<CVE>" })` +returns all ecosystem entries. For npm/maven library affected packages + fix versions → filter/focus on `generic` entry. `getVulnerability` requires `name` + `ecosystem` — use `searchVulnerabilities` when ecosystem unknown. ### PublicLicense -License metadata with permission, condition, and limitation details. +License metadata with permission, condition, limitation details. | Field | Description | |-------|-------------| -| `name` | License name (e.g. `Apache-2.0`, `MIT`) | +| `name` | License name (`Apache-2.0`, `MIT`) | | `spdxId` | SPDX identifier | -| `permissions` | What the license permits | +| `permissions` | What license permits | | `limitations` | Restrictions imposed | | `patentConditions` | Patent grant conditions | | `noticeFiles` | Required notices | @@ -150,23 +141,23 @@ Query: `publicLegalInfo.searchLicenses(where: {...})`. ### PublicPackageOperationalInfo -Operational risk assessment for packages and versions. +Operational risk assessment for packages + versions. | Entity | Key data | |--------|----------| -| **OpenSSF scorecard** | Overall score, individual checks with scores and pass/fail | -| **End-of-life** | Whether the package or version is EOL, justification | -| **Popularity** | JFrog popularity by segment and subscription tier, download counts | +| **OpenSSF scorecard** | Overall score + check scores/pass-fail | +| **End-of-life** | Package/version EOL status + justification | +| **Popularity** | JFrog popularity by segment/tier, download counts | ### MCP services and tools -The Public Catalog also indexes MCP (Model Context Protocol) services: +Public Catalog also indexes MCP (Model Context Protocol) services: | Entity | Description | |--------|-------------| -| `PublicMcpService` | An MCP service with name, description, version | -| `PublicMcpTool` | A tool exposed by an MCP service with arguments | -| `PublicMcpRemote` | Remote MCP server configuration | +| `PublicMcpService` | MCP service: name, description, version | +| `PublicMcpTool` | MCP service tool + arguments | +| `PublicMcpRemote` | Remote MCP server config | Query: `publicRemoteServices.searchMcpServices(where: {...})`. @@ -174,7 +165,7 @@ Query: `publicRemoteServices.searchMcpServices(where: {...})`. ### CustomPackage -A package in the organization's private catalog view. +Package in org private catalog view. | Field | Description | |-------|-------------| @@ -188,27 +179,25 @@ Connections: `versionsConnection`, `legalInfo`, ### CustomCatalogLabel -Organization-defined labels for categorizing packages. +Org-defined labels for categorizing packages. | Field | Description | |-------|-------------| | `name` | Label name | -| `description` | What the label represents | +| `description` | What label represents | | `color` | Display color | | `labelType` | `MANUAL` or `AUTOMATIC` | -| `assignmentInfo` | How and when the label was assigned | +| `assignmentInfo` | How/when label assigned | -Labels can be assigned to both custom packages and public packages/versions -within the org's catalog scope. The Custom Catalog mutations allow -creating, updating, and deleting labels. +Labels assignable to custom packages + public packages/versions within org catalog scope. Custom Catalog mutations: create, update, delete labels. ### CustomCatalogFederation -Configuration for federating catalog data across JFrog deployments. +Config for federating catalog data across JFrog deployments. ## Catalog vs. Xray vs. Stored Packages -These three domains provide different views of package and security data: +Three domains, different views of package + security data: | Aspect | Catalog | Xray | Stored Packages | |--------|---------|------|-----------------| diff --git a/skills/jfrog/references/cli-command-discovery.md b/skills/jfrog/references/cli-command-discovery.md new file mode 100644 index 0000000..bbe6263 --- /dev/null +++ b/skills/jfrog/references/cli-command-discovery.md @@ -0,0 +1,39 @@ +# CLI command discovery + +> **Tier B MUST** when discovery beyond `--help` is needed. Not every CLI / setup. + +Use `--help` to verify uncertain options. Do not rely on memorized commands +outside this skill — they may be outdated. + +1. `jf --help` — namespaces and top-level commands +2. `jf <namespace> --help` — subcommands in a namespace +3. `jf <command> --help` — usage, arguments, options + +## CLI namespaces + +| Namespace | Alias | Product | +|-----------|-------|---------| +| `rt` | | Artifactory | +| `xr` | | Xray | +| `ds` | | Distribution V1 | +| `at` | `apptrust` | AppTrust | +| `evd` | | Evidence | +| `mc` | | Mission Control | +| `worker` | | Workers | +| `config` | `c` | CLI server configuration | +| `plugin` | | CLI plugin management | +| `ide` | | IDE integration | + +> **Sunset notice:** JFrog Pipelines has been sunset and is no longer supported. +> Do not use the `pl` CLI namespace or the Pipelines REST API +> (`/pipelines/api/...`). If a user asks about Pipelines, inform them the +> product has been sunset. + +Top-level lifecycle commands (no namespace): `rbc`, `rbp`, `rbd`, `rba`, +`rbf`, `rbe`, `rbi`, `rbs`, `rbu`, `rbdell`, `rbdelr`. + +Top-level security commands: `audit`, `scan`, `build-scan`, `curation-audit`, +`sbom-enrich`. + +Top-level other: `access-token-create` (`atc`), `login`, `how`, `stats`, +`generate-summary-markdown`, `exchange-oidc-token`, `completion`. diff --git a/skills/jfrog/references/cli-gotchas.md b/skills/jfrog/references/cli-gotchas.md new file mode 100644 index 0000000..410b205 --- /dev/null +++ b/skills/jfrog/references/cli-gotchas.md @@ -0,0 +1,80 @@ +# CLI and `jf api` gotchas + +> **Tier B MUST** before `jf api` / AQL / advanced CLI I/O / MCP-via-shell. +> Not tips. Not required for every CLI / `jf setup` (use SKILL.md Tier A floor). +> Tier A bullets do **not** replace this file on Tier B paths. + +Hard rules and known failure modes: + +## MCP tools + +- MCP tools return structured data in the tool result. Read response fields + directly; do not pipe MCP output through shell commands or `jq`. + +## CLI and `jf api` + +- `jf api` requires the **product prefix** in the path. Omitting it returns + 404. See `references/jf-api.md` for the full product-prefix table. +- `jf api` writes the body (success or error JSON) to **stdout** and + `[Info] Http Status: NNN` to **stderr** on every call; non-2xx also exits + 1 and adds `[Warn] jf api: <method> <url> returned NNN`. Pipe stdout to + `jq` directly; **never `2>&1 | jq`** — stderr corrupts the JSON. To keep + diagnostics: `jf api <path> 2>/tmp/err-$$.log | jq .`. +- `jf api` has **no `-L`** (follow redirects) and **no `-o`** (output file). + Save bodies with shell redirection + (`jf api ... > /tmp/out-$$.json`); for + binary downloads through the Artifactory remote proxy prefer `jf rt dl`, + which handles the cache and redirect semantics natively. +- Remote repository content is stored in a `-cache` suffixed repo. Properties + and AQL queries for remote repo artifacts must target the cache repo. + Conversely, `/api/repositories/<key>` only accepts the parent remote key + (without `-cache`) — strip the suffix for configuration lookups. +- **Do not use `jf rt search`** — always use a direct AQL query via + `jf api /artifactory/api/search/aql -X POST -H "Content-Type: text/plain" -d '<aql>'`. + See `references/artifactory-aql-syntax.md`. +- Use `--quiet` flag for non-interactive execution (suppresses confirmation + prompts). **Caution:** `--quiet` is not a global flag — commands that do not + support it (e.g. `jf rt s`, `jf rt ping`) will fail with misleading errors + like "Wrong number of arguments" or "flag provided but not defined". Check + `--help` for a command before adding `--quiet`. +- Use `--server-id` when targeting a non-default server. If a command fails + with `--server-id`, do not retry without it — that silently targets the + default server instead. See `SKILL.md` → Server selection rules. +- Never use interactive commands. All JFrog CLI operations must be performed + non-interactively. Known interactive commands to avoid: `jf config add`, + `jf login`, `jf rt repo-template`, `jf rt permission-target-template`, and + `jf rt replication-template`. For server setup, follow `references/jfrog-login-flow.md`. + For templates, use JSON schemas or REST API. If a command prompts for input + unexpectedly, find the non-interactive alternative via `--help` or REST API. +- `jf config export` output is base64-encoded JSON. Decode with + `base64 -d | jq` to extract fields. +- Build info lookups require a scope (`?buildRepo=` or `?project=`) — + resolve it before calling the API. See `references/artifactory-operations.md` + §Retrieving build info for the full workflow. +- If a `jf api` call returns 401, the configured token may have expired or + been rotated — ask the user to re-run the login flow (see + `references/jfrog-login-flow.md`) for the **same** server. If 403, the + token lacks required permissions. If 404, verify the endpoint path + (especially the product prefix) and target server version. On any of + these errors, do not try a different configured server as a workaround — + that targets a different environment. Report the error and ask the user. +- **Xray contextual analysis:** the summary artifact response has two + applicability fields — `applicability` (top-level, often null) and + `applicability_details` (always present with a `result` string). **Use + `applicability_details[].result` for counts and summaries.** Using the + top-level `applicability` field for aggregation produces wrong counts because + it is null when no scanner exists. See `references/xray-entities.md` + §Contextual analysis for the eight possible result values and jq snippets. +- **OneModel GraphQL:** always fetch the supergraph schema from the **same** + server you query before building operations (schemas differ by deployment); + cache, validate, and execute per `references/onemodel-graphql.md`. +- Never duplicate a network-fetching command to retry `jq` parsing — save the + response to a temp file first (see `references/preserving-command-output.md`). +- When collecting detail responses in a loop (e.g. per-repo GETs), validate + each body with `jq -e .` before appending to a results file. One non-JSON + or empty response corrupts a downstream `jq -s` slurp. Write validated + lines to an NDJSON file, then `jq -s '.' file.ndjson` to produce the final + array. See `references/general-bulk-operations-and-agent-patterns.md`. +- Accumulated edge cases from real tasks live in `references/general-use-case-hints.md` + — read when debugging odd failures; **append** a short entry when you confirm + a new, reusable gotcha. diff --git a/skills/jfrog/references/general-bulk-operations-and-agent-patterns.md b/skills/jfrog/references/general-bulk-operations-and-agent-patterns.md index 2b44979..2fe30f4 100644 --- a/skills/jfrog/references/general-bulk-operations-and-agent-patterns.md +++ b/skills/jfrog/references/general-bulk-operations-and-agent-patterns.md @@ -1,70 +1,55 @@ # Bulk operations and agent execution patterns -Platform-wide guidance for agents that gather data from multiple JFrog products -(Artifactory, Xray, Access, Distribution, etc.), run long shell -sequences, or parallelize work. Product-specific field names and endpoints live -in the other `references/*` files; this document describes **patterns**, not -one workflow. +Platform-wide guidance for agents gathering data from multiple JFrog products +(Artifactory, Xray, Access, Distribution, etc.), long shell sequences, or +parallel work. Product field names/endpoints in other `references/*` files; +this document = **patterns**, not one workflow. ## List vs detail responses -Many REST surfaces expose a **light list** (keys, names, minimal fields) and a -**richer GET by id or key**. Fields needed for audits, reporting, joins, or -permission checks may appear **only** on the detail response. Before building a -multi-step flow on a single list call, confirm in API docs or with a sample GET -whether the fields you need are present. +REST: **light list** + **detail GET**. Audit/join/permission fields often +detail-only — confirm via docs or sample GET before building on list alone. ## Volume, batching, and timeouts -- Estimate **N** round-trips (list + per-item GETs, paginated APIs, etc.) before - starting so execution time and tool timeouts stay predictable. -- Prefer batching independent reads in one Shell invocation when credentials and - tier match (see SKILL.md **Batch and parallel execution**). -- Split very large work across chunks, parallel Shell calls, or subagents when - the skill's tiering guidance says so. -- Before starting an N+1 loop (list + per-item detail), **estimate wall time** - as roughly `N * 1.5s` for sequential calls. Set `block_until_ms` to at - least that estimate plus a 30-second buffer. -- For loops exceeding ~60 items, prefer a single Shell invocation that writes - progress to a log file (`>> /tmp/jf-progress-$$.log`) so partial results - are visible even if the job is interrupted. -- If the task is read-only and items are independent, consider Tier 2 or - Tier 3 parallelism (see `general-parallel-execution.md`) to reduce total time — - but respect rate limits and keep concurrency modest (4-8 parallel calls). +- Estimate **N** round-trips before starting. +- Batch independent reads in one Shell when credentials/tier match (SKILL.md + **Batch and parallel execution**). +- Large work → chunks, parallel Shell, or subagents per tiering. +- N+1 loop: wall time ≈ `N * 1.5s`; `block_until_ms` ≥ estimate + 30s. +- > ~60 items: Shell + progress log (`>> /tmp/jf-progress-$$.log`). +- Read-only independent items: Tier 2/3 (`general-parallel-execution.md`); + rate limits; 4-8 parallel calls. ## Parallelism and shared files -**Unsafe:** Multiple concurrent processes appending lines to the **same** file -(JSONL, logs, ndjson) without synchronization. Output can interleave on one -line and break parsers (e.g. JSON "Extra data" errors). +**Unsafe:** Concurrent processes appending to **same** file (JSONL, logs, ndjson) +without sync → interleaved lines, broken parsers (JSON "Extra data" errors). **Safer:** - Write sequentially to one file; or - One temp file per worker or chunk, then concatenate; or -- Use advisory locking (`flock`) if one file must be shared. +- Advisory locking (`flock`) if one file must be shared. -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). +Bulk API/CLI output: `/tmp` or `mktemp`; not `~/.jfrog/skills-cache/` except +`jfrog-skill-state.json` and OneModel schema (main SKILL.md). ## Shell hygiene -- Use `set -euo pipefail` in non-trivial scripts so failures are not silent. -- Use unique temp paths (e.g. `$$` in the filename) and **echo the expanded - path** so it can be reused across Shell calls (see SKILL.md **Preserving - command output** for the `$$` + echo, session ID, and hardcoded patterns). -- Parse CLI and API JSON with **`jq`**. +- `set -euo pipefail` in non-trivial scripts — failures not silent. +- Unique temp paths (`$$` in filename) + **echo expanded path** for cross-call + reuse (SKILL.md **Preserving command output** — `$$` + echo, session ID, hardcoded patterns). +- Parse CLI/API JSON with **`jq`**. ## Safe multi-response collection -When looping over items (repos, builds, users) and fetching detail for each: +Looping items (repos, builds, users) + per-item detail: -1. Save each response to a variable or per-item file. +1. Save each response to variable or per-item file. 2. Validate with `jq -e . >/dev/null 2>&1` before appending. -3. On validation failure, write a structured error line so the caller can - report partial results instead of crashing. -4. After the loop, `jq -s '.' results.ndjson` to produce a single array. +3. On validation failure, structured error line → partial results without crash. +4. After loop, `jq -s '.' results.ndjson` → single array. ```bash : >results.ndjson @@ -79,8 +64,7 @@ done < <(jq -r '.[].key' list.json) jq -s '.' results.ndjson > details.json ``` -Never pipe a loop of `jf api` calls directly into `jq -s` without -per-body validation. +Never pipe loop of `jf api` calls directly into `jq -s` without per-body validation. ## Where to find product specifics diff --git a/skills/jfrog/references/general-parallel-execution.md b/skills/jfrog/references/general-parallel-execution.md index a0bb9ba..42bb48d 100644 --- a/skills/jfrog/references/general-parallel-execution.md +++ b/skills/jfrog/references/general-parallel-execution.md @@ -1,20 +1,17 @@ # Batch and Parallel Execution -When a task requires multiple independent operations, use the lightest -parallelism mechanism that fits. Three tiers are available, from lightest to -heaviest: +Multiple independent operations → use lightest parallelism tier: | Tier | Mechanism | Best for | |------|-----------|----------| | 1 | Single Shell call with `&&` | Few commands, same credentials | -| 2 | Parallel Shell tool calls | Independent commands that can run concurrently | -| 3 | Parallel subagents (Task tool) | Large multi-step jobs where each branch needs its own reasoning | +| 2 | Parallel Shell tool calls | Independent commands, concurrency helps | +| 3 | Parallel subagents (Task tool) | Large multi-step jobs, each branch needs reasoning | ## Tier 1: Batch within a single Shell call -Combine independent commands with `&&`. All JFrog API calls go through the -same `jf api` command and the same `jf config` server, so batching them -together is both safe and efficient: +Combine independent commands with `&&`. All JFrog API calls share `jf api` + +`jf config` server — batching is safe and efficient: ```bash jf api /artifactory/api/repositories > /tmp/jf-repos-$$.json && \ @@ -32,8 +29,8 @@ jf api /access/api/v2/permissions/ > /tmp/jf-perms-$$.json ## Tier 2: Parallel Shell tool calls -Use multiple Shell tool calls in the same message when the commands are -independent and the total runtime benefits from concurrency: +Multiple Shell tool calls in one message when commands are independent and +concurrency cuts runtime: ```bash # Shell call 1 — echo the expanded path so the agent can reference it later @@ -45,20 +42,13 @@ OUT=/tmp/jf-users-$$.json jf api /access/api/v2/users/ > "$OUT" && echo "$OUT" ``` -Each parallel Shell call gets a different PID, so `$$` expands to different -values. Echo the path so the agent knows the literal filename for cross-call -use (see SKILL.md **Preserving command output**). +Each parallel Shell call gets different PID → `$$` differs. Echo path for +cross-call use (see SKILL.md **Preserving command output**). ## Tier 3: Parallel subagents -For tasks with multiple independent branches that each require several steps -or their own reasoning — such as generating a platform health report with -separate sections, auditing both repository config and security policies, or -comparing configurations across servers the user explicitly named — launch -parallel subagents using the Task tool. - -Each subagent runs autonomously, executes its own CLI/API calls, and returns -a structured result. The parent agent assembles the final answer. +Multi-branch tasks (health reports, audits, user-named cross-server compare) +→ Task tool subagents. Each runs autonomously; parent merges results. ### Example — platform audit with three parallel subagents @@ -80,26 +70,20 @@ Subagent 3 (shell): "Collect user and permission data" → Return user count, group count, admin users ``` -All three subagents run concurrently. Once all complete, the parent agent -merges their results into a unified report. +All three run concurrently. Parent merges into unified report. ### How to structure a subagent prompt -1. State the goal clearly (e.g. "Collect all Xray policies and watches"). -2. Provide the exact commands to run, or name the API tier and let the - subagent discover via `--help`. -3. Tell the subagent to save output to `/tmp/jf-<label>-$$.json`, echo the - 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. +1. State goal clearly (e.g. "Collect all Xray policies and watches"). +2. Exact commands, or API tier + `--help` discovery. +3. Save to `/tmp/jf-<label>-$$.json`, echo expanded path, return structured summary. +4. Specify return fields (counts, lists) so parent need not re-read raw data. ### Subagent type selection -- Use `subagent_type="shell"` for straightforward command sequences where the - commands are known ahead of time. -- Use `subagent_type="generalPurpose"` when the subagent needs to read skill - references, discover commands via `--help`, or adapt its approach based on - intermediate results. +- `subagent_type="shell"` — known command sequences. +- `subagent_type="generalPurpose"` — needs skill references, `--help` discovery, + or adaptive approach from intermediate results. ## When to use each tier @@ -113,19 +97,15 @@ merges their results into a unified report. ## When NOT to parallelize -- A later command depends on the output of an earlier one (use sequential - calls instead and process the output in between). -- The calls include **mutating operations** — keep those separate so the user - can review each one. -- Commands would target different servers — only operate on the server(s) the - user explicitly named (or the default). Never fall back to or iterate - through other configured servers. See SKILL.md **Server selection rules**. -- The task is small enough that a single Shell call completes in seconds — the - overhead of launching subagents is not justified. +- Later command depends on earlier output → sequential calls. +- **Mutating operations** — keep separate for user review. +- Different servers — only user-named (or default). Never fallback/iterate + configured servers. See SKILL.md **Server selection rules**. +- Small task completing in seconds — subagent overhead not justified. ## Aggregating many outputs (JSONL, logs, ndjson) Do **not** have multiple background processes append **unsynchronized** to the -same file — lines can interleave and corrupt machine-readable output. Prefer -sequential writes, one file per worker or chunk then concatenate, or file -locking. See `references/general-bulk-operations-and-agent-patterns.md`. +same file — lines interleave, corrupt machine-readable output. Prefer sequential +writes, one file per worker/chunk then concatenate, or file locking. +See `references/general-bulk-operations-and-agent-patterns.md`. diff --git a/skills/jfrog/references/jf-api.md b/skills/jfrog/references/jf-api.md new file mode 100644 index 0000000..b4ecaf4 --- /dev/null +++ b/skills/jfrog/references/jf-api.md @@ -0,0 +1,63 @@ +# Invoking platform APIs with `jf api` + +> **Tier B MUST** before `jf api`. Not domain on-demand; not required for every CLI / setup. + +`jf api` is the Tier 3 entry point for JFrog Platform REST and GraphQL +endpoints, auto-authenticated against the resolved server. **Do not use +`jf rt curl` or `jf xr curl`** — superseded by `jf api`. + +## Product-prefix table + +`jf api` requires the **full** path including the product prefix; omitting it +returns 404. + +| Product | Path prefix | +|---------|-------------| +| Artifactory | `/artifactory/api/...` | +| Xray | `/xray/api/...` | +| Access (users, groups, tokens, permissions, projects) | `/access/api/...` | +| Evidence | `/evidence/api/...` | +| Release Lifecycle | `/lifecycle/api/...` | +| AppTrust | `/apptrust/api/...` | +| Distribution | `/distribution/api/...` | +| OneModel (GraphQL) | `/onemodel/api/v1/graphql`, `/onemodel/api/v1/supergraph/schema` | +| Mission Control | `/mc/api/...` | +| Curation | `/xray/api/v1/curation/...` (lives under Xray) | + +## Examples + +```bash +jf api /artifactory/api/repositories +jf api --server-id <SID> /artifactory/api/system/version + +# AQL (POST with text/plain body) +jf api /artifactory/api/search/aql \ + -X POST -H "Content-Type: text/plain" -d '<aql-query>' +``` + +Common flags: `-X/--method`, `-H/--header`, `-d/--data`, `--input <file>`, +`--server-id`, `--timeout`. Body on stdout, status on stderr — see +`references/cli-gotchas.md`. + +## GraphQL (OneModel) + +OneModel is the unified GraphQL API. **Do not** embed the query inside a JSON +literal (`-d '{"query":"..."}'`) — escaping breaks requests. Build the payload +with `jq -n --arg`, pass it via `--input`, and save the response to a file +before running `jq` on it. + +```bash +QUERY='{ evidence { searchEvidence(first: 5, where: { hasSubjectWith: { repositoryKey: "my-repo-local" } }) { totalCount } } }' +PAYLOAD=/tmp/onemodel-payload-$$.json RESPONSE=/tmp/onemodel-$$.json +jq -n --arg q "$QUERY" '{query:$q}' > "$PAYLOAD" +jf api /onemodel/api/v1/graphql -X POST \ + -H "Content-Type: application/json" --input "$PAYLOAD" > "$RESPONSE" +jq . "$RESPONSE" +``` + +Schema discovery: `jf api /onemodel/api/v1/supergraph/schema > "$SCHEMA_FILE"` +(store only under `~/.jfrog/skills-cache/`, never query responses). Read +`references/onemodel-graphql.md` for the full workflow (schema fetch, +validation, pagination, errors), plus `references/onemodel-query-examples.md` +and `references/onemodel-common-patterns.md` for query shapes, pagination, +variables, and dates. 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/references/jfrog-entity-index.md b/skills/jfrog/references/jfrog-entity-index.md index b17eb03..da36320 100644 --- a/skills/jfrog/references/jfrog-entity-index.md +++ b/skills/jfrog/references/jfrog-entity-index.md @@ -2,14 +2,13 @@ When to read this file: -- The user mentions a JFrog entity and you need to identify which **domain** it belongs to. -- You are planning an operation that spans **multiple products** (e.g. build → scan → release). -- You need a quick **one-line definition** before deciding whether to load the full domain reference. -- You need **GraphQL (OneModel)** entry points (workflow, examples, patterns) — +- User mentions JFrog entity — need to identify **domain**. +- Planning operation spanning **multiple products** (e.g. build → scan → release). +- Need quick **one-line definition** before loading full domain reference. +- Need **GraphQL (OneModel)** entry points (workflow, examples, patterns) — see [GraphQL (OneModel)](#graphql-onemodel) below. -After identifying the domain, follow the pointer in the **Reference** column for -detailed definitions, relationships, and agent rules. +After identifying domain, follow **Reference** column pointer for detailed definitions, relationships, agent rules. ## Cross-product flow @@ -35,78 +34,70 @@ flowchart TD Cat[Catalog] -.->|enriches metadata| SP ``` -Key takeaway: **artifacts** are the atomic unit. Builds reference them, -Xray scans them, release bundles collect them, distribution delivers them. -**Applications** orchestrate the top-level release flow. **Stored Packages** -bridge artifacts to the package abstraction. **Catalog** enriches packages -with global security, legal, and operational metadata. **Evidence** attests -to entities across all domains. +Key takeaway: **artifacts** = atomic unit. Builds reference them, Xray scans them, release bundles collect them, distribution delivers them. **Applications** orchestrate top-level release flow. **Stored Packages** bridge artifacts → package abstraction. **Catalog** enriches packages with global security, legal, operational metadata. **Evidence** attests entities across all domains. ## GraphQL (OneModel) -For the unified **OneModel GraphQL** API (cross-product list/search over -applications, packages, evidence, release bundles, catalog, and related -entities on the platform base URL): +Unified **OneModel GraphQL** API (cross-product list/search over applications, packages, evidence, release bundles, catalog, related entities on platform base URL): -- **Workflow** — mandatory per-server supergraph schema, validation, execution, - errors: `onemodel-graphql.md` -- **Query templates and domain examples** — `onemodel-query-examples.md` +- **Workflow** — mandatory per-server supergraph schema, validation, execution, errors: `onemodel-graphql.md` +- **Query templates + domain examples** — `onemodel-query-examples.md` - **Pagination, variables, date formats** — `onemodel-common-patterns.md` -Also see **GraphQL (OneModel)** in the base `SKILL.md` (Tier 3 curl). +Also see **GraphQL (OneModel)** in base `SKILL.md` (Tier 3 curl). ## Entity lookup | Entity | Domain | Definition | Reference | |--------|--------|------------|-----------| -| **Local repository** | Artifactory | Stores artifacts deployed directly (upload, promote, move) | `artifactory-entities.md` | -| **Remote repository** | Artifactory | Proxy/cache for an external source; cached artifacts live in its `-cache` repo | `artifactory-entities.md` | -| **Virtual repository** | Artifactory | Aggregates local and remote repos under a single resolution URL | `artifactory-entities.md` | -| **Federated repository** | Artifactory | Local repo that synchronizes across multiple Platform Deployments | `artifactory-entities.md` | -| **Artifact** | Artifactory | A file stored in a repository, identified by repo + path + name | `artifactory-entities.md` | -| **Property** | Artifactory | Key-value metadata attached to an artifact or folder | `artifactory-entities.md` | -| **Package type** | Artifactory | Repo-level setting that determines layout, metadata indexing, and client protocol | `artifactory-entities.md` | -| **Build info** | Artifactory | Metadata record linking a CI/CD build to the artifacts it produced | `artifactory-entities.md` | -| **Build promotion** | Artifactory | Status change on a build that can move/copy artifacts between repos | `artifactory-entities.md` | -| **Permission** | Artifactory / Access | RBAC policy mapping resources (repos, builds, bundles, destinations) to user/group actions. V2 via Access, V1 (permission targets) via Artifactory | `artifactory-entities.md` | -| **Replication** | Artifactory | Sync configuration that copies artifacts/properties between repos | `artifactory-entities.md` | -| **Indexed resource** | Xray | A repository, build, or release bundle that Xray indexes for scanning | `xray-entities.md` | -| **Component** | Xray | A software package identified and tracked by Xray during scanning | `xray-entities.md` | -| **Vulnerability** | Xray | A known security issue (CVE) associated with a component version | `xray-entities.md` | -| **Contextual analysis** | Xray | Assessment of whether a vulnerability is reachable in the usage context | `xray-entities.md` | -| **License** | Xray | License metadata associated with a component, used for compliance policies | `xray-entities.md` | -| **Watch** | Xray | Links resources (repos, builds) to policies for continuous monitoring | `xray-entities.md` | -| **Policy** | Xray | Rules that evaluate components and produce violations when matched | `xray-entities.md` | -| **Violation** | Xray | Generated when a component in a watched resource matches a policy rule | `xray-entities.md` | -| **Ignore rule** | Xray | Suppresses specific violations by component, CVE, path, or other criteria | `xray-entities.md` | -| **Exposure** | Xray (Advanced Security) | Actionable finding from exposures scanning — secrets, IaC issues, service misconfigurations, or application security risks detected in artifacts | `xray-entities.md` | -| **Curation audit event** | Xray (Curation) | Record of a package check through a curated repository — approved or blocked, with policy details; supports dry-run analysis | `xray-entities.md` | -| **Report** | Xray | On-demand security, license, or operational analysis over a defined scope | `xray-entities.md` | -| **Release Bundle** | Release Lifecycle | Immutable, versioned collection of artifacts for promotion and distribution | `release-lifecycle-entities.md` | -| **Lifecycle stage** | Release Lifecycle | Progression of a release bundle through environments (e.g. DEV → PROD) | `release-lifecycle-entities.md` | -| **Distribution** | Release Lifecycle | Delivery of a release bundle to Edge nodes or other Platform Deployments | `release-lifecycle-entities.md` | -| **Evidence** | Release Lifecycle | Cryptographic attestation attached to release bundles, apps, or packages (cross-domain) | `release-lifecycle-entities.md` | -| **Evidence subject** | Release Lifecycle | Cross-domain anchor linking evidence to its target entity via `fullPath` | `release-lifecycle-entities.md` | -| **Application** | AppTrust | Software application with versions, owners, criticality, and maturity level | `apptrust-entities.md` | -| **Application version** | AppTrust | Versioned instance with releasables, sources, promotion history, and release status | `apptrust-entities.md` | -| **Releasable** | AppTrust | Deployable unit within an app version — a package version or individual artifact | `apptrust-entities.md` | -| **Application version promotion** | AppTrust | Stage-to-stage progression of an app version (with status tracking) | `apptrust-entities.md` | -| **Application version source** | AppTrust | What produced releasables: Build, ReleaseBundle, ApplicationVersion, or Direct | `apptrust-entities.md` | -| **Stored package** | Stored Packages | Package as known to Artifactory's metadata layer (name + type + versions) | `stored-packages-entities.md` | -| **Stored package version** | Stored Packages | Specific version with locations, artifacts, tags, qualifiers, and stats | `stored-packages-entities.md` | -| **Stored package version location** | Stored Packages | Where a package version lives (repo key + path) — bridge to Applications and Evidence | `stored-packages-entities.md` | -| **Stored package artifact** | Stored Packages | Binary file within a package version (checksums, size, mime type) | `stored-packages-entities.md` | -| **Public package** | Catalog | Package in JFrog's global database with security, legal, and operational metadata | `catalog-entities.md` | -| **Public package version** | Catalog | Version with vulnerability, license, operational info, and dependencies | `catalog-entities.md` | -| **Public vulnerability** | Catalog | CVE with CVSS v2/v3/v4, EPSS, advisories (NVD, GHSA, JFrog), known exploits | `catalog-entities.md` | -| **Public license** | Catalog | License metadata with permissions, limitations, and patent conditions | `catalog-entities.md` | -| **Custom package** | Catalog | Package in the organization's private catalog view with custom labels | `catalog-entities.md` | -| **Custom catalog label** | Catalog | Organization-defined label for categorizing packages (manual or automatic) | `catalog-entities.md` | -| **MCP service** | Catalog | Model Context Protocol service registered in the public catalog | `catalog-entities.md` | -| **Project** | Platform / Access | Organizational container with its own members, roles, and resources | `platform-access-entities.md` | -| **Project role** | Platform / Access | Per-project role definition scoping actions to environments | `platform-access-entities.md` | -| **Project member** | Platform / Access | User or group assigned a role within a project | `platform-access-entities.md` | -| **Environment** | Platform / Access | Groups resources and scopes RBAC; used in projects and release lifecycle | `platform-access-entities.md` | -| **User** | Platform / Access | Platform identity that authenticates and is granted permissions | `platform-access-entities.md` | -| **Group** | Platform / Access | Named collection of users; simplifies permission management | `platform-access-entities.md` | -| **Access token** | Platform / Access | Bearer credential with scoped permissions and optional expiry | `platform-access-entities.md` | +| **Local repository** | Artifactory | Direct artifact storage (upload, promote, move) | `artifactory-entities.md` | +| **Remote repository** | Artifactory | External proxy/cache; artifacts in `-cache` repo | `artifactory-entities.md` | +| **Virtual repository** | Artifactory | Aggregates local + remote repos under one resolution URL | `artifactory-entities.md` | +| **Federated repository** | Artifactory | Local repo synced across Platform Deployments | `artifactory-entities.md` | +| **Artifact** | Artifactory | Repo file; id = repo + path + name | `artifactory-entities.md` | +| **Property** | Artifactory | Key-value metadata on artifact/folder | `artifactory-entities.md` | +| **Package type** | Artifactory | Repo setting: layout, indexing, client protocol | `artifactory-entities.md` | +| **Build info** | Artifactory | CI/CD build metadata → produced artifacts | `artifactory-entities.md` | +| **Build promotion** | Artifactory | Build status change; move/copy artifacts between repos | `artifactory-entities.md` | +| **Permission** | Artifactory / Access | RBAC: resources → user/group actions. V2=Access, V1=Artifactory permission targets | `artifactory-entities.md` | +| **Replication** | Artifactory | Sync config for artifacts/properties between repos | `artifactory-entities.md` | +| **Indexed resource** | Xray | Repo, build, or release bundle indexed for scanning | `xray-entities.md` | +| **Component** | Xray | Package identified + tracked during Xray scan | `xray-entities.md` | +| **Vulnerability** | Xray | Known CVE on component version | `xray-entities.md` | +| **Contextual analysis** | Xray | Vulnerability reachability in usage context | `xray-entities.md` | +| **License** | Xray | Component license metadata for compliance | `xray-entities.md` | +| **Watch** | Xray | Links repos/builds → policies for monitoring | `xray-entities.md` | +| **Policy** | Xray | Component rules → violations when matched | `xray-entities.md` | +| **Violation** | Xray | Component in watched resource matches policy rule | `xray-entities.md` | +| **Ignore rule** | Xray | Suppresses violations by component, CVE, path, etc. | `xray-entities.md` | +| **Exposure** | Xray (Advanced Security) | Exposures scan finding — secrets, IaC, misconfigs, appsec risks | `xray-entities.md` | +| **Curation audit event** | Xray (Curation) | Curated repo package check — approved/blocked + policy; dry-run supported | `xray-entities.md` | +| **Report** | Xray | On-demand security/license/operational analysis | `xray-entities.md` | +| **Release Bundle** | Release Lifecycle | Immutable versioned artifact set for promotion + distribution | `release-lifecycle-entities.md` | +| **Lifecycle stage** | Release Lifecycle | Bundle progression through environments (DEV → PROD) | `release-lifecycle-entities.md` | +| **Distribution** | Release Lifecycle | Bundle delivery to Edge nodes / Platform Deployments | `release-lifecycle-entities.md` | +| **Evidence** | Release Lifecycle | Crypto attestation on bundles, apps, packages (cross-domain) | `release-lifecycle-entities.md` | +| **Evidence subject** | Release Lifecycle | Cross-domain evidence anchor via `fullPath` | `release-lifecycle-entities.md` | +| **Application** | AppTrust | App with versions, owners, criticality, maturity | `apptrust-entities.md` | +| **Application version** | AppTrust | Versioned instance: releasables, sources, promotion history, status | `apptrust-entities.md` | +| **Releasable** | AppTrust | Deployable unit — package version or artifact | `apptrust-entities.md` | +| **Application version promotion** | AppTrust | Stage-to-stage app version progression + status | `apptrust-entities.md` | +| **Application version source** | AppTrust | Releasable source: Build, ReleaseBundle, ApplicationVersion, Direct | `apptrust-entities.md` | +| **Stored package** | Stored Packages | Artifactory metadata package (name + type + versions) | `stored-packages-entities.md` | +| **Stored package version** | Stored Packages | Version: locations, artifacts, tags, qualifiers, stats | `stored-packages-entities.md` | +| **Stored package version location** | Stored Packages | Package version location (repo key + path); bridge to Apps + Evidence | `stored-packages-entities.md` | +| **Stored package artifact** | Stored Packages | Package version binary (checksums, size, mime type) | `stored-packages-entities.md` | +| **Public package** | Catalog | JFrog global package + security/legal/operational metadata | `catalog-entities.md` | +| **Public package version** | Catalog | Version: vulns, licenses, operational info, dependencies | `catalog-entities.md` | +| **Public vulnerability** | Catalog | CVE: CVSS v2/v3/v4, EPSS, advisories (NVD, GHSA, JFrog), exploits | `catalog-entities.md` | +| **Public license** | Catalog | License: permissions, limitations, patent conditions | `catalog-entities.md` | +| **Custom package** | Catalog | Org private catalog package + custom labels | `catalog-entities.md` | +| **Custom catalog label** | Catalog | Org label for packages (manual/automatic) | `catalog-entities.md` | +| **MCP service** | Catalog | MCP service in public catalog | `catalog-entities.md` | +| **Project** | Platform / Access | Org container: members, roles, resources | `platform-access-entities.md` | +| **Project role** | Platform / Access | Per-project role scoped to environments | `platform-access-entities.md` | +| **Project member** | Platform / Access | User/group + project role | `platform-access-entities.md` | +| **Environment** | Platform / Access | Resource grouping + RBAC scope; projects + lifecycle | `platform-access-entities.md` | +| **User** | Platform / Access | Platform identity + permissions | `platform-access-entities.md` | +| **Group** | Platform / Access | User collection for permission management | `platform-access-entities.md` | +| **Access token** | Platform / Access | Scoped bearer credential + optional expiry | `platform-access-entities.md` | diff --git a/skills/jfrog/references/jfrog-login-flow.md b/skills/jfrog/references/jfrog-login-flow.md index 4bfe5c3..4bde434 100644 --- a/skills/jfrog/references/jfrog-login-flow.md +++ b/skills/jfrog/references/jfrog-login-flow.md @@ -1,18 +1,16 @@ # Server Login Flow -How to add or authenticate a JFrog Platform server. The agent drives this -flow — the user only interacts via their browser. +Add or authenticate a JFrog Platform server. Agent drives flow — user interacts via browser. -Requires Artifactory 7.64.0+ and the JFrog CLI (`jf`). +Requires Artifactory 7.64.0+ and JFrog CLI (`jf`). ## Security rules - Never print, echo, or display access tokens in terminal output or chat. -- When confirming auth status, say "authenticated as user X" — never show -the token. -- `jf config` is the sole credential store. Never store tokens in files, -env var profiles, or project directories. -- Validate URLs with the ping endpoint before using them in shell commands. +- Confirm auth: "authenticated as user X" — never show the token. +- `jf config` = sole credential store. Never store tokens in files, env var + profiles, or project directories. +- Validate URLs with ping endpoint before shell use. ## Resolve the active environment @@ -20,13 +18,12 @@ env var profiles, or project directories. jf config show 2>/dev/null ``` -- **0 servers** — ask the user for their JFrog Platform URL, then go to -Web Login. +- **Command errors** (nonzero exit / config unreadable) — stop and report; do **not** treat empty output as 0 servers. +- **0 servers** (success, empty) — ask user for JFrog Platform URL → Web Login. - **1 server** — use it: `jf config use <server-id>`, done. -- **2+ servers** — if the user named a specific server, use that one. Otherwise -use the current default. If no default is set, list server IDs and URLs and -ask the user which to use. **Never iterate through servers or fall back to -another server on error** — see SKILL.md **Server selection rules**. +- **2+ servers** — user named specific server → use it. Else current default. + No default → list server IDs/URLs, ask user. **Never iterate servers or + fallback on error** — see SKILL.md **Server selection rules**. ## Web login (preferred) @@ -36,8 +33,7 @@ another server on error** — see SKILL.md **Server selection rules**. bash <skill_path>/scripts/jfrog-login-register-session.sh "https://mycompany.jfrog.io" ``` -The script pings the server, generates a session UUID, and registers it with -the Access API. On success it outputs: +Pings server, generates session UUID, registers with Access API. On success: ``` SESSION_UUID=<uuid> @@ -48,13 +44,13 @@ Exit codes: 0 = success, 2 = server unreachable, 3 = registration failed. ### 2. Show the user the verification code and login link -Build the login URL: +Build login URL: ``` ${JFROG_PLATFORM_URL}/ui/login?jfClientSession=${SESSION_UUID}&jfClientName=JFrog-Skills&jfClientCode=1 ``` -Show the verification code prominently, then the clickable link: +Show verification code prominently, then clickable link: > ## Verification code: `<last 4 chars of SESSION_UUID>` > @@ -62,7 +58,7 @@ Show the verification code prominently, then the clickable link: > > Let me know when you're done. -Wait for the user to confirm. Do not poll automatically. +Wait for user confirmation. Do not poll automatically. ### 3. Retrieve token, save credentials, verify @@ -72,13 +68,12 @@ bash <skill_path>/scripts/jfrog-login-save-credentials.sh \ "<SESSION_UUID from step 1>" ``` -Substitute the literal platform URL and session UUID from step 1 output. +Substitute literal platform URL and session UUID from step 1 output. -The script retrieves the one-time token, derives a server ID from the URL, -saves credentials via `jf config add`, and verifies with an Artifactory -version check. It leaves the current default `jf` server unchanged — pass -`--server-id=<id>` explicitly on subsequent calls (the SKILL.md "Server -selection rules" require this anyway). On success it outputs: +Retrieves one-time token, derives server ID from URL, saves via `jf config add`, +verifies with Artifactory version check. Leaves default `jf` server unchanged — +pass `--server-id=<id>` on subsequent calls (SKILL.md "Server selection rules"). +On success: ``` SERVER_ID=<derived-id> @@ -90,8 +85,8 @@ Exit codes: 0 = success, 2 = token retrieval failed (user may not have completed browser login — HTTP 400), 3 = empty token, 4 = config save or verification failed. -**The token endpoint is one-time-use.** If consumed (even in a failed save), -the session UUID is invalidated and the flow must restart from step 1. +**The token endpoint is one-time-use.** If consumed (even in failed save), +session UUID invalidated — restart from step 1. ## Post-login handoff (mandatory gate) @@ -106,11 +101,11 @@ Before any other JFrog operation against the new server, ask the user: ## Fallback: manual token setup -If web login fails (server too old, network restrictions): +Web login fails (old server, network restrictions): -1. Ask the user to generate a token in the JFrog UI: +1. Ask user to generate token in JFrog UI: **Administration > Identity and Access > Access Tokens > Generate Token** -2. Save it non-interactively: +2. Save non-interactively: ```bash jf config add <server-id> \ @@ -121,12 +116,9 @@ jf config add <server-id> \ ## Gotchas -- The token endpoint (`/token/{uuid}`) is **one-time-use**. If consumed -(even in a failed save), the session UUID is invalidated and the flow -must restart from step 1. The save-credentials script handles cleanup, -but if it exits non-zero after consuming the token, restart from step 1. -- Server ID is derived from the hostname: `https://mycompany.jfrog.io` -becomes `mycompany`. Self-hosted URLs are slugified: -`https://artifactory.internal.corp` becomes `artifactory-internal-corp`. -- `**jf`**, `**uuidgen**` (register-session), and `**jq**` (save-credentials) must be on PATH. - +- Token endpoint (`/token/{uuid}`) **one-time-use**. Consumed (even failed save) + → session UUID invalidated → restart step 1. save-credentials script handles + cleanup; non-zero exit after token consumed → restart step 1. +- Server ID from hostname: `https://mycompany.jfrog.io` → `mycompany`. + Self-hosted slugified: `https://artifactory.internal.corp` → `artifactory-internal-corp`. +- `**jf**`, `**uuidgen**` (register-session), and `**jq**` (save-credentials) must be on PATH. diff --git a/skills/jfrog/references/onemodel-graphql.md b/skills/jfrog/references/onemodel-graphql.md index 41e03e5..a140d68 100644 --- a/skills/jfrog/references/onemodel-graphql.md +++ b/skills/jfrog/references/onemodel-graphql.md @@ -1,61 +1,55 @@ # OneModel GraphQL (JFrog Platform) -Run OneModel GraphQL queries against the JFrog Platform to fetch information -about applications, release bundles, artifacts, builds, evidence, packages, -catalog data, and more through the unified OneModel endpoint. +Run OneModel GraphQL against the JFrog Platform for applications, release +bundles, artifacts, builds, evidence, packages, catalog data, and more via +the unified endpoint. -**When to read this file:** Any OneModel GraphQL query, schema discovery, or -when the user asks to list or search platform entities via GraphQL. For -domain-specific query shapes, read `onemodel-query-examples.md`. For -pagination, variables, and date formatting, read `onemodel-common-patterns.md`. +**Read when:** OneModel GraphQL query, schema discovery, or list/search +platform entities via GraphQL. Domain shapes → `onemodel-query-examples.md`. +Pagination / variables / dates → `onemodel-common-patterns.md`. -In examples below, `<skill_path>` is this skill's directory (parent of -`references/`). +In examples, `<skill_path>` is this skill's directory (parent of `references/`). ## `~/.jfrog/skills-cache/` policy -The skill cache lives under `${JFROG_CLI_HOME_DIR:-$HOME/.jfrog}/skills-cache/` -(co-located with `jf config`, **not** inside the installed skill tree). It holds -**only**: +Cache: `${JFROG_CLI_HOME_DIR:-$HOME/.jfrog}/skills-cache/` (with `jf config`, +**not** inside the installed skill). Holds **only**: 1. **`onemodel-schema-${JFROG_SERVER_ID}.graphql`** — this workflow (supergraph - SDL cache). **Always** use the path in [Fetch the schema](#2-fetch-the-schema); - do not mirror the schema under `/tmp`. -2. **`jfrog-skill-state.json`** — environment check (see main SKILL.md); scripts - manage it; do not delete or replace it casually. + SDL). **Always** use [Fetch the schema](#2-fetch-the-schema); never mirror + under `/tmp`. +2. **`jfrog-skill-state.json`** — env check (main SKILL.md); scripts own it; + do not delete/replace casually. -**Never** store GraphQL **query responses**, REST bodies, reports, or other -scratch files under `skills-cache/`. For responses, use `/tmp` with a unique name -(`$$`, `mktemp -d`) as in [Execute the query](#6-execute-the-query) — the -example `RESPONSE_FILE` paths must stay outside `skills-cache/`. +**Never** store GraphQL **responses**, REST bodies, reports, or scratch under +`skills-cache/`. Responses → `/tmp` with unique name (`$$`, `mktemp -d`) as in +[Execute the query](#6-execute-the-query). `RESPONSE_FILE` must stay outside +`skills-cache/`. ## Prerequisites -- **JFrog CLI** (`jf`) configured with at least one server — follow the main - SKILL.md environment check and **Server selection rules** before querying. -- **Artifactory 7.104.1+** — OneModel GraphQL requires this minimum version. -- **`jq`** on `PATH` (same as the base skill). HTTP calls go through - `jf api`; no standalone `curl` is needed. +- **JFrog CLI** (`jf`) with ≥1 server — main SKILL.md env check + **Server + selection rules** before querying. +- **Artifactory 7.104.1+** — OneModel GraphQL minimum. +- **`jq`** on `PATH` (base skill). HTTP via `jf api`; no standalone `curl`. ## Workflow -Follow these steps in order. Skipping the schema fetch (step 2) is the most -common source of errors — queries built from assumptions or cached knowledge -will fail on servers whose schema differs from what you expect. +Follow in order. Skipping schema fetch (step 2) is the top error source — +assumed/cached shapes fail when the server schema differs. -1. **Resolve the target server** — derive `JFROG_SERVER_ID` from `jf config` -2. **Fetch the schema** — always fetch the supergraph schema from the server -3. **Understand the query intent** — map the user's request to domains and types -4. **Construct the GraphQL query** — build from the resolved schema only -5. **Validate the query against the schema** — verify every field and type -6. **Execute the query** — POST to the OneModel endpoint; save response to a file -7. **Handle the response** — paginate if needed; present results clearly +1. **Resolve the target server** — `JFROG_SERVER_ID` from `jf config` +2. **Fetch the schema** — always fetch supergraph from the server +3. **Understand the query intent** — map request → domains/types +4. **Construct the GraphQL query** — from resolved schema only +5. **Validate against the schema** — every field and type +6. **Execute** — POST OneModel; save response to a file +7. **Handle the response** — paginate if needed; present clearly ### 1. Resolve the target server -Authentication is handled automatically by `jf api` against the active (or -`--server-id`-specified) server. You only need the server-id locally — for -caching the schema file per server. Derive it from `jf config`: +`jf api` authenticates against the active (or `--server-id`) server. You only +need the server-id locally — for per-server schema cache. From `jf config`: ```bash # User-specified server: @@ -66,40 +60,32 @@ JFROG_SERVER_ID=$(jf config show --server-id 2>/dev/null \ || jf config export | base64 -d | jq -r '.servers[] | select(.isDefault==true).serverId') ``` -If the user named a specific server, pass `--server-id "$JFROG_SERVER_ID"` to -every `jf api` invocation in steps 2 and 6 so the query hits that server -(see SKILL.md § *Server selection rules*). +If the user named a server, pass `--server-id "$JFROG_SERVER_ID"` on every +`jf api` in steps 2 and 6 (SKILL.md § *Server selection rules*). ### 2. Fetch the schema -**This step is mandatory for custom or novel queries.** You need the supergraph -schema from the specific JFrog server you are working with. +**Mandatory for custom/novel queries.** Need the supergraph from this server. #### Shortcut for well-known query patterns -When using a query shape that comes directly from `onemodel-query-examples.md` -**without modifications** (same fields, same filters, same argument types), you -may skip the full schema fetch and execute immediately. The example queries in -that file are maintained against real servers and are unlikely to drift for -stable domains like `publicPackages`, `storedPackages`, and `evidence`. +Query shape from `onemodel-query-examples.md` **without modifications** (same +fields, filters, arg types) → may skip full schema fetch and execute. Those +examples track real servers; stable domains (`publicPackages`, +`storedPackages`, `evidence`) rarely drift. -**Fallback rule:** If the query returns `GRAPHQL_VALIDATION_FAILED` or -unexpected empty results, fetch the schema (as described below), verify the -query against it, and retry. Do not attempt more than one execution without -schema verification. +**Fallback:** `GRAPHQL_VALIDATION_FAILED` or unexpected empty → fetch schema +(below), verify, retry. Never more than one execution without schema check. -The schema is large. Cache it under the skill cache directory (the JFrog CLI -home — outside the installed skill tree) keyed by the concrete -`JFROG_SERVER_ID` from step 1 (the CLI `serverId`, never a placeholder like -`default`). +Schema is large. Cache under skill cache (CLI home, outside installed skill), +keyed by concrete `JFROG_SERVER_ID` from step 1 (CLI `serverId`, never a +placeholder like `default`). -**Always use this exact path** — do not save the schema to `/tmp/` or any other -location. The cache path is: +**Always this exact path** — not `/tmp/` or elsewhere: `${JFROG_CLI_HOME_DIR:-$HOME/.jfrog}/skills-cache/onemodel-schema-${JFROG_SERVER_ID}.graphql` -Run the following block as-is. It checks for an existing cached file and only -fetches when missing: +Run as-is — uses cache when present, fetches when missing: ```bash SCHEMA_FILE="${JFROG_CLI_HOME_DIR:-$HOME/.jfrog}/skills-cache/onemodel-schema-${JFROG_SERVER_ID}.graphql" @@ -114,133 +100,115 @@ else fi ``` -(Omit `--server-id "$JFROG_SERVER_ID"` to target the active default server.) +(Omit `--server-id "$JFROG_SERVER_ID"` for the active default server.) -After the block runs, **read `$SCHEMA_FILE` from disk** for all subsequent -schema lookups — never re-fetch to a different path. +After the block: **read `$SCHEMA_FILE` from disk** for all schema lookups — +never re-fetch to a different path. -If the fetch fails (HTTP 401/403, empty file, or network error), verify the -access token, wildcard audience, base URL (no trailing path beyond the host), and -server version. If the schema file is empty or contains an HTML error page, -delete it and retry the block above. +Fetch fails (401/403/404, network, timeout) → **stop and report**; fix +token / wildcard audience / base URL (host only, no trailing path) / server +version first — do not blind-retry. Retry the block only after a **successful** +call left an empty file or HTML error page (delete it first). -The schema file is SDL — namespaces, types, fields, arguments, enums, and -directives for **this** server. +Schema is SDL — namespaces, types, fields, args, enums, directives for **this** +server. #### Navigating the schema -The schema is large (typically 10,000+ lines). Do not read it in full. Use -targeted searches: +Large (typically 10,000+ lines). Do not read in full. Targeted search: -1. **Find available namespaces** — search for lines matching `: ...Queries!` - near the root `Query` definition (e.g. `applications: ApplicationsQueries!`). -2. **Find operations for a namespace** — search for the `...Queries` type name - to see `get...` and `search...` methods. -3. **Find input/filter types** — from the operation signature, look up the - `WhereInput` type to see available filters. -4. **Find output fields** — look up the node type to see which fields you can - request. +1. **Namespaces** — lines matching `: ...Queries!` near root `Query` + (e.g. `applications: ApplicationsQueries!`). +2. **Operations** — search `...Queries` type for `get...` / `search...`. +3. **Input/filter types** — from op signature, look up `WhereInput`. +4. **Output fields** — look up node type for selectable fields. -When reading the schema, **ignore types and fields annotated with -`@inaccessible`.** These are internal federation artifacts and are not queryable -through the OneModel endpoint. +**Ignore `@inaccessible`** — internal federation; not queryable via OneModel. #### Never assume — always verify in the schema -Before constructing any query, look up every type you intend to use. Common -mistakes: +Before constructing, look up every type you will use. Common mistakes: -- **Scalars vs enums** — A name like `FooType` may be a `scalar` (string) - or an `enum`. Search for `scalar FooType` vs `enum FooType` to know - whether to pass a quoted string (`"something"`) or a bare identifier. -- **Connection fields vs plain fields** — Look for `...Connection` naming; - verify exact field names and required arguments on the parent type. -- **Nested types** — When a field returns a complex type, look up that type's - definition for subfields; do not guess names. +- **Scalars vs enums** — `FooType` may be `scalar` (quoted string) or `enum` + (bare id). Search `scalar FooType` vs `enum FooType`. +- **Connection vs plain** — look for `...Connection`; verify field names and + required args on the parent. +- **Nested types** — look up returned complex types for subfields; do not guess. #### Read the descriptions -Schema descriptions (`"""..."""` above types, fields, and arguments) encode -accepted values, matching behavior, and constraints. Read a few lines above -each definition you use. +Schema `"""..."""` above types/fields/args encode accepted values, matching, +constraints. Read a few lines above each definition you use. -**Why this matters:** The OneModel supergraph is composed per server from -products, entitlements, and license. Different servers expose different domains. -The resolved schema is the only reliable source of truth. +**Why:** Supergraph is per-server (products, entitlements, license). Domains +differ. Resolved schema is the only reliable source of truth. **Do NOT rely on:** -- Public documentation alone — it may not list every domain on your server. -- Hardcoded examples without schema verification — see - `onemodel-query-examples.md` as patterns only. +- Public docs alone — may omit domains on your server. +- Hardcoded examples without schema check — `onemodel-query-examples.md` = + patterns only. - Legacy metadata GraphiQL (`/metadata/api/v1/query/graphiql`) — deprecated; - it does not reflect the OneModel schema. + not OneModel. ### 3. Understand the query intent -Using the schema from step 2, map the user's request to available domains. -Search for root `Query` and `: ...Queries!` lines to see namespaces on this -server. +From step-2 schema, map the request to domains. Search root `Query` and +`: ...Queries!` for namespaces on this server. -Common domains you **may** find (always verify in the schema): +Domains you **may** find (always verify): -- **Applications** — applications, versions, bound package versions +- **Applications** — apps, versions, bound package versions - **Release lifecycle** — release bundle versions, artifacts, source builds - **Evidence** — evidence on artifacts, repos, or release bundles -- **Stored packages** — packages and versions in Artifactory repos +- **Stored packages** — packages/versions in Artifactory repos - **Public / custom catalog** — public registry metadata, catalog packages, security/legal/operational info -If no matching types exist, tell the user the capability is not exposed on this -server. +No matching types → tell the user the capability is not on this server. **Note:** Legacy metadata GraphQL (`packages` at `/metadata/api/v1/query`) is -deprecated and **not** part of OneModel. Use `/onemodel/api/v1/graphql` only. +deprecated and **not** OneModel. Use `/onemodel/api/v1/graphql` only. ### 4. Construct the GraphQL query -Build the query using **only** types, fields, and arguments from the resolved -schema. +Build using **only** types, fields, and args from the resolved schema. #### Pre-construction checklist -1. Look up every argument type (`where`, `orderBy`, etc.). -2. Look up every output type and required subfield selections for object types. +1. Look up every argument type (`where`, `orderBy`, …). +2. Look up every output type and required subfield selections for objects. 3. Look up every `WhereInput` and nested filter shape. -4. Trace the full path from root to leaf and confirm each hop exists. +4. Trace root→leaf; confirm each hop exists. #### Principles -- Prefer **one query** that returns what the user needs (nested fields, - filters) to minimize round-trips. +- Prefer **one query** with nested fields/filters (fewer round-trips). - Request **only needed fields**. -- On validation errors, **simplify** the query (e.g. one scalar field per - connection) to isolate the bad filter or field. Request `totalCount` only if - that connection type defines it in the schema (many metadata connections do - not). -- Use **`where`** in the query instead of fetching everything client-side. -- Use **pagination** — include `first` (or `last`) and - `pageInfo { hasNextPage endCursor }` for large sets. -- Use **GraphQL variables** for dynamic values (see `onemodel-common-patterns.md`). +- On validation errors, **simplify** (e.g. one scalar per connection) to + isolate the bad filter/field. Request `totalCount` only if the connection + type defines it (many metadata connections do not). +- Prefer **`where`** over fetch-all + client filter. +- **Pagination** — `first`/`last` + `pageInfo { hasNextPage endCursor }` for + large sets. +- **GraphQL variables** for dynamic values (`onemodel-common-patterns.md`). #### Naming convention - `get...` — single item -- `search...` — list / connection-style results +- `search...` — list / connection-style ### 5. Validate the query against the schema -Before executing, verify: +Before execute, verify: -1. Every field name matches the schema (casing, suffixes like `...Connection`). +1. Every field name matches schema (casing, `...Connection` suffixes). 2. Every object-typed field has a subfield selection. -3. Every argument value matches scalar vs enum vs input rules. -4. Nested `where` paths exist end-to-end on the corresponding input types. -5. Connection fields include pagination arguments as required. -6. **Brace balance** — every `{` in the document (selection sets and input - objects) has exactly one matching `}`. Deep nesting is easy to get wrong in - a single-line shell string; prefer a `.graphql` file or heredoc so structure - is visible (see below). +3. Every arg value matches scalar vs enum vs input rules. +4. Nested `where` paths exist end-to-end on input types. +5. Connection fields include required pagination args. +6. **Brace balance** — every `{` has one matching `}`. Prefer `.graphql` file + or heredoc over a single-line shell string (see below). ### 6. Execute the query @@ -256,15 +224,13 @@ jf api /onemodel/api/v1/graphql \ #### Always save the response to a file -Redirect `jf api`'s stdout to `$RESPONSE_FILE` so you can re-`jq` without -re-querying. **Do not pipe `jf api` directly to `jq`** — a wrong filter -loses the response. **Do not** set `RESPONSE_FILE` under -`~/.jfrog/skills-cache/` — that directory is only for the schema cache and -`jfrog-skill-state.json` (see [`~/.jfrog/skills-cache/` policy](#jfrogskills-cache-policy) -above). +Redirect `jf api` stdout → `$RESPONSE_FILE` so you can re-`jq` without +re-querying. **Do not pipe `jf api` to `jq`** — wrong filter loses the +response. **Do not** put `RESPONSE_FILE` under `~/.jfrog/skills-cache/` — +schema + `jfrog-skill-state.json` only (see +[`~/.jfrog/skills-cache/` policy](#jfrogskills-cache-policy)). -For multiple queries in one shell session, use a temp directory under `/tmp` and -sequential names: +Multiple queries in one shell → temp dir under `/tmp` + sequential names: ```bash ONEMODEL_TMPDIR=$(mktemp -d) @@ -280,19 +246,16 @@ RESPONSE_FILE="$ONEMODEL_TMPDIR/response-$ONEMODEL_QUERY_NUM.json" #### Always use `jq` to build the JSON payload -Do **not** hand-embed the GraphQL string inside a JSON literal — escaping breaks -easily. Build the payload JSON with `jq`, write it to a **file**, and pass the -file to `jf api` with `--input`. `jf api` does not accept stdin for `--data`; -`--input` expects a file path. +Do **not** hand-embed GraphQL inside a JSON literal — escaping breaks easily. +Build payload with `jq` → **file** → `jf api --input`. `jf api` has no stdin +`--data`; `--input` expects a path. ##### Avoid `PARSING_ERROR` (broken GraphQL documents) -A response with `extensions.code: PARSING_ERROR` (often `expected a StringValue, -Name or OperationDefinition` at **line 1, column N**) means the **document -text** is invalid — usually **too many or too few `}`** — before the server -checks fields against the schema. This happens most often when a long query is -pasted into `QUERY='...'` as **one bash line**: braces are hard to count, and a -typo near the end surfaces as an error at a **high column number**. +`extensions.code: PARSING_ERROR` (often `expected a StringValue, Name or +OperationDefinition` at **line 1, column N**) → **document text** invalid — +usually **too many/few `}`** — before schema field checks. Common with long +`QUERY='...'` one-liners: braces hard to count; typo near end → high column. **Do this instead:** @@ -333,15 +296,15 @@ jf api /onemodel/api/v1/graphql \ > "$RESPONSE_FILE" ``` -Strip `#` comments and collapse whitespace only if you need a single-line -payload; often you can pass the file content as-is if it has no comments. +Strip `#` comments / collapse whitespace only if you need a single-line +payload; often pass file content as-is when comment-free. -With variables, add more `--arg` flags and a `variables` object (see -`onemodel-common-patterns.md`). +With variables: more `--arg` flags + `variables` object +(`onemodel-common-patterns.md`). ### 7. Handle the response -Always read from `$RESPONSE_FILE` for further extraction or formatting. +Always read `$RESPONSE_FILE` for further extraction/formatting. #### Success shape @@ -357,7 +320,7 @@ Always read from `$RESPONSE_FILE` for further extraction or formatting. #### Errors -Errors appear in an `errors` array. Partial data may coexist with errors. +Errors in `errors` array. Partial data may coexist. | Symptom | Likely cause | Action | |--------|---------------|--------| @@ -369,23 +332,20 @@ Errors appear in an `errors` array. Partial data may coexist with errors. #### Pagination -If `pageInfo.hasNextPage` is true, pass `endCursor` as `after` on the next -request. Save each page to a new `response-N.json`. Details: -`onemodel-common-patterns.md`. +`pageInfo.hasNextPage` → pass `endCursor` as `after`. Save each page to a new +`response-N.json`. Details: `onemodel-common-patterns.md`. ## GraphQL Playground -The Platform UI includes a GraphQL Playground: **Integrations > GraphQL -Playground**, or: - -`$JFROG_URL/ui/onemodel/playground` +Platform UI: **Integrations > GraphQL Playground**, or +`$JFROG_URL/ui/onemodel/playground`. -Suggest it when: +Suggest when: -- Queries are deeply nested or cross-domain and hard to get right in one turn -- Multiple attempts failed and autocomplete would help -- The user wants to explore capabilities rather than run one fixed query -- The user asks for a UI or visual GraphQL explorer +- Deep/cross-domain queries hard to get right in one turn +- Multiple failed attempts; autocomplete would help +- User wants to explore capabilities, not one fixed query +- User asks for a UI / visual GraphQL explorer Include the resolved base URL so they can open it immediately. @@ -440,7 +400,6 @@ Include the resolved base URL so they can open it immediately. ## Related reference files -- `onemodel-query-examples.md` — illustrative templates per domain (verify - against schema before use). -- `onemodel-common-patterns.md` — Relay-style pagination, filters, variables, - date formatting, response shapes. +- `onemodel-query-examples.md` — domain templates (verify against schema). +- `onemodel-common-patterns.md` — Relay pagination, filters, variables, dates, + response shapes. \ No newline at end of file diff --git a/skills/jfrog/references/platform-access-entities.md b/skills/jfrog/references/platform-access-entities.md index 9e2d809..59108cc 100644 --- a/skills/jfrog/references/platform-access-entities.md +++ b/skills/jfrog/references/platform-access-entities.md @@ -2,16 +2,14 @@ When to read this file: -- Explaining how **Projects**, **repositories**, **members**, **roles**, and +- Explaining how **Projects**, **repositories**, **members**, **roles**, **environments** fit together. -- Working with **users**, **groups**, or **access tokens** at the platform level. -- Building **inventories or reports** that join Artifactory data with - Access / Projects. +- Working with **users**, **groups**, or **access tokens** at platform level. +- Building **inventories or reports** joining Artifactory data with Access / Projects. - Avoiding two common mistakes: inferring project membership from - **repository name**, or assuming **roles** are identical across projects. + **repository name**, or assuming **roles** identical across projects. -For endpoint-level curl examples, see `projects-api.md`. For list-vs-detail -API patterns and batching, see `general-bulk-operations-and-agent-patterns.md`. +Endpoint-level curl examples: `projects-api.md`. List-vs-detail API patterns + batching: `general-bulk-operations-and-agent-patterns.md`. ## Entity relationship overview @@ -31,17 +29,17 @@ erDiagram ## Project -Organizational container for grouping members, roles, and resources. +Organizational container grouping members, roles, resources. | Field | Description | |-------|-------------| -| `project_key` | Unique identifier (short string, used in APIs and repo assignment) | +| `project_key` | Unique identifier (short string; APIs + repo assignment) | | `display_name` | Human-readable name | | `description` | Project description | -| `admin_privileges` | Flags controlling project-level admin behavior | -| `storage_quota` | Storage limits for the project | +| `admin_privileges` | Project-level admin behavior flags | +| `storage_quota` | Storage limits | -A project hosts **members** (users and groups with roles) and **resources** +Project hosts **members** (users/groups with roles) + **resources** (repositories, builds, Release Bundles) assigned to it. API: `GET /access/api/v1/projects`, `GET /access/api/v1/projects/<project-key>`. @@ -51,50 +49,44 @@ Documentation: [Get Started with Projects](https://docs.jfrog.com/projects/docs) ## Project role -Per-project role definition that scopes what members may do. +Per-project role scoping what members may do. | Field | Description | |-------|-------------| | `name` | Role name (e.g. `Developer`, `Release Manager`) | | `type` | `PREDEFINED` or `CUSTOM` | -| `environments` | List of environments where the role applies (e.g. `["DEV", "PROD"]`) | -| `actions` | Permitted actions within those environments | +| `environments` | Environments where role applies (e.g. `["DEV", "PROD"]`) | +| `actions` | Permitted actions in those environments | -Predefined role templates exist, but projects can define **custom roles**. +Predefined role templates exist; projects can define **custom roles**. Two projects may have different custom roles or different definitions for -roles with the same name — always fetch per project when reporting. +same-named roles — always fetch per project when reporting. API: `GET /access/api/v1/projects/<project-key>/roles`. ## Project member -A user or group assigned a role within a project. +User or group with role in project. | Field | Description | |-------|-------------| | `name` | Username or group name | -| `roles` | List of role names assigned in this project | +| `roles` | Role names assigned in this project | -Membership is **not** the same as global platform administration. Roles are -evaluated in a project context — a user can be a Developer in one project -and a Release Manager in another. +Membership ≠ global platform administration. Roles evaluated in project context — user can be Developer in one project, Release Manager in another. API: `GET /access/api/v1/projects/<project-key>/users`, `GET /access/api/v1/projects/<project-key>/groups`. ## Environment -Environments group resources and scope RBAC so that roles can have different -permissions per environment (e.g. separate DEV vs PROD behavior). +Environments group resources + scope RBAC so roles have different permissions per environment (e.g. separate DEV vs PROD behavior). | Field | Description | |-------|-------------| | `name` | Environment name (e.g. `DEV`, `STAGING`, `PROD`) | -Environments can be defined at **global** scope (available across projects) or -**project** scope. Repositories can be assigned to one or more environments. -Environments are also used in release bundle promotion and application version -promotion (see `release-lifecycle-entities.md` and `apptrust-entities.md`). +Defined at **global** scope (cross-project) or **project** scope. Repositories assignable to one or more environments. Also used in release bundle promotion + application version promotion (see `release-lifecycle-entities.md`, `apptrust-entities.md`). API: `GET /access/api/v1/environments`. @@ -102,55 +94,52 @@ Documentation: [Environments](https://docs.jfrog.com/administration/docs/environ ## User -A platform identity that authenticates and is granted permissions. +Platform identity authenticating + granted permissions. | Field | Description | |-------|-------------| | `username` | Unique login name | | `email` | Email address | | `status` | `enabled` or `disabled` | -| `admin` | Whether the user has platform admin privileges | -| `groups` | Groups the user belongs to | +| `admin` | Platform admin privileges | +| `groups` | User's groups | | `realm` | Authentication realm (e.g. `internal`, `ldap`, `saml`) | -Users can be managed via REST API or synced from external identity providers -(LDAP, SAML, SCIM). +Managed via REST API or synced from external IdPs (LDAP, SAML, SCIM). API: `GET /access/api/v2/users/`, `GET /access/api/v2/users/<username>`. ## Group -A named collection of users that simplifies permission management. +Named user collection simplifying permission management. | Field | Description | |-------|-------------| | `name` | Group name | | `description` | Group description | -| `auto_join` | Whether new users automatically join this group | -| `admin_privileges` | Whether group members have admin privileges | +| `auto_join` | Whether new users auto-join | +| `admin_privileges` | Whether members have admin privileges | | `realm` | Source realm (e.g. `internal`, `ldap`) | -| `external_id` | External identity provider ID (for synced groups) | +| `external_id` | External IdP ID (synced groups) | -Groups can be assigned permissions and project roles, applying them to all -members at once. +Groups assignable to permissions + project roles — applies to all members at once. API: `GET /access/api/v2/groups/`, `GET /access/api/v2/groups/<group-name>`. ## Access token -A bearer credential with scoped permissions and optional expiry. +Bearer credential with scoped permissions + optional expiry. | Field | Description | |-------|-------------| | `token_id` | Unique token identifier | -| `subject` | The user or service the token represents | +| `subject` | User or service token represents | | `scope` | Permission scope (e.g. `applied-permissions/admin`, `applied-permissions/groups:readers`) | | `expires_in` | TTL in seconds (0 = non-expiring) | -| `refreshable` | Whether the token can be refreshed | +| `refreshable` | Whether token refreshable | | `description` | Human-readable description | -Tokens are the primary authentication mechanism for API and CLI access. -They can be scoped to specific groups, projects, or admin-level permissions. +Primary auth mechanism for API + CLI. Scopeable to specific groups, projects, or admin-level permissions. CLI: `jf access-token-create [username] [options]`. @@ -158,33 +147,29 @@ API: `POST /access/api/v1/tokens`. ## Repository–Project assignment -A repository is linked to **at most one** project via the `projectKey` field -in its configuration. +Repository linked to **at most one** project via `projectKey` in configuration. | Rule | Detail | |------|--------| -| **Authoritative field** | `projectKey` on the repository configuration | -| **Not authoritative** | Repository name — a naming pattern like `<project-key>-<suffix>` is a convention, not a guarantee | -| **Unassigned** | Missing or empty `projectKey` means the repo is not tied to any project | +| **Authoritative field** | `projectKey` on repository configuration | +| **Not authoritative** | Repository name — pattern like `<project-key>-<suffix>` = convention, not guarantee | +| **Unassigned** | Missing/empty `projectKey` = not tied to any project | ## Agent rules ### 1. Repository to project (authoritative) -1. Obtain repository keys from `GET /api/repositories` (lite list). -2. For each key, call `GET /api/repositories/<repo-key>` and read `projectKey`. -3. Treat missing or empty `projectKey` as **unassigned**, regardless of - whether the repo name looks like `<project-key>-...`. +1. Obtain repo keys from `GET /api/repositories` (lite list). +2. Per key: `GET /api/repositories/<repo-key>` → read `projectKey`. +3. Missing/empty `projectKey` = **unassigned**, regardless of name looking like `<project-key>-...`. -Do **not** infer project membership from naming alone. A name-prefix filter is -only a heuristic when detail calls are impossible, and is not authoritative. +Do **not** infer project membership from naming alone. Name-prefix filter = heuristic only when detail calls impossible — not authoritative. -Cost: one list plus N detail calls. Batch in one Shell invocation; reuse -captured JSON per SKILL.md "Preserving command output" when iterating with `jq`. +Cost: one list + N detail calls. Batch in one Shell invocation; reuse captured JSON per SKILL.md "Preserving command output" when iterating with `jq`. ### 2. Project roles (per project) -For each `project_key` in a multi-project report or comparison, call: +Per `project_key` in multi-project report/comparison: `GET /access/api/v1/projects/<project-key>/roles` diff --git a/skills/jfrog/references/preserving-command-output.md b/skills/jfrog/references/preserving-command-output.md new file mode 100644 index 0000000..c7f75e9 --- /dev/null +++ b/skills/jfrog/references/preserving-command-output.md @@ -0,0 +1,42 @@ +# Preserving command output + +> **Tier B MUST** before advanced CLI/API I/O. Not domain on-demand; not every CLI / setup. + +When a CLI command or API call returns data, redirect output to a temp file so +you can re-read it without re-executing the call: + +```bash +OUT=/tmp/jf-repos-$$.json +jf api /artifactory/api/repositories > "$OUT" +echo "$OUT" +``` + +Use `$$` (the shell PID) in the filename to prevent collisions across +concurrent sessions or processes. + +**Cross-call gotcha:** each Shell tool invocation runs in a new process with a +different PID, so `$$` expands to a different value in each call. Always +**echo the expanded filename** so the agent can read it from the output and +reuse the literal path in subsequent calls. Three patterns, in priority order: + +1. **`$$` + echo** (preferred): use `$$` for collision safety, echo the path + as shown above. The agent reads `/tmp/jf-repos-12345.json` from the output + and passes that literal value to the next Shell call. +2. **Session ID**: when many files share a prefix across calls, generate an ID + once (`SID=$(date +%s)-$$`), echo it, and reuse in later calls. +3. **Hardcoded names**: last resort — risks collisions when parallel calls or + subagents write to the same path. + +This protects against wasted round-trips when you need to retry parsing — for +example, if a `jq` filter fails or you extract the wrong field on the first +attempt. Re-read the file instead of hitting the server again. + +Do **not** duplicate the same **network** request in a shell pipeline (e.g. with +`||`) only to re-run `jq` or to reveal jq diagnostics—the duplicate call +adds load on JFrog without fetching new data. Run +`jq '<filter>' /tmp/jf-*-$$.json` (or redirect stdin from the file) instead +of re-running the same `jf api` or other identical network-backed command. + +Do **not** reuse saved output across unrelated steps or changed contexts (different +server, user, or intent). The file is only valid for the immediate sequence of +operations that motivated the original call. diff --git a/skills/jfrog/references/projects-api.md b/skills/jfrog/references/projects-api.md index 06f9f18..d568b42 100644 --- a/skills/jfrog/references/projects-api.md +++ b/skills/jfrog/references/projects-api.md @@ -1,20 +1,13 @@ # JFrog Projects API -**See also:** `references/platform-access-entities.md` for how Projects relate to -repositories, members, roles, and environments. +**See also:** `references/platform-access-entities.md`. -Projects are managed through the Access API. There is no CLI subcommand — -invoke the endpoints via `jf api` (see the base skill's *Invoking platform -APIs with `jf api`* section). Authentication against the resolved JFrog -server is automatic. - -All endpoints below use full product-prefixed paths (`/access/api/...`, -`/artifactory/api/...`). +Projects via Access API — no CLI; use `jf api` (base skill *Invoking platform +APIs with `jf api`*). Paths: `/access/api/...`, `/artifactory/api/...`. ## Authentication -Credentials are resolved automatically by `jf api` from the active `jf config` -server — no token extraction or `curl` wiring is needed. +`jf api` resolves credentials from active `jf config` — no manual token/`curl`. ## Projects @@ -24,8 +17,8 @@ server — no token extraction or `curl` wiring is needed. jf api /access/api/v1/projects ``` -Returns an array of project objects with `project_key`, `display_name`, -`description`, `admin_privileges`, `storage_quota_bytes`, etc. +Returns project objects: `project_key`, `display_name`, `description`, +`admin_privileges`, `storage_quota_bytes`, etc. ### Get a single project @@ -50,8 +43,7 @@ jf api /access/api/v1/projects \ }' ``` -The `project_key` must be 2-32 lowercase alphanumeric characters (hyphens -allowed, no leading/trailing hyphen). +`project_key`: 2-32 lowercase alphanumeric (hyphens allowed, no leading/trailing hyphen). ### Update a project @@ -97,9 +89,8 @@ jf api /access/api/v1/projects/<project-key>/users/<username> -X DELETE jf api /access/api/v1/projects/<project-key>/groups ``` -The response may list group entries under **`members`**, **`groups`**, or both, -depending on platform version (same general shape as users: `name` and -`roles`). Parsers should accept whichever key is present. +Response may list groups under **`members`**, **`groups`**, or both (platform +version dependent; same shape as users: `name`, `roles`). Accept whichever key present. ### Add a group @@ -117,16 +108,15 @@ jf api /access/api/v1/projects/<project-key>/groups/<group-name> \ jf api /access/api/v1/projects/<project-key>/roles ``` -Returns an array of role objects. Each has `name`, `description`, `type` +Returns role objects: `name`, `description`, `type` (`PREDEFINED`, `ADMIN`, or `CUSTOM`), `environments` (e.g. `["DEV","PROD"]`), -and `actions` (permission strings). +`actions` (permission strings). -Predefined roles: Project Admin, Developer, Contributor, Viewer, Release -Manager, Security Manager, AppTrust Manager, Model Governor, Model Developer. +Predefined: Project Admin, Developer, Contributor, Viewer, Release Manager, +Security Manager, AppTrust Manager, Model Governor, Model Developer. -**Multi-project reports:** Call this endpoint **once per `project_key`**. Custom -roles and definitions can differ by project; do not assume one project's role -list matches another. See `references/platform-access-entities.md`. +**Multi-project reports:** one call per `project_key` — roles differ by project. +See `references/platform-access-entities.md`. ### Create a custom role @@ -144,8 +134,8 @@ jf api /access/api/v1/projects/<project-key>/roles \ ## Environments -The product supports **global** and **project-scoped** environment concepts for -RBAC and resource grouping; see +**Global** and **project-scoped** environment concepts for RBAC and resource +grouping; see [Environments (Administration)](https://docs.jfrog.com/administration/docs/environments) and `references/platform-access-entities.md`. @@ -155,8 +145,7 @@ and `references/platform-access-entities.md`. jf api /access/api/v1/environments ``` -Returns `[{"name": "DEV"}, {"name": "PROD"}, ...]` -- the platform environment -list available through this Access API path. +Returns `[{"name": "DEV"}, {"name": "PROD"}, ...]` — platform environment list. ### Create an environment @@ -166,13 +155,13 @@ jf api /access/api/v1/environments \ -d '{"name": "STAGING"}' ``` -Environment names are uppercase by convention. +Environment names uppercase by convention. ## Repository assignment ### Assign a repository to a project -Assign a repository to a project by updating its configuration: +Update repo configuration: ```bash jf api /artifactory/api/repositories/<repo-key> \ @@ -182,8 +171,7 @@ jf api /artifactory/api/repositories/<repo-key> \ ### List repositories for a project -`GET /artifactory/api/repositories` supports optional query parameters that can -be combined: +`GET /artifactory/api/repositories` — optional combinable query params: | Parameter | Values | Example | |-----------|--------|---------| @@ -202,40 +190,34 @@ jf api "/artifactory/api/repositories?project=<project-key>&type=local&packageTy jf api "/artifactory/api/repositories?type=remote" ``` -Returns a lite list with `key`, `type`, `packageType`, and `url` per repo. -See `references/artifactory-api-gaps.md` for additional filter examples. +Lite list: `key`, `type`, `packageType`, `url` per repo. +See `references/artifactory-api-gaps.md` for filter examples. ### Get repository detail -To retrieve the full configuration of a specific repository (including fields -like `projectKey`, `description`, storage settings, etc. that are absent from -the lite list), use the detail endpoint: +Full configuration (including `projectKey`, `description`, storage settings +absent from lite list): ```bash jf api "/artifactory/api/repositories/<repo-key>" ``` -Use this when you have a specific repo or a short list of repos to inspect -- -not for filtering large sets. For filtering, use the query parameters above. +For specific repo or short list — not for filtering large sets. Filter via query params above. ### Name-prefix heuristic (unreliable -- last resort) -Project-scoped repos often follow a `<project-key>-*` naming convention, but -the API does **not** enforce this. Repos can belong to a project without the -prefix, or carry the prefix without belonging. Always prefer -`?project=<project-key>` for authoritative results. Use name-prefix matching -only when the `project` query parameter is unavailable (e.g. older Artifactory -versions). +Project-scoped repos often follow `<project-key>-*` naming, but API does **not** +enforce. Repos can belong without prefix, or carry prefix without belonging. +Prefer `?project=<project-key>`. Name-prefix only when `project` param unavailable +(e.g. older Artifactory). ## Common error responses -- **Empty members/groups**: projects with no members return - `{"members": []}`, not 404. The groups list endpoint may use the same - `members` key for group entries; empty lists look like `{"members": []}`. - Always check the array rather than the status code alone. -- **Invalid project key on create**: returns 400 if `project_key` is outside - 2-32 chars, contains uppercase letters, or has leading/trailing hyphens. -- **Project not found**: returns 404 with `{"errors": [{"message": "..."}]}`. -- **Insufficient permissions**: `jf api` exits with code 1 on non-2xx and - prints `[Warn] jf api: ... returned 403` on stderr when the token lacks - project admin or platform admin privileges. +- **Empty members/groups**: no members → `{"members": []}`, not 404. Groups + endpoint may use same `members` key; empty = `{"members": []}`. Check array, + not status code alone. +- **Invalid project key on create**: 400 if `project_key` outside 2-32 chars, + uppercase, or leading/trailing hyphens. +- **Project not found**: 404 with `{"errors": [{"message": "..."}]}`. +- **Insufficient permissions**: `jf api` exits 1 on non-2xx; stderr + `[Warn] jf api: ... returned 403` when token lacks project/platform admin. diff --git a/skills/jfrog/references/release-lifecycle-entities.md b/skills/jfrog/references/release-lifecycle-entities.md index 4d66e2f..73f0d74 100644 --- a/skills/jfrog/references/release-lifecycle-entities.md +++ b/skills/jfrog/references/release-lifecycle-entities.md @@ -3,10 +3,10 @@ When to read this file: - Working with **release bundles** (create, promote, distribute, delete). -- Understanding the **lifecycle stages** a release bundle passes through. +- Understanding **lifecycle stages** release bundle passes through. - Setting up **distribution** to Edge nodes or other Platform Deployments. - Working with **evidence** (supply chain attestations). -- Mapping CLI commands (`rbc`, `rbp`, `rbd`, etc.) to their lifecycle meaning. +- Mapping CLI commands (`rbc`, `rbp`, `rbd`, etc.) → lifecycle meaning. ## Entity relationship overview @@ -23,46 +23,39 @@ flowchart TD ## Release Bundles (v2) -A release bundle is an **immutable, versioned collection of artifacts** -assembled from Artifactory. It represents a releasable unit that moves through -lifecycle stages toward production. +Release bundle = **immutable, versioned artifact collection** assembled from Artifactory. Releasable unit moving through lifecycle stages → production. | Field | Description | |-------|-------------| | `name` | Bundle name (e.g. `my-app`) | | `version` | Semantic or custom version string (e.g. `1.2.0`) | -| `artifacts` | Set of artifacts referenced by repo path and checksum | -| `created` | Timestamp of creation | +| `artifacts` | Artifacts by repo path + checksum | +| `created` | Creation timestamp | | `status` | Current lifecycle status | -Bundles can be assembled from: -- **AQL queries** — dynamically select artifacts matching criteria -- **Build info** — include all artifacts from a published build +Assembly sources: +- **AQL queries** — dynamically select matching artifacts +- **Build info** — all artifacts from published build - **Explicit list** — specify repo paths directly -Once created, a bundle's artifact list is **immutable** — the same version -always refers to the exact same set of artifacts. This is enforced by -checksums. +Once created, artifact list **immutable** — same version = exact same artifacts (enforced by checksums). -> **v1 vs v2:** Release Bundle v1 was managed by the Distribution service and -> is deprecated. Release Bundle v2 is managed by the Lifecycle service and is -> the current model. The CLI `rbc`/`rbp`/`rbd` commands default to v2. +> **v1 vs v2:** Release Bundle v1 managed by Distribution service — deprecated. v2 managed by Lifecycle service — current model. CLI `rbc`/`rbp`/`rbd` default to v2. ### CLI commands | Command | Operation | Description | |---------|-----------|-------------| -| `jf rbc` | Create | Assemble a new release bundle version | -| `jf rbp` | Promote | Move a bundle to the next lifecycle stage | -| `jf rbd` | Distribute | Deliver a bundle to target nodes | +| `jf rbc` | Create | Assemble new release bundle version | +| `jf rbp` | Promote | Move bundle to next lifecycle stage | +| `jf rbd` | Distribute | Deliver bundle to target nodes | | `jf rbs` | Sign | (v1 only — v2 signs on creation) | -| `jf rbdell` | Delete local | Remove a bundle version locally | -| `jf rbdelr` | Delete remote | Remove a distributed bundle from targets | +| `jf rbdell` | Delete local | Remove bundle version locally | +| `jf rbdelr` | Delete remote | Remove distributed bundle from targets | ## Lifecycle stages -A release bundle progresses through **stages** that typically correspond to -environments (DEV → STAGING → PROD). Each stage transition is a **promotion**. +Release bundle progresses through **stages** typically matching environments (DEV → STAGING → PROD). Each transition = **promotion**. ``` Created ──promote──▶ DEV ──promote──▶ STAGING ──promote──▶ PROD @@ -73,108 +66,91 @@ Created ──promote──▶ DEV ──promote──▶ STAGING ──promote ``` Promotion (`jf rbp`): -- Moves the bundle to a target **environment** -- Requires the bundle to have passed any required quality gates (Xray scans, approvals) -- Each promotion is **recorded** with timestamp, user, source and target environment -- Promotions are auditable — the full history is preserved +- Moves bundle to target **environment** +- Requires bundle passed required quality gates (Xray scans, approvals) +- Each promotion **recorded** with timestamp, user, source + target environment +- Promotions auditable — full history preserved -Environments used in promotion are the same environments configured in the -platform (see `platform-access-entities.md`). They scope which repos are -accessible and which roles apply at each stage. +Promotion environments = platform environments (see `platform-access-entities.md`). Scope repo access + roles per stage. ## Distribution -Distribution delivers a release bundle to **Edge nodes** or other JFrog -Platform Deployments. +Distribution delivers release bundle to **Edge nodes** or other JFrog Platform Deployments. | Concept | Description | |---------|-------------| -| **Distribution target** | A JFrog Edge node or Platform Deployment registered to receive bundles | -| **Distribution rules** | Configuration mapping targets to the bundle version being delivered | -| **Site** | A named destination in the distribution rule | +| **Distribution target** | JFrog Edge node or Platform Deployment registered to receive bundles | +| **Distribution rules** | Config mapping targets → bundle version being delivered | +| **Site** | Named destination in distribution rule | -Distribution (`jf rbd`) copies the bundle's artifacts to the target nodes, -preserving checksums and metadata. The target nodes receive the artifacts in -their local repositories. +Distribution (`jf rbd`) copies bundle artifacts to target nodes, preserving checksums + metadata. Target nodes receive artifacts in local repositories. -Distribution is typically the **final step** after a bundle has been promoted -to a production-ready stage. +Typically **final step** after bundle promoted to production-ready stage. ## Release Bundles in GraphQL (OneModel) -Release bundle versions are also queryable via the OneModel GraphQL API -which exposes additional relationships not available -through the CLI: +Release bundle versions also queryable via OneModel GraphQL — additional relationships beyond CLI: | Field | Description | |-------|-------------| | `createdBy`, `createdAt` | Audit fields | | `artifactsConnection` | Paginated artifacts with path, name, sha256, packageType, packageName, packageVersion, size, properties | -| `evidenceConnection` | Evidence attached to the bundle version | -| `fromBuilds` | Builds that sourced the bundle (name, number, startedAt, repositoryKey) | +| `evidenceConnection` | Evidence on bundle version | +| `fromBuilds` | Builds sourcing bundle (name, number, startedAt, repositoryKey) | -Each artifact within a bundle also has its own `evidenceConnection`, allowing -per-artifact attestation queries. +Each bundle artifact has own `evidenceConnection` — per-artifact attestation queries. -For the OneModel query workflow (credentials, schema fetch, validation, -execution), read `references/onemodel-graphql.md`. +OneModel query workflow (credentials, schema fetch, validation, execution): `references/onemodel-graphql.md`. Query: `releaseBundleVersion.getReleaseBundleVersion(name: "...", version: "...", ...)`. ## Evidence -Evidence provides **cryptographic attestations** about artifacts, builds, -release bundles, application versions, and stored packages for supply chain -integrity. +Evidence = **cryptographic attestations** about artifacts, builds, release bundles, application versions, stored packages for supply chain integrity. ### Evidence entity | Field | Description | |-------|-------------| | `evidenceId` | Unique identifier | -| `subject` | The entity being attested (see Evidence subjects below) | +| `subject` | Entity attested (see Evidence subjects below) | | `predicateCategory` | Category (e.g. `distribution`) | | `predicateType` | Full type URI (e.g. `https://jfrog.com/evidence/distribution/v1`) | | `predicateSlug` | Short form (e.g. `distribution-v1`) | | `predicate` | Predicate data as JSON | -| `verified` | Whether the evidence signature has been verified | -| `signingKey` | Signing key with `alias` and `publicKey` for DSSE verification | -| `providerId` | ID of the evidence provider | -| `stageName` | Stage in which evidence was created (for release bundles and app versions) | +| `verified` | Whether evidence signature verified | +| `signingKey` | Signing key with `alias` + `publicKey` for DSSE verification | +| `providerId` | Evidence provider ID | +| `stageName` | Stage evidence created (release bundles + app versions) | | `createdBy`, `createdAt` | Audit fields | | `attachments` | File attachments (e.g. legal documents) with name, sha256, type, downloadPath | -Evidence records create a verifiable chain of trust: -- Build systems attest to build provenance -- Test frameworks attest to test results -- Approvers attest to manual reviews -- Security scans attest to vulnerability status -- Distribution records attest to delivery +Evidence records = verifiable chain of trust: +- Build systems → build provenance +- Test frameworks → test results +- Approvers → manual reviews +- Security scans → vulnerability status +- Distribution → delivery records ### Evidence subjects -Evidence subjects are **cross-domain** — the `EvidenceSubject` type is shared -across multiple domains via the `fullPath` key: +Evidence subjects **cross-domain** — `EvidenceSubject` type shared across domains via `fullPath` key: | Subject type | Domain | Example | |-------------|--------|---------| | Release bundle version | Release Lifecycle | Bundle attestation before distribution | -| Release bundle artifact | Release Lifecycle | Per-artifact attestation within a bundle | +| Release bundle artifact | Release Lifecycle | Per-artifact attestation in bundle | | Application version | AppTrust | App version attestation before promotion | -| Application version artifact | AppTrust | Per-artifact attestation within an app version | -| Stored package version location | Stored Packages | Package attestation at a specific repo location | +| Application version artifact | AppTrust | Per-artifact attestation in app version | +| Stored package version location | Stored Packages | Package attestation at specific repo location | -This means evidence can be queried from any of these entry points — you don't -need to start from the Evidence query root. For example, -`applications.getApplicationVersion(...).evidenceSubject` reaches the same -evidence as `evidence.searchEvidence(where: {...})`. +Evidence queryable from any entry point — no need to start from Evidence query root. Example: `applications.getApplicationVersion(...).evidenceSubject` = same evidence as `evidence.searchEvidence(where: {...})`. ### CLI and GraphQL access -- **CLI**: `jf evd` namespace. Use `jf evd --help` for available commands. +- **CLI**: `jf evd` namespace. `jf evd --help` for commands. - **GraphQL**: `evidence.searchEvidence(where: {...})`, `evidence.getEvidenceById(id: "...")`, or `evidence.getEvidence(repositoryKey: "...", path: "...", name: "...")`. -Evidence can be queried to verify that all required attestations exist before -promotion or distribution. +Query evidence to verify required attestations exist before promotion or distribution. diff --git a/skills/jfrog/references/stored-packages-entities.md b/skills/jfrog/references/stored-packages-entities.md index 0659c0b..862c276 100644 --- a/skills/jfrog/references/stored-packages-entities.md +++ b/skills/jfrog/references/stored-packages-entities.md @@ -2,18 +2,16 @@ When to read this file: -- Querying **packages stored in Artifactory** at the package level (not raw artifacts). -- Finding **where a package version lives** (which repository, which path). -- Looking up **download statistics**, **tags**, or **qualifiers** on packages. -- Using the OneModel GraphQL API with the `storedPackages` query root. -- Understanding how the **Metadata layer bridges** Artifactory storage with - Applications and Catalog. +- Querying **packages stored in Artifactory** at package level (not raw artifacts). +- Finding **where a package version lives** (repository, path). +- **Download statistics**, **tags**, or **qualifiers** on packages. +- OneModel GraphQL with `storedPackages` query root. +- How **Metadata layer bridges** Artifactory storage with Applications and Catalog. -Stored Packages entities are accessed via the **OneModel GraphQL API** -(`/onemodel/api/v1/graphql`). +Stored Packages via **OneModel GraphQL API** (`/onemodel/api/v1/graphql`). -For the OneModel query workflow (credentials, schema fetch, validation, -execution), read `references/onemodel-graphql.md`. +OneModel workflow (credentials, schema fetch, validation, execution): +`references/onemodel-graphql.md`. ## Entity relationship overview @@ -32,9 +30,7 @@ erDiagram ## StoredPackage -A software package as known to Artifactory's metadata layer. This is the -**package-centric abstraction** over raw artifact storage — it groups related -artifacts into named, typed, versioned packages. +Package in Artifactory metadata — **package-centric abstraction** over raw storage. | Field | Description | |-------|-------------| @@ -55,8 +51,7 @@ Query: `storedPackages.getPackage(name: "...", type: "...")` or ### Repository package type mapping -The `repositoryPackageType` enum canonicalizes Artifactory repo types. Notable -aliases: +`repositoryPackageType` canonicalizes Artifactory repo types. Notable aliases: | Artifactory type | Enum value | |------------------|------------| @@ -67,12 +62,12 @@ aliases: | `terraformprovider`, `terraformmodule` | `TERRAFORM` | | `hfdataset` | `HUGGINGFACEML` | -The full enum includes 40+ types. Use `repositoryPackageType` for filtering -when the Artifactory repo type name differs from the canonical form. +Full enum: 40+ types. Use `repositoryPackageType` when Artifactory repo type +≠ canonical form. ## StoredPackageVersion -A specific version of a package, with location and artifact details. +Specific package version with location and artifact details. | Field | Description | |-------|-------------| @@ -92,19 +87,13 @@ Query: `storedPackages.searchPackageVersions(where: {...})`. ### Filtering capabilities -StoredPackageVersion supports rich filtering: -- By version string (exact, prefix, contains) -- By project key -- By creation/modification date ranges -- By version size -- By associated tags, qualifiers, locations, artifacts, licenses -- `ignorePreRelease` flag to exclude pre-release versions +Filter by: version (exact/prefix/contains), project key, date ranges, size, +tags, qualifiers, locations, artifacts, licenses; `ignorePreRelease` excludes pre-release. ## StoredPackageVersionLocation -The **bridge entity** connecting a package version to a physical repository -location in Artifactory. This is the key entity for answering "where does -package X version Y live?" +**Bridge entity** — package version → physical repository location. Key for +"where does package X version Y live?" | Field | Description | |-------|-------------| @@ -116,17 +105,13 @@ package X version Y live?" | `evidenceSubject` | Evidence attestation anchor (shared across domains) | | `stats` | Location-specific download count and last-downloaded timestamps | -The `evidenceSubject` field connects to the Evidence domain — evidence can be -attached to a specific package version in a specific repo, not just to the -version globally. +`evidenceSubject` → Evidence domain — evidence per package version in specific repo. -The `stats` block includes `downloadCount`, `lastDownloadedAt`, and -`remoteLastDownloadedAt` — the last field tracks when the artifact was last -fetched from a remote repository source. +`stats`: `downloadCount`, `lastDownloadedAt`, `remoteLastDownloadedAt` (last fetch from remote source). ## StoredPackageArtifact -An individual binary file within a package version. +Individual binary file within a package version. | Field | Description | |-------|-------------| @@ -137,22 +122,18 @@ An individual binary file within a package version. | `mimeType` | Content type | | `qualifiers` | Artifact-level key-value qualifiers | -Filtering supports `isLeadArtifact` to identify the primary artifact in a -package version, and `projectKey` for project-scoped queries. +Filtering: `isLeadArtifact` (primary artifact), `projectKey` (project-scoped queries). ## Cross-domain connections Stored Packages bridge Artifactory storage to higher-level domains: - **Applications (AppTrust)** — `ApplicationVersionReleasable.packageVersionLocation` - links to `StoredPackageVersionLocation`. Applications reference where their - package releasables physically reside. -- **Evidence** — `StoredPackageVersionLocation.evidenceSubject` connects to - the Evidence domain via `EvidenceSubject.fullPath`. Evidence can attest to - a specific package version at a specific repository location. -- **Catalog** — Stored Packages represent what's *in your Artifactory*, while - the Catalog represents the global knowledge base *about* those packages. - The package `type` + `name` can join across both. + → `StoredPackageVersionLocation` (where package releasables reside). +- **Evidence** — `StoredPackageVersionLocation.evidenceSubject` → Evidence via + `EvidenceSubject.fullPath` (attestation at specific repo location). +- **Catalog** — Stored Packages = what's *in Artifactory*; Catalog = global + knowledge *about* packages. Join on `type` + `name`. ## Stored Packages vs. raw Artifactory diff --git a/skills/jfrog/references/xray-entities.md b/skills/jfrog/references/xray-entities.md index fdb5b7a..f8d5caa 100644 --- a/skills/jfrog/references/xray-entities.md +++ b/skills/jfrog/references/xray-entities.md @@ -1,15 +1,14 @@ # Xray entities -When to read this file: +Read when working with: -- Working with **security scanning**, **vulnerabilities**, or **license compliance**. -- Configuring or querying **watches**, **policies**, or **violations**. -- Debugging why a scan produced unexpected results or why violations are missing. -- Generating **security reports** or **SBOM** data. -- Searching for **artifacts impacted by a CVE** or containing a specific package. +- **Security scanning**, **vulnerabilities**, or **license compliance** +- **Watches**, **policies**, or **violations** (configure / query / debug missing results) +- **Security reports** or **SBOM** data +- **Artifacts impacted by a CVE** or containing a specific package -For CLI commands: `jf xr --help`, `jf audit --help`, `jf scan --help`. -For REST fallback: `jf api /xray/api/v2/...` (see the base skill's *Invoking platform APIs with `jf api`* section). +CLI: `jf xr --help`, `jf audit --help`, `jf scan --help`. +REST: `jf api /xray/api/v2/...` (see base skill *Invoking platform APIs with `jf api`*). ## Entity relationship overview @@ -29,85 +28,74 @@ erDiagram IgnoreRule }o--o{ Violation : "suppresses" ``` -The core chain: **Watch** monitors **Resources** using **Policies**. When a -**Component** in a resource matches a policy **Rule**, Xray generates a -**Violation**. +Core chain: **Watch** monitors **Resources** via **Policies**. When a +**Component** matches a policy **Rule** → Xray generates a **Violation**. ## Indexed resources -Before Xray can scan or monitor a resource, it must be **indexed**. Indexing -tells Xray to decompose artifacts in that resource into components and track -them continuously. +Xray must **index** a resource before scan/monitor. Indexing decomposes +artifacts into components and tracks them continuously. -Indexable resource types: -- **Repositories** — local and remote repos (Xray indexes the `-cache` for remote repos) -- **Builds** — build info records published to Artifactory +Indexable types: +- **Repositories** — local and remote (Xray indexes the `-cache` for remotes) +- **Builds** — build info published to Artifactory - **Release Bundles** — release bundle versions -Indexing is configured in the Xray UI or via the -`PUT /api/v1/binMgr/builds` / `PUT /api/v1/binMgr/repos` endpoints. +Configure via Xray UI or `PUT /api/v1/binMgr/builds` / `PUT /api/v1/binMgr/repos`. ## Components -A component is a software package that Xray identifies during scanning. -Xray decomposes artifacts (JARs, Docker layers, npm tarballs, etc.) into -their constituent components and maps each to its vulnerability and license -data. +Software package Xray identifies during scanning. Artifacts (JARs, Docker +layers, npm tarballs, …) decompose into components mapped to vulnerability +and license data. -Component identifiers vary by package type: +IDs by package type: - Maven: `gav://group:artifact:version` - npm: `npm://package:version` - Docker: `docker://image:tag` - Python: `pypi://package:version` - Go: `go://module:version` -- Generic: identified by checksum +- Generic: by checksum ## Vulnerabilities -A vulnerability is a known security issue associated with specific component -versions. +Known security issue tied to specific component versions. | Field | Description | |-------|-------------| | `cve` | CVE identifier (e.g. `CVE-2021-44228`) | | `xray_id` | JFrog-assigned identifier | | `severity` | `Critical`, `High`, `Medium`, `Low`, `Unknown` | -| `cvss_v3` | Numeric score extracted from the CVSS v3 string (e.g. `"7.2/CVSS:3.1/..."` → `7.2`) | +| `cvss_v3` | Numeric score from CVSS v3 string (e.g. `"7.2/CVSS:3.1/..."` → `7.2`) | | `fixed_versions` | Component versions where the vulnerability is resolved | | `references` | Links to advisories and patches | -When asked about CVSS score, always use 'cvss_v3' field. -Xray maintains its own vulnerability database, updated continuously. +CVSS score → always use `cvss_v3`. Xray maintains its own continuously updated DB. ## Contextual analysis -Contextual analysis evaluates whether a vulnerability is **actually -reachable** in the specific usage context, going beyond the raw CVE data. -It considers factors like whether vulnerable code paths are invoked, whether -mitigating configurations are present, and whether the component is used in a -way that exposes the vulnerability. +Evaluates whether a vulnerability is **actually reachable** in this usage +context (beyond raw CVE data): invoked code paths, mitigating configs, +exposure via how the component is used. -The result is an **applicability** status that helps prioritize remediation: -a Critical CVE that is not applicable in context is lower priority than a -High CVE that is confirmed applicable. +**Applicability** status drives remediation priority: Critical + not +applicable ≺ High + confirmed applicable. -Available for supported package types and vulnerability types; check Xray -documentation for current coverage. +Coverage varies by package/vulnerability type — check Xray docs. ### Response fields: `applicability` vs `applicability_details` -The summary artifact API returns **two** contextual analysis fields per issue. -They are not interchangeable — use the correct one for the task: +Summary artifact API returns **two** contextual fields per issue — not +interchangeable: | Field | Scope | Use for | |-------|-------|---------| | `applicability` | Top-level array; only populated when a scanner ran and produced a definitive `true`/`false` result. Many issues have `applicability: null`. | Checking whether a specific CVE is confirmed applicable or not applicable, and reading the `info` field for the human-readable reason. | | `applicability_details` | Array present on every issue with exactly one entry per component-vulnerability pair. Always has a `result` string. | **Counting and summarizing** contextual analysis across all issues. This is the authoritative source for breakdowns. | -**Always use `applicability_details[].result` for counts and summaries.** The -top-level `applicability` field is null for issues where no scanner exists or -where the result is undetermined, which leads to incorrect "not analyzed" -buckets if used for aggregation. +**Always use `applicability_details[].result` for counts and summaries.** +Top-level `applicability` is null when no scanner exists or result is +undetermined — aggregating on it mis-buckets "not analyzed". ### `applicability_details` result values @@ -143,17 +131,13 @@ jf api /xray/api/v2/summary/artifact \ ## Licenses -License metadata associated with a component, identified by SPDX identifier -or license name (e.g. `Apache-2.0`, `MIT`, `GPL-3.0`). - -Used in **license compliance policies** — organizations define which licenses -are approved, restricted, or banned, and Xray enforces these rules through -watches and policies. +Component license metadata (SPDX ID or name, e.g. `Apache-2.0`, `MIT`, +`GPL-3.0`). Feeds **license compliance policies** — approved / restricted / +banned lists enforced via watches and policies. ## Watches -A watch is the central **monitoring configuration** that connects resources to -policies. +Central **monitoring config** linking resources to policies. | Field | Description | |-------|-------------| @@ -163,15 +147,14 @@ policies. | `active` | Whether the watch is enabled | | `project_key` | Optional project scope | -When an indexed resource changes (new artifact, updated component data), Xray -re-evaluates all watches that include that resource. +Indexed resource change (new artifact, updated component data) → Xray +re-evaluates watches that include that resource. API: `GET/POST/PUT/DELETE /api/v2/watches` ## Policies -A policy defines **rules** that Xray evaluates against components found in -watched resources. +**Rules** Xray evaluates against components in watched resources. | Policy type | Rule evaluates | Common conditions | |-------------|---------------|-------------------| @@ -179,16 +162,15 @@ watched resources. | **License** | Licenses | Allowed/banned license list | | **Operational risk** | Package metadata | End-of-life, no new versions, low activity | -Each rule has: -- **Condition** — what triggers the rule (severity ≥ High, license in banned list, etc.) -- **Actions** — what happens on match: generate violation, block download, fail build, send notification +Each rule: +- **Condition** — trigger (severity ≥ High, license in banned list, …) +- **Actions** — on match: violation, block download, fail build, notify API: `GET/POST/PUT/DELETE /api/v2/policies` ## Violations -A violation is generated when a component in a watched resource matches a -policy rule. +Generated when a watched component matches a policy rule. | Field | Description | |-------|-------------| @@ -203,26 +185,22 @@ policy rule. | `description` | Violation description (markdown from Xray 3.42.3+) | | `matched_policies` | Policies that matched | -Violations are the primary output that security teams act on. They accumulate -until the underlying component is updated, the artifact is removed, or the -violation is suppressed via an ignore rule. +Primary security-team output. Accumulates until the component is updated, the +artifact is removed, or an ignore rule suppresses it. -Starting from Xray 3.42.3, JFrog Security CVE Research and Enrichment data is -included in the response. The `short_description`, `full_description`, and -`remediation` fields are markdown. +From Xray 3.42.3+: JFrog Security CVE Research/Enrichment in the response; +`short_description`, `full_description`, `remediation` are markdown. ### API: `POST /api/v1/violations` -Search violations with filters and pagination. Requires Read permissions. +Search with filters + pagination. Requires Read permissions. -**Performance warning:** On large or shared instances the violations API can -hang indefinitely when called without narrowing filters. Always include at -least one of `watch_name` or `created_from` (or both) to avoid timeouts. There -is no server-side query timeout — the request simply never returns. If you need -violations across all watches, iterate per-watch rather than issuing a single -unfiltered call. The API also has no `package_type` filter, so filtering by -component type (e.g. npm-only) must be done client-side on `infected_components` -or by querying watches that cover specific repository types. +**Performance warning:** On large/shared instances, violations API can hang +indefinitely without narrowing filters. Always include at least `watch_name` +or `created_from` (or both). No server-side query timeout — request may never +return. For all watches: iterate per-watch; do not issue one unfiltered call. +No `package_type` filter — filter client-side on `infected_components`, or +query watches covering specific repo types. ```bash jf api /xray/api/v1/violations \ @@ -282,62 +260,48 @@ Narrow violations to specific artifacts, builds, or release bundles: | `release_bundles` | `name`, `version` | | `release_bundles_v2` | `name`, `version`, `project` | -**There is no `component` filter.** To find violations for a specific component -(e.g. `npm://lodash:4.17.19`), filter by the resource that contains it -(artifact path, build, or release bundle) or use `cve_id`/`issue_id` to -narrow by vulnerability, then inspect `infected_components` in the response. +**No `component` filter.** For a component (e.g. `npm://lodash:4.17.19`), +filter by containing resource (artifact path, build, release bundle) or by +`cve_id`/`issue_id`, then inspect `infected_components`. ## Ignore rules -An ignore rule suppresses specific violations so they no longer surface in -reports or block downloads. - -Ignore rules can be scoped by: -- **Vulnerability** — specific CVE or Xray ID -- **Component** — specific component and version -- **Artifact** — specific repo path -- **Docker layer** — specific layer in a Docker image -- **Build** — specific build name -- **Release bundle** — specific bundle name/version +Suppress specific violations so they no longer surface or block downloads. -Each rule has optional `expires_at` and `notes` fields. +Scope by: **Vulnerability** (CVE / Xray ID), **Component**, **Artifact**, +**Docker layer**, **Build**, **Release bundle**. Optional `expires_at`, `notes`. API: `GET/POST/DELETE /api/v1/ignore_rules` -**Version note:** The ignore rules API uses **v1** only. The `/api/v2/ignore_rules` -endpoint does not exist and returns 404. +**Version note:** Ignore rules are **v1 only**. `/api/v2/ignore_rules` → 404. ## Summary APIs -On-demand security, license, and operational risk lookups for artifacts -stored in Artifactory. Use **only for security and compliance queries**. +On-demand security, license, and operational-risk lookups for Artifactory +artifacts. Use **only for security/compliance queries**. -**Which endpoint to use:** -- Know the Artifactory path and the repo is indexed → `/api/v2/summary/artifact` -- Know the component ID (GAV, npm, pypi) or the artifact is not indexed → `/api/v1/summary/component` -- Not sure if indexed → try component summary first (always works if the component exists in Xray's DB) +**Which endpoint:** +- Know Artifactory path + repo indexed → `/api/v2/summary/artifact` +- Know component ID (GAV, npm, pypi) or artifact not indexed → `/api/v1/summary/component` +- Unsure if indexed → try component summary first (works if component is in Xray DB) -**Prerequisite — Xray indexing:** These endpoints return data only if the -artifact's repository is indexed by Xray **and** Xray has already scanned -the artifact. An artifact can exist in Artifactory while Xray knows nothing -about it — either because the repository was not marked for indexing, or -because Xray has not yet processed it. Empty results do **not** mean the -artifact is clean; they mean Xray has no data. When results are empty, -report that the artifact may not be indexed rather than declaring it -vulnerability-free. +**Prerequisite — Xray indexing:** Data only if the repo is indexed **and** +Xray has scanned the artifact. Artifact may exist in Artifactory while Xray +knows nothing (repo not marked for indexing, or not yet processed). Empty +results ≠ clean — means no Xray data. Report possibly not indexed; do not +declare vulnerability-free. ### `/api/v1/summary/component` -**v1 only — there is no `/api/v2/summary/component`.** Calling v2 returns 404. +**v1 only — no `/api/v2/summary/component`.** v2 → 404. -Query by component identifier. Returns `issues[]`, `licenses[]`, and -`operational_risks[]` per component. Useful for looking up vulnerabilities -affecting a specific package version without needing to know its Artifactory -path, or when the artifact's repository is not indexed by Xray (making the -artifact summary endpoint return empty). +Query by component ID. Returns `issues[]`, `licenses[]`, +`operational_risks[]` per component. Use when you know the package version +but not the Artifactory path, or when the repo is not indexed (artifact +summary would be empty). -The request body uses `component_details` (an array of objects with -`component_id`), **not** `component_ids`. +Body uses `component_details` (array of `{component_id}`), **not** +`component_ids`. ```bash jf api /xray/api/v1/summary/component \ @@ -345,8 +309,7 @@ jf api /xray/api/v1/summary/component \ -d '{"component_details": [{"component_id": "npm://lodash:4.17.19"}]}' ``` -Component ID format follows the same convention as component identifiers -elsewhere in Xray (see [Components](#components) above): +Component ID format matches [Components](#components) above: - npm: `npm://package:version` - Maven: `gav://group:artifact:version` - Python: `pypi://package:version` @@ -355,24 +318,21 @@ elsewhere in Xray (see [Components](#components) above): ### `/api/v1/summary/artifact` and `/api/v2/summary/artifact` -Query by Artifactory path or SHA-256 checksum. Returns `issues[]`, -`licenses[]`, and `operational_risks[]` per artifact. +Query by Artifactory path or SHA-256. Returns `issues[]`, `licenses[]`, +`operational_risks[]` per artifact. -- **v1** — base response with vulnerability, license, and operational risk data -- **v2** — same structure plus `components[]` inside each issue, containing - `component_id`, `version`, `pkg_type`, and `fixed_versions[]` +- **v1** — vulnerability, license, operational risk +- **v2** — same + `components[]` per issue (`component_id`, `version`, + `pkg_type`, `fixed_versions[]`) -Use v2 when you need to know which component is affected and what version -fixes the vulnerability. Use v1 when fixed-version data is not needed. +Prefer v2 when you need affected component + fix version; v1 otherwise. -Either `paths` or `checksums` must be provided in the request body. If both -are provided, checksums are ignored. +Provide `paths` or `checksums` (if both, checksums ignored). -**Paths must point to specific artifacts, not repositories.** A path like -`default/my-repo/com/example/lib-1.0.jar` works; a repo-level path like -`default/my-repo` returns empty results. To get a security summary for an -entire repository, query individual artifact paths (discovered via AQL or -`jf rt search`) or use the violations API / reports API instead. +**Paths must be specific artifacts, not repos.** +`default/my-repo/com/example/lib-1.0.jar` works; `default/my-repo` → empty. +For a whole repo: query individual paths (AQL / `jf rt search`) or use +violations / reports APIs. ```bash # v1 — by path @@ -390,13 +350,13 @@ See `SKILL.md` § *Invoking platform APIs with `jf api`* for the full response s ## Impacted resources search -`GET /api/v2/search/impactedResources` — find all resources (artifacts, builds, -release bundles) impacted by a specific CVE **or** containing a specific -package. **Preferred over `/api/v1/component/searchByCves`** when you need -artifact paths, repos, and scan dates rather than just component identifiers. +`GET /api/v2/search/impactedResources` — resources (artifacts, builds, release +bundles) impacted by a CVE **or** containing a package. **Prefer over +`/api/v1/component/searchByCves`** when you need paths, repos, scan dates +(not just component IDs). -Requires the **Reports Manager** permission and the **SBOM Service** (returns -403 if SBOM is disabled on self-hosted). Available since Xray 3.131. +Needs **Reports Manager** + **SBOM Service** (403 if SBOM disabled +self-hosted). Since Xray 3.131. ### Search modes @@ -406,7 +366,7 @@ Requires the **Reports Manager** permission and the **SBOM Service** (returns | By package version | `name` + `type` + `version` | "Where is log4j-core 2.14.1 used?" | | By package (all versions) | `name` + `type` | "Where is lodash used, any version?" | -All parameters are **query string** params (not request body): +All params are **query string** (not body): | Param | Description | |-------|-------------| @@ -478,7 +438,7 @@ jf api "/xray/api/v2/search/impactedResources?name=lodash&type=npm" ### Pagination -Page through results using `last_key`: +Page with `last_key`: ```bash # First page @@ -491,17 +451,14 @@ jf api "/xray/api/v2/search/impactedResources?vulnerability=CVE-2021-23337&limit ## Exposures (Advanced Security) -Exposures are actionable security findings produced by JFrog Advanced Security -that go beyond traditional vulnerability scanning. While vulnerabilities -identify known CVEs in software components, exposures detect **real-world -exploitable threats** in binaries, source code, and configurations — such as -hard-coded secrets, insecure Infrastructure-as-Code templates, and service -misconfigurations. This helps prioritize critical fixes over theoretical risks. +Actionable findings from JFrog Advanced Security beyond CVE scanning: +hard-coded secrets, insecure IaC, service misconfigs — real exploitable +threats in binaries, source, and configs (vs theoretical CVEs). -Exposures require **JFrog Advanced Security** to be enabled on the Xray -instance. Artifacts must be in an indexed repository and already scanned. +Requires **JFrog Advanced Security** enabled. Artifact must be in an indexed +repo and already scanned. -After getting results, keep only results with status==`to_fix` unless asked otherwise. +After results: keep only `status==to_fix` unless asked otherwise. ### Exposure categories @@ -527,8 +484,8 @@ After getting results, keep only results with status==`to_fix` unless asked othe ### API: Get exposure results -`GET /api/v1/{category}/results` — returns a paginated list of exposure scan -results for a specific artifact. Available since Xray 3.59.4. +`GET /api/v1/{category}/results` — paginated exposure results for one +artifact. Since Xray 3.59.4. | Parameter | Required | Description | |-----------|----------|-------------| @@ -593,13 +550,12 @@ done ### Discovering artifact paths for exposures -The exposures API requires a specific artifact `path` — it cannot scan an -entire repository in one call. For Docker images the scannable artifact is -the **manifest**: `<image>/<tag>/manifest.json`. For other package types use -the artifact filename (e.g. `app-1.0.0.tgz`, `lib-2.3.jar`). +Exposures API needs a specific artifact `path` — cannot scan a whole repo in +one call. Docker: scannable artifact is the **manifest** +`<image>/<tag>/manifest.json`. Other types: artifact filename +(e.g. `app-1.0.0.tgz`, `lib-2.3.jar`). -When the caller doesn't know the artifact paths, discover them first with AQL -and then fan out to the exposures endpoint. +Unknown paths → discover with AQL, then fan out to exposures. **Docker repos** — find all manifests: @@ -613,9 +569,8 @@ echo "$OUT" jq -r '.results[] | .path + "/" + .name' "$OUT" ``` -The `$nmatch` filter excludes temporary upload layers. Each result path -(e.g. `my-image/latest/manifest.json`) can be passed directly to the -exposures API's `path` parameter. +`$nmatch` excludes temporary upload layers. Each result path +(e.g. `my-image/latest/manifest.json`) → exposures API `path` param. **Non-Docker repos** — find scannable artifacts: @@ -627,10 +582,8 @@ jf api /artifactory/api/search/aql \ ## Curation audit events -Curation audits every package check that passes through a curated repository -and records whether the download was **approved** or **blocked**. The audit -log also captures **dry-run** policy evaluations (policies configured in -dry-run mode). +Curation logs every package check through a curated repo as **approved** or +**blocked**, plus **dry-run** policy evaluations. ### Get audit logs @@ -638,13 +591,12 @@ dry-run mode). GET /xray/api/v1/curation/audit/packages ``` -Since 3.82.x. Requires `VIEW_POLICIES` permission. +Since 3.82.x. Requires `VIEW_POLICIES`. -**Time-range limit:** The maximum allowed window between `created_at_start` and -`created_at_end` is **168 hours (7 days)**. Requests exceeding this return an -error (`"Maximum allowed duration is 168 hours"`). To query longer periods, -split into consecutive 7-day (or shorter) chunks and merge results client-side. -Use 6-day windows to avoid edge-case overflows from hour-level rounding. +**Time-range limit:** Max window `created_at_start`→`created_at_end` is +**168 hours (7 days)**. Longer → error `"Maximum allowed duration is 168 hours"`. +Split into ≤7-day chunks (prefer 6-day to avoid hour-rounding overflow) and +merge client-side. | Parameter | Type | Default | Description | |-----------|------|---------|-------------| @@ -705,28 +657,25 @@ Response shape (key fields): } ``` -The `action` field is either `"blocked"` or `"approved"`. Each event includes -the `policies` array listing every non-dry-run policy that affected the -decision (blocking, bypassed, and waived). +`action` is `"blocked"` or `"approved"`. `policies` lists every non-dry-run +policy that affected the decision (blocking, bypassed, waived). ### Pagination -Use `offset` + `num_of_rows` for pagination. The `meta.next_offset` field -gives the offset for the next page. Set `include_total=true` on the first -request to know the total number of events. +`offset` + `num_of_rows`. `meta.next_offset` → next page. First request: +`include_total=true` for total event count. ### Common use cases -- **Export all blocked packages**: paginate with `num_of_rows=2000`, filter - results by `action == "blocked"`. -- **Dry-run analysis**: set `dry_run=true` to see what *would* be blocked if - dry-run policies were enforced. -- **CSV export**: set `format=csv` for bulk export. Narrow the time range if - the response indicates incomplete data (`audit_packages_incomplete.csv`). +- **Export blocked packages**: paginate `num_of_rows=2000`, filter + `action == "blocked"`. +- **Dry-run analysis**: `dry_run=true` → what *would* block if enforced. +- **CSV export**: `format=csv`. Narrow time range if + `audit_packages_incomplete.csv`. ## Reports -On-demand analysis over a defined scope, produced asynchronously. +On-demand scoped analysis, async. | Report type | Analyzes | |-------------|----------| @@ -735,6 +684,5 @@ On-demand analysis over a defined scope, produced asynchronously. | **Violations** | Policy violations across watched resources | | **Operational risks** | Package health metrics | -Reports can be scoped to repositories, builds, release bundles, or projects. -They are generated via `POST /api/v1/reports/{type}` and retrieved after -completion. +Scope: repos, builds, release bundles, or projects. +`POST /api/v1/reports/{type}` → retrieve after completion. diff --git a/skills/jfrog/scripts/check-environment.sh b/skills/jfrog/scripts/check-environment.sh index 2df4ff2..8541daa 100755 --- a/skills/jfrog/scripts/check-environment.sh +++ b/skills/jfrog/scripts/check-environment.sh @@ -1,8 +1,4 @@ #!/usr/bin/env bash -# Copyright (c) JFrog Ltd. 2026 -# Licensed under the Apache License, Version 2.0 -# https://www.apache.org/licenses/LICENSE-2.0 -# # check-environment.sh — Cached JFrog CLI environment check # # Checks if jf is installed and its version, using a 24h-TTL cache @@ -39,6 +35,17 @@ FORCE=false # skill) landed in 2.100.0; older CLIs fail with "unknown command: api". MIN_CLI_VERSION="2.100.0" +# CLIs >= this version emit ai-agent/ + ai-client/ + ai-model/ (Client→Agent→Model +# via jfrog-cli-core #1602 + jfrog-cli #3645). Omit tool=/client= in the skill UA +# to avoid double-encoding. +# +# MERGE / RELEASE PIN: tip `CliVersion` is still 2.119.0 while the identity code +# is already on master. Released 2.118/2.119 only appended ai-agent/. Keep this +# gate at the first *released* CLI that ships full Client→Agent→Model (expected +# 2.120.0). When that release cuts, confirm the tag and update this constant if +# the version number differs — do not lower it to tip's 2.119.0. +AGENT_UA_MIN_CLI_VERSION="2.120.0" + MODEL_SLUG="" for arg in "$@"; do if [[ "$arg" == "--force" ]]; then @@ -147,55 +154,160 @@ EOF return 1 } -# Detect the calling harness from environment signals. Output is one of: -# claude, cursor, gemini, goose, copilot, codex, unknown — or empty -# string when no agent signal is present (direct CLI/CI invocation). -# Naming matches the JFrog CLI's DetectExecutionContext() vocabulary. +# Lowercase and keep only [a-z0-9._-], then truncate to 64 chars — mirrors the +# Go CLI sanitizeToken (cardinality bound + no header-splitting on the wire). +sanitize_token() { + local s + s="$(printf '%s' "$1" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | tr '[:upper:]' '[:lower:]' | tr -cd 'a-z0-9._-')" + printf '%s' "${s:0:64}" +} + +# Map a generic AI_AGENT/AGENT value (agents.md proposal, @vercel/detect-agent) +# to a canonical name. Strips a version suffix (e.g. "goose@1.2.3") and lowercases. +# Empty input → nothing; unrecognized non-empty → "unknown". +# Accepts both hyphenated ecosystem ids and our underscore/canonical forms so +# AI_AGENT=roo_code / amazon_q / qwen round-trip the same as the Go CLI. +canonical_agent_name() { + local raw + raw="$(printf '%s' "$1" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')" + raw="${raw%%@*}" + raw="$(printf '%s' "$raw" | tr '[:upper:]' '[:lower:]')" + case "$raw" in + "") ;; + claude-code|claude) echo "claude" ;; + gemini-cli|gemini) echo "gemini" ;; + goose) echo "goose" ;; + cursor-cli|cursor) echo "cursor" ;; + github-copilot|copilot-cli|copilot) echo "copilot" ;; + kilocode) echo "kilocode" ;; + roo-code|roo_code) echo "roo_code" ;; + codex) echo "codex" ;; + windsurf) echo "windsurf" ;; + aider) echo "aider" ;; + cline) echo "cline" ;; + opencode) echo "opencode" ;; + amp) echo "amp" ;; + augment) echo "augment" ;; + qwen-code|qwen) echo "qwen" ;; + antigravity) echo "antigravity" ;; + crush) echo "crush" ;; + iflow) echo "iflow" ;; + trae) echo "trae" ;; + amazon-q-cli|amazon-q|amazon_q) echo "amazon_q" ;; + *) echo "unknown" ;; + esac +} + +# Detect the calling harness from environment signals. Output and first-match +# order match the JFrog CLI's DetectExecutionContext() table (claude, gemini, +# goose, cursor, copilot, kilocode, roo_code, codex, windsurf, cline, +# opencode, amp, augment, qwen, antigravity, crush, iflow, trae; plus +# aider/amazon_q via AI_AGENT/AGENT only) — or empty when no agent signal. +# Devin Desktop is not detected here — see harness-common.md (agent identity +# + VSCODE_IPC_HOOK). The TERM_PROGRAM client hint is agent-session-only. +# MODEL_SLUG→unknown fallback is applied by emit_skill_env (not here) so the +# emitter can still carry tool= when the CLI will not emit ai-agent/. detect_harness() { - if [[ -n "${CLAUDECODE:-}" || -n "${CLAUDE_CODE_ENTRYPOINT:-}" ]]; then + # Keep in lockstep with jfrog-cli-core agentEnvDetectors (session markers only). + if [[ -n "${CLAUDE_CODE_CHILD_SESSION:-}" ]]; then echo "claude" - elif [[ -n "${CURSOR_AGENT:-}" || -n "${CURSOR_CLI:-}" || -n "${CURSOR_TRACE_ID:-}" ]]; then - echo "cursor" elif [[ -n "${GEMINI_CLI:-}" ]]; then echo "gemini" elif [[ -n "${GOOSE_TERMINAL:-}" ]]; then echo "goose" - elif [[ -n "${COPILOT_CLI:-}" ]]; then + elif [[ -n "${CURSOR_AGENT:-}" || "${CURSOR_EXTENSION_HOST_ROLE:-}" == "agent-exec" ]]; then + echo "cursor" + elif [[ -n "${COPILOT_CLI:-}" || -n "${COPILOT_AGENT_SESSION_ID:-}" ]]; then echo "copilot" + elif [[ -n "${KILOCODE_FEATURE:-}" || -n "${KILO_PID:-}" ]]; then + echo "kilocode" + elif [[ -n "${ROO_ACTIVE:-}" || -n "${ROO_CLI_RUNTIME:-}" ]]; then + echo "roo_code" elif [[ -n "${CODEX_CI:-}" || -n "${CODEX_THREAD_ID:-}" || -n "${CODEX_SANDBOX:-}" ]]; then echo "codex" - elif [[ -n "${AGENT:-}" || -n "$MODEL_SLUG" ]]; then - # Agent invoked us but we can't name it. - echo "unknown" + elif [[ -n "${WINDSURF_CASCADE_TERMINAL:-}" ]]; then + echo "windsurf" + elif [[ -n "${CLINE_ACTIVE:-}" ]]; then + echo "cline" + elif [[ -n "${OPENCODE:-}" || -n "${OPENCODE_SESSION_ID:-}" ]]; then + echo "opencode" + elif [[ -n "${AMP_CURRENT_THREAD_ID:-}" ]]; then + echo "amp" + elif [[ -n "${AUGMENT_AGENT:-}" ]]; then + echo "augment" + elif [[ -n "${QWEN_CODE:-}" ]]; then + echo "qwen" + elif [[ -n "${ANTIGRAVITY_AGENT:-}" ]]; then + echo "antigravity" + elif [[ -n "${CRUSH:-}" ]]; then + echo "crush" + elif [[ -n "${IFLOW_CLI:-}" ]]; then + echo "iflow" + elif [[ -n "${TRAE_AI_SHELL_ID:-}" ]]; then + echo "trae" + elif [[ -n "${AI_AGENT:-}" || -n "${AGENT:-}" ]]; then + # aider and amazon_q have no reliable session env — AI_AGENT / AGENT only. + canonical_agent_name "${AI_AGENT:-${AGENT:-}}" fi - # No match → print nothing; emitter omits the parens block entirely. + # No match → print nothing; emitter may still apply MODEL_SLUG→unknown. } # Emit skill-level env vars to stdout (for eval by the caller) emit_skill_env() { - local skill_version cli_version ua harness + local skill_version cli_version ua harness harness_from_model_fallback=false # Parse version from SKILL.md YAML frontmatter (metadata.version) skill_version="$(awk '/^---$/{n++; next} n==1 && /^[[:space:]]*version:/{gsub(/["'"'"']/, "", $2); print $2; exit}' "$SKILL_ROOT/SKILL.md" 2>/dev/null | tr -d '[:space:]')" skill_version="${skill_version:-unknown}" - cli_version=$(jq -r '.cli_version // "unknown"' "$CACHE_FILE" 2>/dev/null || echo "unknown") + # Prefer a live `jf --version` so AGENT_UA_MIN omit-gate is not stuck on a + # stale cache for up to 24h after the user upgrades the CLI. + cli_version="$(jf --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1 || true)" + cli_version="${cli_version:-$(jq -r '.cli_version // "unknown"' "$CACHE_FILE" 2>/dev/null || echo "unknown")}" + # Sanitize the model slug once so the MODEL_SLUG→unknown fallback and the + # wire model= key share the same cardinality-bounded, header-safe value + # (mirrors the CLI's sanitizeToken on JFROG_CLI_AI_MODEL). + MODEL_SLUG="$(sanitize_token "${MODEL_SLUG:-}")" harness=$(detect_harness) - # Build the parens block: semicolon-separated key=value pairs. - local meta="" + # Defense: re-canonicalize so alias wire values (e.g. claude-code from a + # hand-built AI_AGENT / future detector slip) never reach tool=. Empty stays + # empty; known aliases map; unrecognized non-empty → unknown. if [[ -n "$harness" ]]; then - meta="tool=${harness}" + h2=$(canonical_agent_name "$harness") + [[ -n "$h2" ]] && harness=$h2 fi - if [[ -n "$MODEL_SLUG" ]]; then - if [[ -n "$meta" ]]; then - meta="${meta}; model=${MODEL_SLUG}" - else - meta="model=${MODEL_SLUG}" - fi + # Agent invoked us (passed a model slug) but set no harness signal the CLI + # shares — CLI will not emit ai-agent/, so the skill must still carry tool=. + if [[ -z "$harness" && -n "$MODEL_SLUG" ]]; then + harness="unknown" + harness_from_model_fallback=true + fi + # Client (TERM_PROGRAM): app hosting the agent. Agent sessions only. + local client + client="$(sanitize_token "${TERM_PROGRAM:-}")" + # Omit tool=/client= when the CLI emits ai-agent/ai-client itself. Keep them + # when harness came only from the model-slug fallback (CLI IsAgent=false). + local carry_agent_ua="false" + if [[ "$cli_version" == "unknown" ]] || version_lt "$cli_version" "$AGENT_UA_MIN_CLI_VERSION" || [[ "$harness_from_model_fallback" == "true" ]]; then + carry_agent_ua="true" fi - ua="jfrog-skills/${skill_version}" - if [[ -n "$meta" ]]; then - ua="${ua} (${meta})" + # Build the parens block: semicolon-separated key=value pairs. + # trigger=skill always leads — this script only runs on the skill path. + # (APR agent-hooks set trigger=hook when they spawn jf; see eager-setup.) + local meta="trigger=skill" + if [[ "$carry_agent_ua" == "true" && -n "$harness" ]]; then + meta="${meta}; tool=${harness}" + fi + if [[ "$carry_agent_ua" == "true" && -n "$harness" && -n "$client" ]]; then + meta="${meta}; client=${client}" + fi + # model= is emitted regardless of CLI version (not deduped like tool=/client=): + # the CLI's own ai-model/ token is conditional on it detecting the agent via + # env AND the caller exporting JFROG_CLI_AI_MODEL, so the skill can't know + # whether the CLI will carry it. Keeping model= here guarantees the slug is + # always recorded; Coralogix coalesces the two sources so it isn't counted twice. + if [[ -n "$MODEL_SLUG" ]]; then + meta="${meta}; model=${MODEL_SLUG}" fi - ua="${ua} jfrog-cli-go/${cli_version}" + ua="jfrog-skills/${skill_version} (${meta}) jfrog-cli-go/${cli_version}" printf '%s\n' "$ua" } diff --git a/skills/jfrog/scripts/jfrog-login-register-session.sh b/skills/jfrog/scripts/jfrog-login-register-session.sh index c3b4cc8..d02e4cf 100755 --- a/skills/jfrog/scripts/jfrog-login-register-session.sh +++ b/skills/jfrog/scripts/jfrog-login-register-session.sh @@ -1,8 +1,4 @@ #!/usr/bin/env bash -# Copyright (c) JFrog Ltd. 2026 -# Licensed under the Apache License, Version 2.0 -# https://www.apache.org/licenses/LICENSE-2.0 -# # jfrog-login-register-session.sh — Verify a JFrog server and start a web login session # # Pings the server, generates a session UUID, and registers it with @@ -53,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 diff --git a/skills/jfrog/scripts/jfrog-login-save-credentials.sh b/skills/jfrog/scripts/jfrog-login-save-credentials.sh index 1f239ec..f6ba42b 100755 --- a/skills/jfrog/scripts/jfrog-login-save-credentials.sh +++ b/skills/jfrog/scripts/jfrog-login-save-credentials.sh @@ -1,8 +1,4 @@ #!/usr/bin/env bash -# Copyright (c) JFrog Ltd. 2026 -# Licensed under the Apache License, Version 2.0 -# https://www.apache.org/licenses/LICENSE-2.0 -# # jfrog-login-save-credentials.sh — Complete web login by retrieving token and saving credentials # # Retrieves the one-time access token from a completed web login session,