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
6 changes: 4 additions & 2 deletions .codex-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "jfrog",
"version": "0.1.9",
"version": "0.1.10",
"description": "JFrog skills and the JFrog MCP server for Codex \u2014 interact with the JFrog Platform.",
"author": {
"name": "JFrog",
Expand All @@ -14,10 +14,12 @@
"codex",
"plugin",
"skills",
"mcp"
"mcp",
"package-resolution"
],
"skills": "./skills/",
"mcpServers": "./.mcp.json",
"hooks": "./hooks/hooks.json",
"interface": {
"displayName": "JFrog",
"shortDescription": "JFrog Platform skills for Codex",
Expand Down
7 changes: 7 additions & 0 deletions .github/scripts/sync-modules-vendor.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"repo": "JFROG/jfrog-agent-hooks",
"pin": "jfrog-agent-hooks/v0.12.0",
"paths": [
"modules"
]
}
66 changes: 66 additions & 0 deletions .github/scripts/sync-modules.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#!/usr/bin/env node
// Vendors modules bundle from jfrog-agent-hooks into this plugin.
//
// Usage:
// JFROG_AGENT_HOOKS_PATH=/path/to/jfrog-agent-hooks node .github/scripts/sync-modules.mjs
//
// Defaults JFROG_AGENT_HOOKS_PATH to ../jfrog-agent-hooks (sibling clone).
// Reads paths from sync-modules-vendor.json.

import { promises as fs } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";

const scriptDir = path.dirname(fileURLToPath(import.meta.url));
const repoRoot = path.resolve(scriptDir, "..", "..");
const vendorPath = path.join(scriptDir, "sync-modules-vendor.json");

async function fileExists(p) {
try {
await fs.access(p);
return true;
} catch {
return false;
}
}

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: ${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)}`);
}

async function main() {
const vendor = JSON.parse(await fs.readFile(vendorPath, "utf8"));
const paths = vendor.paths;
if (!Array.isArray(paths) || paths.length === 0) {
throw new Error(`${vendorPath} must define a non-empty paths array`);
}

const hooksRoot =
process.env.JFROG_AGENT_HOOKS_PATH?.trim() ||
path.resolve(repoRoot, "..", "jfrog-agent-hooks");

if (!(await fileExists(hooksRoot))) {
throw new Error(
`jfrog-agent-hooks not found at ${hooksRoot}. Set JFROG_AGENT_HOOKS_PATH.`,
);
}

const destPrefix = (vendor.dest_prefix ?? "").replace(/^\/+|\/+$/g, "");
const destRoot = destPrefix ? path.join(repoRoot, destPrefix) : repoRoot;

console.log(`--- sync from ${hooksRoot} (pin: ${vendor.pin ?? "local"}) ---`);
for (const rel of paths) {
await copyPath(hooksRoot, destRoot, rel);
}
console.log("done.");
}

await main();
54 changes: 52 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ The JFrog plugin provides the following capabilities, grouped by component:
| **MCP** | JFrog MCP server | Bundled `jfrog` MCP server ([`.mcp.json`](.mcp.json)) at `https://<JFROG_PLATFORM_URL>/mcp`; this server signs in via OAuth (`codex mcp login jfrog`), so it needs no API key. |
| **Skill** | JFrog Platform | Interact with Artifactory repositories, builds, permissions, users, access tokens, projects, release bundles, and platform administration via the JFrog CLI and REST/GraphQL APIs. Also covers security audits, CVE lookups, and Advanced Security exposure queries. |
| **Skill** | Package curation | Check whether npm, Maven, PyPI, Go, and other packages are safe, curated, or allowed, then download them through Artifactory remote caches or curation-aware package managers. |
| **Hook + Skill** | Agent Package Resolution (Preview) | Automatically route packages installed by the AI agent through your organization's JFrog Artifactory, keeping agent-driven installs inside your Curation, Xray, and governance perimeter. |
| **Skill** | Agent Guard | Codex manages MCPs through the JFrog Agent Guard. Through the Agent Guard 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. |

