hyperframes 0.8.57 → 0.8.59

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (182) hide show
  1. package/dist/cli.js +5300 -3428
  2. package/dist/docs/data-attributes.md +2 -1
  3. package/dist/fontLocalizeCli.js +73 -12
  4. package/dist/hyperframe-runtime.js +25 -25
  5. package/dist/hyperframe.manifest.json +1 -1
  6. package/dist/hyperframe.runtime.iife.js +25 -25
  7. package/dist/hyperframes-player.global.js +3 -3
  8. package/dist/registry/localSemantic.js +548 -0
  9. package/dist/skills/hyperframes-cli/SKILL.md +2 -2
  10. package/dist/skills/media-use/SKILL.md +96 -0
  11. package/dist/skills/media-use/audio/assets/sfx/CREDITS.md +35 -0
  12. package/dist/skills/media-use/audio/assets/sfx/chime.mp3 +0 -0
  13. package/dist/skills/media-use/audio/assets/sfx/click-soft.mp3 +0 -0
  14. package/dist/skills/media-use/audio/assets/sfx/click.mp3 +0 -0
  15. package/dist/skills/media-use/audio/assets/sfx/error.mp3 +0 -0
  16. package/dist/skills/media-use/audio/assets/sfx/glitch-1.mp3 +0 -0
  17. package/dist/skills/media-use/audio/assets/sfx/glitch-2.mp3 +0 -0
  18. package/dist/skills/media-use/audio/assets/sfx/glitch-3.mp3 +0 -0
  19. package/dist/skills/media-use/audio/assets/sfx/impact-bass-1.mp3 +0 -0
  20. package/dist/skills/media-use/audio/assets/sfx/impact-bass-2.mp3 +0 -0
  21. package/dist/skills/media-use/audio/assets/sfx/key-press.mp3 +0 -0
  22. package/dist/skills/media-use/audio/assets/sfx/manifest.json +97 -0
  23. package/dist/skills/media-use/audio/assets/sfx/notification.mp3 +0 -0
  24. package/dist/skills/media-use/audio/assets/sfx/ping.mp3 +0 -0
  25. package/dist/skills/media-use/audio/assets/sfx/pop.mp3 +0 -0
  26. package/dist/skills/media-use/audio/assets/sfx/riser.mp3 +0 -0
  27. package/dist/skills/media-use/audio/assets/sfx/sparkle.mp3 +0 -0
  28. package/dist/skills/media-use/audio/assets/sfx/typing.mp3 +0 -0
  29. package/dist/skills/media-use/audio/assets/sfx/whoosh-cinematic.mp3 +0 -0
  30. package/dist/skills/media-use/audio/assets/sfx/whoosh-short.mp3 +0 -0
  31. package/dist/skills/media-use/audio/assets/sfx/whoosh.mp3 +0 -0
  32. package/dist/skills/media-use/audio/references/bgm.md +74 -0
  33. package/dist/skills/media-use/audio/references/captions/authoring.md +163 -0
  34. package/dist/skills/media-use/audio/references/captions/motion.md +87 -0
  35. package/dist/skills/media-use/audio/references/captions/transcript-handling.md +97 -0
  36. package/dist/skills/media-use/audio/references/remove-background.md +143 -0
  37. package/dist/skills/media-use/audio/references/requirements.md +29 -0
  38. package/dist/skills/media-use/audio/references/sfx.md +42 -0
  39. package/dist/skills/media-use/audio/references/transcribe.md +52 -0
  40. package/dist/skills/media-use/audio/references/tts-to-captions.md +26 -0
  41. package/dist/skills/media-use/audio/references/tts.md +165 -0
  42. package/dist/skills/media-use/audio/scripts/audio.mjs +295 -0
  43. package/dist/skills/media-use/audio/scripts/audio.test.mjs +49 -0
  44. package/dist/skills/media-use/audio/scripts/heygen-tts.mjs +121 -0
  45. package/dist/skills/media-use/audio/scripts/lib/audio-meta.mjs +39 -0
  46. package/dist/skills/media-use/audio/scripts/lib/audio-meta.test.mjs +136 -0
  47. package/dist/skills/media-use/audio/scripts/lib/bgm.mjs +257 -0
  48. package/dist/skills/media-use/audio/scripts/lib/bgm.test.mjs +30 -0
  49. package/dist/skills/media-use/audio/scripts/lib/concurrency.mjs +14 -0
  50. package/dist/skills/media-use/audio/scripts/lib/concurrency.test.mjs +41 -0
  51. package/dist/skills/media-use/audio/scripts/lib/heygen.mjs +156 -0
  52. package/dist/skills/media-use/audio/scripts/lib/heygen.test.mjs +103 -0
  53. package/dist/skills/media-use/audio/scripts/lib/python.mjs +63 -0
  54. package/dist/skills/media-use/audio/scripts/lib/python.test.mjs +68 -0
  55. package/dist/skills/media-use/audio/scripts/lib/sfx.mjs +143 -0
  56. package/dist/skills/media-use/audio/scripts/lib/sfx.test.mjs +69 -0
  57. package/dist/skills/media-use/audio/scripts/lib/tts.mjs +379 -0
  58. package/dist/skills/media-use/audio/scripts/lib/tts.spawn.test.mjs +158 -0
  59. package/dist/skills/media-use/audio/scripts/lib/tts.test.mjs +157 -0
  60. package/dist/skills/media-use/audio/scripts/lyria-recipe.py +136 -0
  61. package/dist/skills/media-use/audio/scripts/wait-bgm.mjs +171 -0
  62. package/dist/skills/media-use/audio/scripts/wait-bgm.test.mjs +65 -0
  63. package/dist/skills/media-use/luts/README.md +28 -0
  64. package/dist/skills/media-use/luts/index.json +57 -0
  65. package/dist/skills/media-use/references/audio.md +19 -0
  66. package/dist/skills/media-use/references/grading.md +160 -0
  67. package/dist/skills/media-use/references/media-treatment-recipes.md +929 -0
  68. package/dist/skills/media-use/references/media-treatments.md +202 -0
  69. package/dist/skills/media-use/references/memory.md +36 -0
  70. package/dist/skills/media-use/references/meta.md +61 -0
  71. package/dist/skills/media-use/references/operations.md +325 -0
  72. package/dist/skills/media-use/references/resolve.md +150 -0
  73. package/dist/skills/media-use/references/setup-providers.md +80 -0
  74. package/dist/skills/media-use/references/telemetry-dashboard.md +49 -0
  75. package/dist/skills/media-use/registry/localSemantic.js +548 -0
  76. package/dist/skills/media-use/scripts/audio-duck.mjs +113 -0
  77. package/dist/skills/media-use/scripts/compatibility.test.mjs +27 -0
  78. package/dist/skills/media-use/scripts/dither.mjs +328 -0
  79. package/dist/skills/media-use/scripts/dither.test.mjs +176 -0
  80. package/dist/skills/media-use/scripts/eval.mjs +369 -0
  81. package/dist/skills/media-use/scripts/lib/adopt.mjs +128 -0
  82. package/dist/skills/media-use/scripts/lib/adopt.test.mjs +95 -0
  83. package/dist/skills/media-use/scripts/lib/bgm-provider.mjs +20 -0
  84. package/dist/skills/media-use/scripts/lib/brand-provider.mjs +59 -0
  85. package/dist/skills/media-use/scripts/lib/bundled-sfx-provider.mjs +119 -0
  86. package/dist/skills/media-use/scripts/lib/bundled-sfx-provider.test.mjs +139 -0
  87. package/dist/skills/media-use/scripts/lib/cache.mjs +150 -0
  88. package/dist/skills/media-use/scripts/lib/candidates.mjs +91 -0
  89. package/dist/skills/media-use/scripts/lib/candidates.test.mjs +155 -0
  90. package/dist/skills/media-use/scripts/lib/codex-provider.mjs +161 -0
  91. package/dist/skills/media-use/scripts/lib/codex-provider.test.mjs +34 -0
  92. package/dist/skills/media-use/scripts/lib/coverage.test.mjs +132 -0
  93. package/dist/skills/media-use/scripts/lib/cube-build.mjs +181 -0
  94. package/dist/skills/media-use/scripts/lib/cube-build.test.mjs +80 -0
  95. package/dist/skills/media-use/scripts/lib/cube-validate.mjs +198 -0
  96. package/dist/skills/media-use/scripts/lib/cube-validate.test.mjs +125 -0
  97. package/dist/skills/media-use/scripts/lib/cutlist.mjs +184 -0
  98. package/dist/skills/media-use/scripts/lib/cutlist.test.mjs +162 -0
  99. package/dist/skills/media-use/scripts/lib/duck.mjs +95 -0
  100. package/dist/skills/media-use/scripts/lib/duck.test.mjs +153 -0
  101. package/dist/skills/media-use/scripts/lib/error-diffusion.mjs +230 -0
  102. package/dist/skills/media-use/scripts/lib/error-diffusion.test.mjs +102 -0
  103. package/dist/skills/media-use/scripts/lib/freeze.mjs +62 -0
  104. package/dist/skills/media-use/scripts/lib/freeze.test.mjs +46 -0
  105. package/dist/skills/media-use/scripts/lib/grade-analyzer.mjs +265 -0
  106. package/dist/skills/media-use/scripts/lib/grade-analyzer.test.mjs +285 -0
  107. package/dist/skills/media-use/scripts/lib/heygen-cli.mjs +191 -0
  108. package/dist/skills/media-use/scripts/lib/heygen-cli.test.mjs +301 -0
  109. package/dist/skills/media-use/scripts/lib/heygen-search.mjs +45 -0
  110. package/dist/skills/media-use/scripts/lib/heygen-search.test.mjs +49 -0
  111. package/dist/skills/media-use/scripts/lib/heygen-video-provider.mjs +138 -0
  112. package/dist/skills/media-use/scripts/lib/heygen-video-provider.test.mjs +435 -0
  113. package/dist/skills/media-use/scripts/lib/image-provider.mjs +44 -0
  114. package/dist/skills/media-use/scripts/lib/index-gen.mjs +63 -0
  115. package/dist/skills/media-use/scripts/lib/local-media-search.mjs +32 -0
  116. package/dist/skills/media-use/scripts/lib/local-models.mjs +318 -0
  117. package/dist/skills/media-use/scripts/lib/local-models.test.mjs +220 -0
  118. package/dist/skills/media-use/scripts/lib/local-run.mjs +78 -0
  119. package/dist/skills/media-use/scripts/lib/local-run.test.mjs +121 -0
  120. package/dist/skills/media-use/scripts/lib/logo-provider.mjs +223 -0
  121. package/dist/skills/media-use/scripts/lib/logo-provider.test.mjs +154 -0
  122. package/dist/skills/media-use/scripts/lib/ltx-video-provider.mjs +94 -0
  123. package/dist/skills/media-use/scripts/lib/ltx-video-provider.test.mjs +316 -0
  124. package/dist/skills/media-use/scripts/lib/lut-preset-provider.mjs +271 -0
  125. package/dist/skills/media-use/scripts/lib/lut-preset-provider.test.mjs +199 -0
  126. package/dist/skills/media-use/scripts/lib/manifest.mjs +229 -0
  127. package/dist/skills/media-use/scripts/lib/manifest.test.mjs +388 -0
  128. package/dist/skills/media-use/scripts/lib/match.mjs +47 -0
  129. package/dist/skills/media-use/scripts/lib/media-fetch.mjs +71 -0
  130. package/dist/skills/media-use/scripts/lib/media-fetch.test.mjs +169 -0
  131. package/dist/skills/media-use/scripts/lib/media-search.d.mts +9 -0
  132. package/dist/skills/media-use/scripts/lib/media-search.mjs +107 -0
  133. package/dist/skills/media-use/scripts/lib/mflux-provider.mjs +128 -0
  134. package/dist/skills/media-use/scripts/lib/mflux-provider.test.mjs +174 -0
  135. package/dist/skills/media-use/scripts/lib/misses.mjs +49 -0
  136. package/dist/skills/media-use/scripts/lib/misses.test.mjs +78 -0
  137. package/dist/skills/media-use/scripts/lib/npx-sync.mjs +31 -0
  138. package/dist/skills/media-use/scripts/lib/npx-sync.test.mjs +50 -0
  139. package/dist/skills/media-use/scripts/lib/parakeet-words.mjs +27 -0
  140. package/dist/skills/media-use/scripts/lib/parakeet-words.test.mjs +44 -0
  141. package/dist/skills/media-use/scripts/lib/prefs-store.mjs +180 -0
  142. package/dist/skills/media-use/scripts/lib/prefs-store.test.mjs +189 -0
  143. package/dist/skills/media-use/scripts/lib/probe.mjs +39 -0
  144. package/dist/skills/media-use/scripts/lib/probe.test.mjs +31 -0
  145. package/dist/skills/media-use/scripts/lib/providers.mjs +5 -0
  146. package/dist/skills/media-use/scripts/lib/recipe-store.mjs +367 -0
  147. package/dist/skills/media-use/scripts/lib/recipe-store.test.mjs +367 -0
  148. package/dist/skills/media-use/scripts/lib/registry.mjs +218 -0
  149. package/dist/skills/media-use/scripts/lib/registry.test.mjs +262 -0
  150. package/dist/skills/media-use/scripts/lib/search.mjs +24 -0
  151. package/dist/skills/media-use/scripts/lib/search.test.mjs +32 -0
  152. package/dist/skills/media-use/scripts/lib/sfx-provider.mjs +23 -0
  153. package/dist/skills/media-use/scripts/lib/specs.mjs +132 -0
  154. package/dist/skills/media-use/scripts/lib/specs.test.mjs +157 -0
  155. package/dist/skills/media-use/scripts/lib/stats.mjs +117 -0
  156. package/dist/skills/media-use/scripts/lib/stats.test.mjs +165 -0
  157. package/dist/skills/media-use/scripts/lib/telemetry.mjs +193 -0
  158. package/dist/skills/media-use/scripts/lib/telemetry.test.mjs +373 -0
  159. package/dist/skills/media-use/scripts/lib/tts-local-provider.mjs +92 -0
  160. package/dist/skills/media-use/scripts/lib/tts-local-provider.test.mjs +72 -0
  161. package/dist/skills/media-use/scripts/lib/usage.mjs +57 -0
  162. package/dist/skills/media-use/scripts/lib/usage.test.mjs +54 -0
  163. package/dist/skills/media-use/scripts/lib/voice-provider.mjs +57 -0
  164. package/dist/skills/media-use/scripts/lib/voice-provider.test.mjs +46 -0
  165. package/dist/skills/media-use/scripts/lib/words.mjs +18 -0
  166. package/dist/skills/media-use/scripts/prefs.mjs +70 -0
  167. package/dist/skills/media-use/scripts/recipe.mjs +103 -0
  168. package/dist/skills/media-use/scripts/resolve.mjs +1287 -0
  169. package/dist/skills/media-use/scripts/transcribe.mjs +180 -0
  170. package/dist/skills/media-use/scripts/transcript-cut.mjs +239 -0
  171. package/dist/skills/registry/localSemantic.js +548 -0
  172. package/dist/studio/assets/{hyperframes-player-c23wnIfc.js → hyperframes-player-BBub3ZdI.js} +3 -3
  173. package/dist/studio/assets/{index-IDNr3X1H.js → index-BxZ5Cw5w.js} +1 -1
  174. package/dist/studio/assets/{index-DgkAzx3n.js → index-CI7WON-E.js} +1 -1
  175. package/dist/studio/assets/{index-gbOCDCuZ.js → index-D1KNPijK.js} +192 -192
  176. package/dist/studio/assets/{index-DKJpHYYd.css → index-DZBHG9TW.css} +1 -1
  177. package/dist/studio/index.d.ts +577 -8
  178. package/dist/studio/index.html +2 -2
  179. package/dist/studio/index.js +22537 -21600
  180. package/dist/studio/index.js.map +1 -1
  181. package/dist/studio/styles.css +1 -1
  182. package/package.json +3 -3
