dsh-hyperframes 0.10.1 → 0.11.0

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 (113) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.en.md +4 -3
  3. package/README.md +4 -3
  4. package/docs/WORKBENCH.md +6 -0
  5. package/lib/client.js +40 -2
  6. package/lib/media-templates.d.ts +1 -1
  7. package/lib/media-templates.js +1 -1
  8. package/package.json +2 -2
  9. package/plugin.json +1 -1
  10. package/skills/NOTICE.md +1 -1
  11. package/skills/UPSTREAM.json +93 -8
  12. package/skills/embedded-captions/SKILL.md +1 -1
  13. package/skills/faceless-explainer/SKILL.md +1 -1
  14. package/skills/general-video/SKILL.md +1 -1
  15. package/skills/hyperframes-animation/SKILL.md +1 -1
  16. package/skills/hyperframes-audio/SKILL.md +1 -1
  17. package/skills/hyperframes-core/SKILL.md +1 -1
  18. package/skills/hyperframes-creative/SKILL.md +1 -1
  19. package/skills/hyperframes-keyframes/SKILL.md +1 -1
  20. package/skills/hyperframes-studio/SKILL.md +1 -1
  21. package/skills/media-use/scripts/lib/adopt.mjs +128 -0
  22. package/skills/media-use/scripts/lib/adopt.test.mjs +95 -0
  23. package/skills/media-use/scripts/lib/bgm-provider.mjs +20 -0
  24. package/skills/media-use/scripts/lib/brand-provider.mjs +59 -0
  25. package/skills/media-use/scripts/lib/bundled-sfx-provider.mjs +119 -0
  26. package/skills/media-use/scripts/lib/bundled-sfx-provider.test.mjs +139 -0
  27. package/skills/media-use/scripts/lib/cache.mjs +162 -0
  28. package/skills/media-use/scripts/lib/cache.test.mjs +110 -0
  29. package/skills/media-use/scripts/lib/candidates.mjs +91 -0
  30. package/skills/media-use/scripts/lib/candidates.test.mjs +155 -0
  31. package/skills/media-use/scripts/lib/codex-provider.mjs +161 -0
  32. package/skills/media-use/scripts/lib/codex-provider.test.mjs +34 -0
  33. package/skills/media-use/scripts/lib/config-lock.d.mts +3 -0
  34. package/skills/media-use/scripts/lib/config-lock.test.mjs +79 -0
  35. package/skills/media-use/scripts/lib/coverage.test.mjs +136 -0
  36. package/skills/media-use/scripts/lib/cube-build.mjs +181 -0
  37. package/skills/media-use/scripts/lib/cube-build.test.mjs +80 -0
  38. package/skills/media-use/scripts/lib/cube-validate.mjs +197 -0
  39. package/skills/media-use/scripts/lib/cube-validate.test.mjs +125 -0
  40. package/skills/media-use/scripts/lib/cutlist.test.mjs +174 -0
  41. package/skills/media-use/scripts/lib/duck.test.mjs +319 -0
  42. package/skills/media-use/scripts/lib/error-diffusion.test.mjs +102 -0
  43. package/skills/media-use/scripts/lib/ff-binaries.mjs +37 -0
  44. package/skills/media-use/scripts/lib/freeze.mjs +72 -0
  45. package/skills/media-use/scripts/lib/freeze.test.mjs +138 -0
  46. package/skills/media-use/scripts/lib/grade-analyzer.mjs +277 -0
  47. package/skills/media-use/scripts/lib/grade-analyzer.test.mjs +285 -0
  48. package/skills/media-use/scripts/lib/heygen-cli.mjs +208 -0
  49. package/skills/media-use/scripts/lib/heygen-cli.test.mjs +315 -0
  50. package/skills/media-use/scripts/lib/heygen-search.mjs +45 -0
  51. package/skills/media-use/scripts/lib/heygen-search.test.mjs +49 -0
  52. package/skills/media-use/scripts/lib/heygen-video-provider.mjs +138 -0
  53. package/skills/media-use/scripts/lib/heygen-video-provider.test.mjs +435 -0
  54. package/skills/media-use/scripts/lib/image-provider.mjs +44 -0
  55. package/skills/media-use/scripts/lib/local-media-search.mjs +32 -0
  56. package/skills/media-use/scripts/lib/local-models.mjs +318 -0
  57. package/skills/media-use/scripts/lib/local-models.test.mjs +220 -0
  58. package/skills/media-use/scripts/lib/local-run.mjs +78 -0
  59. package/skills/media-use/scripts/lib/local-run.test.mjs +121 -0
  60. package/skills/media-use/scripts/lib/logo-provider.mjs +244 -0
  61. package/skills/media-use/scripts/lib/logo-provider.test.mjs +298 -0
  62. package/skills/media-use/scripts/lib/ltx-video-provider.mjs +94 -0
  63. package/skills/media-use/scripts/lib/ltx-video-provider.test.mjs +316 -0
  64. package/skills/media-use/scripts/lib/lut-preset-provider.mjs +271 -0
  65. package/skills/media-use/scripts/lib/lut-preset-provider.test.mjs +199 -0
  66. package/skills/media-use/scripts/lib/main-module.mjs +13 -0
  67. package/skills/media-use/scripts/lib/manifest.d.mts +20 -0
  68. package/skills/media-use/scripts/lib/manifest.test.mjs +397 -0
  69. package/skills/media-use/scripts/lib/match.mjs +47 -0
  70. package/skills/media-use/scripts/lib/media-fetch.test.mjs +197 -0
  71. package/skills/media-use/scripts/lib/media-home.test.mjs +61 -0
  72. package/skills/media-use/scripts/lib/media-search.d.mts +9 -0
  73. package/skills/media-use/scripts/lib/media-search.mjs +107 -0
  74. package/skills/media-use/scripts/lib/mflux-provider.mjs +128 -0
  75. package/skills/media-use/scripts/lib/mflux-provider.test.mjs +174 -0
  76. package/skills/media-use/scripts/lib/misses.mjs +49 -0
  77. package/skills/media-use/scripts/lib/misses.test.mjs +89 -0
  78. package/skills/media-use/scripts/lib/npx-sync.mjs +2 -28
  79. package/skills/media-use/scripts/lib/npx-sync.test.mjs +50 -0
  80. package/skills/media-use/scripts/lib/openverse.mjs +134 -0
  81. package/skills/media-use/scripts/lib/openverse.test.mjs +219 -0
  82. package/skills/media-use/scripts/lib/parakeet-words.test.mjs +44 -0
  83. package/skills/media-use/scripts/lib/prefs-store.concurrent.test.mjs +173 -0
  84. package/skills/media-use/scripts/lib/prefs-store.test.mjs +245 -0
  85. package/skills/media-use/scripts/lib/probe.mjs +46 -0
  86. package/skills/media-use/scripts/lib/probe.test.mjs +221 -0
  87. package/skills/media-use/scripts/lib/providers.mjs +5 -0
  88. package/skills/media-use/scripts/lib/recipe-store.test.mjs +391 -0
  89. package/skills/media-use/scripts/lib/registry.mjs +214 -0
  90. package/skills/media-use/scripts/lib/registry.test.mjs +262 -0
  91. package/skills/media-use/scripts/lib/search.mjs +24 -0
  92. package/skills/media-use/scripts/lib/search.test.mjs +32 -0
  93. package/skills/media-use/scripts/lib/sfx-provider.mjs +33 -0
  94. package/skills/media-use/scripts/lib/specs.mjs +132 -0
  95. package/skills/media-use/scripts/lib/specs.test.mjs +157 -0
  96. package/skills/media-use/scripts/lib/stats.mjs +117 -0
  97. package/skills/media-use/scripts/lib/stats.test.mjs +165 -0
  98. package/skills/media-use/scripts/lib/svg-sanitize.mjs +21 -0
  99. package/skills/media-use/scripts/lib/telemetry.test.mjs +437 -53
  100. package/skills/media-use/scripts/lib/test-home.test.mjs +10 -0
  101. package/skills/media-use/scripts/lib/transcriptCutFade.test.mjs +33 -0
  102. package/skills/media-use/scripts/lib/tts-local-provider.mjs +94 -0
  103. package/skills/media-use/scripts/lib/tts-local-provider.test.mjs +72 -0
  104. package/skills/media-use/scripts/lib/usage.mjs +57 -0
  105. package/skills/media-use/scripts/lib/usage.test.mjs +54 -0
  106. package/skills/media-use/scripts/lib/voice-provider.mjs +57 -0
  107. package/skills/media-use/scripts/lib/voice-provider.test.mjs +46 -0
  108. package/skills/media-use/scripts/resolve.mjs +1360 -27
  109. package/skills/motion-graphics/SKILL.md +1 -1
  110. package/skills/music-to-video/SKILL.md +1 -1
  111. package/skills/pr-to-video/SKILL.md +1 -1
  112. package/skills/product-launch-video/SKILL.md +1 -1
  113. package/skills/talking-head-recut/SKILL.md +1 -1