---
Expand Down Expand Up @@ -41,7 +42,10 @@ codex plugin marketplace add jfrog/codex-plugin
codex plugin add jfrog@codex-plugin
```

Browse installed plugins in the Codex TUI with `/plugins`.
Browse installed plugins in the Codex TUI with `/plugins`. Installing the plugin
does **not** trust the SessionStart hook — restart Codex, run `/hooks`, and
trust the JFrog Package Resolution command. ChatGPT **web** does not run hook
scripts.

### Local development

Expand Down Expand Up @@ -116,9 +120,14 @@ restarting Codex, confirm:
JFrog skills appear. See [Discovering and invoking skills](#discovering-and-invoking-skills).
3. **MCP server is connected** — run `codex mcp list` and confirm `jfrog` is
connected (after `codex mcp login jfrog`).
4. **SessionStart hook is trusted** — `/hooks` lists the JFrog Package Resolution
command as trusted. Without that, Agent Package Resolution does not inject.
5. **`jf rt ping`** — succeeds against your configured server (required for
routing mode).

If any check fails, see [Recovery](#recovery). Setting MCP environment variables
by hand does not repair a failed initialization — re-run `jfrog-init` instead.
by hand does not repair a failed MCP initialization — re-run `jfrog-init`
instead. An untrusted SessionStart hook is a `/hooks` step, not an init failure.

---

Expand All @@ -130,6 +139,24 @@ by hand does not repair a failed initialization — re-run `jfrog-init` instead.
| `jfrog-init` stopped at CLI/auth | Follow the skill prompt (`jf config add`, web login, or token path), then **re-run `jfrog-init`**. | Skip init and only export env vars. |
| Placeholder still in `.mcp.json` | Set the host in `<install-path>/.mcp.json`, run `codex mcp login jfrog`, restart Codex. | Reinstall the plugin when only the host placeholder is wrong. |
| Plugin not listed | Re-run `codex plugin add jfrog@codex-plugin` outside Codex, then restart Codex. | Run install commands from inside the Codex TUI. |
| `/hooks` shows the Package Resolution command as untrusted, or no Artifactory routing in a new session | Restart Codex, open `/hooks`, and trust the exact command. A later change to the hook definition requires trust again. | Assume `codex plugin add` approved the hook. Do not use `--dangerously-bypass-hook-trust` as the normal path. |
| ChatGPT web never routes installs | Use Codex CLI or the ChatGPT desktop Codex surface. | Expect hook scripts to run on ChatGPT web. |
| `modules/` missing after a local checkout | Use a published release or re-sync with `JFROG_AGENT_HOOKS_PATH=… node .github/scripts/sync-modules.mjs`. | Hand-edit files under `modules/`. |

---

## Agent Package Resolution (Preview)

> **Preview Notice:** This feature is in preview and licensed under the Apache License 2.0. For clarity: This software is provided "as-is" without warranty of any kind, and without support obligations or service level commitments. Behavior, APIs, conventions, and structure may change without notice between releases. JFrog makes no guarantees of backward compatibility during the preview release cycle. Use in production environments is at your own risk.

The plugin can now automatically route the packages your AI agent installs (npm, PyPI, Maven, Go, Docker, Helm, and NuGet) through your organization's JFrog Artifactory instead of public registries. This keeps agent-driven dependency installs inside your organization's governance perimeter.

Agent Package Resolution is in preview. The shipped template enables it with empty repository bindings (nothing is routed until Consent Enable or an admin adds `defaultGlobalRepos`). To get started:

- **Users:** see the [User Guide](docs/package-resolution-user-guide.md).
- **Admins:** see the [Admin Guide](docs/package-resolution-admin-guide.md).

Installing the plugin does not skip `/hooks` trust. ChatGPT **web** does not run the SessionStart hook.

---

Expand Down Expand Up @@ -170,6 +197,16 @@ If a newly installed skill doesn't show up, restart Codex so it re-scans plugins
| "Is this Maven package approved for use?" | Checks curation entitlement and policy for the requested package. |
| "Download `requests` via JFrog." | Resolves the package through an Artifactory remote cache or curation-aware package manager. |

### Agent Package Resolution

When Agent Package Resolution is enabled and configured, no special prompt syntax is required. Ask the agent to install or use a package as you normally would, and the plugin routes supported package operations through your organization's Artifactory.

| Ask the agent… | What happens |
| -------------------------------------- | ------------------------------------------------------------------------ |
| "Add `lodash` to this project." | Resolves the npm package through the configured Artifactory repository. |
| "Add Excel file import to this app." | The agent selects a suitable package and resolves it through the configured Artifactory repository. |
| "Pull the `alpine` Docker image." | Pulls the image through the configured Artifactory Docker repository. |

### MCP server management (Agent Guard)

| Ask the agent… | What happens |
Expand Down Expand Up @@ -221,6 +258,19 @@ To pull a newer upstream release into this repo:

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

### Updating the vendored modules

The `modules/` tree is vendored from GHE `jfrog-agent-hooks` at the pin in
[`.github/scripts/sync-modules-vendor.json`](.github/scripts/sync-modules-vendor.json).
Automated `chore/sync-modules-v*` PRs replace that tree. To refresh locally:

```bash
JFROG_AGENT_HOOKS_PATH=/path/to/jfrog-agent-hooks node .github/scripts/sync-modules.mjs
```

Do not hand-edit files under `modules/`. `hooks/hooks.json` is owned by this
repo and is not part of the vendor slice.

---

## Releasing
Expand Down
23 changes: 23 additions & 0 deletions VENDOR.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,26 @@ node scripts/sync-skills.mjs
```

The script reads its sibling [`sync-skills-vendor.json`](scripts/sync-skills-vendor.json), downloads the pinned upstream tarball from `codeload.github.com`, and replaces the directories listed in `paths` (today: `skills/`).

---

# Vendored modules

The `modules/` bundle is vendored from **jfrog-agent-hooks** (GHE) and committed to `main`.

| | |
| --- | --- |
| **Repository** | `github.jfrog.info/JFROG/jfrog-agent-hooks` |
| **Pinned release** | see `pin` in [`.github/scripts/sync-modules-vendor.json`](.github/scripts/sync-modules-vendor.json) |

The bundle contains harness runners (`core/`, `*-session-start.mjs`), the `package-resolution/` capability, and `assets/agents-default-conf.json`. Automated sync PRs (`chore/sync-modules-v*`) update this tree on each `jfrog-agent-hooks` release.

`hooks/hooks.json` is **not** part of the vendor slice. Sync replaces `modules/` only; this plugin owns the Codex SessionStart assembly (APR only — no Agent Guard / MCP-align scripts).

## Refreshing modules

```bash
JFROG_AGENT_HOOKS_PATH=/path/to/jfrog-agent-hooks node .github/scripts/sync-modules.mjs
```

The script reads `paths` from `sync-modules-vendor.json` (today: `["modules"]`) and replaces the whole `modules/` tree. After a local refresh, stamp `PKG_VERSION` and the pin with the official copy script if you are matching a Sync Plugins drop. A hand refresh copies bytes only; it does not bump plugin versions.
Loading
Loading