@@ -0,0 +1,57 @@
1
+ import { HEYGEN_CLIENT_SOURCE_ARGV, runHeygenJson } from "./heygen-cli.mjs";
2
+
3
+ // Voice / TTS generation via the HeyGen CLI — the only external CLI media-use
4
+ // shells (CLI-only invariant: media-use holds no keys; the CLI owns auth).
5
+ // Flags verified against `heygen voice speech create --help` (v0.3.0).
6
+
7
+ function result(url, duration, provider, intent) {
8
+ if (!url) return null;
9
+ return {
10
+ url,
11
+ source: "generated",
12
+ metadata: {
13
+ description: intent,
14
+ provider,
15
+ ...(duration != null && { duration }),
16
+ provenance: { prompt: intent },
17
+ },
18
+ };
19
+ }
20
+
21
+ // HeyGen TTS requires a starfish-engine voice. Default to the first one the
22
+ // catalog returns (deterministic order); pass ctx.voiceId to override.
23
+ // ponytail: listed once per process; the resolved asset is frozen + cached after
24
+ // first use, so the network list only happens on a cache miss. Cache only a
25
+ // truthy id -- a transient list failure must not poison the cache with `null`
26
+ // and permanently disable TTS for the rest of the process.
27
+ let cachedVoiceId;
28
+ function defaultVoiceId() {
29
+ if (cachedVoiceId) return cachedVoiceId;
30
+ const j = runHeygenJson(
31
+ "heygen",
32
+ ["voice", "list", "--engine", "starfish", "--limit", "1"],
33
+ "voice list",
34
+ );
35
+ cachedVoiceId = j?.data?.[0]?.voice_id || null;
36
+ return cachedVoiceId;
37
+ }
38
+
39
+ export async function heygenTtsGenerate(intent, ctx) {
40
+ const voiceId = ctx?.voiceId || defaultVoiceId();
41
+ if (!voiceId) return null;
42
+ const p = runHeygenJson(
43
+ "heygen",
44
+ [
45
+ ...HEYGEN_CLIENT_SOURCE_ARGV,
46
+ "voice",
47
+ "speech",
48
+ "create",
49
+ "--text",
50
+ intent,
51
+ "--voice-id",
52
+ voiceId,
53
+ ],
54
+ "tts",
55
+ );
56
+ return result(p?.data?.audio_url, p?.data?.duration, "heygen.tts", intent);
57
+ }
@@ -0,0 +1,46 @@
1
+ import { strict as assert } from "node:assert";
2
+ import { chmodSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { test } from "node:test";
6
+ import { heygenTtsGenerate } from "./voice-provider.mjs";
7
+
8
+ test("tags TTS generation but not voice discovery with the media-use client source", async () => {
9
+ const dir = mkdtempSync(join(tmpdir(), "media-use-voice-provider-"));
10
+ const capturePath = join(dir, "argv.log");
11
+ const heygenPath = join(dir, "heygen");
12
+ const previousPath = process.env.PATH;
13
+ const previousCapturePath = process.env.HEYGEN_CAPTURE_PATH;
14
+
15
+ writeFileSync(
16
+ heygenPath,
17
+ `#!/bin/sh
18
+ printf '%s\\n' "$*" >> "$HEYGEN_CAPTURE_PATH"
19
+ case "$*" in
20
+ *"voice list"*) printf '%s\\n' '{"data":[{"voice_id":"voice-123"}]}' ;;
21
+ *"voice speech create"*) printf '%s\\n' '{"data":{"audio_url":"https://example.com/voice.mp3","duration":1.5}}' ;;
22
+ esac
23
+ `,
24
+ );
25
+ chmodSync(heygenPath, 0o755);
26
+ process.env.PATH = `${dir}:${previousPath ?? ""}`;
27
+ process.env.HEYGEN_CAPTURE_PATH = capturePath;
28
+
29
+ try {
30
+ const result = await heygenTtsGenerate("Hello from media-use", {});
31
+ const invocations = readFileSync(capturePath, "utf8").trim().split("\n");
32
+
33
+ assert.equal(invocations.length, 2);
34
+ assert.match(invocations[0], /^voice list /);
35
+ assert.doesNotMatch(invocations[0], /X-HeyGen-Client-Source/);
36
+ assert.match(invocations[1], /voice speech create/);
37
+ assert.match(invocations[1], /X-HeyGen-Client-Source: media-use/);
38
+ assert.equal(result?.url, "https://example.com/voice.mp3");
39
+ } finally {
40
+ if (previousPath === undefined) delete process.env.PATH;
41
+ else process.env.PATH = previousPath;
42
+ if (previousCapturePath === undefined) delete process.env.HEYGEN_CAPTURE_PATH;
43
+ else process.env.HEYGEN_CAPTURE_PATH = previousCapturePath;
44
+ rmSync(dir, { recursive: true, force: true });
45
+ }
46
+ });
@@ -0,0 +1,18 @@
1
+ export function normalizeWords(input) {
2
+ const raw = Array.isArray(input) ? input : Array.isArray(input?.words) ? input.words : [];
3
+ return raw
4
+ .map((w, index) => {
5
+ const text = String(w?.text ?? w?.word ?? "").trim();
6
+ const start = Number(w?.start);
7
+ const end = Number(w?.end);
8
+ if (!text || !Number.isFinite(start) || !Number.isFinite(end)) return null;
9
+ return { id: w?.id ?? `w${index}`, text, start, end };
10
+ })
11
+ .filter(Boolean);
12
+ }
13
+
14
+ export function wordListsFromMediaMeta(input) {
15
+ if (Array.isArray(input) || Array.isArray(input?.words)) return [normalizeWords(input)];
16
+ if (!Array.isArray(input?.voices)) return [];
17
+ return input.voices.map((voice) => normalizeWords(voice)).filter((words) => words.length > 0);
18
+ }
@@ -0,0 +1,70 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Remembered defaults CLI — the lightweight tier of HyperFrames user memory.
4
+ *
5
+ * node prefs.mjs get --hyperframes . [--json]
6
+ * Print the merged view (project `.media/preferences.json` over user
7
+ * `~/.media/preferences.json`), each key with its source and the receipt
8
+ * material (confirmed_in, updated_at).
9
+ *
10
+ * node prefs.mjs record --hyperframes . --key destination --value x-feed [--workflow <w>]
11
+ * Record one confirmed brief answer into the project tier; the same value
12
+ * confirmed in two different projects promotes the key to the user tier.
13
+ *
14
+ * Consumption rules live in hyperframes/references/brief-contract.md § 2
15
+ * (Remembered defaults): a remembered value becomes the recommended option
16
+ * with a receipt — it never skips a question.
17
+ */
18
+ import { parseArgs } from "node:util";
19
+ import { mergedPreferences, recordPreference } from "./lib/prefs-store.mjs";
20
+
21
+ const { values: args, positionals } = parseArgs({
22
+ options: {
23
+ hyperframes: { type: "string", default: "." },
24
+ key: { type: "string" },
25
+ value: { type: "string" },
26
+ workflow: { type: "string" },
27
+ json: { type: "boolean", default: false },
28
+ },
29
+ allowPositionals: true,
30
+ });
31
+
32
+ const verb = positionals[0];
33
+
34
+ function fail(message) {
35
+ console.error(message);
36
+ process.exit(1);
37
+ }
38
+
39
+ if (verb === "get") {
40
+ const merged = mergedPreferences(args.hyperframes);
41
+ if (args.json) {
42
+ console.log(JSON.stringify(merged, null, 2));
43
+ } else if (Object.keys(merged).length === 0) {
44
+ console.log("no remembered preferences yet");
45
+ } else {
46
+ for (const [key, entry] of Object.entries(merged)) {
47
+ console.log(
48
+ `${key} = ${entry.value} (${entry.source}; confirmed in ${entry.confirmed_in.join(", ")})`,
49
+ );
50
+ }
51
+ }
52
+ } else if (verb === "record") {
53
+ if (!args.key || !args.value) fail("record needs --key and --value");
54
+ try {
55
+ const result = recordPreference({
56
+ projectDir: args.hyperframes,
57
+ key: args.key,
58
+ value: args.value,
59
+ workflow: args.workflow,
60
+ });
61
+ const promotion = result.promoted ? "; promoted to user tier" : "";
62
+ console.log(`recorded ${result.key} = ${result.value} (project${promotion})`);
63
+ } catch (err) {
64
+ fail(err instanceof Error ? err.message : String(err));
65
+ }
66
+ } else {
67
+ fail(
68
+ "usage: prefs.mjs <get|record> --hyperframes . [--key <k> --value <v> --workflow <w>] [--json]",
69
+ );
70
+ }
@@ -0,0 +1,103 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Recipes CLI — the heavyweight tier of HyperFrames user memory.
4
+ *
5
+ * node recipe.mjs freeze --hyperframes . --name <n> [--workflow <w>] [--blocks a,b,c]
6
+ * Freeze the current approved run as a named recipe: frame.md + the
7
+ * storyboard skeleton (structure kept, content blanked) + the brief
8
+ * skeleton (when BRIEF.md exists) + the confirmed brief values.
9
+ * The workflow is read from BRIEF.md; --workflow only covers projects
10
+ * briefed before BRIEF.md existed. Re-freezing the same name bumps the
11
+ * version and archives the old folder as <name>@v<N>. Promotes to
12
+ * ~/.media/recipes/ immediately.
13
+ *
14
+ * node recipe.mjs list --hyperframes . [--workflow <w>] [--json]
15
+ * Two-tier merged listing (project wins), newest approval first.
16
+ *
17
+ * node recipe.mjs use --hyperframes . --name <n> [--json]
18
+ * Adopt a recipe: import it from the user tier if needed, copy its
19
+ * frame.md over the project's, and print the brief values + skeleton path.
20
+ *
21
+ * When recipes are offered/consumed is the review loop's and the intent
22
+ * layer's business — see hyperframes/references/review-loop.md § 4 and
23
+ * the intent layer's recipe check (hyperframes SKILL.md § 4).
24
+ */
25
+ import { parseArgs } from "node:util";
26
+ import { freezeRecipe, listRecipes, useRecipe } from "./lib/recipe-store.mjs";
27
+
28
+ const { values: args, positionals } = parseArgs({
29
+ options: {
30
+ hyperframes: { type: "string", default: "." },
31
+ name: { type: "string" },
32
+ workflow: { type: "string" },
33
+ blocks: { type: "string" },
34
+ json: { type: "boolean", default: false },
35
+ },
36
+ allowPositionals: true,
37
+ });
38
+
39
+ const verb = positionals[0];
40
+
41
+ function fail(message) {
42
+ console.error(message);
43
+ process.exit(1);
44
+ }
45
+
46
+ try {
47
+ if (verb === "freeze") {
48
+ if (!args.name) fail("freeze needs --name");
49
+ const frozen = freezeRecipe({
50
+ projectDir: args.hyperframes,
51
+ name: args.name,
52
+ workflow: args.workflow,
53
+ blocks: args.blocks
54
+ ? args.blocks
55
+ .split(",")
56
+ .map((b) => b.trim())
57
+ .filter(Boolean)
58
+ : undefined,
59
+ });
60
+ if (args.json) console.log(JSON.stringify({ ok: true, ...frozen }));
61
+ else {
62
+ console.log(
63
+ `froze recipe ${frozen.slug} (v${frozen.version}, ${frozen.workflow}) → ${frozen.dir}`,
64
+ );
65
+ if (frozen.workflowOverridden)
66
+ console.log(` (BRIEF.md says "${frozen.workflow}" — the --workflow flag was ignored)`);
67
+ if (!frozen.briefSkeleton)
68
+ console.log(" (no BRIEF.md in the project — brief skeleton skipped)");
69
+ }
70
+ } else if (verb === "list") {
71
+ const list = listRecipes({ projectDir: args.hyperframes, workflow: args.workflow });
72
+ if (args.json) console.log(JSON.stringify(list));
73
+ else if (list.length === 0) console.log("no recipes yet");
74
+ else {
75
+ for (const r of list) {
76
+ console.log(
77
+ `${r.name} v${r.version} ${r.workflow} (${r.source}; approved ${String(r.approved_at ?? "?").slice(0, 10)})`,
78
+ );
79
+ }
80
+ }
81
+ } else if (verb === "use") {
82
+ if (!args.name) fail("use needs --name");
83
+ const used = useRecipe({ projectDir: args.hyperframes, name: args.name });
84
+ if (args.json) console.log(JSON.stringify({ ok: true, ...used }));
85
+ else {
86
+ console.log(
87
+ `using recipe ${used.recipe.name} (v${used.recipe.version}, ${used.recipe.workflow})`,
88
+ );
89
+ console.log(` frame spec → ${used.frameSpecPath} (copied over)`);
90
+ console.log(` storyboard skeleton → ${used.skeletonPath}`);
91
+ if (used.briefSkeletonPath) console.log(` brief skeleton → ${used.briefSkeletonPath}`);
92
+ for (const key of ["destination", "aspect", "language", "voice", "style_preset"]) {
93
+ if (used.recipe[key]) console.log(` ${key}: ${used.recipe[key]}`);
94
+ }
95
+ }
96
+ } else {
97
+ fail(
98
+ "usage: recipe.mjs <freeze|list|use> --hyperframes . [--name <n>] [--workflow <w>] [--blocks a,b] [--json]",
99
+ );
100
+ }
101
+ } catch (err) {
102
+ fail(err instanceof Error ? err.message : String(err));
103
+ }