@@ -0,0 +1,117 @@
1
+ import { statSync } from "node:fs";
2
+ import { readGlobalManifest } from "./cache.mjs";
3
+ import { readManifest } from "./manifest.mjs";
4
+ import { readMisses } from "./misses.mjs";
5
+
6
+ const TOP_MISSES = 5;
7
+
8
+ function emptyReport() {
9
+ return {
10
+ total_resolves: 0,
11
+ by_type: {},
12
+ by_source: {},
13
+ by_provider: {},
14
+ by_via: {},
15
+ misses: 0,
16
+ hit_rate: null,
17
+ top_missed_intents: {},
18
+ global_cache_assets: 0,
19
+ global_cache_disk_bytes: 0,
20
+ cross_project_reuse: 0,
21
+ };
22
+ }
23
+
24
+ function increment(map, key) {
25
+ if (!key) return;
26
+ map[key] = (map[key] || 0) + 1;
27
+ }
28
+
29
+ function timestampOf(record) {
30
+ return record?.ts || record?.timestamp || record?.created_at || record?.createdAt || null;
31
+ }
32
+
33
+ function inWindow(record, cutoff) {
34
+ if (!cutoff) return true;
35
+ const ts = timestampOf(record);
36
+ // Older manifest records may not carry a timestamp; keep them in the report
37
+ // because --days can only window records/misses that carry a ts/timestamp.
38
+ if (!ts) return true;
39
+ const time = Date.parse(ts);
40
+ return Number.isNaN(time) ? true : time >= cutoff;
41
+ }
42
+
43
+ function sourceOf(record) {
44
+ return record?._source || record?.source || record?.provenance?.source || "unknown";
45
+ }
46
+
47
+ function normalizeIntent(intent) {
48
+ return String(intent ?? "")
49
+ .trim()
50
+ .toLowerCase()
51
+ .replace(/\s+/g, " ");
52
+ }
53
+
54
+ function topMissedIntents(misses) {
55
+ const grouped = {};
56
+ for (const miss of misses) {
57
+ const type = miss?.type || "unknown";
58
+ const intent = normalizeIntent(miss?.intent);
59
+ if (!intent) continue;
60
+ grouped[type] ||= {};
61
+ grouped[type][intent] = (grouped[type][intent] || 0) + 1;
62
+ }
63
+ const out = {};
64
+ for (const [type, intents] of Object.entries(grouped)) {
65
+ out[type] = Object.entries(intents)
66
+ .map(([intent, count]) => ({ intent, count }))
67
+ .sort((a, b) => b.count - a.count || a.intent.localeCompare(b.intent))
68
+ .slice(0, TOP_MISSES);
69
+ }
70
+ return out;
71
+ }
72
+
73
+ function diskBytes(records) {
74
+ let total = 0;
75
+ for (const record of records) {
76
+ const p = record?.cached_path || record?.path;
77
+ if (!p) continue;
78
+ try {
79
+ total += statSync(p).size;
80
+ } catch {
81
+ // cache entries can outlive files; stats skips missing files
82
+ }
83
+ }
84
+ return total;
85
+ }
86
+
87
+ export function buildStats({ projectDir, days, now = Date.now() } = {}) {
88
+ // Only a positive finite --days windows the report; null / NaN / <= 0 mean
89
+ // "all time" rather than silently excluding everything (a negative cutoff
90
+ // would land in the future and drop every record). The reads below are each
91
+ // best-effort (they return [] / skip on IO errors), so there is no top-level
92
+ // catch masking a real logic bug as an all-zero "no usage" report.
93
+ const n = Number(days);
94
+ const cutoff = Number.isFinite(n) && n > 0 ? Number(now) - n * 24 * 60 * 60 * 1000 : null;
95
+ const records = (projectDir ? readManifest(projectDir) : []).filter((r) => inWindow(r, cutoff));
96
+ const misses = readMisses().filter((miss) => inWindow(miss, cutoff));
97
+ const globalRecords = readGlobalManifest();
98
+ const report = emptyReport();
99
+
100
+ report.total_resolves = records.length;
101
+ report.misses = misses.length;
102
+ for (const record of records) {
103
+ increment(report.by_type, record?.type || "unknown");
104
+ increment(report.by_source, sourceOf(record));
105
+ increment(report.by_provider, record?.provenance?.provider);
106
+ increment(report.by_via, record?.provenance?.via);
107
+ }
108
+
109
+ const attempts = report.total_resolves + report.misses;
110
+ report.hit_rate = attempts === 0 ? null : report.total_resolves / attempts;
111
+ report.top_missed_intents = topMissedIntents(misses);
112
+ report.global_cache_assets = globalRecords.length;
113
+ report.global_cache_disk_bytes = diskBytes(globalRecords);
114
+ report.cross_project_reuse = globalRecords.filter((r) => r?.provenance?.reused_by).length;
115
+
116
+ return report;
117
+ }
@@ -0,0 +1,165 @@
1
+ import { strict as assert } from "node:assert";
2
+ import { test } from "node:test";
3
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
4
+ import { tmpdir } from "node:os";
5
+ import { join } from "node:path";
6
+ import { buildStats } from "./stats.mjs";
7
+
8
+ function sandbox() {
9
+ const root = mkdtempSync(join(tmpdir(), "mu-stats-"));
10
+ const home = join(root, "home");
11
+ const projectDir = join(root, "project");
12
+ mkdirSync(home, { recursive: true });
13
+ mkdirSync(projectDir, { recursive: true });
14
+ process.env.HYPERFRAMES_MEDIA_HOME = home;
15
+ return { root, home, projectDir };
16
+ }
17
+
18
+ function restoreEnv(saved) {
19
+ for (const k of Object.keys(process.env)) if (!(k in saved)) delete process.env[k];
20
+ Object.assign(process.env, saved);
21
+ }
22
+
23
+ function seedManifest(dir, records) {
24
+ mkdirSync(join(dir, ".media"), { recursive: true });
25
+ writeFileSync(
26
+ join(dir, ".media/manifest.jsonl"),
27
+ records.map((record) => JSON.stringify(record)).join("\n") + "\n",
28
+ );
29
+ }
30
+
31
+ function isoDaysAgo(days) {
32
+ return new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString();
33
+ }
34
+
35
+ test("buildStats aggregates resolves, misses, providers, sources, and reuse", () => {
36
+ const savedEnv = { ...process.env };
37
+ const { root, home, projectDir } = sandbox();
38
+ try {
39
+ seedManifest(projectDir, [
40
+ {
41
+ id: "bgm_001",
42
+ type: "bgm",
43
+ source: "search",
44
+ ts: isoDaysAgo(0),
45
+ provenance: { provider: "heygen.audio.sounds", prompt: "upbeat", via: "url" },
46
+ },
47
+ {
48
+ id: "image_001",
49
+ type: "image",
50
+ _source: "reused-explicit",
51
+ timestamp: isoDaysAgo(0),
52
+ provenance: { provider: "local", reused_by: "agent" },
53
+ },
54
+ {
55
+ id: "bgm_002",
56
+ type: "bgm",
57
+ source: "generated",
58
+ provenance: { provider: "codex" },
59
+ },
60
+ ]);
61
+ const cachedFile = join(home, ".media/mu-v1-cache/asset.wav");
62
+ mkdirSync(join(cachedFile, ".."), { recursive: true });
63
+ writeFileSync(cachedFile, "12345");
64
+ seedManifest(home, [
65
+ {
66
+ id: "bgm_009",
67
+ type: "bgm",
68
+ reusable: true,
69
+ cached_path: cachedFile,
70
+ provenance: { provider: "heygen.audio.sounds", reused_by: "agent" },
71
+ },
72
+ ]);
73
+ writeFileSync(
74
+ join(home, ".media/misses.jsonl"),
75
+ JSON.stringify({
76
+ ts: isoDaysAgo(0),
77
+ type: "bgm",
78
+ intent: "dark cinematic riser",
79
+ }) + "\n",
80
+ { flag: "a" },
81
+ );
82
+
83
+ const stats = buildStats({ projectDir });
84
+ assert.equal(stats.total_resolves, 3);
85
+ assert.deepEqual(stats.by_type, { bgm: 2, image: 1 });
86
+ assert.deepEqual(stats.by_source, { search: 1, "reused-explicit": 1, generated: 1 });
87
+ assert.equal(stats.by_provider["heygen.audio.sounds"], 1);
88
+ assert.equal(stats.by_via.url, 1);
89
+ assert.equal(stats.misses, 1);
90
+ assert.equal(stats.hit_rate, 0.75);
91
+ assert.deepEqual(stats.top_missed_intents.bgm[0], {
92
+ intent: "dark cinematic riser",
93
+ count: 1,
94
+ });
95
+ assert.equal(stats.global_cache_assets, 1);
96
+ assert.equal(stats.global_cache_disk_bytes, 5);
97
+ assert.equal(stats.cross_project_reuse, 1);
98
+ } finally {
99
+ restoreEnv(savedEnv);
100
+ rmSync(root, { recursive: true, force: true });
101
+ }
102
+ });
103
+
104
+ test("buildStats returns a zeroed report when nothing exists", () => {
105
+ const savedEnv = { ...process.env };
106
+ const { root, projectDir } = sandbox();
107
+ try {
108
+ const stats = buildStats({ projectDir });
109
+ assert.equal(stats.total_resolves, 0);
110
+ assert.deepEqual(stats.by_type, {});
111
+ assert.deepEqual(stats.by_source, {});
112
+ assert.deepEqual(stats.by_provider, {});
113
+ assert.deepEqual(stats.by_via, {});
114
+ assert.equal(stats.misses, 0);
115
+ assert.equal(stats.hit_rate, null);
116
+ assert.deepEqual(stats.top_missed_intents, {});
117
+ assert.equal(stats.global_cache_assets, 0);
118
+ assert.equal(stats.global_cache_disk_bytes, 0);
119
+ assert.equal(stats.cross_project_reuse, 0);
120
+ } finally {
121
+ restoreEnv(savedEnv);
122
+ rmSync(root, { recursive: true, force: true });
123
+ }
124
+ });
125
+
126
+ test("buildStats returns JSON-safe output", () => {
127
+ const savedEnv = { ...process.env };
128
+ const { root, projectDir } = sandbox();
129
+ try {
130
+ const stats = buildStats({ projectDir });
131
+ assert.deepEqual(JSON.parse(JSON.stringify(stats)), stats);
132
+ } finally {
133
+ restoreEnv(savedEnv);
134
+ rmSync(root, { recursive: true, force: true });
135
+ }
136
+ });
137
+
138
+ test("buildStats applies days window to timestamped records and misses", () => {
139
+ const savedEnv = { ...process.env };
140
+ const { root, home, projectDir } = sandbox();
141
+ try {
142
+ seedManifest(projectDir, [
143
+ { id: "bgm_old", type: "bgm", ts: isoDaysAgo(100), provenance: { provider: "old" } },
144
+ { id: "bgm_new", type: "bgm", ts: isoDaysAgo(1), provenance: { provider: "new" } },
145
+ { id: "image_untimed", type: "image", provenance: { provider: "none" } },
146
+ ]);
147
+ mkdirSync(join(home, ".media"), { recursive: true });
148
+ writeFileSync(
149
+ join(home, ".media/misses.jsonl"),
150
+ [
151
+ JSON.stringify({ ts: isoDaysAgo(100), type: "bgm", intent: "old miss" }),
152
+ JSON.stringify({ ts: isoDaysAgo(1), type: "bgm", intent: "new miss" }),
153
+ ].join("\n"),
154
+ );
155
+
156
+ const stats = buildStats({ projectDir, days: 7 });
157
+ assert.equal(stats.total_resolves, 2);
158
+ assert.deepEqual(stats.by_type, { bgm: 1, image: 1 });
159
+ assert.equal(stats.misses, 1);
160
+ assert.equal(stats.top_missed_intents.bgm[0].intent, "new miss");
161
+ } finally {
162
+ restoreEnv(savedEnv);
163
+ rmSync(root, { recursive: true, force: true });
164
+ }
165
+ });