miriad-viz 0.3.5 → 0.4.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 (64) hide show
  1. package/dist-cli/chunk-3NPPXJ6N.js +50 -0
  2. package/dist-cli/chunk-BD2KEZI4.js +38 -0
  3. package/dist-cli/chunk-SKRQW7PY.js +58 -0
  4. package/dist-cli/{chunk-KY56F5QU.js → chunk-X2MHAVAE.js} +60 -28
  5. package/dist-cli/{curate-CN2LX2QE.js → curate-7VM5QLE2.js} +4 -4
  6. package/dist-cli/{extract-ER32ZVS5.js → extract-7SPUMODE.js} +1 -1
  7. package/dist-cli/from-pacing-chain-J22MLDQP.js +197 -0
  8. package/dist-cli/index.js +556 -332
  9. package/dist-cli/pacing-FZQ6ZKHP.js +8 -0
  10. package/dist-cli/{preview-E36FRG4G.js → preview-QFLKQHQA.js} +1 -1
  11. package/dist-cli/read-audio-durations-TIGU4A7K.js +43 -0
  12. package/dist-cli/{render-SMXMJ6TV.js → render-F5W4QY3Q.js} +1 -1
  13. package/dist-cli/scaffold-pacing-PSJNXQ2Q.js +50 -0
  14. package/dist-cli/script-V3LKE4ZU.js +8 -0
  15. package/dist-cli/{timeline-7O4LOYMP.js → timeline-A7HQN2XO.js} +7 -0
  16. package/dist-cli/{transform-CQHC2WV2.js → transform-BO6MECNO.js} +88 -5
  17. package/dist-lib/{chunk-KEDL5TGW.cjs → chunk-AQ5ZU7P2.cjs} +17 -10
  18. package/dist-lib/chunk-AQ5ZU7P2.cjs.map +1 -0
  19. package/dist-lib/{chunk-RKGGKGNS.js → chunk-B6WP6SLB.js} +11 -2
  20. package/dist-lib/chunk-B6WP6SLB.js.map +1 -0
  21. package/dist-lib/{chunk-6IXCK4U7.cjs → chunk-IHAAQH6X.cjs} +11 -2
  22. package/dist-lib/chunk-IHAAQH6X.cjs.map +1 -0
  23. package/dist-lib/{chunk-X7NUVUHN.cjs → chunk-LJG3H4FA.cjs} +30 -31
  24. package/dist-lib/chunk-LJG3H4FA.cjs.map +1 -0
  25. package/dist-lib/{chunk-AF5HJJGT.js → chunk-TCRXL725.js} +24 -25
  26. package/dist-lib/chunk-TCRXL725.js.map +1 -0
  27. package/dist-lib/{chunk-3AG7TGON.js → chunk-ZMLFTPCN.js} +13 -6
  28. package/dist-lib/chunk-ZMLFTPCN.js.map +1 -0
  29. package/dist-lib/{frame-state-eeHrDxl-.d.ts → frame-state-DGL9dfEW.d.cts} +6 -0
  30. package/dist-lib/{frame-state-eeHrDxl-.d.cts → frame-state-DGL9dfEW.d.ts} +6 -0
  31. package/dist-lib/index.cjs +149 -16
  32. package/dist-lib/index.cjs.map +1 -1
  33. package/dist-lib/index.d.cts +99 -4
  34. package/dist-lib/index.d.ts +99 -4
  35. package/dist-lib/index.js +130 -3
  36. package/dist-lib/index.js.map +1 -1
  37. package/dist-lib/{layout-Cc23UM-j.d.cts → layout-C2wb71ib.d.cts} +12 -0
  38. package/dist-lib/{layout-Cc23UM-j.d.ts → layout-C2wb71ib.d.ts} +12 -0
  39. package/dist-lib/renderer/index.cjs +11 -11
  40. package/dist-lib/renderer/index.d.cts +2 -2
  41. package/dist-lib/renderer/index.d.ts +2 -2
  42. package/dist-lib/renderer/index.js +2 -2
  43. package/dist-lib/timing-jVmHotfy.d.cts +59 -0
  44. package/dist-lib/timing-jVmHotfy.d.ts +59 -0
  45. package/dist-lib/viewer/exports.cjs +355 -152
  46. package/dist-lib/viewer/exports.cjs.map +1 -1
  47. package/dist-lib/viewer/exports.d.cts +96 -2
  48. package/dist-lib/viewer/exports.d.ts +96 -2
  49. package/dist-lib/viewer/exports.js +311 -110
  50. package/dist-lib/viewer/exports.js.map +1 -1
  51. package/docs/miriad-viz-curation.md +9 -9
  52. package/docs/miriad-viz-pipeline.md +61 -32
  53. package/docs/miriad-viz-script-writing.md +142 -0
  54. package/docs/miriad-viz-sound-design.md +207 -0
  55. package/docs/miriad-viz-viz-timing.md +190 -0
  56. package/docs/miriad-viz-voice-generation.md +154 -0
  57. package/docs/miriad-viz.md +8 -4
  58. package/package.json +2 -1
  59. package/dist-lib/chunk-3AG7TGON.js.map +0 -1
  60. package/dist-lib/chunk-6IXCK4U7.cjs.map +0 -1
  61. package/dist-lib/chunk-AF5HJJGT.js.map +0 -1
  62. package/dist-lib/chunk-KEDL5TGW.cjs.map +0 -1
  63. package/dist-lib/chunk-RKGGKGNS.js.map +0 -1
  64. package/dist-lib/chunk-X7NUVUHN.cjs.map +0 -1
