mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-10 22:20:14 +00:00
fix(cli,skills): install workflow skills on demand instead of re-pulling the full set (#2012)
* fix(cli,skills): install workflow skills on demand instead of re-pulling the full set Users report every init re-pulls all 21 skills into ~/.agents/skills whenever anything is stale or missing - heavy, noisy, and it re-expands deliberate partial installs. Split the set into two tiers: - core: the /hyperframes router + hyperframes-* domain skills + media-use, which every workflow references structurally. init and bare 'skills update' keep these (plus anything already installed) fresh, and never expand the install. - on demand: the end-user workflow skills (and figma). They install at trigger time via 'skills update <name...>' - positional names are the only way update expands an install: one targeted 'skills add --skill <name>' covering only stale/missing targets, a fast no-op when current, presence-verified after install, exit 1 on unknown names, and a presence-only degrade when GitHub is unreachable. The /hyperframes router now runs 'skills update <workflow>' after routing and before reading the workflow skill, so a routed workflow is guaranteed present even on a machine that only has the core set. Each on-demand skill also opens with the same self-maintenance step (run 'npx hyperframes skills update <name>' silently), so a workflow triggered directly - without the router - still refreshes itself and restores any missing core skill before relying on it. When the manifest is unreachable (offline / rate-limited) the engine degrades honestly instead of claiming success: named runs presence-check the request plus a pinned fallback core list (unit-pinned to skills/) and blind-install whatever is absent; a bare strict update fails loudly so the 'check || update' chain can't pass while everything stays stale; init reports the skipped freshness check. --json emits structured errors on failure paths. skills check still lists every skill, but exits non-zero only for stale installed skills, an incomplete core set, or removed leftovers - workflow skills not yet installed are reported as available on demand. Bare 'hyperframes skills' (and 'skills add --all') remain the explicit full-set installs. Verified end-to-end with a sandboxed $HOME: fresh init installs the 9 core skills only; 'skills update slideshow' adds exactly that skill (no-op on re-run, exit 1 on unknown names); bare update refreshes without expanding; a live Claude Code run routed PR-to-video, executed the router's update step, and the workflow skill appeared before use; and a second live run triggered an installed workflow directly, whose opening maintenance step restored a deliberately removed core skill. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(skills): clarify update-engine contracts + document lazy-install model - skills.ts: note the UpdateSkillsResult.unknown strict-mode contract, verifyInstalled's non-strict (warn-not-throw) intent, and that a partial install stays "refreshed but never expanded" (review nits). - docs/guides/skills.mdx: add a "Keeping skills current" section covering the core-eager / workflow-on-demand model and the skills check|update commands, per the repo's catalog-maintenance rule. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: kiritowoo <295860553+kiritowoo@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Miao Yang <miao.yang@heygen.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
kiritowoo
Miao Yang
parent
e52bfc246a
commit
81884a7495
@@ -577,48 +577,46 @@ async function scaffoldProject(
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the 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 up-to-date
|
||||
* machine is a no-op. Best-effort: if the version check can't reach GitHub, it
|
||||
* installs anyway. The install itself (`installAllSkills`) installs the full set
|
||||
* once GLOBALLY (~/.claude/skills + ~/.agents/skills) and mirrors it into every
|
||||
* other installed agent, so it is project-independent — the check is global-first
|
||||
* to match.
|
||||
* Keep the AI coding skills present and current — TARGETED, not the full
|
||||
* set. Guarantees the core set (the `/hyperframes` entry router + shared
|
||||
* domain skills) and refreshes any skill already installed; the end-user
|
||||
* workflow skills are NOT pulled here — they install on demand when their
|
||||
* workflow is triggered (`hyperframes skills update <name>`, which the router
|
||||
* runs before entering a workflow). Re-running `init` on an up-to-date machine
|
||||
* is a no-op, and `init` never expands a deliberate partial install.
|
||||
* Best-effort: offline, it degrades to a presence check and never breaks init.
|
||||
* The install itself lands once GLOBALLY (~/.claude/skills + ~/.agents/skills)
|
||||
* and mirrors into every other installed agent, so it is project-independent —
|
||||
* the check is global-first to match.
|
||||
*/
|
||||
async function ensureSkillsCurrent(destDir: string): Promise<void> {
|
||||
const { installAllSkills } = await import("./skills.js");
|
||||
const { checkSkills } = await import("../utils/skillsManifest.js");
|
||||
async function keepSkillsCurrent(destDir: string): Promise<void> {
|
||||
const { updateSkills } = await import("./skills.js");
|
||||
|
||||
console.log();
|
||||
console.log(c.bold("Checking AI coding skills against GitHub..."));
|
||||
let needsInstall = true;
|
||||
// Wrap defensively (non-strict already swallows most failures): a
|
||||
// skills-install failure can never break `init` itself — it warns and
|
||||
// proceeds, since --skip-skills no longer escapes this path.
|
||||
try {
|
||||
const result = await checkSkills({ cwd: destDir });
|
||||
needsInstall = result.updateAvailable;
|
||||
} catch {
|
||||
// Couldn't reach GitHub (offline, rate-limited) — install anyway.
|
||||
}
|
||||
|
||||
if (needsInstall) {
|
||||
// installAllSkills installs the full set once globally and mirrors it into
|
||||
// every installed agent's global dir — project-independent, so a freshly
|
||||
// scaffolded project doesn't need any agent folders yet.
|
||||
//
|
||||
// Best-effort: installAllSkills (non-strict here) already swallows its own
|
||||
// failures, but now that --skip-skills no longer escapes this path every
|
||||
// init runs it — including offline ones, where checkSkills throws and we
|
||||
// fall through to "install anyway". Wrap defensively so a skills-install
|
||||
// failure can never break `init` itself; it only warns and proceeds.
|
||||
try {
|
||||
await installAllSkills({ cwd: destDir });
|
||||
} catch (err) {
|
||||
const result = await updateSkills({ refreshInstalled: true, cwd: destDir });
|
||||
if (result.presenceOnly) {
|
||||
// Freshness never got checked (GitHub unreachable) — don't claim
|
||||
// "up to date"; the engine already reported what it could verify or
|
||||
// blind-install. Point at the recovery command instead.
|
||||
console.log(
|
||||
c.dim(`AI coding skills install skipped: ${err instanceof Error ? err.message : err}`),
|
||||
c.dim("Skills freshness unverified — run `npx hyperframes skills update` when online."),
|
||||
);
|
||||
} else if (result.installed.length === 0) {
|
||||
console.log(c.success("AI coding skills are already up to date."));
|
||||
} else {
|
||||
console.log(
|
||||
c.dim("Workflow skills not installed here are added on demand, when first used."),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
console.log(c.success("AI coding skills are already up to date."));
|
||||
} catch (err) {
|
||||
console.log(
|
||||
c.dim(`AI coding skills install skipped: ${err instanceof Error ? err.message : err}`),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -871,7 +869,7 @@ export default defineCommand({
|
||||
}
|
||||
|
||||
if (!skipSkills) {
|
||||
await ensureSkillsCurrent(destDir);
|
||||
await keepSkillsCurrent(destDir);
|
||||
}
|
||||
|
||||
console.log();
|
||||
@@ -1087,11 +1085,12 @@ export default defineCommand({
|
||||
const files = readdirSync(destDir);
|
||||
clack.note(files.map((f) => c.accent(f)).join("\n"), c.success(`Created ${name}/`));
|
||||
|
||||
// Check skills against GitHub and (re)install only if outdated or missing —
|
||||
// init is the one place the full set is pulled. The --skip-skills flag is
|
||||
// temporarily neutered (see above); CI/tests opt out via HYPERFRAMES_SKIP_SKILLS=1.
|
||||
// Check skills against GitHub and refresh only what's stale — the core set
|
||||
// plus anything already installed; workflow skills install on demand. The
|
||||
// --skip-skills flag is temporarily neutered (see above); CI/tests opt out
|
||||
// via HYPERFRAMES_SKIP_SKILLS=1.
|
||||
if (!skipSkills) {
|
||||
await ensureSkillsCurrent(destDir);
|
||||
await keepSkillsCurrent(destDir);
|
||||
}
|
||||
|
||||
// Auto-launch studio preview
|
||||
|
||||
Reference in New Issue
Block a user