Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "jfrog",
"displayName": "JFrog",
"description": "Official JFrog plugin. Connect Claude Code to JFrog to manage, secure, and govern your software supply chain. Give agents the context to build secure, compliant software.",
"version": "0.2.3",
"version": "0.2.4",
"author": {
"name": "JFrog Ltd.",
"email": "devrel@jfrog.com",
Expand Down
5 changes: 5 additions & 0 deletions .github/scripts/sync-skills-vendor.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
Comment thread
davida-jfrog marked this conversation as resolved.
"repo": "jfrog/jfrog-skills",
"pin": "v0.11.0",
"paths": ["skills"]
}
116 changes: 116 additions & 0 deletions .github/scripts/sync-skills.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
#!/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
// sync-skills-vendor.json, then run this
// script to regenerate `skills/`, then commit both alongside each other.
//
// Usage:
// node .github/scripts/sync-skills.mjs
//
// Steps the script performs:
// 1. Reads sync-skills-vendor.json to learn which repo + ref to pull.
// 2. Downloads that tarball from codeload.github.com (public, no auth).
// 3. Extracts it into a temp directory.
// 4. Copies the requested paths (e.g. "skills") into the repo root,
// replacing any existing tree.
//
// The pin in sync-skills-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";
import { fileURLToPath } from "node:url";

// 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)}`);
}

// Sync this plugin: read sync-skills-vendor.json, download + extract + copy.
//
// Paths are resolved relative to the script itself rather than CWD, so
// the script works regardless of where it's invoked from. The repo root
// is two levels up from .github/scripts/.
async function main() {
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
const repoRoot = path.resolve(scriptDir, "..", "..");
const vendorPath = path.join(scriptDir, "sync-skills-vendor.json");
if (!(await fileExists(vendorPath))) {
throw new Error(`missing sync-skills-vendor.json at ${vendorPath}`);
}

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(`--- ${repo} (ref: ${pin}) ---`);

const workDir = await fs.mkdtemp(path.join(tmpdir(), "sync-skills-"));
try {
// `slug` is just a unique filename for this tarball + extract dir.
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, repoRoot, rel);
} finally {
await fs.rm(workDir, { recursive: true, force: true });
}
console.log("done.");
}

await main();
6 changes: 3 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
.env.*
!.env.example

# IDE settings
.idea/

# OS and editor
.DS_Store
*.swp
Expand All @@ -13,8 +16,5 @@
tmp/
temp/

# Local extract when refreshing vendored skills
.tmp-jfrog-skills/

