Skip to content
Closed
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
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 @@
{
"repo": "jfrog/jfrog-skills",
"pin": "v0.11.0",
"paths": ["skills"]
}
117 changes: 117 additions & 0 deletions .github/scripts/sync-skills.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
#!/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 plugin
// directory (plugin/), 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 plugin
// directory is plugin/ at the repo root (two levels up from
// .github/scripts/).
async function main() {
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
const pluginDir = path.resolve(scriptDir, "..", "..", "plugin");
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, pluginDir, rel);
} finally {
await fs.rm(workDir, { recursive: true, force: true });
}
console.log("done.");
}

await main();
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# IDE settings
.idea/
55 changes: 55 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# 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`. The full refresh flow lives in [`README.md`](README.md#updating-the-vendored-skills) and [`VENDOR.md`](VENDOR.md).

## 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: `.github/scripts/sync-skills-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 <devrel@jfrog.com>.
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ The JFrog plugin provides the following capabilities, grouped by component:
| Component | Feature | Description |
| --- | --- | --- |
| **Hook** | Agent Guard | Copilot manage MCPs through the JFrog Agent Guard. Through it you can discover, install, configure, update, and remove MCP servers from the JFrog AI Catalog approved for your project, and authenticate to remote HTTP MCPs via OAuth, API key, or bearer token. |
| **Skills** | JFrog Platform skills | Bundled [JFrog skills](https://github.com/jfrog/jfrog-skills) that teach Copilot how to interact with the JFrog Platform via the JFrog CLI, JFrog MCP server, and REST/GraphQL APIs — including a package safety and download workflow that consults the JFrog Public Catalog and curation policy before fetching packages. |

---

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

See [`CONTRIBUTING.md`](CONTRIBUTING.md) for development setup, coding conventions, and the pull-request process.

## Updating the vendored skills

The `plugin/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: `plugin/skills/`).
3. Bump `version` in [`plugin/.claude-plugin/plugin.json`](plugin/.claude-plugin/plugin.json) so users actually receive the update — Claude Code skips installs whose resolved version hasn't changed.
4. Commit the pin bump, the regenerated `plugin/skills/` tree, and the version bump together, and open a PR.

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

---

## Security

See [`SECURITY.md`](SECURITY.md) for how to report vulnerabilities.
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 `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 [`.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 `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 `.github/scripts/sync-skills-vendor.json`, downloads the pinned upstream tarball from `codeload.github.com`, and replaces the directories listed in `paths` (today: `plugin/skills/`).
1 change: 0 additions & 1 deletion marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
{
"name": "jfrog",
"description": "JFrog Platform integration with MCP, security skills, and supply-chain best practices",
"version": "1.0.1",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please make sure to try that this still works as expected without the version. It should be fine according to the claude marketplace documentation, and claude is usually the one that does tighter validation, so I expect this to be fine. But worth the validation.

"source": "plugin",
"categories": ["security", "artifact-management", "supply-chain", "devops", "mcp", "mlops", "agent-guard", "ai-catalog"],
"platforms": ["darwin", "linux", "windows"],
Expand Down
2 changes: 1 addition & 1 deletion plugin/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "jfrog",
"description": "JFrog Platform integration with MCP, security skills, and supply-chain best practices",
"version": "0.1.0",
Comment thread
davida-jfrog marked this conversation as resolved.
"version": "0.2.0",
"author": { "name": "JFrog", "url": "https://jfrog.com" },
"hooks": "hooks/hooks.json"
}
Loading