Skip to content
Open
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 .cursor-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
},
"metadata": {
"description": "JFrog Platform plugins for Cursor",
"version": "0.5.14",
"version": "0.5.15",
"pluginRoot": "plugins"
},
"plugins": [
Expand Down
2 changes: 1 addition & 1 deletion .github/scripts/sync-modules-vendor.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"repo": "JFROG/jfrog-agent-hooks",
"pin": "jfrog-agent-hooks/v0.8.1",
"pin": "jfrog-agent-hooks/v0.11.0",
"paths": [
"modules"
]
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ JFrog plugin for [Cursor](https://cursor.com): artifact management, security sca

## What's new

- **Agent Package Resolution (Preview).** A new opt-in hook automatically routes the packages your AI agent installs through your JFrog Artifactory instead of public registries. See [Agent Package Resolution](#agent-package-resolution-preview).
- **Agent Package Resolution (Preview).** Routes packages your AI agent installs through your JFrog Artifactory instead of public registries (enabled by default in the shipped template; bind repos via Consent Enable or admin config). See [Agent Package Resolution](#agent-package-resolution-preview).
- **AI Catalog skill.** New `jfrog-ai-catalog-skills` skill to discover, install, update, and publish agent skills hosted in the JFrog AI Catalog.
---

Expand Down Expand Up @@ -69,7 +69,7 @@ Run `jf login` for browser-based setup, or set the `JFROG_ACCESS_TOKEN` environm

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 and opt-in. To get started:
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).
Expand Down
459 changes: 309 additions & 150 deletions docs/package-resolution-admin-guide.md

Large diffs are not rendered by default.

10 changes: 6 additions & 4 deletions docs/package-resolution-user-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@ This is the state you'll be in almost all the time.

## Turning it on

Agent Package Resolution is disabled by default; it needs to be enabled for any of the above to take effect. It can be turned on by your org, or by you directly.
The shipped template turns Agent Package Resolution **on** (`enabled: true`) with empty repository bindings. Nothing is routed to Artifactory until your org (or Consent Enable in chat) adds keys under `defaultGlobalRepos`.

Edit `~/.jfrog/agents-conf.json` (created automatically the first time you use the plugin, so the file already exists) and set:
To bind package types yourself, edit `~/.jfrog/agents-conf.json` (created automatically the first time you use the plugin) and set repository keys that exist on your JFrog Platform:

```json
{
Expand All @@ -60,7 +60,7 @@ Edit `~/.jfrog/agents-conf.json` (created automatically the first time you use t
}
```

If a repository key isn't accurate for your org, update it to the correct one. If you don't know the correct key, or a key doesn't exist on your JFrog Platform, that package type simply stays unrouted until someone corrects it; nothing breaks. The change takes effect automatically, no need to start a new session.
If a repository key isn't accurate for your org, update it to the correct one. If you don't know the correct key, or a key doesn't exist on your JFrog Platform, that package type simply stays unrouted until someone corrects it; nothing breaks. Start a **new agent session** after changing the file so SessionStart reloads policy.

## Turning it off

Expand All @@ -72,6 +72,8 @@ export JF_AGENT_PACKAGE_RESOLUTION_DISABLE=1

Restart your IDE for it to take effect. This overrides `agents-conf.json`, so it works even if your org has enabled the feature centrally. Remove the variable (or restart without it set) to turn routing back on. Please also report the issue (see [Feedback](#feedback)) so we can fix it.

To turn it off in the config file itself, set `"enabled": false`. If your file is still the untouched shipped scaffold, also set `"onboardingPrompt": "off"` — otherwise the next session can migrate `enabled` back to `true`. Setting only `"onboardingPrompt": "off"` silences Consent Enable offers; it does **not** disable APR while `enabled` remains `true`.

---

## Good to know (doesn't require you to do anything)
Expand All @@ -86,7 +88,7 @@ Restart your IDE for it to take effect. This overrides `agents-conf.json`, so it
| Symptom | What to do |
|---------|-------------|
| Install fails with `401` / `403` even though routing looked ready | Your token is expired or revoked, not a repository problem; this isn't caught until an install actually fails. Log in again for that server |
| Nothing seems to be happening / no mention of Artifactory | It's off by default until someone turns it on; see [Turning it on](#turning-it-on) above to turn it on yourself, or check with your admin |
| Nothing seems to be happening / no mention of Artifactory | Confirm `enabled` is `true` and `defaultGlobalRepos` has the package type; see [Turning it on](#turning-it-on), or check with your admin. Pending mode (no usable `jf` config) only shows a setup advisory |
| Install used the wrong repository | Check whether your project has a `.jfrog/local/package-resolution.json` override, or ask your admin what the org default is for that package type. See [Advanced](#advanced-project-specific-repository-overrides) below |
| You want to temporarily turn this off | See [Turning it off](#turning-it-off) above |
| Something looks broken | Check `~/.jfrog/logs/agent-hooks.log` for details, and let us know (see below); this is exactly the kind of thing we want to hear about during the preview |
Expand Down
2 changes: 1 addition & 1 deletion plugins/jfrog/.cursor-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "jfrog",
"displayName": "JFrog Platform",
"version": "0.5.14",
"version": "0.5.15",
"description": "JFrog Platform integration with MCP, security skills, Agent Package Resolution, supply-chain best practices, and JFrog Agent Guard governance for adding, removing, and listing MCP servers.",
"author": {
"name": "JFrog",
Expand Down
2 changes: 1 addition & 1 deletion plugins/jfrog/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ The **jfrog-package-safety-and-download** skill (`skills/jfrog-package-safety-an

The plugin can 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 and opt-in. To get started:
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](https://github.com/jfrog/cursor-plugin/blob/main/docs/package-resolution-user-guide.md).
- **Admins:** see the [Admin Guide](https://github.com/jfrog/cursor-plugin/blob/main/docs/package-resolution-admin-guide.md).
Expand Down
30 changes: 30 additions & 0 deletions plugins/jfrog/modules/assets/agents-conf-fingerprints.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"schemaVersion": 1,
"fingerprints": [
{
"id": "v0-placeholders-no-onboardingPrompt",
"sha256": "452b737ede2af5da3ea660cb0a2226d422b5624fa1684bc883279422c0728421",
"note": "Legacy template with example repo keys, before onboardingPrompt"
},
{
"id": "v1-placeholders-onboardingPrompt-auto",
"sha256": "b19251b4671db244a8050885bcbaf5f217f0e4eecfec34c0338264b08fa7c871",
"note": "Legacy template with example repo keys + onboardingPrompt: auto"
},
{
"id": "v2-empty-defaultGlobalRepos",
"sha256": "5a104c83c4cb67f2cb01d71ad0044a438f9125bab868ef76e24bd7be7828b82b",
"note": "Empty defaultGlobalRepos after #84 (no onboardingPrompt)"
},
{
"id": "v3-empty-onboardingPrompt-auto",
"sha256": "8b68d55af89e2dadf4ff0c3ae0784b70051c24d1fb75e3ea6df8ba3044c5cefa",
"note": "Legacy template: enabled false + empty defaultGlobalRepos + onboardingPrompt: auto"
},
{
"id": "v4-enabled-onboardingPrompt-auto",
"sha256": "f0481d915f1f7f2a1e7d88ab23ce7b9430d3e44e41aaabb4d4d13e2b40963ae2",
"note": "Current shipped template: enabled true + empty defaultGlobalRepos + onboardingPrompt: auto"
}
]
}
14 changes: 3 additions & 11 deletions plugins/jfrog/modules/assets/agents-default-conf.json
Original file line number Diff line number Diff line change
@@ -1,19 +1,11 @@
{
"logLevel": "info",
"packageResolution": {
"enabled": false,
"enabled": true,
"verifyRepos": true,
"cacheTtlDays": 7,
"defaultGlobalRepos": {
"npm": "npm-virtual",
"pypi": "pypi-virtual",
"maven": "maven-virtual",
"gradle": "gradle-virtual",
"go": "go-virtual",
"docker": "docker-virtual",
"helm": "helm-virtual",
"nuget": "nuget-virtual"
},
"onboardingPrompt": "auto",
"defaultGlobalRepos": {},
"autoSetup": []
}
}
12 changes: 10 additions & 2 deletions plugins/jfrog/modules/claude-session-start.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,16 @@
import process from "node:process";

import { runCapability } from "./core/run-capability.mjs";
import { ensureAgentsConfigScaffold, agentsConfigLoadWarnings } from "./core/agents-config.mjs";
import { readStdin, parseSessionId, detectHarness, parseWorkspaceRoots } from "./core/io.mjs";
import {
ensureAgentsConfigScaffold,
agentsConfigLoadWarnings,
} from "./core/agents-config.mjs";
import {
readStdin,
parseSessionId,
detectHarness,
parseWorkspaceRoots,
} from "./core/io.mjs";
import { setLogContext, createLogger } from "./core/logger.mjs";

const HARNESS_ID = "claude_code";
Expand Down
90 changes: 90 additions & 0 deletions plugins/jfrog/modules/copilot-session-start.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
#!/usr/bin/env node
// GitHub Copilot Chat SessionStart hook runner (installed via the VS Code
// Copilot plugin — see jfrog/vscode-plugin).
//
// Usage: node copilot-session-start.mjs <capability>
// Example: node copilot-session-start.mjs package-resolution
//
// stdout: JSON with hookSpecificOutput.additionalContext. "{}" is a no-op.

import process from "node:process";

import { runCapability } from "./core/run-capability.mjs";
import {
ensureAgentsConfigScaffold,
agentsConfigLoadWarnings,
} from "./core/agents-config.mjs";
import {
readStdin,
parseSessionId,
detectHarness,
parseWorkspaceRoots,
} from "./core/io.mjs";
import { setLogContext, createLogger } from "./core/logger.mjs";

const HARNESS_ID = "copilot";
const log = createLogger("session-start");

/** @returns {string | null} JSON stdout payload, or null when there is nothing to inject. */
function formatSessionStartStdout(text) {
if (!text?.trim()) return null;
return JSON.stringify({
hookSpecificOutput: {
hookEventName: "SessionStart",
additionalContext: text,
},
});
}

function writeStdout(payload) {
if (payload === null) {
writeNoOp();
return;
}
process.stdout.write(payload);
}

function writeNoOp() {
process.stdout.write("{}");
}

async function main() {
const capability = process.argv[2];
if (!capability) {
writeNoOp();
return;
}

const startedAtMs = Date.now();
const stdinRaw = await readStdin();
const harness = detectHarness(stdinRaw);
if (harness && harness !== HARNESS_ID) {
setLogContext({ ide: HARNESS_ID, sessionId: parseSessionId(stdinRaw) });
log.warn("harness mismatch; wrong adapter invoked", {
expected: HARNESS_ID,
detected: harness,
adapter: "copilot-session-start",
});
writeNoOp();
return;
}
const sessionId = parseSessionId(stdinRaw);
const workspaceRoots = parseWorkspaceRoots(stdinRaw);
setLogContext({ ide: HARNESS_ID, sessionId });
ensureAgentsConfigScaffold();
for (const w of agentsConfigLoadWarnings()) {
log.warn(w.message, { path: w.path });
}
const text = await runCapability(capability, {
ide: HARNESS_ID,
sessionId,
workspaceRoots,
startedAtMs,
});
writeStdout(formatSessionStartStdout(text));
}

main().catch(() => {
writeNoOp();
process.exit(0);
});
Loading
Loading