# Skill runtime caches (never commit)
**/local-cache/
15 changes: 13 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ All contributors must sign the [JFrog CLA](https://jfrog.com/cla/) before contri
node scripts/validate-claude-plugin.mjs
```

Before a release or directory submission, also run **`claude plugin validate`** (requires [Claude Code](https://code.claude.com/docs) CLI).
This checks `.claude-plugin/plugin.json` and walks every `skills/*/SKILL.md` for required YAML frontmatter. Before a release or directory submission, also run **`claude plugin validate`** (requires [Claude Code](https://code.claude.com/docs) CLI).

4. **Test** by loading the repository as the plugin (the repo root is the plugin root):

Expand All @@ -29,17 +29,28 @@ Exercise the skills you changed (for example `/jfrog:<skill-name>`). Run `/reloa
5. **Commit** with a clear, descriptive message.
6. Open a **pull request** against `main` with a summary of what changed and why.

### Updating the vendored skills

The `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 [`.github/scripts/sync-skills-vendor.json`](.github/scripts/sync-skills-vendor.json):

```bash
node .github/scripts/sync-skills.mjs
```

This downloads the pinned upstream tarball and replaces the contents of `skills/`. Commit the result alongside any pin/version bumps.

## Pre-release checklist

- [ ] `node scripts/validate-claude-plugin.mjs` passes.
- [ ] `claude plugin validate` passes (before directory submission or major releases).
- [ ] Version bumped in [`.claude-plugin/plugin.json`](.claude-plugin/plugin.json) when the plugin changes.
- [ ] No secrets, credentials, or files under `**/local-cache/` committed.
- [ ] If the skill tree changed: `pin` in `.github/scripts/sync-skills-vendor.json` matches the upstream tag the new tree was generated from.
- [ ] Smoke-test: `claude --plugin-dir .` from the repo root.

### Submitting to the Claude plugin directory

Use [Submitting your plugin](https://claude.com/docs/plugins/submit). Submit the **public GitHub URL** of this repository — the **repository root** is the plugin root (manifest in `.claude-plugin/`, skills in `skills/`).
Use [Submitting your plugin](https://claude.com/docs/plugins/submit). Submit the **public GitHub URL** of this repository — the **repository root** is the plugin root (manifest in `.claude-plugin/`, skills committed under `skills/`, vendored from [jfrog/jfrog-skills](https://github.com/jfrog/jfrog-skills)).

Compliance: [Anthropic Software Directory Terms](https://support.claude.com/en/articles/13145338-anthropic-software-directory-terms), [Anthropic Software Directory Policy](https://support.claude.com/en/articles/13145358-anthropic-software-directory-policy).

Expand Down
24 changes: 22 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ Before installing, make sure you have:

- **JFrog host URL and access token** — Your JFrog platform URL and a valid access token.
- **Claude Code CLI** (≥ 1.0) — The Claude Code CLI.
- **Node.js** (≥ 14) — with `npx` on your `PATH`.
- **JFrog CLI** (≥ 2.x, optional) — Recommended for `jf config add` authentication (see [Authentication](#authentication)).
- **Node.js** (≥ 14) — with `npx` on your `PATH` (used by the Agent Guard hook).
- **Skill runtime requirements** — `jf` CLI, `jq`, and `curl` on `PATH`, plus a configured JFrog instance. For the minimum versions, see the upstream skills [`Requirements`](https://github.com/jfrog/jfrog-skills/blob/v0.11.0/README.md#requirements). Configure the CLI with `jf config add` see [Authentication](#authentication).
- **JFrog AI Catalog** (optional) — If you want to use the Agent Guard feature, your JFrog subscription needs to include the AI Catalog entitlement. Contact your JFrog account team if you're unsure whether it's enabled.
- **JFrog project** (optional) — If you want to use the Agent Guard feature.

Expand Down Expand Up @@ -117,6 +117,26 @@ See the [JFrog MCP Registry troubleshooting guide](https://docs.jfrog.com/ai-ml/

---

## Updating the vendored skills

The `skills/` tree is vendored from [`jfrog/jfrog-skills`](https://github.com/jfrog/jfrog-skills) at the version pinned in [`.github/scripts/sync-skills-vendor.json`](.github/scripts/sync-skills-vendor.json). To pull a newer upstream release into this repo:

1. Bump `pin` in `.github/scripts/sync-skills-vendor.json` to the new tag (e.g. `v0.12.0`).
2. Run the sync script from the repo root:

```bash
node .github/scripts/sync-skills.mjs
```

It downloads the pinned tarball from `codeload.github.com`, extracts it, and replaces the directories listed in `paths` (today: `skills/`).
3. Bump `version` in [`.claude-plugin/plugin.json`](.claude-plugin/plugin.json) so users actually receive the update — Claude Code skips installs whose resolved version hasn't changed.
4. Update the pinned-version link in the [Prerequisites](#prerequisites) section so the skill runtime requirements point at the new tag.
5. Commit the pin bump, the regenerated `skills/` tree, the version bump, and the README link bump together, and open a PR.

See [`VENDOR.md`](VENDOR.md) for the full picture.

---

## Contributing

See [`CONTRIBUTING.md`](CONTRIBUTING.md) for development setup, coding conventions, and the pull-request process.
Expand Down
26 changes: 26 additions & 0 deletions VENDOR.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Vendored skills

The skill packages under `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 [`.github/scripts/sync-skills-vendor.json`](.github/scripts/sync-skills-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 [`.github/scripts/sync-skills-vendor.json`](.github/scripts/sync-skills-vendor.json) to the new tag.
2. Re-syncs and commits the refreshed `skills/` tree.
3. Bumps `version` in [`.claude-plugin/plugin.json`](.claude-plugin/plugin.json) so users actually receive the update (Claude Code skips 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 its sibling [`sync-skills-vendor.json`](.github/scripts/sync-skills-vendor.json), downloads the pinned upstream tarball from `codeload.github.com`, and replaces the directories listed in `paths` (today: `skills/`).
13 changes: 0 additions & 13 deletions skills/VENDOR.md

This file was deleted.

Loading
Loading