fix(cli): install skills into project dir on init#1671
Conversation
8e8fad0 to
4cbe4c8
Compare
vanceingalls
left a comment
There was a problem hiding this comment.
Review
Verdict: NIT (close to LGTM — one scope-gap to call out, otherwise a tight, surgical fix).
Head SHA: 4cbe4c8. Base: main (standalone, not a stack — Graphite URL notwithstanding).
The three claimed bugs are real and the fixes line up:
- Non-interactive branch now actually invokes the installer rather than just printing a hint —
packages/cli/src/commands/init.ts:804-810. runSkillsAddcorrectly threadscwdtospawn—packages/cli/src/commands/skills.ts:21-26.- Templates updated to the canonical
npx skills add heygen-com/hyperframes—AGENTS.md/CLAUDE.mdlines 24-25.
Refactor is clean: installAllSkills is a thin, well-typed wrapper, the default-run arm just delegates, and extraArgs ?? ["--all"] preserves the prior interactive contract on the standalone hyperframes skills command.
Finding (scope gap vs PR description — NIT, not block)
The PR body's "Why #2 — Wrong CWD" reads as a general root-cause statement, but the fix is non-interactive-only. The interactive path still has the bug:
packages/cli/src/commands/init.ts:1027-1039:
if (!skipSkills) {
const installSkills = await clack.confirm({ ... });
...
if (installSkills) {
const skillsCmd = await import("./skills.js").then((m) => m.default);
await runCommand(skillsCmd, { rawArgs: [] });
}
}runCommand(skillsCmd, { rawArgs: [] }) → installAllSkills() (no opts) → runSkillsAdd(repo, {}) → spawn(..., { cwd: undefined }), which means process.cwd() — i.e. the caller's directory, not destDir. A human running hyperframes init my-video in their home dir and confirming the skill prompt will still get skills written next to wherever they invoked the CLI, not inside my-video/. No process.chdir between the prompt and the spawn (greped the file).
Two reasonable resolutions:
- Tighten the PR description to "fixes wrong-CWD on the non-interactive path; interactive parity tracked separately" and ship as-is.
- One-line fix in the interactive arm:
instead of
await installAllSkills({ cwd: destDir });
runCommand(skillsCmd, { rawArgs: [] }). Same behavior, same--alldefault, just with the correct target. Symmetric with the non-interactive arm.
I'd prefer (2) — the interactive path is the one humans actually use, and shipping a description that overstates the fix makes the next regression harder to spot.
Smaller nits (non-blocking)
- The non-interactive arm passes
["--yes"]or["--agent","claude-code","--yes"]— neither includes--all. Test plan reports 18 skills landed via--yesalone, so the upstreamskillsCLI presumably defaults to all in non-interactive mode. Worth a brief code-comment confirming that's intentional (since the interactive branch still relies on the explicit--alldefault inrunSkillsAdd). - "Agent's native dir" in the title is fulfilled only for Claude Code (via
CLAUDECODE). Cursor, Codex, Windsurf, etc. in non-interactive mode get the upstream default (presumably.agents/skills/). That matches the PR body's narrower "When Claude Code is driving" wording, but the title is slightly oversold. Not blocking — the missing-agent fallback is "do nothing surprising," which is the safe behavior.
CI: all required green at 4cbe4c8 (Build / Lint / Typecheck / Test / Fallow / CLI smoke / CLI shim ubuntu+macos+windows / SDK / runtime contract / Studio smoke / Windows render / preview-regression / regression / CodeQL all passing). Earlier CANCELLED runs are a superseded workflow generation.
Review by Via
james-russo-rames-d-jusso
left a comment
There was a problem hiding this comment.
R1 review @ HEAD 4cbe4c80
Scope-correct fix for the stated non-interactive bug. Verified against upstream skills semantics + existing in-repo helpers. LGTM-with-followups — no merge blockers; two asymmetries are worth tracking before / shortly after.
| file:line | finding | |
|---|---|---|
packages/cli/src/commands/init.ts:1037-1038 |
Interactive path still calls runCommand(skillsCmd, { rawArgs: [] }) which goes through installAllSkills() with no opts — so cwd is NOT set to destDir and there's no --agent claude-code detection. PR body item #2 ("Wrong CWD … Skills landed in the caller's working directory, not inside the scaffolded project") reads as a general bug, but only the non-interactive branch is patched. A human running hyperframes init my-vid inside Claude Code's TTY hits the same wrong-CWD landing the PR fixes for agents. Title scopes to "non-interactive", so non-blocking; flag as the obvious follow-up. Fix shape: have the interactive branch also call installAllSkills({ cwd: destDir, extraArgs: claudeCodeArgs }). |
|
packages/cli/src/commands/init.ts:809 |
Agent detection narrowed to CLAUDECODE only. packages/cli/src/telemetry/agent_runtime.ts (lines 49-183) already ships a multi-agent fingerprinter (Codex, Cursor, Gemini CLI, Windsurf, Cline, …) — and the superseded #1667 reused it via resolveSkillsAgent(). This rewrite drops that mapping. Worst-case is benign: a Cursor/Codex driver falls through to ["--yes"], upstream skills add auto-detects 0 installed-agents in the fresh destDir, and installs into all-agents (i.e. .agents/skills/ + everything else, per vercel-labs/skills add.ts:661-663). Works, but noisy. Suggest a tiny resolveSkillsAgent() mapping over detectAgentRuntime() → --agent <id> so Cursor/Codex drivers also land natively. |
|
| 🟡 | packages/cli/src/commands/init.ts:809 |
const args = … shadows the outer run({ args }) destructured parameter. Lint accepts it, but rename to skillArgs / extraArgs for readability — esp. since args.something lookups appear elsewhere in the same function. |
| 🟡 | .github/workflows/ci.yml (CLI smoke) |
Both inside-monorepo and outside-monorepo CLI smoke runs hyperframes init … --skip-skills, so the new installAllSkills({ cwd, extraArgs }) code path has no CI coverage — the manual test plan in the body is the only signal. Existing skills.test.ts happens to still cover the standalone hyperframes skills command because installAllSkills() with no opts falls through to the --all default. Suggest a tiny unit test that asserts (a) cwd is threaded into spawn, (b) CLAUDECODE=1 → --agent claude-code --yes, (c) CLAUDECODE unset → --yes only. The existing node:child_process mock in skills.test.ts already exposes spawnCalls[].env / opts, so the lift is small. |
| 🟢 | packages/cli/src/commands/skills.ts:17-43 |
cwd + extraArgs plumbing into spawn is clean; --all default preserved for the existing standalone hyperframes skills path; existing skills.test.ts assertions on --all and Windows shim still hold. |
| 🟢 | upstream skills@1.5.12 semantics | Verified against vercel-labs/skills src/add.ts:587-614: --yes without --skill installs all skills (test plan's "18 skills" matches). --agent claude-code lands in .claude/skills/ per src/agents.ts:124. PR's flag combos are correct. |
| 🟢 | packages/cli/src/templates/_shared/{CLAUDE,AGENTS}.md |
Files remain byte-identical (intentional — shared content, two filenames for two conventions). Wording change is correct + clearer than the prior "ask the user to run npx hyperframes skills" path. |
CI: all required green (CLI smoke, build, lint, typecheck, win shim, smoke: global install). Note the caveat above re: smoke coverage of the new code path.
Stamp routing: I'm not stamping. PR is non-trivial enough (changes the install contract for agent drivers) that it deserves an explicit author go-ahead on the asymmetry follow-up before lighting up. Miguel — if you want this stamped as-is and the interactive-parity follow-up tracked separately, tag <@U0ARJFN5S6Q> (Rames Jusso) and he can land it; I'll have vetted it first.
— Rames D Jusso
`hyperframes init` only installed AI coding skills on the interactive path (behind a clack confirm). When an agent drives it non-interactively (no TTY), it just printed `npx skills add ...` and returned — so skills were never installed and the agent later hit `Unknown skill: <workflow>`. - init: both interactive and non-interactive branches now run `npx skills add` with `cwd` set to the new project dir so skills land there, not in the caller's working directory. Non-interactive additionally passes `--yes`; when Claude Code is driving (CLAUDECODE env var), adds `--agent claude-code` so skills target `.claude/skills/`. - skills: `runSkillsAdd` accepts `cwd` and `extraArgs` so callers can control where and how skills are installed. - templates: CLAUDE.md / AGENTS.md now tell agents to run `npx skills add heygen-com/hyperframes` to install or update skills. Co-Authored-By: Wenbo Zhu <295860553+kiritowoo@users.noreply.github.com>
4cbe4c8 to
7b8823d
Compare
Supersedes #1667. Incorporates the original fix from @kiritowoo (non-interactive init now installs skills), simplified and with additional fixes found during outside-the-monorepo testing.
What
Both interactive and non-interactive
hyperframes initnow install skills into the correct project directory.Why
Three things were broken:
Non-interactive init skipped skill install — it printed
npx skills add ...but never ran it. Agents hitUnknown skill: <workflow>and free-handed compositions.Wrong CWD (both paths) — neither the interactive nor non-interactive branch set
cwdwhen spawningnpx skills add. Skills landed in the caller's working directory, not inside the scaffolded project.Template pointed to wrong command — CLAUDE.md / AGENTS.md told agents to run
npx hyperframes skills, but the canonical path isnpx skills add heygen-com/hyperframes.How
runSkillsAddacceptscwdandextraArgs—cwdsets the install target,extraArgslets callers pass flags like--agent claude-code --yes.cwd: destDir. Non-interactive also detects Claude Code via theCLAUDECODEenv var to pass--agent claude-code.npx skills add heygen-com/hyperframes.Test plan
Built the branch, tested outside the monorepo with
CLAUDECODE=1:hyperframes init test-project --non-interactive→ 18 skills intest-project/.claude/skills/, nothing in parent CWD--skip-skillsflag still skips installationnpx skills add heygen-com/hyperframes