@@ -0,0 +1,50 @@
1
+ // src/types/pacing.ts
2
+ import { z } from "zod";
3
+ var PacingLineSchema = z.object({
4
+ /** Line ID — must match script.json line ID */
5
+ id: z.string().min(1),
6
+ /** Text from script (read-only, for context while editing pacing) */
7
+ text: z.string().min(1),
8
+ /** Audio clip duration in seconds (measured from file, read-only) */
9
+ clipDuration: z.number().positive(),
10
+ /** Silence after this line in seconds (overrides defaultPauseSec) */
11
+ pauseAfter: z.number().nonnegative().optional(),
12
+ /** Viz speed multiplier DURING this clip's audio. >1 = faster, <1 = slower */
13
+ vizSpeed: z.number().positive().optional(),
14
+ /** Viz speed multiplier DURING the pause after this clip (defaults to defaultVizSpeed) */
15
+ gapVizSpeed: z.number().positive().optional(),
16
+ /** Agent's reasoning for pacing choices (human reads during review) */
17
+ note: z.string().optional()
18
+ });
19
+ var PacingFileSchema = z.object({
20
+ /** Schema version — always 1 for now */
21
+ version: z.literal(1),
22
+ /** Seconds of viz playback before first narration line */
23
+ leadInSec: z.number().nonnegative().optional(),
24
+ /** Seconds of viz playback after last narration line */
25
+ tailOutSec: z.number().nonnegative().optional(),
26
+ /** Default pause between lines in seconds (used when pauseAfter not set) */
27
+ defaultPauseSec: z.number().nonnegative(),
28
+ /** Default viz speed multiplier (used when vizSpeed/gapVizSpeed not set) */
29
+ defaultVizSpeed: z.number().positive(),
30
+ /** Ordered list of pacing lines — must match script.json order */
31
+ lines: z.array(PacingLineSchema).min(1)
32
+ }).superRefine((data, ctx) => {
33
+ const ids = /* @__PURE__ */ new Set();
34
+ for (let i = 0; i < data.lines.length; i++) {
35
+ const id = data.lines[i].id;
36
+ if (ids.has(id)) {
37
+ ctx.addIssue({
38
+ code: z.ZodIssueCode.custom,
39
+ message: `Duplicate line ID: "${id}" (line index ${i})`,
40
+ path: ["lines", i, "id"]
41
+ });
42
+ }
43
+ ids.add(id);
44
+ }
45
+ });
46
+
47
+ export {
48
+ PacingLineSchema,
49
+ PacingFileSchema
50
+ };
@@ -0,0 +1,38 @@
1
+ // src/types/script.ts
2
+ import { z } from "zod";
3
+ var ScriptLineSchema = z.object({
4
+ /** Unique identifier for this line (e.g., 'narrator-01', 'lead-03') */
5
+ id: z.string().min(1),
6
+ /** Who speaks this line (e.g., 'narrator', 'lead', 'snorre') */
7
+ speaker: z.string().min(1),
8
+ /** The spoken text */
9
+ text: z.string().min(1),
10
+ /** Rendering style: narrator voice (cursive) or agent quote (normal) */
11
+ style: z.enum(["narrator", "quote"]),
12
+ /** Optional phase this line belongs to (for grouping in the viz) */
13
+ phaseId: z.string().optional()
14
+ });
15
+ var ScriptFileSchema = z.object({
16
+ /** Schema version — always 1 for now */
17
+ version: z.literal(1),
18
+ /** Ordered list of script lines */
19
+ lines: z.array(ScriptLineSchema).min(1)
20
+ }).superRefine((data, ctx) => {
21
+ const ids = /* @__PURE__ */ new Set();
22
+ for (let i = 0; i < data.lines.length; i++) {
23
+ const id = data.lines[i].id;
24
+ if (ids.has(id)) {
25
+ ctx.addIssue({
26
+ code: z.ZodIssueCode.custom,
27
+ message: `Duplicate line ID: "${id}" (line index ${i})`,
28
+ path: ["lines", i, "id"]
29
+ });
30
+ }
31
+ ids.add(id);
32
+ }
33
+ });
34
+
35
+ export {
36
+ ScriptLineSchema,
37
+ ScriptFileSchema
38
+ };
@@ -0,0 +1,58 @@
1
+ // src/types/timing.ts
2
+ import { z } from "zod";
3
+ var TimedLineSchema = z.object({
4
+ /** Line ID — matches script.json and pacing.json */
5
+ id: z.string().min(1),
6
+ /** Speaker identifier (e.g., 'narrator', 'lead', 'snorre') */
7
+ speaker: z.string().min(1),
8
+ /** The spoken text */
9
+ text: z.string().min(1),
10
+ /** Rendering style: narrator voice (cursive) or agent quote (normal) */
11
+ style: z.enum(["narrator", "quote"]),
12
+ /** Wall-clock start time in seconds from video start */
13
+ startSec: z.number().nonnegative(),
14
+ /** Audio clip duration in seconds */
15
+ durationSec: z.number().positive(),
16
+ /** Pause after this line in seconds (resolved from defaults) */
17
+ pauseAfterSec: z.number().nonnegative(),
18
+ /** Audio file path relative to audio directory (e.g., 'narrator-01.mp3') */
19
+ audioFile: z.string().min(1),
20
+ /** Viz progress at clip start [0, 1] */
21
+ progressStart: z.number().min(0).max(1),
22
+ /** Viz progress at clip end [0, 1] */
23
+ progressEnd: z.number().min(0).max(1),
24
+ /** Viz speed multiplier during this clip */
25
+ vizSpeed: z.number().positive(),
26
+ /** Viz speed multiplier during the pause after this clip */
27
+ gapVizSpeed: z.number().positive(),
28
+ /** Phase this line belongs to (optional) */
29
+ phase: z.string().optional()
30
+ }).refine((line) => line.progressStart <= line.progressEnd, {
31
+ message: "progressStart must be <= progressEnd",
32
+ path: ["progressStart"]
33
+ });
34
+ var TimingFileSchema = z.object({
35
+ /** Schema version — always 1 for now */
36
+ version: z.literal(1),
37
+ /** Total video duration in seconds */
38
+ totalDurationSec: z.number().positive(),
39
+ /** Ordered list of timed lines — fully resolved, all fields required */
40
+ lines: z.array(TimedLineSchema).min(1)
41
+ }).superRefine((data, ctx) => {
42
+ const ids = /* @__PURE__ */ new Set();
43
+ for (let i = 0; i < data.lines.length; i++) {
44
+ const id = data.lines[i].id;
45
+ if (ids.has(id)) {
46
+ ctx.addIssue({
47
+ code: z.ZodIssueCode.custom,
48
+ message: `Duplicate line ID: "${id}" (line index ${i})`,
49
+ path: ["lines", i, "id"]
50
+ });
51
+ }
52
+ ids.add(id);
53
+ }
54
+ });
55
+
56
+ export {
57
+ TimingFileSchema
58
+ };
@@ -1,25 +1,26 @@
1
1
  // src/cli/guided/types.ts
