Skip to content

fix(skills): plugin launcher and media helpers run the pinned CLI, not a project copy - #7552

Merged
jrusso1020 merged 3 commits into
mainfrom
fix/skills-pinned-cli-resolution
Nov 11, 2026
Merged

jrusso1020 merged 3 commits into
mainfrom
fix/skills-pinned-cli-resolution

Conversation

@jrusso1020

Copy link
Copy Markdown
Collaborator

The plugin launcher and the media helpers now always run the CLI the plugin pins, or the one their caller hands over. Before, npm could pick it up from the user's project folder.

What

  • skills/hyperframes/scripts/plugin-cli.mjs starts npx --yes hyperframes@<plugin version> from the home folder rather than the project. It passes the project folder in HYPERFRAMES_PLUGIN_CWD.
  • packages/cli/bin/hyperframes.mjs changes into HYPERFRAMES_PLUGIN_CWD before anything else runs, and removes the variable so nested CLI runs keep the folder their caller gives them.
  • In --script mode the launcher sets HYPERFRAMES_CLI_ENTRY to itself.
  • The helpers that call the CLI are the Kokoro voice and transcribeWav in tts.mjs, transcribe.mjs through npx-sync.mjs, and the resolve shim. Each runs the handed-over CLI. When no CLI is handed over, each stops with a message that says how to supply one, and no longer resolves npx hyperframes. The CLI's own media-use command already hands over its entry (fix(cli): keep project folders from choosing the programs and config the CLI runs #7511), so nothing changes when the CLI runs them.

Why

npm reads the package, its bin links and .npmrc from the folder npx starts in. The CLI that runs should depend only on the plugin release, or on what the user chose explicitly.

Related work

Refs #7511 (CLI hands its entry to the media-use engine) and #7524 (helper programs are taken only from install locations).

