mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-09 20:07:39 +00:00
feat(cli): add skills version check, update, and freshness manifest (#1738)
* feat(cli): add skills version check, update, and freshness manifest
Give the HyperFrames skill bundle a content fingerprint so agents and
users can tell whether installed skills are the latest version, on any
platform that can run the CLI.
- skills-manifest.json (repo root): per-skill sha256 over the whole skill
directory; minimal {source, skills}, no version/timestamp so it is fully
deterministic. Generated by scripts/gen-skills-manifest.ts.
- `hyperframes skills check` [--json]: compares installed skills to the
manifest; exits non-zero when something is outdated (agent/CI gate).
- `hyperframes skills update`: thin wrapper over `npx skills update`.
- Passive nudge on render/lint/validate when skills are stale (24h cache,
same opt-out as the CLI self-update notice).
- "latest" resolved via `git ls-remote` + SHA-pinned raw URL to dodge
GitHub raw-CDN lag, falling back to the main branch URL.
- CI job + lefthook hook keep skills-manifest.json in sync with skills/.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(cli): add execFile to child_process mock in skills test
skills.test.ts mocks node:child_process but only declared execFileSync
and spawn. Loading skills.js transitively loads skillsManifest.ts, which
runs promisify(execFile) at module load, so vitest threw on the missing
execFile named export. Add a bare stub — these tests never invoke it.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(cli): init installs all skills; skills update pulls the full set
Make `hyperframes init` the single place skills are pulled in full, and
make "update" mean "get everything" rather than "refresh what's there".
- init now always installs/refreshes ALL skills (incl. ones not yet
present) instead of prompting "Install AI coding skills?" — opt out
with `init --skip-skills`. Both the interactive and non-interactive
paths pass `--all --yes` so the complete set is fetched.
- `hyperframes skills update` switches from `npx skills update` (which
only refreshes already-installed skills) to `skills add --all`, so it
installs missing skills too — the same install step init runs.
- SKILL.md documents init-installs-all and the new update semantics.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(cli): skills check treats missing skills as needing an update
The full skill set is now the goal (init and `skills update` both pull
all, including ones not installed), so a partial install is no longer
"a choice" — it's something to fix.
- diffSkills: updateAvailable is now true when anything is outdated OR
missing (local-only still doesn't count). So `skills check` exits
non-zero — and renders "Update:" instead of "up to date" — whenever a
skill is missing, not just when one is stale.
- The passive render/lint/validate nudge follows suit: it now counts
missing alongside outdated ("N skills out of date or missing"),
tracked via a new skillsMissingCount cache field.
- SKILL.md documents the stricter check.
Note: platforms that intentionally vendor only a subset of skills (e.g.
a Codex snapshot) will now see check report non-zero.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(cli): install/update skills straight from the GitHub repo
`skills add owner/repo` can resolve through the skills.sh registry, which
lags behind the repo — so `update` could install a stale version while
`check` (which resolves latest directly from GitHub) keeps reporting
"outdated", an endless loop.
Switch the install source to the full GitHub URL
(https://github.com/heygen-com/hyperframes), which makes `skills add`
git-clone the repo directly at latest main, bypassing the registry. This
covers `hyperframes skills`, `hyperframes skills update`, and `init`'s
skill install — all of which go through SOURCES. Now install/update and
check agree on what "latest" means.
The init "install skills" hint now points at `npx hyperframes skills
update` so the manual path uses the same GitHub-direct fetch.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(cli): init checks skills against GitHub, installs only when stale
`hyperframes init` now runs the skills version check first and only
(re)installs when something is outdated or missing — instead of
unconditionally re-pulling every time. Re-running init on an
already-current project is now a no-op ("skills are already up to date").
- New ensureSkillsCurrent() helper, shared by both the interactive and
non-interactive init paths (no duplicated install logic).
- The check resolves "latest" straight from GitHub (same source the
install uses); best-effort — if it can't reach GitHub it installs anyway.
- SKILL.md updated to describe the check-then-install behavior.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(cli): address skills manifest review feedback
From the PR review (points 1, 2, 4, 5):
1. Remove the `local-only` skill status. checkSkills only ever hashes
manifest-listed skills, so a local-only status could never appear in
the end-to-end output — and making it appear would wrongly flag
unrelated skills (the `.../skills` dir is shared across sources).
diffSkills now reports only on manifest skills; skills on disk that
aren't in the manifest are ignored.
2. Drop the redundant per-directory sort in listFilesSorted — the single
final out.sort() is what guarantees a deterministic hash (verified:
manifest unchanged).
4. resolveLatestManifest local-path detection now uses path.isAbsolute,
so Windows absolute paths (C:\...) are treated as local instead of
falling through to a remote fetch.
5. fetchManifest validates the response shape (asSkillsManifest) instead
of a blind `as` cast, so a CDN error page served as 200 fails with a
clear error rather than a cryptic crash later in diffSkills.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(cli): strict skills update + auto-discover any agent host
Address PR review (Magi blocker + James/Rames robustness):
- Blocker (Magi): `skills update` is the documented recovery path for
`skills check || skills update`, but it delegated to installAllSkills()
which swallowed missing-npx and failed `skills add` as "skipped",
exiting 0 even when nothing changed. Add a strict mode that throws on
failure; update sets a non-zero exit (init stays best-effort). New tests
simulate a non-zero `skills add` (exit 1) and the success path.
- Robustness (James/Rames #2): the upstream `skills` CLI installs into
~72 agent conventions; a hard-coded list (4, or even 11) can't track
that. Replace defaultSkillRoots with discoverSkillRoots — it scans cwd +
$HOME for any `<host>/skills/<manifest-skill>/SKILL.md` (plus the XDG
`.config/<host>/skills`), so detection is structural and future-proof,
no closed list. agentFromDir infers the host from the path.
- Tests (Rames #3): temp-fixture detection tests for every convention ×
{project, global}, scope priority, claude-code preference, the
no-install case, the --dir override, and an unknown/new host (proving
the no-closed-list property).
- Docs (Rames #4/#5): SKILL.md notes init's best-effort GitHub round-trip;
findRepoManifest climbs 16 levels (was 8) for deep monorepos.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(cli): resolve CodeQL file-system race + de-flake Windows npx test
Two CI fixes:
- CodeQL (high, js/file-system-race) at gen-skills-manifest.ts: the
existsSync(outPath) precheck followed by writeFileSync(outPath) is a
check-then-write race. Read the committed manifest directly in a
try/catch instead (missing/unreadable ⇒ "no committed manifest"), so
there's no precheck to race against. Behavior is unchanged.
- Windows Tests: npxCommand.test.ts's real `npx --version` smoke test
cold-starts slower than vitest's 5s default on Windows runners and
timed out. Give the test 60s headroom (and a 30s exec timeout). Kept
as a real execution check — mocking would reduce it to a tautology.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(cli): repair garbled npx smoke-test timeout comment
The explanatory comment for the 60s timeout was scrambled across the
callback/timeout arguments, failing oxfmt --check (and thus preflight,
which in turn skipped preview-parity and failed the regression gate).
Move it above the it() call so it no longer sits between call arguments.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
0c1e236dcd
commit
d70ee134cc
@@ -573,6 +573,41 @@ async function scaffoldProject(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the project's AI coding skills are present and current. Checks the
|
||||
* installed skills against the latest published on GitHub and only (re)installs
|
||||
* when something is outdated or missing — so re-running `init` on an already
|
||||
* up-to-date project is a no-op. Best-effort: if the version check can't reach
|
||||
* GitHub, it installs anyway. The install itself (`installAllSkills`) pulls the
|
||||
* full set straight from the GitHub repo.
|
||||
*/
|
||||
async function ensureSkillsCurrent(destDir: string): Promise<void> {
|
||||
const { installAllSkills } = await import("./skills.js");
|
||||
const { checkSkills } = await import("../utils/skillsManifest.js");
|
||||
// --all pulls every skill (incl. ones not yet installed); --yes keeps it
|
||||
// non-interactive. When Claude Code is driving, target its native dir so
|
||||
// skills land in .claude/skills/.
|
||||
const extraArgs = process.env["CLAUDECODE"]
|
||||
? ["--all", "--agent", "claude-code", "--yes"]
|
||||
: ["--all", "--yes"];
|
||||
|
||||
console.log();
|
||||
console.log(c.bold("Checking AI coding skills against GitHub..."));
|
||||
let needsInstall = true;
|
||||
try {
|
||||
const result = await checkSkills({ cwd: destDir });
|
||||
needsInstall = result.updateAvailable;
|
||||
} catch {
|
||||
// Couldn't reach GitHub (offline, rate-limited) — install anyway.
|
||||
}
|
||||
|
||||
if (needsInstall) {
|
||||
await installAllSkills({ cwd: destDir, extraArgs });
|
||||
} else {
|
||||
console.log(c.success("AI coding skills are already up to date."));
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Exported command
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -802,12 +837,7 @@ export default defineCommand({
|
||||
}
|
||||
|
||||
if (!skipSkills) {
|
||||
const { installAllSkills } = await import("./skills.js");
|
||||
// --yes keeps it non-interactive. When Claude Code is driving
|
||||
// (CLAUDECODE env var), target its native dir so skills land in
|
||||
// .claude/skills/ instead of only .agents/skills/.
|
||||
const args = process.env["CLAUDECODE"] ? ["--agent", "claude-code", "--yes"] : ["--yes"];
|
||||
await installAllSkills({ cwd: destDir, extraArgs: args });
|
||||
await ensureSkillsCurrent(destDir);
|
||||
}
|
||||
|
||||
console.log();
|
||||
@@ -815,7 +845,7 @@ export default defineCommand({
|
||||
console.log();
|
||||
if (skipSkills) {
|
||||
console.log(` ${c.accent("1.")} Install AI coding skills (one-time):`);
|
||||
console.log(` ${c.accent("npx skills add heygen-com/hyperframes --yes")}`);
|
||||
console.log(` ${c.accent("npx hyperframes skills update")}`);
|
||||
} else {
|
||||
console.log(
|
||||
` ${c.accent("1.")} Restart your AI agent (new session) so it loads the skills.`,
|
||||
@@ -1023,20 +1053,10 @@ export default defineCommand({
|
||||
const files = readdirSync(destDir);
|
||||
clack.note(files.map((f) => c.accent(f)).join("\n"), c.success(`Created ${name}/`));
|
||||
|
||||
// Offer to install AI coding skills
|
||||
// Check skills against GitHub and (re)install only if outdated or missing —
|
||||
// init is the one place the full set is pulled. Opt out with --skip-skills.
|
||||
if (!skipSkills) {
|
||||
const installSkills = await clack.confirm({
|
||||
message: "Install AI coding skills? (for Claude Code, Cursor, Codex, etc.)",
|
||||
initialValue: true,
|
||||
});
|
||||
if (clack.isCancel(installSkills)) {
|
||||
clack.cancel("Setup cancelled.");
|
||||
process.exit(0);
|
||||
}
|
||||
if (installSkills) {
|
||||
const { installAllSkills } = await import("./skills.js");
|
||||
await installAllSkills({ cwd: destDir });
|
||||
}
|
||||
await ensureSkillsCurrent(destDir);
|
||||
}
|
||||
|
||||
// Auto-launch studio preview
|
||||
|
||||
Reference in New Issue
Block a user