2
- var STEPS = [
2
+ var STEPS_V2 = [
3
3
  "init",
4
4
  "extract",
5
- "curate",
6
- "transform",
7
- "preview",
8
- "audio",
5
+ "script",
6
+ "voices",
7
+ "viz-timing",
8
+ "sound-design",
9
9
  "render"
10
10
  ];
11
+ var STEPS = STEPS_V2;
11
12
  function createProgressFile(project) {
12
13
  const now = (/* @__PURE__ */ new Date()).toISOString();
13
14
  return {
14
- version: 1,
15
+ version: 2,
15
16
  project,
16
17
  steps: {
17
18
  init: { status: "complete", completedAt: now },
18
19
  extract: { status: "pending" },
19
- curate: { status: "pending" },
20
- transform: { status: "pending" },
21
- preview: { status: "pending" },
22
- audio: { status: "pending" },
20
+ script: { status: "pending" },
21
+ voices: { status: "pending" },
22
+ "viz-timing": { status: "pending" },
23
+ "sound-design": { status: "pending" },
23
24
  render: { status: "pending" }
24
25
  },
25
26
  lastUpdated: now
@@ -33,6 +34,34 @@ function getNextStep(progress) {
33
34
  }
34
35
  return null;
35
36
  }
37
+ function migrateV1toV2(v1) {
38
+ const now = (/* @__PURE__ */ new Date()).toISOString();
39
+ const init = v1.steps.init ?? { status: "pending" };
40
+ const extract = v1.steps.extract ?? { status: "pending" };
41
+ const render = v1.steps.render ?? { status: "pending" };
42
+ const script = v1.steps.curate ?? { status: "pending" };
43
+ const voices = v1.steps.audio ?? { status: "pending" };
44
+ const transformComplete = v1.steps.transform?.status === "complete";
45
+ const previewComplete = v1.steps.preview?.status === "complete";
46
+ const bothComplete = transformComplete && previewComplete;
47
+ const vizTiming = bothComplete ? { status: "complete", completedAt: v1.steps.transform?.completedAt ?? now } : { status: "pending" };
48
+ const soundDesign = bothComplete ? { status: "complete", completedAt: v1.steps.preview?.completedAt ?? now } : { status: "pending" };
49
+ return {
50
+ version: 2,
51
+ project: v1.project,
52
+ extractConfig: v1.extractConfig,
53
+ steps: {
54
+ init,
55
+ extract,
56
+ script,
57
+ voices,
58
+ "viz-timing": vizTiming,
59
+ "sound-design": soundDesign,
60
+ render
61
+ },
62
+ lastUpdated: now
63
+ };
64
+ }
36
65
  var STEP_CONFIG = {
37
66
  init: {
38
67
  mode: "inline",
@@ -45,30 +74,27 @@ var STEP_CONFIG = {
45
74
  logFile: "extract.log",
46
75
  outputFiles: ["data/git-commits.json", "data/chat-activity.json"]
47
76
  },
48
- curate: {
77
+ script: {
49
78
  mode: "inline",
50
- label: "Curate editorial content",
79
+ label: "Write narrative script",
51
80
  creative: true,
52
- outputFiles: [
53
- "data/retro-page-data.json",
54
- "data/retro-page-quotes.json",
55
- "data/timeline-events.json"
56
- ]
81
+ outputFiles: ["data/script.json"]
57
82
  },
58
- transform: {
83
+ voices: {
59
84
  mode: "inline",
60
- label: "Transform to viz-data",
61
- outputFiles: ["output/viz-data.json"]
85
+ label: "Generate voices (TTS)",
86
+ creative: true,
87
+ outputFiles: []
62
88
  },
63
- preview: {
89
+ "viz-timing": {
64
90
  mode: "inline",
65
- label: "Preview visualization",
91
+ label: "Set visualization timing",
66
92
  creative: true,
67
- outputFiles: ["viewer/public/data/viz-data.json"]
93
+ outputFiles: ["output/timing.json"]
68
94
  },
69
- audio: {
95
+ "sound-design": {
70
96
  mode: "inline",
71
- label: "Generate audio (ElevenLabs)",
97
+ label: "Sound design (SFX + music)",
72
98
  creative: true,
73
99
  outputFiles: []
74
100
  },
@@ -108,10 +134,16 @@ function readProgress(dir) {
108
134
  if (!existsSync(path)) return null;
109
135
  try {
110
136
  const raw = readFileSync(path, "utf-8");
111
- const data = JSON.parse(raw);
112
- if (data.version !== 1) {
113
- throw new Error(`Unsupported progress file version: ${data.version}`);
137
+ const raw_data = JSON.parse(raw);
138
+ if (raw_data.version === 1) {
139
+ const migrated = migrateV1toV2(raw_data);
140
+ writeProgress(dir, migrated);
141
+ return migrated;
142
+ }
143
+ if (raw_data.version !== 2) {
144
+ throw new Error(`Unsupported progress file version: ${raw_data.version}`);
114
145
  }
146
+ const data = raw_data;
115
147
  if (migrateProgressSteps(data)) {
116
148
  writeProgress(dir, data);
117
149
  }
@@ -1,15 +1,15 @@
1
- import {
2
- inferRole
3
- } from "./chunk-HFGJKQTB.js";
4
1
  import {
5
2
  extractMentions,
6
3
  filterByDateRange
7
4
  } from "./chunk-R7QITZ76.js";
5
+ import {
6
+ inferRole
7
+ } from "./chunk-HFGJKQTB.js";
8
8
  import {
9
9
  markComplete,
10
10
  markInProgress,
11
11
  writeProgress
12
- } from "./chunk-KY56F5QU.js";
12
+ } from "./chunk-X2MHAVAE.js";
13
13
 
14
14
  // src/cli/guided/steps/curate.ts
15
15
  import { existsSync, readFileSync, writeFileSync } from "fs";
@@ -11,7 +11,7 @@ import {
11
11
  markError,
12
12
  markInProgress,
13
13
  writeProgress
14
- } from "./chunk-KY56F5QU.js";
14
+ } from "./chunk-X2MHAVAE.js";
15
15
 
16
16
  // src/cli/guided/steps/extract.ts
17
17
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
@@ -0,0 +1,197 @@
1
+ import {
2
+ TimingFileSchema
3
+ } from "./chunk-SKRQW7PY.js";
4
+ import {
5
+ PacingFileSchema
6
+ } from "./chunk-3NPPXJ6N.js";
7
+ import {
8
+ ScriptFileSchema
9
+ } from "./chunk-BD2KEZI4.js";
10
+
11
+ // src/cli/from-pacing-chain.ts
12
+ import { existsSync, readFileSync, writeFileSync } from "fs";
13
+ import { resolve } from "path";
14
+
15
+ // src/cli/generate-timing.ts
16
+ function generateTiming(script, pacing) {
17
+ if (script.lines.length !== pacing.lines.length) {
18
+ throw new Error(
19
+ `Line count mismatch: script has ${script.lines.length} lines, pacing has ${pacing.lines.length}`
20
+ );
21
+ }
22
+ for (let i = 0; i < script.lines.length; i++) {
23
+ if (script.lines[i].id !== pacing.lines[i].id) {
24
+ throw new Error(
25
+ `Line ID mismatch at index ${i}: script has "${script.lines[i].id}", pacing has "${pacing.lines[i].id}"`
26
+ );
27
+ }
28
+ }
29
+ const leadIn = pacing.leadInSec ?? 0;
30
+ const tailOut = pacing.tailOutSec ?? 0;
31
+ const defaultPause = pacing.defaultPauseSec;
32
+ const defaultSpeed = pacing.defaultVizSpeed;
33
+ const resolved = [];
34
+ let wallClock = leadIn;
35
+ for (let i = 0; i < pacing.lines.length; i++) {
36
+ const pl = pacing.lines[i];
37
+ const sl = script.lines[i];
38
+ const pauseAfter = pl.pauseAfter ?? defaultPause;
39
+ const vizSpeed = pl.vizSpeed ?? defaultSpeed;
40
+ const gapVizSpeed = pl.gapVizSpeed ?? defaultSpeed;
41
+ resolved.push({
42
+ id: sl.id,
43
+ speaker: sl.speaker,
44
+ text: sl.text,
45
+ style: sl.style,
46
+ phase: sl.phaseId,
47
+ clipDuration: pl.clipDuration,
48
+ pauseAfter,
49
+ vizSpeed,
50
+ gapVizSpeed,
51
+ startSec: wallClock
52
+ });
53
+ wallClock += pl.clipDuration + pauseAfter;
54
+ }
55
+ const totalDurationSec = wallClock + tailOut;
56
+ let totalProjectTime = 0;
57
+ totalProjectTime += leadIn * defaultSpeed;
58
+ for (const line of resolved) {
59
+ totalProjectTime += line.clipDuration * line.vizSpeed;
60
+ totalProjectTime += line.pauseAfter * line.gapVizSpeed;
61
+ }
62
+ totalProjectTime += tailOut * defaultSpeed;
63
+ let accumulatedProjectTime = leadIn * defaultSpeed;
64
+ const timedLines = resolved.map((line) => {
65
+ const clipProjectTime = line.clipDuration * line.vizSpeed;
66
+ const gapProjectTime = line.pauseAfter * line.gapVizSpeed;
67
+ const progressStart = totalProjectTime > 0 ? accumulatedProjectTime / totalProjectTime : 0;
68
+ accumulatedProjectTime += clipProjectTime;
69
+ const progressEnd = totalProjectTime > 0 ? accumulatedProjectTime / totalProjectTime : 0;
70
+ accumulatedProjectTime += gapProjectTime;
71
+ return {
72
+ id: line.id,
73
+ speaker: line.speaker,
74
+ text: line.text,
75
+ style: line.style,
76
+ startSec: line.startSec,
77
+ durationSec: line.clipDuration,
78
+ pauseAfterSec: line.pauseAfter,
79
+ audioFile: `${line.id}.mp3`,
80
+ progressStart,
81
+ progressEnd,
82
+ vizSpeed: line.vizSpeed,
83
+ gapVizSpeed: line.gapVizSpeed,
84
+ phase: line.phase
85
+ };
86
+ });
87
+ return {
88
+ version: 1,
89
+ totalDurationSec,
90
+ lines: timedLines
91
+ };
92
+ }
93
+ function formatTimingPreview(timing, pacing) {
94
+ const rows = [];
95
+ const header = [
96
+ pad("Line ID", 18),
97
+ pad("Speaker", 12),
98
+ pad("Duration", 10),
99
+ pad("Pause", 8),
100
+ pad("VizSpeed", 10),
101
+ pad("Screen Time", 12)
102
+ ].join("| ");
103
+ rows.push(header);
104
+ rows.push("-".repeat(header.length));
105
+ for (const line of timing.lines) {
106
+ const screenTime = line.durationSec + line.pauseAfterSec;
107
+ rows.push(
108
+ [
109
+ pad(line.id, 18),
110
+ pad(line.speaker, 12),
111
+ pad(`${line.durationSec.toFixed(1)}s`, 10),
112
+ pad(`${line.pauseAfterSec.toFixed(1)}s`, 8),
113
+ pad(`${line.vizSpeed.toFixed(1)}x`, 10),
114
+ pad(`${screenTime.toFixed(1)}s`, 12)
115
+ ].join("| ")
116
+ );
117
+ }
118
+ rows.push("-".repeat(header.length));
119
+ const extras = [];
120
+ if (pacing?.leadInSec) extras.push(`lead-in: ${pacing.leadInSec.toFixed(1)}s`);
121
+ if (pacing?.tailOutSec) extras.push(`tail-out: ${pacing.tailOutSec.toFixed(1)}s`);
122
+ const extrasStr = extras.length > 0 ? ` (${extras.join(", ")})` : "";
123
+ rows.push(`TOTAL: ${timing.totalDurationSec.toFixed(1)}s${extrasStr}`);
124
+ return rows.join("\n");
125
+ }
126
+ function pad(str, width) {
127
+ return str.padEnd(width);
128
+ }
129
+
130
+ // src/cli/from-pacing-chain.ts
131
+ function runFromPacingChain(dataDir) {
132
+ const scriptPath = resolve(dataDir, "script.json");
133
+ const pacingPath = resolve(dataDir, "pacing.json");
134
+ const timingPath = resolve(dataDir, "timing.json");
135
+ if (!existsSync(scriptPath)) {
136
+ return {
137
+ success: false,
138
+ error: `script.json not found at ${scriptPath}. Write the script first (Step 2).`
139
+ };
140
+ }
141
+ let script;
142
+ try {
143
+ const raw = JSON.parse(readFileSync(scriptPath, "utf-8"));
144
+ script = ScriptFileSchema.parse(raw);
145
+ } catch (err) {
146
+ const message = err instanceof Error ? err.message : String(err);
147
+ return {
148
+ success: false,
149
+ error: `Invalid script.json: ${message}`
150
+ };
151
+ }
152
+ if (!existsSync(pacingPath)) {
153
+ return {
154
+ success: false,
155
+ error: `pacing.json not found at ${pacingPath}. Run 'npx miriad-viz scaffold-pacing' first.`
156
+ };
157
+ }
158
+ let pacing;
159
+ try {
160
+ const raw = JSON.parse(readFileSync(pacingPath, "utf-8"));
161
+ pacing = PacingFileSchema.parse(raw);
162
+ } catch (err) {
163
+ const message = err instanceof Error ? err.message : String(err);
164
+ return {
165
+ success: false,
166
+ error: `Invalid pacing.json: ${message}`
167
+ };
168
+ }
169
+ let timing;
170
+ try {
171
+ timing = generateTiming(script, pacing);
172
+ } catch (err) {
173
+ const message = err instanceof Error ? err.message : String(err);
174
+ return {
175
+ success: false,
176
+ error: `Timing generation failed: ${message}`
177
+ };
178
+ }
179
+ const validated = TimingFileSchema.safeParse(timing);
180
+ if (!validated.success) {
181
+ return {
182
+ success: false,
183
+ error: `Generated timing.json failed validation: ${validated.error.message}`
184
+ };
185
+ }
186
+ writeFileSync(timingPath, `${JSON.stringify(timing, null, 2)}
187
+ `);
188
+ const previewTable = formatTimingPreview(timing, pacing);
189
+ return {
190
+ success: true,
191
+ timingPath,
192
+ previewTable
193
+ };
194
+ }
195
+ export {
196
+ runFromPacingChain
197
+ };