diff --git a/.github/scripts/sync-skills.mjs b/.github/scripts/sync-skills.mjs new file mode 100644 index 0000000..cbca3ed --- /dev/null +++ b/.github/scripts/sync-skills.mjs @@ -0,0 +1,129 @@ +#!/usr/bin/env node +// Vendors skill content from the upstream jfrog/jfrog-skills repository +// into this plugin. Run manually when bumping the pin: bump `pin` in +// /.vendor.json, then run this script to regenerate the +// plugin's skills/ tree, then commit both alongside each other. +// +// Usage: +// node .github/scripts/sync-skills.mjs +// +// Steps the script performs: +// 1. Reads marketplace.json and walks each plugin entry. +// 2. For each plugin, reads /.vendor.json to learn which +// repo + ref to pull. +// 3. Downloads that tarball from codeload.github.com (public, no auth). +// 4. Extracts it into a temp directory. +// 5. Copies the requested paths (e.g. "skills") into the plugin folder, +// replacing any existing tree. +// +// The pin in .vendor.json is the single source of truth — there is no +// runtime override. To ship a different skill version, change the pin +// in a PR and commit the synced tree alongside it. + +import { promises as fs, createWriteStream } from "node:fs"; +import { Readable } from "node:stream"; +import { pipeline } from "node:stream/promises"; +import path from "node:path"; +import { spawnSync } from "node:child_process"; +import { tmpdir } from "node:os"; + +// filesystem helpers +async function readJson(filePath) { + return JSON.parse(await fs.readFile(filePath, "utf8")); +} + +async function fileExists(filePath) { + try { await fs.access(filePath); return true; } catch { return false; } +} + +// download the upstream tarball + +// codeload.github.com serves any public repo's archive over HTTPS +// without auth, accepting a tag, branch, or commit SHA as the ref. +async function downloadTarball(repo, ref, destPath) { + const url = `https://codeload.github.com/${repo}/tar.gz/${encodeURIComponent(ref)}`; + const res = await fetch(url, { redirect: "follow" }); + if (!res.ok) throw new Error(`Could not download ${repo}@${ref} (HTTP ${res.status})`); + await pipeline(Readable.fromWeb(res.body), createWriteStream(destPath)); + console.log(` fetched ${url}`); +} + +// extract the tarball + +// Shells out to the system `tar` instead of pulling in an npm tar library — +// keeps the script zero-dependency. +// +// GitHub tarballs always have exactly one top-level directory whose +// name encodes the repo + commit. We return that path so the caller +// knows where to find the extracted tree. +async function extractTarball(tarballPath, intoDir) { + await fs.mkdir(intoDir, { recursive: true }); + const result = spawnSync("tar", ["-xzf", tarballPath, "-C", intoDir], { stdio: "inherit" }); + if (result.status !== 0) throw new Error(`tar exited with status ${result.status}`); + const [topLevel] = await fs.readdir(intoDir); + return path.join(intoDir, topLevel); +} + +// copy one path from the extracted tree into the plugin + +// Removes the destination first so we never end up with stale leftovers +// from a previous sync, then creates the destination's parent directory then copies. +async function copyPath(fromDir, toDir, relativePath) { + const from = path.join(fromDir, relativePath); + const to = path.join(toDir, relativePath); + if (!(await fileExists(from))) { + throw new Error(`path missing in upstream tarball: ${relativePath}`); + } + await fs.rm(to, { recursive: true, force: true }); + await fs.mkdir(path.dirname(to), { recursive: true }); + await fs.cp(from, to, { recursive: true }); + console.log(` ${relativePath} -> ${path.relative(process.cwd(), to)}`); +} + +// Resolves the plugin's local directory from the marketplace `source` field. +function localPluginDir(plugin) { + if (typeof plugin.source === "string") return plugin.source; + if (plugin.source && typeof plugin.source.path === "string") return plugin.source.path; + return null; +} + +// Sync one plugin: read its .vendor.json, download + extract + copy. +// Plugins without a local path or without a .vendor.json are silently skipped. +async function syncPlugin(plugin, workDir) { + const localPath = localPluginDir(plugin); + if (!localPath) return; + const pluginDir = path.resolve(localPath); + const vendorPath = path.join(pluginDir, ".vendor.json"); + if (!(await fileExists(vendorPath))) return; + + const { repo, pin, paths } = await readJson(vendorPath); + if (!repo || !pin || !Array.isArray(paths) || paths.length === 0) { + throw new Error(`${vendorPath} must define 'repo', 'pin' and a non-empty 'paths' array`); + } + + console.log(`--- ${plugin.name} (ref: ${pin}) ---`); + + // `slug` is just a unique filename for this plugin's tarball + extract. + const slug = `${repo.replace("/", "-")}-${pin.replace(/[^A-Za-z0-9._-]/g, "_")}`; + const tarball = path.join(workDir, `${slug}.tar.gz`); + await downloadTarball(repo, pin, tarball); + const extracted = await extractTarball(tarball, path.join(workDir, slug)); + for (const rel of paths) await copyPath(extracted, pluginDir, rel); +} + +// Entry point: walk marketplace.json, sync each plugin sequentially, +// always clean up the temp work directory. +async function main() { + const marketplace = await readJson("marketplace.json"); + const workDir = await fs.mkdtemp(path.join(tmpdir(), "sync-skills-")); + try { + for (const plugin of marketplace.plugins ?? []) { + await syncPlugin(plugin, workDir); + } + } finally { + await fs.rm(workDir, { recursive: true, force: true }); + } + console.log("done."); +} + +await main(); diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..403744e --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +# IDE settings +.idea/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..e5d0188 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,61 @@ +# Contributing to the JFrog VS Code Plugin + +Thank you for your interest in contributing! This project is maintained by JFrog and licensed under the [Apache License 2.0](LICENSE). + +## Contributor License Agreement (CLA) + +All contributors must sign the [JFrog CLA](https://jfrog.com/cla/) before contributions can be merged. A CLA check runs automatically on every pull request — follow the prompts to sign if you haven't already. + +## How to Contribute + +1. **Fork** the repository and create a feature branch from `main`. +2. Make your changes, ensuring they follow the existing code style and project conventions. +3. **Test** by loading the plugin from source — see [the README](README.md#installation) for setup instructions. +4. **Commit** with a clear, descriptive message. +5. Open a **pull request** against `main` with a summary of what changed and why. + +### Updating the vendored skills + +The `plugin/skills/` tree is vendored from [jfrog/jfrog-skills](https://github.com/jfrog/jfrog-skills) and committed to `main` — see [`VENDOR.md`](VENDOR.md) for the full flow. To regenerate the tree locally against the pin in [`plugin/.vendor.json`](plugin/.vendor.json): + +```bash +node .github/scripts/sync-skills.mjs +``` + +This downloads the pinned upstream tarball and replaces the contents of `plugin/skills/`. Commit the result alongside any pin/version bumps. + +## Pre-release checklist + +- [ ] Version bumped in [`plugin/.claude-plugin/plugin.json`](plugin/.claude-plugin/plugin.json) when the plugin changes. +- [ ] No secrets, credentials, or files under `**/local-cache/` committed. +- [ ] If the skill tree changed: `plugin/.vendor.json` `pin` matches the upstream tag the new tree was generated from. +- [ ] Smoke-test the plugin locally. + +## Reporting Issues + +Open a [GitHub issue](https://github.com/jfrog/vscode-plugin/issues) with: + +- A clear title and description of the problem. +- Steps to reproduce (if applicable). +- Expected vs. actual behavior. + +## Code Guidelines + +- Keep changes focused — one logical change per PR. +- Follow existing patterns and naming conventions in the codebase. +- Do not commit secrets, credentials, or API keys. +- Add copyright headers to new source files: + +``` +// Copyright (c) JFrog Ltd. 2026 +// Licensed under the Apache License, Version 2.0 +// https://www.apache.org/licenses/LICENSE-2.0 +``` + +## Code of Conduct + +Be respectful and constructive. We are committed to providing a welcoming and inclusive experience for everyone. + +## Questions? + +Reach out to the JFrog DevRel team at . diff --git a/VENDOR.md b/VENDOR.md new file mode 100644 index 0000000..71078b2 --- /dev/null +++ b/VENDOR.md @@ -0,0 +1,26 @@ +# Vendored skills + +The skill packages under `plugin/skills/` are vendored from **[jfrog/jfrog-skills](https://github.com/jfrog/jfrog-skills)** and committed to `main`. + +| | | +| --- | --- | +| **Repository** | https://github.com/jfrog/jfrog-skills | +| **Pinned release** | see `pin` in [`plugin/.vendor.json`](plugin/.vendor.json) | + +Included directories: `jfrog/`, `jfrog-package-safety-and-download/` (as of the pinned release). + +## Refreshing + +When the upstream repo publishes a new release, refresh the vendored tree via a PR that: + +1. Bumps `pin` in [`plugin/.vendor.json`](plugin/.vendor.json) to the new tag. +2. Re-syncs and commits the refreshed `plugin/skills/` tree. +3. Bumps `version` in [`plugin/.claude-plugin/plugin.json`](plugin/.claude-plugin/plugin.json) so users actually receive the update (Claude Code/Copilot skip installs whose resolved version hasn't changed). + +To regenerate the tree locally before opening the PR: + +```bash +node .github/scripts/sync-skills.mjs +``` + +The script reads `plugin/.vendor.json`, downloads the pinned upstream tarball from `codeload.github.com`, and replaces the directories listed in `paths` (today: `plugin/skills/`). diff --git a/marketplace.json b/marketplace.json index 5980571..09631a1 100644 --- a/marketplace.json +++ b/marketplace.json @@ -9,7 +9,6 @@ { "name": "jfrog", "description": "JFrog Platform integration with MCP, security skills, and supply-chain best practices", - "version": "1.0.1", "source": "plugin", "categories": ["security", "artifact-management", "supply-chain", "devops", "mcp", "mlops", "agent-guard", "ai-catalog"], "platforms": ["darwin", "linux", "windows"], diff --git a/plugin/.claude-plugin/plugin.json b/plugin/.claude-plugin/plugin.json index 0c70bb8..572908c 100644 --- a/plugin/.claude-plugin/plugin.json +++ b/plugin/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "jfrog", "description": "JFrog Platform integration with MCP, security skills, and supply-chain best practices", - "version": "0.1.0", + "version": "0.1.1", "author": { "name": "JFrog", "url": "https://jfrog.com" }, "hooks": "hooks/hooks.json" } diff --git a/plugin/.vendor.json b/plugin/.vendor.json new file mode 100644 index 0000000..b4c4d06 --- /dev/null +++ b/plugin/.vendor.json @@ -0,0 +1,5 @@ +{ + "repo": "jfrog/jfrog-skills", + "pin": "v0.11.0", + "paths": ["skills"] +} diff --git a/plugin/skills/jfrog-package-safety-and-download/SKILL.md b/plugin/skills/jfrog-package-safety-and-download/SKILL.md new file mode 100644 index 0000000..598318a --- /dev/null +++ b/plugin/skills/jfrog-package-safety-and-download/SKILL.md @@ -0,0 +1,286 @@ +--- +name: jfrog-package-safety-and-download +description: >- + Check JFrog Public Catalog and stored packages for a version, interpret + catalog security signals, and download through Artifactory (JFrog Platform + locations, remote cache, curation-aware package managers, or repo proxy). + Use when the user asks whether a package is safe, allowed, curated, or + wants to download npm, Maven, PyPI, Go, or similar packages via JFrog. + Do NOT use for pure CVE or vulnerability lookups (e.g. "details on + CVE-2021-23337") — those are handled by the jfrog skill's Public security + domain queries without this workflow. +metadata: + role: workflow +--- + +# JFrog Package Safety and Download + +## Prerequisites + +- Read `../jfrog/SKILL.md` for JFrog Platform concepts, domain model, CLI setup, and API patterns. +- **OneModel shapes drift by server version.** Before inventing GraphQL fields or `where` filters, read `../jfrog/references/onemodel-graphql.md` (schema fetch workflow) and `../jfrog/references/onemodel-query-examples.md` (**Public packages**, **Stored packages**). Regenerate or verify queries against `GET "$JFROG_URL/onemodel/api/v1/supergraph/schema"` when examples fail validation. + +## Workflow + +# Package safety check and download workflow + +When to read this file: + +- User asks to **check if a package is safe** and/or **download** it. +- User asks to **download a package** from Artifactory. +- User mentions checking a package for **curation** approval. +- User wants to know if a package is **allowed** or **approved** for use. + +## Workflow overview + +```mermaid +flowchart TD + A[User requests package check / download] --> B{Package in Public Catalog?} + B -->|Yes| C[Get latest version from Catalog] + B -->|No| D{Package in JFrog Platform Stored Packages?} + D -->|Yes| E[Get latest version from Stored Packages] + D -->|No| F[Package not found — stop] + C --> G{Latest version in JFrog Platform?} + E --> G + G -->|Yes| H[Safe — download from JFrog Platform] + G -->|No| I{Curation entitled?} + I -->|Yes| J[Check curation policy via API] + I -->|No| K[Download via remote repo] + J -->|200 Allowed| K + J -->|403 Blocked| M[Report curation blocked — stop] +``` + +### Parallelization opportunities + +Several steps in this workflow are independent and can run in parallel to +reduce total latency: + +- **Step 1 + Step 1 fallback**: When package type is known, query both the + Public Catalog (`getPackage`) and Stored Packages (`getPackage`) in + parallel. Use whichever returns data; if the Public Catalog returns a hit, + prefer its `latestVersion` for Step 2. +- **Step 3 + Step 5**: After determining the version, query stored package + versions (JFrog Platform check) and curation entitlement + (`/api/system/version`) in parallel. Both are independent reads — the + curation result is needed immediately if the JFrog Platform check returns + empty. + +When issuing parallel Shell calls, each `jf api` call authenticates +independently against the active `jf config` server; no shell state needs +to be passed between calls. + +## Step 1: Find the package + +Search the **Public Catalog** first via OneModel GraphQL, then fall back to +**Stored Packages** if not found. + +Execute the query through `jf api` as described in +`../jfrog/references/onemodel-graphql.md`; refer to +`../jfrog/references/onemodel-query-examples.md` for concrete query shapes. + +**When package type is known** (e.g. `npm`, `maven`, `pypi`), use +`publicPackages.getPackage(type:, name:)` (see *Get a public package*). +Include the `latestVersion { version }` selection set — `latestVersion` is +an object, not a scalar. + +**When type is unknown**, use `publicPackages.searchPackages` with +`nameContains` (see *Search public packages*). Add `type:` when the user +narrows the ecosystem. + +- **Found** → note `type` and `latestVersion.version`. Proceed to Step 2. +- **Not found** → the package may be 1st/2nd party. Search **Stored Packages** + using `storedPackages.searchPackages` or `storedPackages.getPackage` (see + *Stored packages domain* in `onemodel-query-examples.md`). Prefer + filtering by `type` when known; if not, use `nameContains` alone. + - **Found** → note `type` and `latestVersionName` (or derive a version from + `versionsConnection`). Proceed to Step 2. + - **Not found in either** → report "package not found" and stop. + +If multiple results with different `type` values, ask the user which package +type they mean. + +## Step 2: Determine latest version + +| Source | Version field | +|--------|--------------| +| Public Catalog | `latestVersion.version` (object selection required) | +| JFrog Platform Stored Packages | `latestVersionName` on `StoredPackage`, or highest entry from `versionsConnection` | + +## Step 3: Check if package + latest version exists in JFrog Platform + +Query stored package versions using `storedPackages.searchPackageVersions` +with a `hasPackageWith` filter (see `../jfrog/references/onemodel-query-examples.md` +→ *Search stored package versions*). Add a `version` filter for the specific +version from Step 2, and request `locationsConnection` to get repository +details (`repositoryKey`, `repositoryType`, `leadArtifactPath`). + +Execute the query through `jf api` (see +`../jfrog/references/onemodel-graphql.md` for the invocation pattern). + +- **Found with locations** → package is in the JFrog Platform. Report as **safe to + download**. Proceed to Step 4. +- **Not found** → proceed to Step 5. + +## Step 4: Download from JFrog Platform + +Use the location info from Step 3. Binary artifact downloads go through +`jf rt dl` — **not** `jf api`. `jf api` is the unified entry point for the +JFrog REST APIs (metadata, admin, curation, etc.) and does not expose the +`-L` / `-o` flags needed to stream binary content through a redirect chain. + +**`` must be a full file path** (e.g. +`./downloads/lodash-4.18.1.tgz`), not a bare directory. `jf rt dl --flat` +treats the target as a file name; passing a directory causes a misleading +"open path: is a directory" error. + +| `repositoryType` | Strategy | +|-------------------|----------| +| `local` or `federated` | `jf rt dl "/" --flat` | +| `remote` | `jf rt dl` against the **base** remote repo (strip any trailing `-cache`) — it transparently triggers the remote fetch when the artifact is not yet cached | + +**local / federated / remote download:** + +```bash +jf rt dl "/" --flat +``` + +**Resolving the remote repo key:** The `repositoryKey` returned by OneModel +for remote locations often already ends in `-cache` (e.g. +`devNPM-remote-cache`). `jf rt dl` needs the **base remote repo name** +(without `-cache`). Strip the `-cache` suffix when present (e.g. +`devNPM-remote-cache` → `devNPM-remote`). If the key does not end in +`-cache`, use it as-is. + +See the **Protocol endpoints** table below for the package-type-specific +path format inside the repo. + +## Step 5: Check curation entitlement + +```bash +jf api /artifactory/api/system/version \ + | jq '.addons | index("curation") != null' +``` + +- `true` → curation is entitled. Proceed to Step 6a. +- `false` → curation not available. Proceed to Step 6b. + +## Step 6a: Check curation policy and download + +When curation is entitled, use the Xray curation API to check whether the +package version is allowed across all repositories before downloading. + +```bash +RESPONSE_FILE="/tmp/curation-status-$$.json" +PAYLOAD_FILE="/tmp/curation-payload-$$.json" +STDERR_FILE="/tmp/curation-err-$$.log" + +jq -n \ + --arg type "" \ + --arg name "" \ + --arg version "" \ + '{packageType:$type, packageName:$name, packageVersion:$version}' \ + > "$PAYLOAD_FILE" + +set +e +jf api /xray/api/v1/curation/package_status/all_repos \ + -X POST -H "Content-Type: application/json" \ + --input "$PAYLOAD_FILE" \ + > "$RESPONSE_FILE" 2> "$STDERR_FILE" +RC=$? +set -e +echo "RC=$RC"; echo "$RESPONSE_FILE" +``` + +Supported `packageType` values: `npm`, `pypi`, `maven`, `go`, `nuget`, +`docker`, `gradle`. + +**Interpreting the result with `jf api`**: unlike plain `curl`, `jf api` +surfaces the HTTP result through its **exit code** and a +`" [Warn] ... returned 4xx/5xx"` line on **stderr** (not a +`%{http_code}` suffix in stdout). The response body is always written to +stdout. Parse both: + +```bash +if [ "$RC" -eq 0 ]; then + echo "Package is allowed by curation." +elif grep -q 'returned 403' "$STDERR_FILE"; then + echo "Blocked by curation policy:" + cat "$RESPONSE_FILE" +else + echo "Curation check failed (rc=$RC):" + cat "$STDERR_FILE" +fi +``` + +**Evaluate the outcome:** + +- **exit 0** → package is **allowed** by curation policy. Proceed to + download via a remote repo (same as Step 6b). +- **`returned 403` on stderr** → package is **blocked** by a curation + policy. The response body explains which policy rule blocked it. Report + the block reason to the user and stop — do not attempt to download. +- **Any other non-zero exit** → treat as an operational failure (auth, DNS, + endpoint disabled) and report. + +## Step 6b: Download without curation + +When curation is not entitled and the package is not in the JFrog Platform, +download directly through a remote repo. + +1. **Find a remote repo** of the right package type: + + ```bash + jf api \ + "/artifactory/api/repositories?type=remote&packageType=" \ + | jq '.[].key' + ``` + +2. **Download** — use `jf rt dl` against the base remote repo (without + `-cache`); it handles both cached and uncached artifacts: + + ```bash + jf rt dl "/" --flat + ``` + +## Artifact paths by package type + +Use these path patterns when `leadArtifactPath` is not available from +OneModel. The leading `/` is the base repo key you pass to `jf rt dl`. + +| Type | `jf rt dl` target pattern | +|--------|-------------------------------------------------------------------------| +| `npm` | `//-/-.tgz` | +| `pypi` | `///-.tar.gz` | +| `maven`| `////-.jar` | +| `go` | `//@v/.zip` | + +## Gotchas + +- **Binary downloads vs. `jf api`**: `jf api` is for REST APIs, not binary + content. It does not follow redirects transparently into a binary payload + and does not expose `-L` / `-o`. Always use `jf rt dl` (against the base + remote repo, not the `-cache` one) for the actual artifact download. +- **`jf rt dl` and uncached remotes**: `jf rt dl "/"` — + targeting the **base** remote repo rather than `-cache/` — + transparently triggers the remote fetch and caches the artifact. Do not + try to pre-query the proxy via `jf api`. +- **`jf rt dl --flat` target must be a file path**: When downloading a + single artifact, pass a full output **file** path (e.g. + `./downloads/lodash-4.18.1.tgz`), not a directory. The CLI opens the target + path as a file; a directory causes a cryptic "open path: is a directory" + error that retries four times before failing. Derive the filename from + `leadArtifactPath` (take the segment after the last `/`). +- **Package type detection**: If the user doesn't specify the package type, + the Public Catalog search by name alone may return multiple types. Ask the + user to disambiguate before proceeding. +- **Curation endpoint lives under Xray**: use + `/xray/api/v1/curation/package_status/all_repos` (via `jf api`). Do not + prefix it with `/artifactory`. +- **Curation result discrimination with `jf api`**: the 200/403 signal comes + from `jf api`'s **exit code** plus a `returned NNN` line on **stderr**, + not from a `%{http_code}` appended to stdout. Capture stderr to a file + (`2> "$STDERR_FILE"`) and branch on `RC` + `grep 'returned 403'` as shown + in Step 6a. +- **Curation API package type values**: Must be lowercase and match one of + `npm`, `pypi`, `maven`, `go`, `nuget`, `docker`, `gradle`. Other values + will return an error. diff --git a/plugin/skills/jfrog/SKILL.md b/plugin/skills/jfrog/SKILL.md new file mode 100644 index 0000000..67e0391 --- /dev/null +++ b/plugin/skills/jfrog/SKILL.md @@ -0,0 +1,529 @@ +--- +name: jfrog +description: >- + Interact with the JFrog Platform via the JFrog CLI, JFrog MCP server and REST/GraphQL APIs. + Use this skill when the user wants to manage Artifactory repositories, + upload or download artifacts, manage builds, configure permissions, + manage users and groups, work with access tokens, configure JFrog CLI + servers, search artifacts, manage properties, set up replication, + manage JFrog Projects, run security audits or scans, look up CVE details, + query exposures scan results from JFrog Advanced Security, manage + release bundles and lifecycle operations, aggregate or export platform + data, or perform any JFrog Platform administration task. + Also use when the user mentions jf, jfrog, artifactory, xray, distribution, + evidence, apptrust, onemodel, graphql, workers, mission control, curation, + advanced security, exposures, or any JFrog product name. +compatibility: >- + Requires jq on PATH. +metadata: + role: base + version: "0.11.0" +--- + +# 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. + +Interact with the JFrog Platform through three tool tiers — see +[Tool selection strategy](#tool-selection-strategy). 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. + +## Tool selection strategy + +Try the tiers in order; move to the next only when the current does not +cover the operation or fails: + +1. **JFrog MCP tools** (preferred): `CallMcpTool` against the JFrog MCP + server. Discover available tools from the server's tool list; never + guess tool names. +2. **`jf` CLI subcommands** (fallback): dedicated commands such as + `jf rt upload`, `jf rt dl`, `jf build-publish`. +3. **`jf api`** (last resort): REST/GraphQL endpoints with no dedicated + 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. + +## Prerequisites + +The following tools must be available on `PATH`: + +| Tool | Purpose | +|------|---------| +| `jq` | JSON parsing of CLI and API output | + +All JFrog HTTP traffic from Tiers 2 and 3 goes through the `jf` CLI itself +(`jf api`, see [Invoking platform APIs with `jf api`](#invoking-platform-apis-with-jf-api) below) — +no standalone `curl` is required for any JFrog interaction. + +**Runtime permission for JFrog calls.** All `jf` calls that touch the network +need an outbound-HTTPS escalation from the agent runtime. The `~/.jfrog/` +credential save (`jf config add` during login) additionally needs a +filesystem-write escalation. + +| Runtime | Network | Network + `~/.jfrog/` write | +| ----------- | --------------------------------------------- | ------------------------------- | +| Cursor | `required_permissions: ["full_network"]` | `required_permissions: ["all"]` | +| Claude Code | `allowed-tools: Bash(jf:*)` + host allowlist | same + filesystem allowlist | +| Other | Configure at the runtime/sandbox layer | same | + +If `jf` exits 1 with empty output, the runtime's network gate is the first +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 `` for the rest of the +session: + +```bash +bash /scripts/check-environment.sh +# stdout (one line): jfrog-skills/ [(tool=; model=)] jfrog-cli-go/ +# 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`. + +### Export `JFROG_CLI_USER_AGENT` once per bash invocation + +At the top of every bash invocation that runs `jf`, export `` once; +all `jf` calls in that invocation pick it up: + +```bash +export JFROG_CLI_USER_AGENT='' +jf config show +jf api /artifactory/api/system/version +``` + +Do **not** repeat the assignment per `jf` call (`JFROG_CLI_USER_AGENT='' 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 `` in its prompt; subagents do not re-run the script. + +| Exit | Meaning | +|------|---------| +| 0 | Cache fresh — CLI ready (Tiers 2 and 3 available), proceed | +| 1 | Cache refreshed — CLI ready (Tiers 2 and 3 available), proceed | +| 2 | `jf` not installed — Tiers 2 and 3 unavailable; only MCP (Tier 1) remains | +| 3 | `jf` below minimum version — Tiers 2 and 3 unavailable; only MCP (Tier 1) remains | + +Exit 2 or 3 is not a fatal error. Attempt to install or upgrade the CLI +(see `references/jfrog-cli-install-upgrade.md`). If installation succeeds, +re-run the environment check. If installation is not possible (no permissions, +restricted environment), proceed with MCP (Tier 1) only. Both `jf` CLI commands +(Tier 2) and `jf api` (Tier 3) require a working `jf` installation. + +### JSON parsing (`jq`) + +Use **`jq`** for all JSON parsing of CLI and API output (pipes, `-r`, filters). + +## `~/.jfrog/skills-cache/` — allowed files only + +`${JFROG_CLI_HOME_DIR:-$HOME/.jfrog}/skills-cache/` is **not** a general scratch +or temp directory. Use it **only** for these two artifacts: + +1. **`jfrog-skill-state.json`** — written by `scripts/check-environment.sh` + (24-hour CLI check cache). +2. **`onemodel-schema-${JFROG_SERVER_ID}.graphql`** — cached OneModel supergraph + schema (see `references/onemodel-graphql.md`). + +**Do not** save HTTP response bodies, GraphQL query results, ad-hoc JSON, reports, +or any other temporary files under `skills-cache/`. Write those to a host temp +path instead (for example `/tmp/-$$.json` or `mktemp -d`), echo the path +when a follow-up Shell step must read the file — same pattern as *Preserving +command output* below. + +## Cautious execution + +Do not run commands speculatively. Before executing 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. + "builds" could mean Artifactory build-info or CI/CD pipeline runs), + **ask the user for clarification** instead of guessing. Never fetch data + from the wrong system — a wrong answer is worse than asking a question. +2. Resolve the target server using the **Server selection rules** below — + there must be no ambiguity about which server is used +3. For mutating operations (create, update, delete, upload), confirm with the + user unless the intent is clearly implied. This applies to all tiers + (MCP tools, CLI commands, and `jf api` with POST/PUT/DELETE). +4. Prefer read operations first to understand current state before making changes +5. **Never invent preparatory mutations.** If the requested operation fails + because a precondition is not met (artifact missing from the specified repo, + repository does not exist, package not at the expected location, build not + found), **stop and report the gap to the user**. Do not perform copy, move, + upload, create-repo, or any other mutating operation to satisfy the + precondition unless the user explicitly asks for it. These "helper" mutations + can have cascading effects the user has not considered — virtual repository + resolution changes, storage quota consumption, replication triggers, Xray + re-indexing, or permission propagation. +6. **Never guess tool names or API paths.** For MCP tools, confirm the tool + exists in the server's tool list. For `jf api` paths, validate against + `/references/` (or + [JFrog OpenAPI specifications](https://docs.jfrog.com/integrations/docs/openapi-specifications) + if you have web access). On a 404, stop and report — never retry with a guessed + alternative path. + +## Server selection rules (mandatory) + +**Single-server invariant.** Every `jf` call MUST pass `--server-id ` +(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. + +**JFrog MCP and CLI use independent auth.** MCP tools authenticate through +the MCP server session (not `jf config`); CLI commands authenticate through +`jf config`. If you switch the CLI target server via `jf config use`, the +MCP connection still points to its original server. Do not mix MCP and CLI +calls targeting different servers in the same session. If the user asks to +switch servers, warn that MCP tools will continue to target the original +server until the MCP connection is re-established. + +**MUST NOT** retry on a second configured server after 401/403/404, empty, or +partial results; **MUST NOT** infer multi-server intent from "my"/"our" or +from seeing extra entries in `jf config show`. **Override:** only when the user +**explicitly** names another id ("on ``, …", "use ``", "compare `` +and ``") — inferred intent is not an override. + +### Resolve the default once per session + +Before your first `jf` call, resolve the default server-id and **remember it** +as `` for the rest of the session, same pattern as ``: + +```bash +jf config show 2>/dev/null \ + | awk '/^Server ID:/{id=$NF} /^Default:[[:space:]]*true/{print id; exit}' +# stdout: the default server-id; if empty, stop and ask which to use +``` + +Pass `--server-id ` to every subsequent `jf` call. The flag goes +**after** the subcommand name, not after `jf` itself: + +- ✅ `jf api --server-id /artifactory/api/system/version` +- ✅ `jf rt ping --server-id ` +- ❌ `jf --server-id api /…` — fails with `flag provided but not defined` + +When launching a subagent, pass `` in its prompt — subagents do not +re-resolve. Examples elsewhere in this skill and in `references/*.md` omit +`--server-id` for readability; the rule is global, same as +`JFROG_CLI_USER_AGENT`. To add a new server, read +`references/jfrog-login-flow.md`. + +### On any error, stop — never switch + +If a `jf` call returns 401/403, 404, network error, timeout, or any other +failure, **stop with no further `jf` calls** and respond: + +> `` returned `` for ``: ``. Other +> configured server(s): `` — 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//?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 + +### Release lifecycle & distribution + +- **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) + +### 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` (~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 + +### OneModel (GraphQL) + +- **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 + +- **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` + +## 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 --help` — list subcommands in a namespace +3. `jf --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. + +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`. + +## 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 /artifactory/api/system/version + +# AQL (POST with text/plain body) +jf api /artifactory/api/search/aql \ + -X POST -H "Content-Type: text/plain" -d '' +``` + +Common flags: `-X/--method`, `-H/--header`, `-d/--data`, `--input `, +`--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. + +## 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: + +```bash +jf api /artifactory/api/repositories/ +``` + +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: returned NNN`. Pipe stdout to + `jq` directly; **never `2>&1 | jq`** — stderr corrupts the JSON. To keep + diagnostics: `jf api 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/` 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 ''`. + 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. + +## 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. + +## 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 '' /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/plugin/skills/jfrog/assets/.gitkeep b/plugin/skills/jfrog/assets/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/plugin/skills/jfrog/references/apptrust-entities.md b/plugin/skills/jfrog/references/apptrust-entities.md new file mode 100644 index 0000000..6ca5088 --- /dev/null +++ b/plugin/skills/jfrog/references/apptrust-entities.md @@ -0,0 +1,154 @@ +# AppTrust entities + +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. + +AppTrust entities are accessed exclusively via the **OneModel GraphQL API** +(`/onemodel/api/v1/graphql`). There are no CLI commands for this domain. + +For the OneModel query workflow (credentials, schema fetch, validation, +execution), read `references/onemodel-graphql.md`. + +## Entity relationship overview + +```mermaid +erDiagram + Application ||--o{ ApplicationVersion : "has versions" + ApplicationVersion ||--o{ Releasable : "contains" + ApplicationVersion ||--o{ Promotion : "promoted through" + ApplicationVersion }o--o{ Source : "assembled from" + Releasable }o--o{ Source : "contributed by" + Releasable ||--o{ Artifact : "contains" + Releasable }o--o| StoredPackageVersionLocation : "located at" + Application }o--o{ Owner : "owned by" + Application }o--o{ Label : "tagged with" + ApplicationVersion }o--o| EvidenceSubject : "attested by" +``` + +## 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. + +| Field | Description | +|-------|-------------| +| `key` | Unique identifier (referenced as `applicationKey` or `appKey` elsewhere) | +| `projectKey` | JFrog Project this application belongs to | +| `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 | + +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. + +| Field | Description | +|-------|-------------| +| `application` | Parent application | +| `version` | Version identifier (semantic or custom) | +| `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) | +| `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. + +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**. + +| Field | Description | +|-------|-------------| +| `name` | Package name or artifact file name | +| `version` | Package version (empty for non-package artifacts) | +| `packageType` | Repository package type (docker, maven, generic, etc.) | +| `releasableType` | `artifact` or `package_version` | +| `sha256` | Leading file checksum (e.g. manifest for Docker images) | +| `totalSize` | Sum of all artifact sizes in bytes | +| `sources` | Sources that contributed to this releasable | +| `artifacts` | Individual files that make up the releasable | +| `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`). + +## Application version promotion + +Records the promotion of an application version from one stage to another. +All promotions are recorded including failed attempts. + +| Field | Description | +|-------|-------------| +| `sourceStageName` | Stage being promoted from (empty for first promotion) | +| `targetStageName` | Stage being promoted to | +| `status` | `SUBMITTED`, `STARTED`, `PENDING`, `COMPLETED`, `FAILED`, `REJECTED` | +| `createdBy`, `createdAt` | Who initiated and when | +| `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. + +## Sources + +Sources describe how releasables were assembled into an application version. +Four types exist: + +| Source type | Fields | Description | +|-------------|--------|-------------| +| **Build** | `name`, `number`, `startedAt`, `repositoryKey` | A CI/CD build that produced releasables | +| **ReleaseBundle** | `name`, `version` | A release bundle whose artifacts were included | +| **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). + +## Artifacts (within application versions) + +Individual files within releasables. + +| Field | Description | +|-------|-------------| +| `filePath` | Path in the repository (excluding repo key) | +| `downloadPath` | Full path for downloading from a Release Bundle repository | +| `sha256` | Checksum | +| `size` | Size in bytes | +| `evidenceSubject` | Evidence attestation anchor | + +## Cross-domain connections + +AppTrust entities connect to other domains via the OneModel GraphQL API: + +- **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. diff --git a/plugin/skills/jfrog/references/artifactory-api-gaps.md b/plugin/skills/jfrog/references/artifactory-api-gaps.md new file mode 100644 index 0000000..1533d8a --- /dev/null +++ b/plugin/skills/jfrog/references/artifactory-api-gaps.md @@ -0,0 +1,206 @@ +# Artifactory API Gaps + +Operations available through REST API but not through CLI commands. +Invoke them via `jf api [flags]` (authentication is handled +automatically against the active `jf config` server; see the base skill's +*Invoking platform APIs with `jf api`* section). + +## Repository management + +### Get repository configuration +```bash +jf api /artifactory/api/repositories/ +``` +Returns the full JSON configuration of a repository. Useful as a template +for creating similar repos. + +### List all repositories +```bash +jf api /artifactory/api/repositories +``` +Optional query params (combinable): `type` (one of `local`, `remote`, +`virtual`, `federated`), `packageType` (e.g. `docker`, `maven`, `npm`, +`pypi`, `generic`), `project`. Examples: +```bash +jf api "/artifactory/api/repositories?type=local" +jf api "/artifactory/api/repositories?packageType=docker" +jf api "/artifactory/api/repositories?type=remote&packageType=maven&project=my-project" +``` + +### Get repositories (v2) +```bash +jf api /artifactory/api/repositories/configurations +``` +Optional query params (combinable, comma-separated values allowed): +`repoType` (case-insensitive; one of `local`, `remote`, `virtual`, +`federated`) and `packageType` (e.g. `maven`, `docker`, `npm`). Note: +`repo_type` is silently ignored — the correct name is `repoType`. +Examples: +```bash +jf api "/artifactory/api/repositories/configurations?repoType=local" +jf api "/artifactory/api/repositories/configurations?packageType=maven" +jf api "/artifactory/api/repositories/configurations?repoType=local,remote&packageType=docker" +``` + +### Check if repository exists +```bash +jf api /artifactory/api/repositories/ -X HEAD +# 200 = exists, 400 = does not exist +``` + +## Storage and system + +### Get storage summary +```bash +jf api /artifactory/api/storageinfo +``` + +### Refresh storage summary +```bash +jf api /artifactory/api/storageinfo/calculate -X POST +``` + +### Get storage item info +```bash +jf api "/artifactory/api/storage//" +``` + +### System ping +```bash +jf api /artifactory/api/system/ping +``` + +### System version +```bash +jf api /artifactory/api/system/version +``` + +### System configuration +```bash +jf api /artifactory/api/system/configuration +``` + +## Search (beyond CLI) + +### AQL queries +```bash +jf api /artifactory/api/search/aql \ + -X POST -H "Content-Type: text/plain" \ + -d 'items.find({"repo":"my-repo","name":{"$match":"*.jar"}})' +``` + +For remote repository content, query the `-cache` suffixed repo: +```bash +jf api /artifactory/api/search/aql \ + -X POST -H "Content-Type: text/plain" \ + -d 'items.find({"repo":"my-remote-cache"})' +``` + +### Property search +```bash +jf api "/artifactory/api/search/prop?key=value&repos=my-repo" +``` + +### Checksum search +```bash +jf api "/artifactory/api/search/checksum?sha256=" +``` + +### GAVC search (Maven) +```bash +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. + +## Metadata calculation + +Trigger metadata recalculation for various package types: +```bash +# Maven +jf api /artifactory/api/maven/calculateMetaData/ -X POST + +# npm +jf api /artifactory/api/npm//reindex -X POST + +# Docker +# (automatic, no manual trigger) + +# PyPI +jf api /artifactory/api/pypi//reindex -X POST + +# Helm +jf api /artifactory/api/helm//reindex -X POST + +# Debian +jf api /artifactory/api/deb/reindex/ -X POST +``` + +## Trash can and garbage collection + +### Empty trash +```bash +jf api /artifactory/api/trash/empty -X POST +``` + +### Restore from trash +```bash +jf api "/artifactory/api/trash/restore//" -X POST +``` + +### Run garbage collection +```bash +jf api /artifactory/api/system/storage/gc -X POST +``` + +## Federated repositories (beyond basic CRUD) + +### Get federation status +```bash +jf api /artifactory/api/federation/status/ +``` + +### Trigger full sync +```bash +jf api "/artifactory/api/federation/fullSyncAll/" -X POST +``` + +## Build info (beyond CLI) + +### 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*. + +```bash +# Project scope — build names (latest per name) +jf api "/artifactory/api/build?project=" + +# Project scope — all run numbers for one build name (response: buildsNumbers) +jf api "/artifactory/api/build/?project=" + +# Build-info repo scope — alternative when you know the repo key +jf api "/artifactory/api/build?buildRepo=" +``` + +### Get build info +```bash +# Default build-info repo only (no project / non-default repo) +jf api "/artifactory/api/build//" + +# Project or custom build-info repo +jf api "/artifactory/api/build//?project=" +jf api "/artifactory/api/build//?buildRepo=" +``` + +### Delete builds +```bash +jf api /artifactory/api/build/delete \ + -X POST -H "Content-Type: application/json" \ + -d '{"buildName":"my-build","buildNumbers":["1","2"]}' +``` diff --git a/plugin/skills/jfrog/references/artifactory-aql-syntax.md b/plugin/skills/jfrog/references/artifactory-aql-syntax.md new file mode 100644 index 0000000..b120275 --- /dev/null +++ b/plugin/skills/jfrog/references/artifactory-aql-syntax.md @@ -0,0 +1,656 @@ +# AQL (Artifactory Query Language) + +AQL queries are sent as POST requests with `Content-Type: text/plain`: + +```bash +jf api /artifactory/api/search/aql \ + -X POST -H "Content-Type: text/plain" -d '' +``` + +## Query structure + +``` +.find() + .include() + .sort() + .offset() + .limit() + .distinct() +``` + +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. + +**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: + +``` +items.find({"name":"commons-lang3-3.12.0.jar"}) + .include("repo","path","name") + .distinct(true) +``` + +## Domains + +AQL has 13 queryable domains. Each domain represents a different entity type +and has its own set of fields. + + +| Domain | Query name | Description | +| -------------------- | ------------------- | ---------------------------------------------- | +| Items | `items` | Artifacts stored in repositories (most common) | +| Properties | `properties` | Key-value properties on items | +| Item infos | `item.infos` | Property modification metadata | +| Statistics | `stats` | Download statistics (local and remote) | +| Builds | `builds` | Build info records | +| Build modules | `modules` | Modules within a build | +| Build artifacts | `artifacts` | Artifacts produced by a build module | +| Build dependencies | `dependencies` | Dependencies consumed by a build module | +| Build properties | `build.properties` | Key-value properties on builds | +| Build promotions | `build.promotions` | Build promotion records | +| Module properties | `module.properties` | Key-value properties on build modules | +| 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. + +```mermaid +erDiagram + items ||--o{ properties : "has" + items ||--o| item_infos : "has" + items ||--o{ stats : "has" + items ||--o{ artifacts : "via checksum" + items ||--o{ dependencies : "via checksum" + items ||--o{ release_artifacts : "has" + artifacts }o--|| modules : "belongs to" + dependencies }o--|| modules : "belongs to" + modules }o--|| builds : "belongs to" + modules ||--o{ module_properties : "has" + builds ||--o{ build_properties : "has" + builds ||--o{ build_promotions : "has" + 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. + +### Cross-domain field paths + +To reference a field from a related domain, use dot-separated domain paths: + +``` +items.find({"artifact.module.build.name":"my-build"}) + .include("name","repo","path","artifact.module.build.number") +``` + +Common cross-domain paths from items: + +- `stat.downloads`, `stat.downloaded` — download statistics +- `property.key`, `property.value` — item properties +- `artifact.module.build.name` — build that produced the item +- `artifact.module.build.number` — build number + +From builds: + +- `module.artifact.name` — artifacts in build modules +- `module.dependency.name` — dependencies of build modules + +## Fields by domain + +Field types: `string`, `date`, `int`, `long`, `itemType` (`file`, `folder`, +or `any`). Fields marked "default" are returned without explicit `.include()`. + +### items + + +| Field | Type | Default | +| --------------- | -------- | ------- | +| `repo` | string | yes | +| `path` | string | yes | +| `name` | string | yes | +| `type` | itemType | yes | +| `size` | long | yes | +| `depth` | int | yes | +| `created` | date | yes | +| `created_by` | string | yes | +| `modified` | date | yes | +| `modified_by` | string | yes | +| `updated` | date | yes | +| `actual_md5` | string | no | +| `actual_sha1` | string | no | +| `sha256` | string | no | +| `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. + +### properties + + +| Field | Type | Default | +| ------- | ------ | ------- | +| `key` | string | yes | +| `value` | string | yes | + + +### stats + + +| Field | Type | Default | +| ---------------------- | ------ | ------- | +| `downloads` | int | yes | +| `downloaded` | date | yes | +| `downloaded_by` | string | yes | +| `remote_downloads` | int | yes | +| `remote_downloaded` | date | yes | +| `remote_downloaded_by` | string | yes | +| `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 | +| `name` | string | yes | +| `number` | string | yes | +| `started` | date | yes | +| `created` | date | yes | +| `created_by` | string | yes | +| `modified` | date | yes | +| `modified_by` | string | yes | +| `repo` | string | no | + + +### modules + + +| Field | Type | Default | +| ------ | ------ | ------- | +| `name` | string | yes | + + +### artifacts + + +| Field | Type | Default | +| ------ | ------ | ------- | +| `name` | string | yes | +| `type` | string | yes | +| `sha1` | string | yes | +| `md5` | string | yes | + + +### dependencies + + +| Field | Type | Default | +| ------- | ------ | ------- | +| `name` | string | yes | +| `scope` | string | yes | +| `type` | string | yes | +| `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 | +| `created_by` | string | yes | +| `status` | string | yes | +| `repo` | string | yes | +| `comment` | string | yes | +| `user` | string | yes | + + +### module.properties + + +| Field | Type | Default | +| ------- | ------ | ------- | +| `key` | string | yes | +| `value` | string | yes | + + +### releases + + +| Field | Type | Default | +| -------------- | --------------------------- | ------- | +| `name` | string | yes | +| `version` | string | yes | +| `status` | string | yes | +| `created` | date | yes | +| `signature` | string | yes | +| `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"}` | +| `$ne` | Not equals | `{"type":{"$ne":"folder"}}` | +| `$eqic` | Equals, case-insensitive | `{"name":{"$eqic":"README.md"}}` | +| `$match` | Wildcard match (`*`, `?`) | `{"name":{"$match":"*.jar"}}` | +| `$matchic` | Wildcard match, case-insensitive | `{"name":{"$matchic":"*.JAR"}}` | +| `$nmatch` | Wildcard not-match | `{"name":{"$nmatch":"*-SNAPSHOT*"}}` | +| `$gt` | Greater than | `{"size":{"$gt":"1000000"}}` | +| `$gte` | Greater than or equal | `{"stat.downloads":{"$gte":"10"}}` | +| `$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"}, + {"$or":[ + {"name":{"$match":"*.jar"}}, + {"name":{"$match":"*.war"}} + ]} +]}) +``` + +### Relative date comparators + +AQL supports relative date queries with `$last` and `$before`: + + +| 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). + +### Multi-property AND + +To match items that have property A=1 **and** property B=2 (different +property rows), use `$and` with `@` shorthand: + +``` +items.find({"$and":[ + {"@build.name":"my-build"}, + {"@build.number":"42"} +]}) +``` + +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. + +## Date queries + +Dates use ISO 8601 format for absolute dates: + +``` +items.find({"modified":{"$gt":"2025-06-01T00:00:00.000Z"}}) +``` + +Or use relative dates (preferred — avoids hardcoding timestamps): + +``` +items.find({"modified":{"$last":"30d"}}) +items.find({"created":{"$before":"6mo"}}) +``` + +## Property queries + +Two equivalent syntaxes for property filtering: + +**`@key` shorthand** — concise, works for single property conditions: + +``` +items.find({"repo":"my-repo","@build.name":"my-build","type":"file"}) +``` + +**Explicit form** — `property.key`/`property.value` pairs: + +``` +items.find({ + "repo":"my-repo", + "property.key":"build.name", + "property.value":"my-build" +}) +``` + +**Multi-property AND** — use `$and` with `@` shorthand to match across +different property rows: + +``` +items.find({"$and":[ + {"@build.name":"my-build"}, + {"@build.number":"42"} +]}) +``` + +> **Note:** The `@key` shorthand works inside `$and`. For `$or`, use the +> explicit `property.key`/`property.value` form if the shorthand does not +> return expected results. + +## Include + +Select which fields to return. Without `.include()`, AQL returns each +domain's default field set. + +**When you use `.include()`, you replace the defaults — so you must +explicitly list any required fields:** + +- `items` domain: always include `"repo","path","name"` (server rejects +the query otherwise) +- `builds` domain: always include `"name","number","repo"` + +``` +items.find({"repo":"my-repo"}) + .include("name","repo","path","size","sha256","stat.downloads") +``` + +Cross-domain includes use dot-separated paths: + +``` +items.find({"repo":"my-repo"}) + .include("name","repo","path","property.key","property.value") +``` + +## Sort and pagination + +``` +items.find({"repo":"my-repo"}) + .sort({"$desc":["modified"]}) + .offset(0) + .limit(50) +``` + +Sort directions: `$asc`, `$desc`. Sort fields must also appear in the result +set (explicit `.include()` or default fields). See +[Before constructing a query](#before-constructing-a-query) for sort +performance rules. + +## Distinct + +Deduplicate result rows: + +``` +items.find({"repo":"my-repo"}).distinct(true) +``` + +## Validation rules + +The server enforces these constraints — violating them produces an error: + +**Non-admin users:** + +- `items` domain queries must include `repo`, `path`, `name` in results +(needed for permission filtering) +- `builds` domain queries must include `name`, `number`, `repo` in results + +**Transitive mode** (`.transitive()` for querying through virtual repos): + +- Only works with `items` domain +- Include subdomains limited to `items` and `properties` +- Repo criteria must use `$eq` (exact match) with a single repository +- No `offset` or `sort` allowed + +## 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`. +5. **Narrow server-side first** — add every applicable filter (`created_by`, + `created`, `type`, `name`) before relying on client-side `jq` filtering. + +## Common query patterns + +### Find all JARs in a repo + +``` +items.find({"repo":"libs-release","name":{"$match":"*.jar"}}) +``` + +### Find large files (> 100 MB) + +``` +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`: + +``` +items.find({"repo":"libs-snapshot","name":{"$match":"*-SNAPSHOT*.jar"},"type":"file"}) +``` + +### Find artifacts modified in the last 7 days + +``` +items.find({"repo":"my-repo","modified":{"$last":"7d"},"type":"file"}) + .sort({"$desc":["modified"]}) + .limit(100) +``` + +### 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)). + +``` +items.find({"repo":"docker-local","path":{"$match":"my-image/*"},"name":"manifest.json"}) +``` + +### Docker image size + +**Do not use AQL** — layer blobs live at `/sha256:/`, not +under `//`. Use the V2 manifest API (returns `layers[].size`): + +```bash +jf api "/artifactory/api/docker//v2//manifests/" \ + -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. + +### Find artifacts with a specific property + +``` +items.find({"repo":"my-repo","@build.name":"my-build","type":"file"}) +``` + +### Find never-downloaded files (zero download count) + +Zero-download items lack a stats row — filter client-side instead +(see [Gotchas](#gotchas)): + +```bash +jf api /artifactory/api/search/aql \ + -X POST -H "Content-Type: text/plain" -d ' +items.find({"repo":"my-repo","type":"file"}) + .include("repo","path","name","size","stat.downloads") +' | jq '[.results[] | select((.stats[0].downloads // 0) == 0) | {repo, path, name, size}]' +``` + +### 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. + +``` +items.find({ + "repo":"my-repo", + "type":"file", + "stat.downloaded":{"$before":"90d"} +}).include("name","repo","path","stat.downloaded","size") +``` + +### Find items by build name (cross-domain) + +``` +items.find({"artifact.module.build.name":"my-service"}) + .include("name","repo","path","artifact.module.build.number") + .sort({"$desc":["modified"]}) + .limit(50) +``` + +### Find builds by name + +Non-admin users must include `name`, `number`, `repo` — omitting any +produces an error. + +``` +builds.find({"name":{"$match":"*my-service*"}}) + .include("name","number","repo","started") + .sort({"$desc":["started"]}) + .limit(10) +``` + +### Find build artifacts + +``` +artifacts.find({"module.build.name":"my-service","module.build.number":"42"}) + .include("name","type","sha1","md5") +``` + +### Find build dependencies + +``` +dependencies.find({"module.build.name":"my-service","module.build.number":"42"}) + .include("name","scope","type","sha1") +``` + +### Remote repository content + +Remote repo artifacts are stored in a `-cache` suffixed repo. Always query +the cache repo, not the remote repo itself: + +``` +items.find({"repo":"npm-remote-cache","name":{"$match":"*.tgz"}}) +``` + +## Gotchas + +- The request body is **plain text**, not JSON — use +`Content-Type: text/plain`. +- String values in criteria must be quoted, including numeric comparisons +(`"size":{"$gt":"1000"}` not `"size":{"$gt":1000}`). +- Remote repo content lives in `-cache`, not ``. +- Sort fields must appear in the result set (included explicitly or by +default). +- Non-admin `items` queries must return `repo`, `path`, `name`. +- Non-admin `builds` queries must return `name`, `number`, `repo`. +- Items connect to builds through checksum matching (SHA-1), so cross-domain +queries between items and builds are valid but traverse multiple joins. +- The `path` value for items at the **root** of a repository is `"."`, not +`""` or `"/"`. Use `"path":"."` to match root-level files. +- **Docker `list.manifest.json`** — multi-arch images store two manifest files per + tag: `manifest.json` (platform-specific manifest) and `list.manifest.json` (OCI + image index). Filtering by `"name":"manifest.json"` is correct for tag listing + (one result per tag), but silently excludes `list.manifest.json` entries. Use + `"name":{"$match":"*manifest.json"}` when querying by uploader, date range, or + any context where all manifest pushes should be counted. +- **`stat.downloads` filters do not match zero-download items** — never-downloaded + items lack a stats row so the join finds nothing. Use the client-side `jq` + approach in "Find never-downloaded files" above. +- `$match` uses SQL-style wildcards: `*` matches any characters, `?` matches +exactly one character. It is **not** regex. Literal `_` and `%` in patterns +are escaped automatically. +- The `builds.number` field is a **string**, not an integer. Build numbers +like `"42"`, `"1.0.3"`, and `"SNAPSHOT-1"` are all valid. +- Release bundle `type` values are uppercase strings: `"SOURCE"` or +`"TARGET"`. +- Dates accept both ISO 8601 format (`"2025-06-01T00:00:00.000Z"`) and +epoch milliseconds as a string (`"1719792000000"`). +- The server silently excludes trash, support-bundle, and in-transit +repository content from AQL results. If an item exists but doesn't appear +in results, it may be in one of these hidden repos. +- Virtual repo queries are rewritten to search the underlying physical repos. +The `repo` field in results shows the physical repo name, not the virtual +repo name you queried. + +## Official documentation + +- [Artifactory Query Language](https://docs.jfrog.com/artifactory/docs/artifactory-query-language) — overview and architecture +- [Query Structure and Syntax](https://docs.jfrog.com/artifactory/docs/aql-syntax) — domain queries, field references, JSON-like syntax rules +- [Search Criteria and Operators](https://docs.jfrog.com/artifactory/docs/aql-search-criteria) — comparators, wildcards, `$msp`, relative time +- [AQL Entities and Fields Reference](https://docs.jfrog.com/artifactory/docs/aql-entities-fields-reference) — complete field list for all domains +- [Query Output and Modifiers](https://docs.jfrog.com/artifactory/docs/aql-query-output) — `.include()`, `.sort()`, `.offset()`, `.limit()`, `.distinct()` +- [Query Execution and Permissions](https://docs.jfrog.com/artifactory/docs/aql-query-execution) — authentication, scoped tokens, HTTP errors, streaming +- [AQL Examples and Common Patterns](https://docs.jfrog.com/artifactory/docs/aql-examples) — ready-to-use queries by use case +- [Repository-Specific Queries](https://docs.jfrog.com/artifactory/docs/aql-repository-queries) — `.transitive()`, virtual repos, remote search +- [Performance and Operational Controls](https://docs.jfrog.com/artifactory/docs/aql-performance) — result limits, timeouts, rate limiting, optimization + diff --git a/plugin/skills/jfrog/references/artifactory-entities.md b/plugin/skills/jfrog/references/artifactory-entities.md new file mode 100644 index 0000000..1e4d950 --- /dev/null +++ b/plugin/skills/jfrog/references/artifactory-entities.md @@ -0,0 +1,236 @@ +# Artifactory entities + +When to read this file: + +- Working with **repositories** and you need to understand the difference between local, remote, virtual, and federated types. +- 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). + +For CLI commands see `artifactory-operations.md`. For API gaps see +`artifactory-api-gaps.md`. For AQL syntax see `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 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) | + +### Key relationships and fields + +- `key` — unique repo identifier (e.g. `libs-release-local`) +- `packageType` — determines layout and protocol (see Package types below) +- `rclass` — `local`, `remote`, `virtual`, or `federated` +- `url` — (remote only) the 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) + +### 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: + +| Pattern | Purpose | +|---------|---------| +| `release-bundles` | Release Bundles V1 metadata | +| `release-bundles-v2` | Release Bundles V2 metadata | +| `artifactory-build-info` | Default build info storage | +| `*-release-bundles` | Project-scoped Release Bundles V1 | +| `*-release-bundles-v2` | Project-scoped Release Bundles V2 | +| `*-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. + +### Remote repository cache + +When Artifactory downloads an artifact through a remote repo, it stores the +cached copy in a **separate local repo** named `-cache`. This is +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 + +The remote repo key itself is used for **configuration** (URL, credentials, +inclusion/exclusion patterns) but 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. + +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. + +```mermaid +erDiagram + VirtualRepo ||--o{ LocalRepo : "aggregates" + VirtualRepo ||--o{ RemoteRepo : "aggregates" + VirtualRepo ||--o| LocalRepo : "defaultDeploymentRepo" + RemoteRepo ||--|| CacheRepo : "has -cache" +``` + +## Artifacts + +An artifact is a file stored in a repository. Each artifact is uniquely +identified by the triple **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) +- `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. + +## Properties + +Key-value metadata pairs attached to 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 + +## 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). + +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. + +## Build info + +A build info record captures CI/CD metadata: which artifacts were produced, +which dependencies were consumed, and the 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) | +| `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 + +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. + +| Field | Description | +|-------|-------------| +| `status` | Target status label (e.g. `staged`, `released`) | +| `sourceRepo` | Where artifacts currently reside | +| `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. + +## Permissions + +Permissions define RBAC policies mapping **resources** and **principals** +(users and groups) to **actions**. Two models exist: + +### 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. + +| Component | Description | +|-----------|-------------| +| `name` | Permission name | +| `resources` | Map of resource type → targets + actions | + +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 + +Actions use uppercase: `READ`, `ANNOTATE`, `DEPLOY/CACHE`, `DELETE/OVERWRITE`, +`MANAGE_XRAY_METADATA`, `MANAGE`. + +API: `POST/PUT/GET/DELETE /access/api/v2/permissions/{permissionName}`. + +Documentation: [Permissions](https://docs.jfrog.com/administration/docs/permissions). + +### 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. + +| 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 | + +Actions use lowercase: `read`, `write`, `annotate`, `delete`, `manage`. + +Does **not** support `destination` or `pipeline_source` resource types. + +API: `PUT /artifactory/api/security/permissions/{permissionName}`. + +### Key differences + +| Aspect | V1 (Permission Targets) | V2 (Access Permissions) | +|--------|------------------------|------------------------| +| Managed by | Artifactory | Access service | +| API base | `/artifactory/api/security/permissions/` | `/access/api/v2/permissions/` | +| Actions | lowercase (`read`, `write`) | uppercase (`READ`, `WRITE`) | +| Resource types | repos, builds, release bundles | + destinations, pipeline sources | +| 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`. + +## Replication + +Replication synchronizes artifacts and properties between repositories, either +within the same instance or across Platform Deployments. + +| Type | Direction | Trigger | +|------|-----------|---------| +| **Push** | Source pushes to target | Scheduled or event-based | +| **Pull** | Target pulls from 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. diff --git a/plugin/skills/jfrog/references/artifactory-operations.md b/plugin/skills/jfrog/references/artifactory-operations.md new file mode 100644 index 0000000..bab2b65 --- /dev/null +++ b/plugin/skills/jfrog/references/artifactory-operations.md @@ -0,0 +1,178 @@ +# 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. + +## Repository management + +Repositories are created from JSON templates. The workflow is: + +1. Get a template: retrieve an existing repo config via + `jf api /artifactory/api/repositories/` + and modify it, or craft JSON manually. + Note: `jf rt repo-template` is interactive and cannot be used by agents. +2. Create: `jf rt repo-create ` +3. Update: `jf rt repo-update ` +4. Delete: `jf rt repo-delete --quiet` + +To list repositories, use: +`jf api /artifactory/api/repositories` + +## File operations + +- Upload: `jf rt upload ` +- Download: `jf rt download [target]` +- Search: `jf rt search ` +- Move: `jf rt move ` +- Copy: `jf rt copy ` +- Delete: `jf rt delete ` +- Set properties: `jf rt set-props "key=value"` +- Delete properties: `jf rt delete-props "key"` + +### Searching across repositories + +`jf rt search` expects a `/` 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. + +Use a direct AQL query with `name` and `path` criteria instead — omitting the +`repo` field searches all accessible repos via indexed columns: + +```bash +jf api /artifactory/api/search/aql \ + -X POST -H "Content-Type: text/plain" \ + -d 'items.find({ + "name":"", + "path":"" + }).include("repo","path","name","size","sha256")' +``` + +Add `"repo":""` to the criteria when the target repo is known, to +narrow the search further. + +## Build info + +**Project scoping rule:** Append `?project=` 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":"-build-info"` (or `"repo":"artifactory-build-info"` for +the default project). + +**Server rule:** A 404 from a `?project=` build call is **not** a signal +to try a different server. Use only the resolved server; on any failure, +report and stop. See `SKILL.md` § *Server selection rules*. + +### Publishing builds + +- Collect env: `jf rt build-collect-env ` +- Add git info: `jf rt build-add-git ` +- Publish: `jf rt build-publish ` +- Promote: `jf rt build-promote ` +- Discard: `jf rt build-discard ` + +### 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`. + +**All builds** (no project scope): + +```bash +jf api /artifactory/api/search/aql \ + -X POST -H "Content-Type: text/plain" \ + -d 'builds.find().include("name","number","repo","created").sort({"$desc":["created"]}).offset(0).limit(100)' +``` + +**Project-scoped** — filter by the project's build-info repository +(`-build-info`, or `artifactory-build-info` for the default +project): + +```bash +jf api /artifactory/api/search/aql \ + -X POST -H "Content-Type: text/plain" \ + -d 'builds.find({"repo":"-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 +first 100 of N results (paginated). Ask for the next batch if needed."* +For subsequent pages, increment `offset` by 100. + +**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. + +### Listing runs of a specific build + +```bash +jf api /artifactory/api/search/aql \ + -X POST -H "Content-Type: text/plain" \ + -d 'builds.find({"name":""}).include("name","number","repo","created").sort({"$desc":["created"]}).offset(0).limit(100)' +``` + +Add `"repo":"-build-info"` to the criteria when a project key +is known. Apply the same pagination rules as above. + +### Retrieving full build info + +Use the REST detail endpoint for a **single** build run. Always include +`?project=` (or `?project=default` when no key is provided): + +```bash +jf api "/artifactory/api/build//?project=" +``` + +This is the only `/api/build` endpoint that should be used — it returns a +single record and does not need pagination. + +### When a build is not found + +If the detail call returns 404, the build likely belongs to a different +project. **Ask the user for the project key** rather than searching across +repos or servers. + +### Repository listing vs build-info + +`GET /artifactory/api/repositories?project=&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. + +## Permissions + +Permission targets use JSON templates. +Note: `jf rt permission-target-template` is interactive. + +- Create: `jf rt permission-target-create ` +- Update: `jf rt permission-target-update ` +- Delete: `jf rt permission-target-delete ` + +## Users and groups + +- Create users: `jf rt users-create --csv ` +- Create single user: `jf rt user-create` (check `--help` for options) +- Delete users: `jf rt users-delete ` +- Create group: `jf rt group-create ` +- Delete group: `jf rt group-delete ` +- Add users to group: `jf rt group-add-users ` + +To get user details or update users, use `jf api`: +``` +jf api /access/api/v2/users/ +``` + +## Replication + +Replication configs use JSON templates. +Note: `jf rt replication-template` is interactive. + +- Create: `jf rt replication-create ` +- Delete: `jf rt replication-delete ` diff --git a/plugin/skills/jfrog/references/catalog-entities.md b/plugin/skills/jfrog/references/catalog-entities.md new file mode 100644 index 0000000..085bea0 --- /dev/null +++ b/plugin/skills/jfrog/references/catalog-entities.md @@ -0,0 +1,219 @@ +# Catalog entities + +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). +- Querying **OpenSSF scorecards**, **ML model metadata**, or **MCP service** registries. +- Using the OneModel GraphQL API with `publicPackages`, `customPackages`, + `publicSecurityInfo`, `publicLegalInfo`, `publicOperationalInfo`, + `publicCatalogLabels`, or `publicRemoteServices` query roots. + +Catalog entities are accessed via the **OneModel GraphQL API** +(`/onemodel/api/v1/graphql`). + +For the OneModel query workflow (credentials, schema fetch, validation, +execution), read `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 | + +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. + +## Public Catalog entities + +### PublicPackage + +A package as known to JFrog's global package database. + +| Field | Description | +|-------|-------------| +| `name` | Package name (e.g. `lodash`, `spring-boot-starter-web`) | +| `type` | Package type (e.g. `npm`, `maven`, `pypi`) | +| `ecosystem` | Ecosystem identifier | +| `description` | Rich-text description | +| `homepage`, `vcsUrl` | Package URLs | +| `vendor` | Maintainer or organization | +| `latestVersion` | Most recent version | +| `trendingScore` | Popularity score | +| `publishedAt`, `modifiedAt` | Timestamps | +| `mlModel` | ML model metadata (for HuggingFace etc.) | + +Connections: `versionsConnection`, `publicLabelsConnection`, `legalInfo`, +`operationalInfo`, `securityInfo`. + +Query: `publicPackages.searchPackages(where: {...})`. + +### PublicPackageVersion + +A specific version with security, legal, and operational analysis. + +| Field | Description | +|-------|-------------| +| `version` | Version string | +| `isLatest` | Whether this is the latest version | +| `isListedVersion` | Whether visible in Catalog UI | +| `publishedAt`, `modifiedAt` | Timestamps | +| `trendingScore` | Version-level popularity | +| `dependencies` | Dependency information | +| `mlModelMetadata`, `mlInfo` | ML/AI-related metadata | + +Each version carries three info blocks: +- `securityInfo` — vulnerability data, maliciousness, contextual analysis +- `legalInfo` — licenses, copyrights +- `operationalInfo` — end-of-life, OpenSSF scores, popularity metrics + +### PublicVulnerability + +Vulnerability data richer than what Xray violations expose. Useful for +deep-dive security analysis and advisory lookups. + +| Field | Description | +|-------|-------------| +| `name` | CVE identifier (e.g. `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 | +| `aliases` | Alternative identifiers | +| `references` | Advisory URLs | +| `publishedAt`, `modifiedAt` | Timestamps | + +Advisory sources (via `advisories` connection): +- **NVD** — NIST National Vulnerability Database +- **GHSA** — GitHub Security Advisory +- **JFrog Advisory** — JFrog's own research (includes impact reasons) +- **Debian Security Tracker** +- **RedHat OVAL** + +Additional connections: `cwesConnection` (CWE entries), `cpesConnection` +(CPE entries), `publicPackageInfo` (affected packages and 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: + +- **Version-level security info** (GraphQL): query + `publicPackages.getPackage(type, name)` and navigate to + `versionsConnection → securityInfo → vulnerabilitiesConnection` to get + CVEs affecting specific versions. +- **Individual CVE lookup**: use `searchVulnerabilities(where: { name: "" })` + and inspect `publicPackageInfo.vulnerablePublicPackagesConnection` on the + `generic` ecosystem entry. + +#### Ecosystem multiplicity + +A single CVE appears as multiple `PublicVulnerability` entries — one per +ecosystem. The `ecosystem` field determines which entry you see: + +| 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) | + +When looking up a CVE by name, `searchVulnerabilities(where: { name: "" })` +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. + +### PublicLicense + +License metadata with permission, condition, and limitation details. + +| Field | Description | +|-------|-------------| +| `name` | License name (e.g. `Apache-2.0`, `MIT`) | +| `spdxId` | SPDX identifier | +| `permissions` | What the license permits | +| `limitations` | Restrictions imposed | +| `patentConditions` | Patent grant conditions | +| `noticeFiles` | Required notices | + +Query: `publicLegalInfo.searchLicenses(where: {...})`. + +### PublicPackageOperationalInfo + +Operational risk assessment for packages and 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 | + +### MCP services and tools + +The 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 | + +Query: `publicRemoteServices.searchMcpServices(where: {...})`. + +## Custom Catalog entities + +### CustomPackage + +A package in the organization's private catalog view. + +| Field | Description | +|-------|-------------| +| `customCatalogId` | Org-scoped identifier | +| `name`, `type`, `ecosystem`, `namespace` | Package identity | +| `isListedPackage` | Whether visible in Catalog UI | +| `customCatalogAddedAt`, `customCatalogModifiedAt` | Org-specific timestamps | + +Connections: `versionsConnection`, `legalInfo`, +`customCatalogLabelsConnection`. + +### CustomCatalogLabel + +Organization-defined labels for categorizing packages. + +| Field | Description | +|-------|-------------| +| `name` | Label name | +| `description` | What the label represents | +| `color` | Display color | +| `labelType` | `MANUAL` or `AUTOMATIC` | +| `assignmentInfo` | How and when the label was 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. + +### CustomCatalogFederation + +Configuration for federating catalog data across JFrog deployments. + +## Catalog vs. Xray vs. Stored Packages + +These three domains provide different views of package and security data: + +| Aspect | Catalog | Xray | Stored Packages | +|--------|---------|------|-----------------| +| **Scope** | Global knowledge base + org overlay | Instance-scoped scanning | Instance-scoped storage | +| **Security** | CVE advisories, EPSS, CVSS v2/v3/v4, known exploits | Watches, policies, violations | Vulnerability summary (deprecated) | +| **Packages** | Public metadata (description, homepage, OpenSSF) | Components identified during scanning | Packages/versions stored in Artifactory | +| **Access** | GraphQL only | REST + CLI (`jf api /xray/...`) | GraphQL only | +| **Use case** | Research, compliance reporting, package evaluation | Runtime enforcement, CI/CD gating | Inventory, location queries | diff --git a/plugin/skills/jfrog/references/general-bulk-operations-and-agent-patterns.md b/plugin/skills/jfrog/references/general-bulk-operations-and-agent-patterns.md new file mode 100644 index 0000000..2b44979 --- /dev/null +++ b/plugin/skills/jfrog/references/general-bulk-operations-and-agent-patterns.md @@ -0,0 +1,93 @@ +# 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. + +## 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. + +## 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). + +## 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). + +**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. + +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). + +## 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`**. + +## Safe multi-response collection + +When looping over items (repos, builds, users) and fetching detail for each: + +1. Save each response to a 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. + +```bash +: >results.ndjson +while read -r key; do + body=$(jf api "/artifactory/api/repositories/$key" || true) + if echo "$body" | jq -e . >/dev/null 2>&1; then + echo "$body" | jq -c . >>results.ndjson + else + printf '{"key":"%s","_error":"invalid_response"}\n' "$key" >>results.ndjson + fi +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. + +## Where to find product specifics + +- Artifactory REST nuances: `references/artifactory-api-gaps.md` +- Platform admin / Access: `references/platform-admin-api-gaps.md` +- JFrog Projects (endpoints): `references/projects-api.md` +- Joining Artifactory repos to Projects (`projectKey`, roles, environments): + `references/platform-access-entities.md` +- Platform API invocation (all products through `jf api`): see + `SKILL.md` § *Invoking platform APIs with `jf api`* diff --git a/plugin/skills/jfrog/references/general-parallel-execution.md b/plugin/skills/jfrog/references/general-parallel-execution.md new file mode 100644 index 0000000..a0bb9ba --- /dev/null +++ b/plugin/skills/jfrog/references/general-parallel-execution.md @@ -0,0 +1,131 @@ +# 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: + +| 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 | + +## 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: + +```bash +jf api /artifactory/api/repositories > /tmp/jf-repos-$$.json && \ +jf api /artifactory/api/system/ping > /tmp/jf-ping-$$.json && \ +jf api /artifactory/api/storageinfo > /tmp/jf-storage-$$.json +``` + +Cross-product reads batch the same way: + +```bash +jf api /access/api/v2/users/ > /tmp/jf-users-$$.json && \ +jf api /access/api/v2/groups/ > /tmp/jf-groups-$$.json && \ +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: + +```bash +# Shell call 1 — echo the expanded path so the agent can reference it later +OUT=/tmp/jf-repos-$$.json +jf api /artifactory/api/repositories > "$OUT" && echo "$OUT" + +# Shell call 2 (parallel) — same pattern, different PID +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**). + +## 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. + +### Example — platform audit with three parallel subagents + +``` +Subagent 1 (shell): "Collect repository data" + → jf api /artifactory/api/repositories + → jf api /artifactory/api/storageinfo + → Return repo count, types, total size + +Subagent 2 (shell): "Collect security configuration" + → jf api /xray/api/v2/policies + → jf api /xray/api/v2/watches + → Return policy count, watch count, coverage gaps + +Subagent 3 (shell): "Collect user and permission data" + → jf api /access/api/v2/users/ + → jf api /access/api/v2/groups/ + → jf api /access/api/v2/permissions/ + → 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. + +### 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-