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
14 changes: 8 additions & 6 deletions .github/scripts/sync-modules-integrity.json
Original file line number Diff line number Diff line change
@@ -1,28 +1,30 @@
{
"schemaVersion": 1,
"pin": "jfrog-agent-hooks/v0.9.0",
"pin": "jfrog-agent-hooks/v0.10.0",
"files": {
"assets/agents-default-conf.json": "04aae9b1dcfc75271c3ed786adceea1635b0a1ae0be64fadd7b1111229f11f01",
"claude-session-start.mjs": "2ca1edc6b939cdff6c5faa6ac4b69636e6e92bc7b079316e1fdee7c53c6e837b",
"copilot-session-start.mjs": "8811e0829c90ff0987bed158f5ef571195ee5eb54dfc8021b4396fe76ad8a499",
"core/agents-config.mjs": "3ade16fd6e08b8ac6cb8570edfbed1e9677b26d9c31513720680dd17513480af",
"core/io.mjs": "63ea75df635a4e15cf36f2158fe78ae42e3ca886abe267ee5ed2577042eb153d",
"core/jf-identity.mjs": "9d0301d4a60b9c9297cde24e0bab0c2660c56f831617f2b69db17c844276b19b",
"core/jf-user-agent.mjs": "6d8debdf32ff44352c79c31e4a5091def97401dd13e099528a61049ae9b38fef",
"core/logger.mjs": "1ebdffcdf4af14b19e3ee8e82cfeb377fb9961a09d4e9d6ecdc922d07b0848c2",
"core/run-capability.mjs": "9fac890b7fd4866f9d3322469b2a7301e28cebfa3faebd77857f9f79d2d1c532",
"cursor-session-start.mjs": "37dd25ffee18e9f357e3cbb8453552766fd89295e85aa09bf93bc208df74aa20",
"package-resolution/scripts/apr-heartbeat.mjs": "3b87b52c06afc5fe311df9c8ecfd29c97198f4b6e1881920ba28231358dc466b",
"package-resolution/scripts/eager-setup-receipt.mjs": "69213084bc1976ec63b346ca26e8a63eb713b0da7ad6ab4fc018934e70fef091",
"package-resolution/scripts/eager-setup.mjs": "d78fc422d15a271e9ae68b754a28fa72ea25a9e86b505b1a0e5e053add864c0d",
"package-resolution/scripts/eager-setup.mjs": "13ecd1c71097e97bd262a8f978fc7efa5bfe1f6523fc061589534330447da3a9",
"package-resolution/scripts/feature-flag.mjs": "18b258e4d1999de31bad54a1f7f3c3f9cf65c598bbb83f328b45f68a739821f3",
"package-resolution/scripts/index.mjs": "f3ea8f71ecd156515a4a5eb14e33de5c61287f4f2e0e7ca90f9b7d010c4d6567",
"package-resolution/scripts/index.mjs": "188469240fb5b5e5bd7aba9a4e68f5056e9724160824b7e6be0d4c5c275a062c",
"package-resolution/scripts/package-manager-family.mjs": "50d066910638e37c2375f696094fde1252181398be56c4218ca14342a76a34e5",
"package-resolution/scripts/print-policy.mjs": "02593c6e401006d226b908221d007ba9e1951a61c4cb060789877c1fe919faa2",
"package-resolution/scripts/render-instruction.mjs": "9e4205b5715e2c79515de19d473a6487d61971368103f2851388530ed0a180c4",
"package-resolution/scripts/render-instruction.mjs": "a2c1a5d19fec1bff0a08d33eafdd33c6ff27581d314a4d3699980c914891e4d1",
"package-resolution/scripts/repo-types.mjs": "b432bcdd6e77f80ca2c9dddfbf4d9e1299019758fd58b04b29fc21b18a86f788",
"package-resolution/scripts/resolver.mjs": "3485575a65fd5579420d69a51f723047d44f3cfa246511565c6e89ca32b02b4d",
"package-resolution/scripts/setup-conflict.mjs": "fdc589561813a9c5e708f50a20a87bacdad3f490158c973b12b217cb984e2845",
"package-resolution/scripts/workspace-config.mjs": "f8f8eaaf0fb8a0c3691938e99afebbc87d8779e508db0ef821fa23f0a55b786a",
"package-resolution/templates/package-resolution-unconfigured.md": "e7645b89d1c4d618fb45692de084d627ca24b5e2e975416176115d3c233e9c00",
"package-resolution/templates/package-resolution.md": "c305751d24fe352b6334a208f7831eb9db58704f1baa693c6921264f6a4456d1"
"package-resolution/templates/package-resolution-unconfigured.md": "d276aa796b3c38f0566bc0b5c3a1553bbbf322d5eaa0d60841e21a3e280a91e0",
"package-resolution/templates/package-resolution.md": "db2b09698041fbd589a5bc8ac09ceb2f28342d371d81608ab6388dba63e34735"
}
}
6 changes: 4 additions & 2 deletions .github/scripts/sync-modules-vendor.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
{
"repo": "JFROG/jfrog-agent-hooks",
"pin": "jfrog-agent-hooks/v0.9.0",
"paths": ["modules"],
"pin": "jfrog-agent-hooks/v0.10.0",
"paths": [
"modules"
],
"dest_prefix": "plugin"
}
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,28 @@ See the [user guide](docs/package-resolution-user-guide.md) for setup and the
[administrator guide](docs/package-resolution-admin-guide.md) for rollout and
governance configuration.

### Confirming it is installed

Agent Package Resolution runs from a SessionStart hook:

- **Start a new Copilot chat after installing the plugin.** Hooks are registered when the session starts, so a chat that was already open when you installed will not run it.
- **The hook is not written into your own configuration.** It stays inside the plugin (`plugin/hooks/hooks.json`) and is merged at runtime — finding nothing in user settings does not mean the install failed.

To confirm it is running, start a new chat and check the hook log:

```bash
tail ~/.jfrog/logs/agent-hooks.log
```

You should see one entry per session naming the mode it resolved to:

| Mode | Meaning |
| --- | --- |
| `off` | Not enabled yet — expected until an admin or you turn it on (see the guides above) |
| `pending` | Enabled, but JFrog identity is missing, unusable, or rejected — nothing is routed yet |
| `routing` | Enabled and configured — packages resolve through Artifactory |

For more detail, set `"logLevel": "debug"` in `~/.jfrog/agents-conf.json` and start another chat.
### Discover, inspect, and install MCPs

| Ask the agent… | What happens |
Expand Down
2 changes: 1 addition & 1 deletion marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
{
"name": "jfrog",
"description": "JFrog Platform integration with MCP, security skills, and supply-chain best practices",
"version": "1.0.16",
"version": "1.0.17",
"license": "Apache-2.0",
"source": "plugin",
"categories": [
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": "1.0.16",
"version": "1.0.17",
"license": "Apache-2.0",
"author": {
"name": "JFrog",
Expand Down
113 changes: 113 additions & 0 deletions plugin/modules/core/jf-user-agent.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// Thin JFROG_CLI_USER_AGENT for jf spawned by APR (eager setup + heartbeat).
//
// Stamp only what sessionStart actually knows right now:
// - trigger=hook
// - jfrog-skills/<hooks-pkg> (Coralogix product filter unity)
// - jfrog-cli-go/<probed>
// - tool= from adapter ctx.ide (via JFROG_APR_UA_TOOL)
// - client= from TERM_PROGRAM when present in this process
//
// Do NOT stamp model= — skills/agent own the model slug and set it when the
// agent is actually running with a known model (usually a later bash tool).
// Spawn env is inherited so CLI DetectExecutionContext can append
// ai-agent/ / ai-client/ / ai-model/ when those signals exist at jf start.

import { createRequire } from "node:module";
import { spawnSync } from "node:child_process";

const require = createRequire(import.meta.url);
// Source tree: ../../package.json is the hooks repo root. Vendored plugin
// layouts have no package.json at that relative path — never throw.
function hooksPkgVersion() {
try {
return String(require("../../package.json").version || "0.0.0");
} catch {
return "0.0.0";
}
}
const PKG_VERSION = hooksPkgVersion();

const MAX_TOKEN_LEN = 64;

/** @type {string | undefined} */
let cachedCliVersion;

/**
* @param {string | undefined | null} raw
* @returns {string}
*/
export function sanitizeToken(raw) {
if (raw == null || raw === "") return "";
let s = String(raw)
.toLowerCase()
.replace(/[^a-z0-9._-]+/g, "");
if (s.length > MAX_TOKEN_LEN) s = s.slice(0, MAX_TOKEN_LEN);
return s;
}

/**
* @param {NodeJS.ProcessEnv} [env]
* @returns {string}
*/
function resolveCliVersion(env = process.env) {
if (env.JFROG_TEST_CLI_VERSION) return String(env.JFROG_TEST_CLI_VERSION);
if (cachedCliVersion) return cachedCliVersion;
try {
// Keep process PATH/HOME even when callers pass a sparse env object
// (unit tests often pass only UA-related keys).
const res = spawnSync("jf", ["--version"], {
encoding: "utf8",
timeout: 3000,
env: { ...process.env, ...env },
});
const out = `${res.stdout ?? ""}\n${res.stderr ?? ""}`;
const m = out.match(/(\d+\.\d+\.\d+(?:-[^\s]+)?)/);
cachedCliVersion = m?.[1] || "unknown";
} catch {
cachedCliVersion = "unknown";
}
return cachedCliVersion;
}

/**
* Axes present on the hook process itself (not invented, not model).
* @param {NodeJS.ProcessEnv} [env]
* @param {{ tool?: string }} [opts]
* @returns {{ tool?: string, client?: string }}
*/
export function resolveHookUaAxes(env = process.env, opts = {}) {
const tool =
sanitizeToken(opts.tool) ||
sanitizeToken(env.JFROG_APR_UA_TOOL) ||
undefined;
const client = sanitizeToken(env.TERM_PROGRAM) || undefined;
return { tool, client };
}

/**
* @param {NodeJS.ProcessEnv} [env]
* @param {{ tool?: string }} [opts]
* @returns {string}
*/
export function buildHookJfUserAgent(env = process.env, opts = {}) {
const axes = resolveHookUaAxes(env, opts);
const parts = ["trigger=hook"];
if (axes.tool) parts.push(`tool=${axes.tool}`);
if (axes.client) parts.push(`client=${axes.client}`);
return `jfrog-skills/${PKG_VERSION} (${parts.join("; ")}) jfrog-cli-go/${resolveCliVersion(env)}`;
}

/**
* Spawn env: full inherit + hook UA override.
* @param {NodeJS.ProcessEnv} [env]
* @param {{ tool?: string }} [opts]
* @returns {NodeJS.ProcessEnv}
*/
export function envWithHookUserAgent(env = process.env, opts = {}) {
return { ...env, JFROG_CLI_USER_AGENT: buildHookJfUserAgent(env, opts) };
}

/** @internal test helper */
export function _resetCliVersionCacheForTests() {
cachedCliVersion = undefined;
}
Loading
Loading