diff --git a/.github/scripts/sync-modules-integrity.json b/.github/scripts/sync-modules-integrity.json index 814ea99..1aff62d 100644 --- a/.github/scripts/sync-modules-integrity.json +++ b/.github/scripts/sync-modules-integrity.json @@ -1,29 +1,30 @@ { "schemaVersion": 1, - "pin": "jfrog-agent-hooks/v0.12.1", + "pin": "jfrog-agent-hooks/v0.12.2", "files": { "assets/agents-conf-fingerprints.json": "11bd418cdf38c8494e04239ae5237a1242bb1532468428c5a561f674f21e2657", "assets/agents-default-conf.json": "774e1bfd5bb1e2f38de06c9ce53b92e12ddd36b82c159e4a3113f4c038bfc3bb", "claude-session-start.mjs": "2ca1edc6b939cdff6c5faa6ac4b69636e6e92bc7b079316e1fdee7c53c6e837b", "codex-session-start.mjs": "05a43de56fec34b9f22bb9e6e328177895767d72a22caea6fe55ed48b09808a9", "copilot-session-start.mjs": "8811e0829c90ff0987bed158f5ef571195ee5eb54dfc8021b4396fe76ad8a499", - "core/agent-guard-check.mjs": "2d3e78084b1c3657596d49189809ab3af3427aa349dc14e2448ba74058c67f4c", + "core/agent-guard-check.mjs": "a3ca0b305afea22e115df50b7f3b5dbcde639743c4f3ab42c932335e27090bec", "core/agents-config.mjs": "743caf9ea1c6d6db35f8854c533b1e17d6449547ed0914ce71385f9812811b7e", "core/entry.mjs": "0b0b218448151d7a06743c37e684d0933ab76225be5761f648627f4db02c1f17", "core/io.mjs": "d545823b08022579907c8b298331a1198de7c61d7838a0b3338f286f13473a7b", - "core/jf-identity.mjs": "063f21a3357361f43ff3ddb45226d3c01216e8c3b11ab0395050c6e507d8d9fd", - "core/jf-user-agent.mjs": "f59f6b1a429da743c06460849385287571b54716b790822cf22c7416371c5fdd", + "core/jf-identity.mjs": "f84d6045ff2562287c7f02bfc001e93200b165219418cf56acb1d24b1b438143", + "core/jf-process.mjs": "ee447b9c7ea2de29bdd3a9f166c73716e0773e91762731b01213149fb5871db5", + "core/jf-user-agent.mjs": "2a7a9b5c7b501474175dbc5aa190b7773a9bee6ce15671f5b24ecd0e35392845", "core/logger.mjs": "1ebdffcdf4af14b19e3ee8e82cfeb377fb9961a09d4e9d6ecdc922d07b0848c2", - "core/rewrite-mcp-json.mjs": "a88733edd33bd146ed960c157e085f0b89a20882719c1d6daee5a56400322d86", + "core/rewrite-mcp-json.mjs": "c8d175faaa219b649a00a43e33348b310c9960bc39eef050dd2324b02aa1fc55", "core/run-capability.mjs": "9fac890b7fd4866f9d3322469b2a7301e28cebfa3faebd77857f9f79d2d1c532", "core/scaffold-fingerprint.mjs": "df7a710ba215e74808fbda5322e353171a613e74a3ac90fbca466d70c91dca67", "cursor-session-start.mjs": "37dd25ffee18e9f357e3cbb8453552766fd89295e85aa09bf93bc208df74aa20", "package-resolution/onboarding/package-resolution-onboarding-procedure.md": "53b71301a1fcacd187a30239b780dd4fff451a6d03612d6dbb05aa35b7735c8c", "package-resolution/onboarding/session-start-nudge.md": "1453717daa9844294e96944c32f3d00dd8736d404ee5c74e731a3d634d6b1948", - "package-resolution/scripts/apr-heartbeat.mjs": "3b87b52c06afc5fe311df9c8ecfd29c97198f4b6e1881920ba28231358dc466b", + "package-resolution/scripts/apr-heartbeat.mjs": "67e88a996a9311414aa7b35d6b7e99c6184f4ef934ac3f8f219119378b21575e", "package-resolution/scripts/configure.mjs": "06377ac044c295f00f3510ed78f2637e4e35477e87932b04f41e4311b2765ac9", "package-resolution/scripts/eager-setup-receipt.mjs": "69213084bc1976ec63b346ca26e8a63eb713b0da7ad6ab4fc018934e70fef091", - "package-resolution/scripts/eager-setup.mjs": "aedfbb7790d55d77f4189cbf42aef0ed0f8ed3f23fb4047e885a7741c550076f", + "package-resolution/scripts/eager-setup.mjs": "c0e2b6aa551b8031c7e9b570ddbc00fd60667c0c275c17c5f79b03ff3b1a664b", "package-resolution/scripts/feature-flag.mjs": "d5aa11e51d8127ff3b07c75b1e7294c5a10846c40b63da8140116f250ead1788", "package-resolution/scripts/index.mjs": "f32cef4be2d43380dc9c2c28dcc6111ab8391be868e1b393d6d83b88c7774f36", "package-resolution/scripts/onboarding-decline-cache.mjs": "776841ecf76f9c0367b97e774b6176a8431b26631d3c44c95b9e4b91becbc849", @@ -36,7 +37,7 @@ "package-resolution/scripts/setup-conflict.mjs": "fdc589561813a9c5e708f50a20a87bacdad3f490158c973b12b217cb984e2845", "package-resolution/scripts/verify-repo.mjs": "73f6180ea04be9aa11ccb7ce399c27a69ba7abbcf1665ce7d5b8dda487db0791", "package-resolution/scripts/workspace-config.mjs": "f8f8eaaf0fb8a0c3691938e99afebbc87d8779e508db0ef821fa23f0a55b786a", - "package-resolution/templates/package-resolution-unconfigured.md": "d276aa796b3c38f0566bc0b5c3a1553bbbf322d5eaa0d60841e21a3e280a91e0", + "package-resolution/templates/package-resolution-unconfigured.md": "d4b68bd5f49588dcc25cdc4ad35f589a930779e1fc77f6b99bb225ebdaf7d35d", "package-resolution/templates/package-resolution.md": "a5499c97767f95d2f541c2ef6dd3053e9d08332098e3eed13e4ff445d462eefd" } } diff --git a/.github/scripts/sync-modules-vendor.json b/.github/scripts/sync-modules-vendor.json index 81e82f4..11a1050 100644 --- a/.github/scripts/sync-modules-vendor.json +++ b/.github/scripts/sync-modules-vendor.json @@ -1,6 +1,6 @@ { "repo": "JFROG/jfrog-agent-hooks", - "pin": "jfrog-agent-hooks/v0.12.1", + "pin": "jfrog-agent-hooks/v0.12.2", "paths": [ "modules" ], diff --git a/docs/package-resolution-admin-guide.md b/docs/package-resolution-admin-guide.md index 3e317bf..0aaef68 100644 --- a/docs/package-resolution-admin-guide.md +++ b/docs/package-resolution-admin-guide.md @@ -1,10 +1,10 @@ # Agent Package Resolution: Admin Guide (Preview) -Route AI-assisted package installs through your JFrog Artifactory repositories when developers use the **JFrog plugin** for Cursor, Claude Code, or VS Code. +Route AI-assisted package installs through your JFrog Artifactory repositories when developers use the **JFrog plugin** for Cursor, Claude Code, VS Code, or Codex. Agent Package Resolution runs at the start of each agent session. When enabled, it injects routing policy and resolved Artifactory URLs into the session so the agent prefers your repositories over public registries. Durable enforcement still comes from **package manager configuration** (`jf setup`) and **JFrog Curation** on the server. -This guide is for **platform administrators** and **developers** onboarding the JFrog coding-agent plugins. For installing the plugin itself, see the JFrog documentation for your IDE ([Cursor](https://docs.jfrog.com/ai-ml/docs/cursor), [Claude Code](https://docs.jfrog.com/ai-ml/docs/claude-code/), [VS Code](https://docs.jfrog.com/ai-ml/docs/vs-code)). +This guide is for **platform administrators** and **developers** onboarding the JFrog coding-agent plugins. For installing the plugin itself, see the JFrog documentation for your IDE ([Cursor](https://docs.jfrog.com/ai-ml/docs/cursor), [Claude Code](https://docs.jfrog.com/ai-ml/docs/claude-code/), [VS Code](https://docs.jfrog.com/ai-ml/docs/vs-code)) or the [Codex plugin README](https://github.com/jfrog/codex-plugin#installation) for Codex. > **Related:** [Use the MCP Registry with Agent Guard](https://docs.jfrog.com/ai-ml/docs/configure-coding-agents) covers MCP governance. Agent Package Resolution is a separate capability in the same JFrog plugin family and uses the same local configuration file for admin settings. @@ -12,15 +12,15 @@ This guide is for **platform administrators** and **developers** onboarding the ## Setup summary -| Step | Action | -| ---- | ---------------------------------------------------------------------------------------------------------- | -| 1 | Install the JFrog plugin in your coding assistant | -| 2 | Install and configure the JFrog CLI (`jf config add`) — required for **routing** mode | -| 3 | Confirm `~/.jfrog/agents-conf.json` (shipped template enables APR with empty bindings; or deploy your own) | -| 4 | Start a **new agent session** — policy and URLs are injected once per session | - -The shipped template turns Agent Package Resolution **on** (`enabled: true`) with empty `defaultGlobalRepos`. Nothing is routed until Consent Enable or an administrator adds bindings. Set `enabled: false` or `JF_AGENT_PACKAGE_RESOLUTION_DISABLE=1` to keep it off. +| Step | Action | +| ---- | ------------------------------------------------------------------------------------------------------------------------- | +| 1 | Install the JFrog plugin in your coding assistant | +| 2 | Install and configure the JFrog CLI (`jf config add`) — required for **routing** mode | +| 3 | If an administrator pre-deployed `.jfrog/agents-conf.json`, confirm it; otherwise continue—the first session creates it | +| 4 | **Codex:** after install, restart, then `/hooks` and trust the SessionStart command (plugin install does not skip this — see the [Codex plugin README](https://github.com/jfrog/codex-plugin#installation)) | +| 5 | Start a **new agent session** — missing config is created, then policy and URLs are injected once per session | +The shipped template turns Agent Package Resolution **on** (`enabled: true`) with empty `defaultGlobalRepos`. Nothing is routed until Consent Enable or an administrator adds bindings. `JF_AGENT_PACKAGE_RESOLUTION_DISABLE=1` always keeps it off; `enabled: false` alone only keeps it off if you deploy your own `agents-conf.json` — on the plugin's auto-created default file it is not durable (see below). **At a glance:** @@ -28,7 +28,6 @@ The shipped template turns Agent Package Resolution **on** (`enabled: true`) wit - **To route installs:** add repository keys under `defaultGlobalRepos` (org config or Consent Enable). - **To turn it off org-wide:** deploy your own `agents-conf.json` with `"enabled": false` (see [Turning Agent Package Resolution off](#turning-agent-package-resolution-off-admins)). Setting `"enabled": false` on the plugin's **default file without also deploying your own** is not durable — the plugin re-enables it on the next session. - --- ## Prerequisites @@ -71,7 +70,7 @@ When a developer opens their first agent session after installing the plugin: 1. If `~/.jfrog/agents-conf.json` **does not exist**, the plugin copies the **shipped default template** into that path. 2. The template ships with Agent Package Resolution **enabled** (`packageResolution.enabled: true`), empty `defaultGlobalRepos`, and `onboardingPrompt: "auto"`. Never-configured legacy scaffolds (`enabled: false` that still match a shipped fingerprint) are migrated to `enabled: true` on SessionStart (hand-edited / MDM configs and `onboardingPrompt: "off"` are left alone). -3. When the offer gate is open (`onboardingPrompt: "auto"` or an untouched scaffold fingerprint) **and** at least one APR package type is missing from `defaultGlobalRepos` and not durably declined, SessionStart injects a short **onboarding nudge** directly into the agent's context (`additional_context` for Cursor, `additionalContext` for Claude Code and VS Code Copilot) — **all three harnesses get it**; nothing is written to disk for the nudge itself. SessionStart injects it on every eligible session; the injected text itself instructs the agent to hold off raising it until a real package-manager install is happening, not on every unrelated chat. It names only the still-offerable types, so it only ever shrinks as types get bound or declined; it is injected fresh on every eligible SessionStart. +3. When the offer gate is open (`onboardingPrompt: "auto"` or an untouched scaffold fingerprint) **and** at least one APR package type is missing from `defaultGlobalRepos` and not durably declined, SessionStart injects a short **onboarding nudge** directly into the agent's context (`additional_context` for Cursor, `additionalContext` for Claude Code, VS Code Copilot, and Codex) — **every supported harness gets it**; nothing is written to disk for the nudge itself. SessionStart injects it on every eligible session; the injected text itself instructs the agent to hold off raising it until a real package-manager install is happening, not on every unrelated chat. It names only the still-offerable types, so it only ever shrinks as types get bound or declined; it is injected fresh on every eligible SessionStart. 4. The offer is **per package type**, not one-time-and-done. **No** for one type runs `dismiss --type `, which durably declines just that type in `~/.jfrog/skills-cache/apr-onboarding-v1.json` — other unbound, undeclined types stay offerable. **Yes** runs Consent Enable / `enable` for binding, which stops offering just the types that got bound. A bare `dismiss` (no `--type`) is the global escape hatch: it sets `onboardingPrompt: "off"` and durably silences every type until that config value changes. 5. Routing policy is injected when `packageResolution.enabled` is `true` **and** `jf` identity is usable (`routing`); otherwise `pending` when enabled but `jf` is missing — including when `defaultGlobalRepos` is still empty. @@ -135,9 +134,6 @@ the `jfrog` skill + `verify-repo`, or manually — see With default `verifyRepos: true`, Consent Enable / `configure.mjs enable` accepts keys Artifactory confirms as virtual repositories of the requested package type. ---- - - ### Turning Agent Package Resolution off (admins) > **Why `enabled: false` alone may not stick.** Because the feature now ships **on**, the plugin re-enables its **own default file** if it finds it still turned off. "Default file" means the `agents-conf.json` the plugin auto-created and that no one has changed except (at most) the `enabled` flag. As soon as you deploy your **own** config, or add any other setting (like `onboardingPrompt`), the plugin treats it as yours and never re-enables it. @@ -163,6 +159,8 @@ keys Artifactory confirms as virtual repositories of the requested package type. Setting **only** `"onboardingPrompt": "off"` stops the Consent Enable prompts but does **not** turn the feature off — leave `enabled: false` in place for that. See also [emergency disable](#environment-variable-emergency-disable) for the environment variable. +--- + ## Admin control: deploy `agents-conf.json` across your organization Use standard endpoint management to place a consistent `agents-conf.json` on every developer machine. @@ -184,8 +182,6 @@ Use standard endpoint management to place a consistent `agents-conf.json` on eve | Auto-configure package managers at first session | Add types to `autoSetup` ([Zero-touch setup](#zero-touch-setup-autosetup)) | | Force all cached state to refresh | Set `"cacheTtlDays": 0` (this also re-runs eligible zero-touch `jf setup` each session), or edit `agents-conf.json` | | Support troubleshooting | Set `"logLevel": "debug"` temporarily; logs go to `~/.jfrog/logs/agent-hooks.log` | -| Keep APR **off** (durable) | Deploy your own file with `"enabled": false`, **or** set `"enabled": false` **and** `"onboardingPrompt": "off"` on the plugin's default file — see [Turning off](#turning-agent-package-resolution-off-admins) | -| Silence Consent Enable offers only | Set `"onboardingPrompt": "off"` (does not disable APR while `enabled` is `true`) | --- @@ -513,4 +509,5 @@ Workspace values win over `agents-conf.json` for matching types during that sess - [Install JFrog Plugin for Cursor](https://docs.jfrog.com/ai-ml/docs/install-jfrog-plugin-for-cursor) - [Install JFrog Plugin for Claude Code](https://docs.jfrog.com/ai-ml/docs/install-jfrog-plugin-for-claude-code) - [Install JFrog Plugin for VS Code](https://docs.jfrog.com/ai-ml/docs/install-jfrog-plugin-for-vs-code) +- [Install JFrog Plugin for Codex](https://github.com/jfrog/codex-plugin#installation) - [Use the MCP Registry with Agent Guard](https://docs.jfrog.com/ai-ml/docs/configure-coding-agents) diff --git a/docs/package-resolution-user-guide.md b/docs/package-resolution-user-guide.md index 04ea939..ded76e1 100644 --- a/docs/package-resolution-user-guide.md +++ b/docs/package-resolution-user-guide.md @@ -1,6 +1,6 @@ # Agent Package Resolution: User Guide (Preview) -**Audience:** Users of Cursor, Claude Code, or VS Code Copilot with the JFrog plugin, whether or not you're a professional developer. +**Audience:** Users of Cursor, Claude Code, VS Code Copilot, or Codex with the JFrog plugin, whether or not you're a professional developer. You (or your org) installed the JFrog plugin. This is what happens next, step by step, when you ask your agent to do something that needs a package: install a dependency to build an app, pull a Docker image, and so on. @@ -12,6 +12,8 @@ The JFrog plugin is installed. That's it; nothing else is required of you up fro On **VS Code Copilot**, also enable both settings (`chat.plugins.enabled` and `chat.useHooks`) so the plugin and SessionStart hook load. +On **Codex**, installing the plugin does not run the SessionStart hook until you trust it. After `codex plugin add jfrog@codex-plugin`, restart Codex, open `/hooks`, and trust the JFrog Package Resolution command. ChatGPT **web** does not run hook scripts. + ## What will happen, by case You're mostly passive in all of this: the agent drives, and it tells you when it needs something from you. diff --git a/marketplace.json b/marketplace.json index 32ed571..d036707 100644 --- a/marketplace.json +++ b/marketplace.json @@ -9,7 +9,7 @@ { "name": "jfrog", "description": "JFrog Platform integration with MCP, security skills, and supply-chain best practices", - "version": "1.1.1", + "version": "1.1.2", "license": "Apache-2.0", "source": "plugin", "categories": [ diff --git a/plugin/.claude-plugin/plugin.json b/plugin/.claude-plugin/plugin.json index b98da3a..f9c3213 100644 --- a/plugin/.claude-plugin/plugin.json +++ b/plugin/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "jfrog", "description": "JFrog Platform integration with MCP, security skills, and supply-chain best practices", - "version": "1.1.1", + "version": "1.1.2", "license": "Apache-2.0", "author": { "name": "JFrog", diff --git a/plugin/modules/core/agent-guard-check.mjs b/plugin/modules/core/agent-guard-check.mjs index e12a511..3284ed4 100644 --- a/plugin/modules/core/agent-guard-check.mjs +++ b/plugin/modules/core/agent-guard-check.mjs @@ -17,6 +17,7 @@ import { execFileSync } from "node:child_process"; import process from "node:process"; import { isMainEntry } from "./entry.mjs"; +import { jfInvocation } from "./jf-process.mjs"; import { skillsProductUserAgent } from "./jf-user-agent.mjs"; export const SETTINGS_PATH = @@ -88,6 +89,7 @@ export function resolveAgentGuardCredentials(opts = {}) { if (explicitServerId) { const fromCli = resolveFromCliConfig({ serverId: explicitServerId, + env, execFileSyncFn: execFn, debug, }); @@ -114,6 +116,7 @@ export function resolveAgentGuardCredentials(opts = {}) { if (explicitServerId) return null; return resolveFromCliConfig({ serverId: undefined, + env, execFileSyncFn: execFn, debug, }); @@ -122,22 +125,28 @@ export function resolveAgentGuardCredentials(opts = {}) { /** * @param {{ * serverId?: string, + * env?: NodeJS.ProcessEnv, * execFileSyncFn?: typeof execFileSync, * debug?: (message: string) => void, * }} opts */ function resolveFromCliConfig(opts) { const debug = opts.debug ?? (() => {}); + const env = opts.env ?? process.env; const execFn = opts.execFileSyncFn ?? execFileSync; const exportArgs = opts.serverId ? ["config", "export", opts.serverId] : ["config", "export"]; + const invocation = jfInvocation(exportArgs, { env }); let exported; try { - exported = execFn("jf", exportArgs, { + exported = execFn(invocation.command, invocation.args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: 2000, + windowsHide: true, + shell: false, + env, }).trim(); } catch (error) { debug( diff --git a/plugin/modules/core/jf-identity.mjs b/plugin/modules/core/jf-identity.mjs index 6e2413b..57a7cb8 100644 --- a/plugin/modules/core/jf-identity.mjs +++ b/plugin/modules/core/jf-identity.mjs @@ -20,10 +20,10 @@ // async. import { createHash } from "node:crypto"; -import { spawnSync } from "node:child_process"; import process from "node:process"; import { createLogger } from "./logger.mjs"; +import { spawnJfSync } from "./jf-process.mjs"; import { skillsProductUserAgent } from "./jf-user-agent.mjs"; const log = createLogger("jf-identity"); @@ -82,7 +82,7 @@ function jfConfigIdentity(serverId) { let result; try { - result = spawnSync("jf", args, { + result = spawnJfSync(args, { encoding: "utf8", timeout: 2000, stdio: ["ignore", "pipe", "pipe"], diff --git a/plugin/modules/core/jf-process.mjs b/plugin/modules/core/jf-process.mjs new file mode 100644 index 0000000..8aca9e7 --- /dev/null +++ b/plugin/modules/core/jf-process.mjs @@ -0,0 +1,50 @@ +// Shell-free JFrog CLI process boundary shared by coding-agent hooks. +// +// Production invokes the native CLI directly (`jf.exe` on Windows, `jf` +// elsewhere). Tests may substitute the Node fake only when the explicit test +// harness gate is active; normal production environments ignore the driver. + +import { spawnSync } from "node:child_process"; +import process from "node:process"; + +/** + * Resolve the shell-free executable and argv for a JFrog CLI invocation. + * @param {string[]} args + * @param {{ env?: NodeJS.ProcessEnv, platform?: NodeJS.Platform }} [options] + * @returns {{ command: string, args: string[] }} + */ +export function jfInvocation( + args, + { env = process.env, platform = process.platform } = {}, +) { + const testDriver = + env.JFROG_TEST_HARNESS === "1" ? env.JFROG_TEST_JF_DRIVER : undefined; + return testDriver + ? { command: process.execPath, args: [testDriver, ...args] } + : { command: platform === "win32" ? "jf.exe" : "jf", args }; +} + +/** + * Synchronously invoke the JFrog CLI without a shell. + * + * Test-only injection requires both: + * JFROG_TEST_HARNESS=1 + * JFROG_TEST_JF_DRIVER=/absolute/path/to/fake-jf.mjs + * + * @param {string[]} args + * @param {import("node:child_process").SpawnSyncOptionsWithStringEncoding} [options] + * @returns {import("node:child_process").SpawnSyncReturns} + */ +export function spawnJfSync(args, options = {}) { + const env = options.env ?? process.env; + const spawnSyncFn = options.spawnSyncFn ?? spawnSync; + const rest = { ...options }; + delete rest.spawnSyncFn; + const invocation = jfInvocation(args, { env }); + return spawnSyncFn(invocation.command, invocation.args, { + windowsHide: true, + shell: false, + ...rest, + env, + }); +} diff --git a/plugin/modules/core/jf-user-agent.mjs b/plugin/modules/core/jf-user-agent.mjs index 31367c9..d28c17c 100644 --- a/plugin/modules/core/jf-user-agent.mjs +++ b/plugin/modules/core/jf-user-agent.mjs @@ -15,12 +15,12 @@ // Spawn env is inherited so CLI DetectExecutionContext can append // ai-agent/ / ai-client/ / ai-model/ when those signals exist at jf start. -import { spawnSync } from "node:child_process"; +import { spawnJfSync } from "./jf-process.mjs"; // Plugin sync stamps this literal with the release semver (jfrog-sync-modules.py // stamp). Only `modules/` is vendored, so nothing outside this tree is readable // at runtime. Unstamped trees (this repo, local dev) report 0.0.0. -const PKG_VERSION = "0.12.1"; +const PKG_VERSION = "0.12.2"; /** Product UA for plugin `fetch()` (no trigger, no jfrog-cli-go). */ export function skillsProductUserAgent() { @@ -55,7 +55,7 @@ function resolveCliVersion(env = process.env) { 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"], { + const res = spawnJfSync(["--version"], { encoding: "utf8", timeout: 3000, env: { ...process.env, ...env }, diff --git a/plugin/modules/core/rewrite-mcp-json.mjs b/plugin/modules/core/rewrite-mcp-json.mjs index 8adcc47..3449b22 100644 --- a/plugin/modules/core/rewrite-mcp-json.mjs +++ b/plugin/modules/core/rewrite-mcp-json.mjs @@ -27,6 +27,7 @@ import path from "node:path"; import process from "node:process"; import { EXIT_ENABLED, runAgentGuardCheck } from "./agent-guard-check.mjs"; +import { jfInvocation } from "./jf-process.mjs"; import { createLogger } from "./logger.mjs"; const log = createLogger("rewrite-mcp-json"); @@ -285,13 +286,16 @@ export function pickDefaultJfCliServer(servers) { export function listJfCliServers(opts = {}) { const env = opts.env ?? process.env; const spawnSyncFn = opts.spawnSyncFn ?? spawnSync; + const invocation = jfInvocation(["config", "show", "--format=json"], { env }); let res; try { - res = spawnSyncFn("jf", ["config", "show", "--format=json"], { + res = spawnSyncFn(invocation.command, invocation.args, { encoding: "utf8", timeout: 5_000, env, stdio: ["ignore", "pipe", "pipe"], + windowsHide: true, + shell: false, }); } catch { return []; diff --git a/plugin/modules/package-resolution/scripts/apr-heartbeat.mjs b/plugin/modules/package-resolution/scripts/apr-heartbeat.mjs index 096f58f..f88926a 100644 --- a/plugin/modules/package-resolution/scripts/apr-heartbeat.mjs +++ b/plugin/modules/package-resolution/scripts/apr-heartbeat.mjs @@ -22,6 +22,7 @@ import path from "node:path"; import { createLogger } from "../../core/logger.mjs"; import { getPlatformIdentity } from "../../core/jf-identity.mjs"; import { envWithHookUserAgent } from "../../core/jf-user-agent.mjs"; +import { jfInvocation } from "../../core/jf-process.mjs"; const log = createLogger("apr-heartbeat"); @@ -183,12 +184,16 @@ export function tryAcquireHeartbeatLock(serverId, opts = {}) { * Detached `jf rt ping --server-id ` with hook User-Agent. * Waits for spawn success vs async error before unref. * @param {string} serverId - * @param {{ spawn?: typeof spawn, env?: NodeJS.ProcessEnv }} [opts] + * @param {{ spawn?: typeof spawn, env?: NodeJS.ProcessEnv, platform?: NodeJS.Platform }} [opts] * @returns {Promise} true if the process started */ export function spawnHeartbeatPing(serverId, opts = {}) { const spawnImpl = opts.spawn ?? spawn; const env = opts.env ?? process.env; + const invocation = jfInvocation(["rt", "ping", "--server-id", serverId], { + env, + platform: opts.platform, + }); return new Promise((resolve) => { let settled = false; const finish = (ok) => { @@ -198,8 +203,9 @@ export function spawnHeartbeatPing(serverId, opts = {}) { }; let child; try { - child = spawnImpl("jf", ["rt", "ping", "--server-id", serverId], { + child = spawnImpl(invocation.command, invocation.args, { detached: true, + windowsHide: true, stdio: "ignore", env: envWithHookUserAgent(env), }); diff --git a/plugin/modules/package-resolution/scripts/eager-setup.mjs b/plugin/modules/package-resolution/scripts/eager-setup.mjs index 40874c1..8d2f501 100644 --- a/plugin/modules/package-resolution/scripts/eager-setup.mjs +++ b/plugin/modules/package-resolution/scripts/eager-setup.mjs @@ -59,6 +59,8 @@ import { packageManagerBinaryOnPath, } from "./package-manager-family.mjs"; import { detectSetupConflict } from "./setup-conflict.mjs"; +import { isMainEntry } from "../../core/entry.mjs"; +import { spawnJfSync } from "../../core/jf-process.mjs"; import { envWithHookUserAgent } from "../../core/jf-user-agent.mjs"; const log = createLogger("eager-setup"); @@ -247,6 +249,7 @@ function spawnWorker(payloadB64, jobCount = 1) { if (process.env.JFROG_EAGER_SETUP_SYNC === "1") { spawnSync(process.execPath, [workerPath(), "--run", payloadB64], { stdio: "ignore", + windowsHide: true, env: process.env, timeout: syncWorkerTimeoutMs(jobCount), }); @@ -255,6 +258,7 @@ function spawnWorker(payloadB64, jobCount = 1) { try { const child = spawn(process.execPath, [workerPath(), "--run", payloadB64], { detached: true, + windowsHide: true, stdio: "ignore", env: process.env, }); @@ -643,7 +647,7 @@ export function releaseLock() { function supportedPackageManagers() { try { // --help is local (no Artifactory traffic); no UA needed for telemetry. - const res = spawnSync("jf", ["setup", "--help"], { + const res = spawnJfSync(["setup", "--help"], { encoding: "utf8", timeout: 5000, env: process.env, @@ -701,7 +705,7 @@ function runJfSetup(packageManager, serverId, repoKey) { "--repo", repoKey, ]; - const res = spawnSync("jf", args, { + const res = spawnJfSync(args, { encoding: "utf8", timeout: PER_PACKAGE_MANAGER_TIMEOUT_MS, env: envWithHookUserAgent(process.env), @@ -838,8 +842,7 @@ export async function runWorker(payload) { // CLI entry (worker mode) // --------------------------------------------------------------------------- -const isMain = import.meta.url === `file://${process.argv[1]}`; -if (isMain && process.argv[2] === "--run") { +if (isMainEntry(import.meta.url) && process.argv[2] === "--run") { const b64 = process.argv[3]; try { const payload = JSON.parse(Buffer.from(b64, "base64").toString("utf8")); diff --git a/plugin/modules/package-resolution/templates/package-resolution-unconfigured.md b/plugin/modules/package-resolution/templates/package-resolution-unconfigured.md index 83ddf57..8806ba3 100644 --- a/plugin/modules/package-resolution/templates/package-resolution-unconfigured.md +++ b/plugin/modules/package-resolution/templates/package-resolution-unconfigured.md @@ -27,9 +27,9 @@ Do not delegate governed installs to `agent -p` or another child while routing i ## After setup — load the routing policy Once `jf` is configured and `jfrog-setup-package-managers` has bound package -managers, load the current routing policy: +managers, load the current routing policy. Run this command: -```bash +```text {{REFRESH_COMMAND}} ```