How

  • The launcher uses the home folder the same way hyperframes skills already does for npx skills@<pin> (fix(cli): keep project folders from choosing the programs and config the CLI runs #7511), so npm only reads config the user owns.
  • The launcher passes the project folder in an environment variable because CLI commands default to the working folder. The bin applies it before the warm check client and dist/cli.js load. The plugin release and the CLI release share one version, so the launcher always runs a CLI that has this bin.
  • Helpers reuse the existing HYPERFRAMES_CLI_ENTRY hand-over. The launcher's --script mode passes its own path, so a helper runs node plugin-cli.mjs tts ... and gets the same pinned, isolated resolution.
  • Behaviour change: a standalone skill install that runs audio.mjs or transcribe.mjs directly now has to set HYPERFRAMES_CLI_ENTRY (to its installed bin/hyperframes.mjs) for the Kokoro, transcription and whisper fallback steps. The docs and the error message both say so.
Claude Opus 5.5

What I measured

  • I ran a real npx against a temp project. The project had node_modules/hyperframes@0.9.33 with a bin that writes a marker, node_modules/.bin/hyperframes, a package.json bin named hyperframes, and an .npmrc with registry=http://127.0.0.1:9/ and a marker-writing script-shell. Each run was plugin-cli.mjs --version:
    • origin/main launcher, all files present: the .npmrc script-shell ran (marker written).
    • origin/main launcher, node_modules only: it ran the project's copy and printed FAKE.
    • origin/main launcher, bogus registry only: hyperframes: not found.
    • This branch, every one of those setups: printed 0.9.33 from the registry CLI, and no marker was written.
  • I ran plugin-cli.mjs --script <plugin>/skills/media-use/scripts/resolve.mjs --doctor in the same project. It ran the genuine CLI's doctor and wrote no marker. Running the shim with no launcher printed the hand-over message and exited 1.
  • HYPERFRAMES_PLUGIN_CWD=/x npx -p hyperframes@0.9.33 node -e ... run from home confirms that npm exec passes the variable through. The child's cwd was home, and the .bin from the npx cache came first on PATH.

Tests

  • plugin-cli.test.mjs: npx starts outside a project that has .npmrc, a .bin entry and a package.json bin, and receives the project folder. --script hands over the launcher.
  • binEntry.test.ts runs the real bin over stub dist files. It checks that the bin moves into the named project and does not pass the variable on, and that it stays put when no folder is named.
  • tts.test.mjs, tts.spawn.test.mjs, transcribe.test.mjs, resolve-plugin.test.mjs, tts-local-provider.test.mjs, npx-sync.test.mjs: these use the handed-over CLI. A missing or relative hand-over fails with the message, and a stub npx on PATH is never run.
  • tts.cleanup.test.mjs and gemini-pipeline.test.mjs now hand over a stub CLI instead of a stub npx.
  • Mutation probes, each reverted and confirmed failing: launcher npx cwd (3 fail), --script hand-over (1), project folder hand-over (2), bin chdir (1), bin variable removal (1), tts.mjs npx fallback (4), npx-sync.mjs npx fallback (2), and the origin/main resolve shim (2).
  • All 655 tests in the CI skills set (find skills packages/cli/src/media-use -name '*.test.mjs' plus the parity tests) pass. So do the CLI vitest files binEntry, media-use and installerDetection, plus lint:skills, gen-skills-manifest --check, oxfmt and oxlint.

What I did NOT exercise

  • A real npx run of a CLI built from this branch. The published 0.9.33 does not have the bin change, so I covered the folder move with the bin test and the env pass-through check above.
  • Windows. The launcher's Windows path gets the same cwd and env, and its unit test covers that, but I did not run it on Windows.
  • When the CLI runs from TypeScript source (.ts entry), media-use does not hand over an entry (unchanged from fix(cli): keep project folders from choosing the programs and config the CLI runs #7511). Kokoro then falls through with the hand-over message instead of running the published CLI.
  • resolveSpawnCommand's Windows npx-cli rerouting is now reachable only for npx packages other than hyperframes, and nothing calls it that way today. I kept it and pointed its tests at a neutral package name. Removing it can be a follow-up.

Test plan

  • Unit tests added/updated
  • Manual testing performed (real npx run against a project with a decoy package, .bin entry, package.json bin and .npmrc)
  • Documentation updated (plugin-installation.md, media-use/references/audio.md, operations.md)
  • Comments follow CONTRIBUTING.md "Comments"

— Rames Jusso

🤖 Generated with Claude Code

…t a project copy

The plugin launcher now starts npx from the home folder, so the project's
node_modules, package.json bin and .npmrc cannot choose which CLI npm runs or
how it starts it. It names the project in HYPERFRAMES_PLUGIN_CWD and the CLI's
bin moves into that folder before anything else runs, then drops the variable
so nested runs keep the folder their caller gives them.

In --script mode the launcher hands itself over as HYPERFRAMES_CLI_ENTRY. The
media helpers (Kokoro voice, transcription, the resolve shim) run the CLI they
were handed, and stop with an actionable message when none was, instead of
resolving `npx hyperframes` from the project.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@heygengenesis heygengenesis Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Verdict: APPROVE · 0 blocker · 1 important · 1 nit · head 2956757
The isolation works: npx starts in home, helpers go through the launcher, and the standalone transcribe.mjs/audio.mjs path stops with the actionable message (checked locally). Both findings are inline. The PWD one is a small user-visible regression on preview and is worth fixing before merge.

Scope

Read end-to-end: bin/hyperframes.mjs, plugin-cli.mjs, both npx-sync.mjs copies, tts.mjs spawn path, tts-local-provider.mjs, resolve.mjs, transcribe.mjs error path, docs. Grepped every npx/"hyperframes" spawn under skills/ and packages/cli/src/media-use; none remain outside the hand-over. Ran a real npx -p hyperframes@0.9.33 from home with PWD=<project> and got PWD=$HOME INIT_CWD=$HOME. CI green on head.

— Genesis

Comment thread packages/cli/bin/hyperframes.mjs Resolved
jrusso1020 and others added 2 commits November 11, 2026 10:48
The CLI bin now sets PWD to the project folder after moving into it, because
npm's shell resets PWD to the folder npx started in and preview names the
project from PWD. The plugin launcher no longer falls back to the temp folder
when there is no home folder; it stops with an error instead, and only when it
is about to start npx.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@heygengenesis heygengenesis Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Verdict: APPROVE · 2 resolved, 0 open, 0 new · 2956757 → a7e4742

— Genesis

@jrusso1020
jrusso1020 added this pull request to the merge queue Nov 11, 2026
Merged via the queue into main with commit a8fe3a5 Nov 11, 2026
70 checks passed
@jrusso1020
jrusso1020 deleted the fix/skills-pinned-cli-resolution branch November 11, 2026 11:46
@miguel-heygen miguel-heygen mentioned this pull request Nov 11, 2026
3 of 4 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant