miriad-viz 0.4.3 → 0.5.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 (34) hide show
  1. package/dist-cli/{chunk-HFGJKQTB.js → chunk-4CGFDD2G.js} +9 -1
  2. package/dist-cli/{chunk-R7QITZ76.js → chunk-CUDVVNEZ.js} +22 -0
  3. package/dist-cli/{chunk-GPVKAGZT.js → chunk-IMD2MNLI.js} +1 -1
  4. package/dist-cli/chunk-PHPII3CV.js +248 -0
  5. package/dist-cli/{curate-LBMQRKKG.js → curate-QUIYJ7VJ.js} +33 -10
  6. package/dist-cli/{extract-S5XKRYPG.js → extract-NDJN7REW.js} +285 -47
  7. package/dist-cli/index.js +133 -44
  8. package/dist-cli/{preview-F7ILLPMG.js → preview-PXC6HTMF.js} +52 -6
  9. package/dist-cli/{render-TC223YOE.js → render-JV57PXIE.js} +12 -1
  10. package/dist-cli/{transform-TEJWDQWZ.js → transform-POVVUALD.js} +13 -2
  11. package/dist-lib/{chunk-TCRXL725.js → chunk-A4KKFJGR.js} +15 -9
  12. package/dist-lib/chunk-A4KKFJGR.js.map +1 -0
  13. package/dist-lib/{chunk-LJG3H4FA.cjs → chunk-M54CHKPH.cjs} +15 -9
  14. package/dist-lib/chunk-M54CHKPH.cjs.map +1 -0
  15. package/dist-lib/index.cjs +9 -1
  16. package/dist-lib/index.cjs.map +1 -1
  17. package/dist-lib/index.js +9 -1
  18. package/dist-lib/index.js.map +1 -1
  19. package/dist-lib/renderer/index.cjs +10 -10
  20. package/dist-lib/renderer/index.d.cts +7 -0
  21. package/dist-lib/renderer/index.d.ts +7 -0
  22. package/dist-lib/renderer/index.js +1 -1
  23. package/dist-lib/viewer/exports.cjs +657 -98
  24. package/dist-lib/viewer/exports.cjs.map +1 -1
  25. package/dist-lib/viewer/exports.d.cts +119 -60
  26. package/dist-lib/viewer/exports.d.ts +119 -60
  27. package/dist-lib/viewer/exports.js +633 -78
  28. package/dist-lib/viewer/exports.js.map +1 -1
  29. package/package.json +1 -1
  30. package/template/remotion/src/AudioLayer.tsx +4 -4
  31. package/template/remotion/src/MiriadViz.tsx +77 -74
  32. package/template/viewer/src/App.tsx +106 -8
  33. package/dist-lib/chunk-LJG3H4FA.cjs.map +0 -1
  34. package/dist-lib/chunk-TCRXL725.js.map +0 -1
@@ -148,7 +148,15 @@ function transformPRs(prs, prAgentMap) {
148
148
  });
149
149
  }
150
150
  function inferArtifactType(slug) {
151
- if (slug.includes(".app.") || slug.endsWith(".ts") || slug.endsWith(".js")) return "code";
151
+ if (slug.startsWith("plan/task-")) return "task";
152
+ if (slug.startsWith("plan/spec-")) return "doc";
153
+ const ext = slug.split(".").pop()?.toLowerCase();
154
+ if (ext && ["ts", "tsx", "js", "jsx", "py", "rs", "go", "sh"].includes(ext)) return "code";
155
+ if (ext && ["png", "jpg", "jpeg", "svg", "gif", "webp", "ico"].includes(ext)) return "asset";
156
+ if (ext && ["md", "txt", "json", "yaml", "yml", "toml"].includes(ext)) return "doc";
157
+ if (slug.includes("/specs/") || slug.includes("/spec/")) return "doc";
158
+ if (slug.includes("/assets/") || slug.includes("/images/")) return "asset";
159
+ if (slug.includes(".app.")) return "code";
152
160
  if (slug.includes("screenshot") || slug.includes("qa-")) return "asset";
153
161
  if (slug.includes("decision")) return "decision";
154
162
  if (slug.includes("task") || slug.includes("phase-")) return "task";
@@ -115,6 +115,26 @@ function buildChatActivity(messages, channelName, knownAgents) {
115
115
  }
116
116
 
117
117
  // src/cli/extract/time-range-filter.ts
118
+ var AUTO_TRIM_PADDING_HOURS = 12;
119
+ function computeMessageWindow(items, dateAccessor, paddingHours = AUTO_TRIM_PADDING_HOURS) {
120
+ if (items.length === 0) return {};
121
+ const getDate = typeof dateAccessor === "function" ? dateAccessor : (item) => item[dateAccessor];
122
+ let minMs = Number.POSITIVE_INFINITY;
123
+ let maxMs = Number.NEGATIVE_INFINITY;
124
+ for (const item of items) {
125
+ const dateStr = getDate(item);
126
+ if (!dateStr) continue;
127
+ const ms = new Date(dateStr).getTime();
128
+ if (ms < minMs) minMs = ms;
129
+ if (ms > maxMs) maxMs = ms;
130
+ }
131
+ if (minMs === Number.POSITIVE_INFINITY) return {};
132
+ const paddingMs = paddingHours * 60 * 60 * 1e3;
133
+ return {
134
+ from: new Date(minMs - paddingMs).toISOString(),
135
+ to: new Date(maxMs + paddingMs).toISOString()
136
+ };
137
+ }
118
138
  function parseDate(dateStr, isEndBound) {
119
139
  if (/^\d{4}-\d{2}-\d{2}$/.test(dateStr)) {
120
140
  if (isEndBound) {
@@ -140,5 +160,7 @@ function filterByDateRange(items, dateAccessor, range) {
140
160
  export {
141
161
  extractMentions,
142
162
  buildChatActivity,
163
+ AUTO_TRIM_PADDING_HOURS,
164
+ computeMessageWindow,
143
165
  filterByDateRange
144
166
  };
@@ -90,7 +90,7 @@ var STEP_CONFIG = {
90
90
  mode: "inline",
91
91
  label: "Set visualization timing",
92
92
  creative: true,
93
- outputFiles: ["output/timing.json"]
93
+ outputFiles: ["data/timing.json"]
94
94
  },
95
95
  "sound-design": {
96
96
  mode: "inline",
@@ -0,0 +1,248 @@
1
+ // src/cli/board-sync.ts
2
+ import { existsSync, readFileSync, readdirSync } from "fs";
3
+ import { mkdirSync, writeFileSync } from "fs";
4
+ import { resolve } from "path";
5
+ function canSync() {
6
+ return !!(process.env.MIRIAD_SPACE_TOKEN && process.env.MIRIAD_API_URL && process.env.MIRIAD_CHANNEL_ID);
7
+ }
8
+ function getBoardSyncConfig(boardDir = "/viz-output") {
9
+ const apiUrl = process.env.MIRIAD_API_URL;
10
+ const channelId = process.env.MIRIAD_CHANNEL_ID;
11
+ const spaceToken = process.env.MIRIAD_SPACE_TOKEN;
12
+ if (!apiUrl || !channelId || !spaceToken) return null;
13
+ return { apiUrl, channelId, spaceToken, boardDir };
14
+ }
15
+ var TIMEOUT_MS = 1e4;
16
+ async function syncTextFile(config, localPath, boardPath) {
17
+ const content = readFileSync(localPath, "utf-8");
18
+ const fullBoardPath = `${config.boardDir}/${boardPath}`;
19
+ const url = `${config.apiUrl}/channels/${config.channelId}/files${fullBoardPath}`;
20
+ const controller = new AbortController();
21
+ const timeout = setTimeout(() => controller.abort(), TIMEOUT_MS);
22
+ try {
23
+ const res = await fetch(url, {
24
+ method: "PUT",
25
+ headers: {
26
+ Authorization: `Bearer ${config.spaceToken}`,
27
+ "Content-Type": "application/json"
28
+ },
29
+ body: JSON.stringify({ content }),
30
+ signal: controller.signal
31
+ });
32
+ if (!res.ok) {
33
+ const body = await res.text().catch(() => "");
34
+ console.warn(` \u26A0 Board sync failed for ${boardPath}: ${res.status} ${body.slice(0, 100)}`);
35
+ }
36
+ } catch (err) {
37
+ const msg = err instanceof Error ? err.message : String(err);
38
+ console.warn(` \u26A0 Board sync failed for ${boardPath}: ${msg}`);
39
+ } finally {
40
+ clearTimeout(timeout);
41
+ }
42
+ }
43
+ async function syncBinaryFile(config, localPath, boardPath, mimeType = "application/octet-stream") {
44
+ const fullBoardPath = `${config.boardDir}/${boardPath}`;
45
+ const uploadUrl = `${config.apiUrl}/channels/${config.channelId}/files/upload`;
46
+ const controller = new AbortController();
47
+ const timeout = setTimeout(() => controller.abort(), TIMEOUT_MS);
48
+ try {
49
+ const res = await fetch(uploadUrl, {
50
+ method: "POST",
51
+ headers: {
52
+ Authorization: `Bearer ${config.spaceToken}`,
53
+ "Content-Type": "application/json"
54
+ },
55
+ body: JSON.stringify({ path: fullBoardPath, mimeType }),
56
+ signal: controller.signal
57
+ });
58
+ if (!res.ok) {
59
+ const body = await res.text().catch(() => "");
60
+ console.warn(
61
+ ` \u26A0 Board upload request failed for ${boardPath}: ${res.status} ${body.slice(0, 100)}`
62
+ );
63
+ return;
64
+ }
65
+ const { uploadUrl: presignedUrl } = await res.json();
66
+ clearTimeout(timeout);
67
+ const uploadController = new AbortController();
68
+ const uploadTimeout = setTimeout(() => uploadController.abort(), TIMEOUT_MS);
69
+ try {
70
+ const fileData = readFileSync(localPath);
71
+ const putRes = await fetch(presignedUrl, {
72
+ method: "PUT",
73
+ headers: { "Content-Type": mimeType },
74
+ body: fileData,
75
+ signal: uploadController.signal
76
+ });
77
+ if (!putRes.ok) {
78
+ console.warn(` \u26A0 Board binary upload failed for ${boardPath}: ${putRes.status}`);
79
+ }
80
+ } finally {
81
+ clearTimeout(uploadTimeout);
82
+ }
83
+ } catch (err) {
84
+ const msg = err instanceof Error ? err.message : String(err);
85
+ console.warn(` \u26A0 Board sync failed for ${boardPath}: ${msg}`);
86
+ } finally {
87
+ clearTimeout(timeout);
88
+ }
89
+ }
90
+ var STEP_FILE_MAP = {
91
+ extract: [".miriad-viz.json"],
92
+ curate: ["retro-page-data.json", "retro-page-quotes.json", "timeline-events.json"],
93
+ script: ["script.json", "pacing.json"],
94
+ voices: [],
95
+ // handled specially — glob for audio/*.mp3
96
+ "viz-timing": ["timing.json"],
97
+ "sound-design": ["audio-manifest.json"],
98
+ transform: ["viz-data.json"],
99
+ render: []
100
+ // render output is the final video — too large for board
101
+ };
102
+ var ALWAYS_SYNC = [".miriad-viz.json"];
103
+ async function syncStepOutputs(config, step, dataDir) {
104
+ const files = STEP_FILE_MAP[step] ?? [];
105
+ const allFiles = [.../* @__PURE__ */ new Set([...files, ...ALWAYS_SYNC])];
106
+ let synced = 0;
107
+ for (const file of allFiles) {
108
+ const localPath = resolve(dataDir, file);
109
+ if (!existsSync(localPath)) continue;
110
+ await syncTextFile(config, localPath, file);
111
+ synced++;
112
+ }
113
+ if (step === "voices" || step === "sound-design") {
114
+ const audioDir = resolve(dataDir, "audio");
115
+ if (existsSync(audioDir)) {
116
+ const audioFiles = readdirSync(audioDir).filter((f) => f.endsWith(".mp3"));
117
+ for (const audioFile of audioFiles) {
118
+ const localPath = resolve(audioDir, audioFile);
119
+ await syncBinaryFile(config, localPath, `audio/${audioFile}`, "audio/mpeg");
120
+ synced++;
121
+ }
122
+ }
123
+ }
124
+ if (synced > 0) {
125
+ console.log(` \u2601 Synced ${synced} file${synced > 1 ? "s" : ""} to board`);
126
+ }
127
+ }
128
+ async function pullFromBoard(config, dataDir) {
129
+ const warnings = [];
130
+ let pulled = 0;
131
+ try {
132
+ const listUrl = `${config.apiUrl}/channels/${config.channelId}/files?glob=${encodeURIComponent(`${config.boardDir}/**`)}`;
133
+ const res = await fetch(listUrl, {
134
+ headers: { Authorization: `Bearer ${config.spaceToken}` }
135
+ });
136
+ if (!res.ok) {
137
+ warnings.push(`Board listing failed: ${res.status}`);
138
+ return { pulled, warnings };
139
+ }
140
+ const { results } = await res.json();
141
+ if (!results || results.length === 0) {
142
+ return { pulled, warnings };
143
+ }
144
+ mkdirSync(dataDir, { recursive: true });
145
+ for (const file of results) {
146
+ const relativePath = file.path.replace(`${config.boardDir}/`, "");
147
+ const localPath = resolve(dataDir, relativePath);
148
+ const parentDir = resolve(localPath, "..");
149
+ mkdirSync(parentDir, { recursive: true });
150
+ try {
151
+ if (file.isText) {
152
+ const fileUrl = `${config.apiUrl}/channels/${config.channelId}/files${file.path}`;
153
+ const fileRes = await fetch(fileUrl, {
154
+ headers: { Authorization: `Bearer ${config.spaceToken}` }
155
+ });
156
+ if (fileRes.ok) {
157
+ const data = await fileRes.json();
158
+ writeFileSync(localPath, data.content);
159
+ pulled++;
160
+ }
161
+ } else {
162
+ const downloadUrl = `${config.apiUrl}/channels/${config.channelId}/files${file.path}/download`;
163
+ const dlRes = await fetch(downloadUrl, {
164
+ headers: { Authorization: `Bearer ${config.spaceToken}` }
165
+ });
166
+ if (dlRes.ok) {
167
+ const { url } = await dlRes.json();
168
+ const binaryRes = await fetch(url);
169
+ if (binaryRes.ok) {
170
+ const buffer = Buffer.from(await binaryRes.arrayBuffer());
171
+ writeFileSync(localPath, buffer);
172
+ pulled++;
173
+ }
174
+ }
175
+ }
176
+ } catch (err) {
177
+ const msg = err instanceof Error ? err.message : String(err);
178
+ warnings.push(`Failed to pull ${relativePath}: ${msg}`);
179
+ }
180
+ }
181
+ const progressPath = resolve(dataDir, ".miriad-viz.json");
182
+ if (existsSync(progressPath)) {
183
+ try {
184
+ const progress = JSON.parse(readFileSync(progressPath, "utf-8"));
185
+ const validationWarnings = validateProgressConsistency(progress, dataDir);
186
+ warnings.push(...validationWarnings);
187
+ } catch {
188
+ warnings.push("Failed to parse .miriad-viz.json for consistency check");
189
+ }
190
+ }
191
+ } catch (err) {
192
+ const msg = err instanceof Error ? err.message : String(err);
193
+ warnings.push(`Board pull failed: ${msg}`);
194
+ }
195
+ return { pulled, warnings };
196
+ }
197
+ var STEP_REQUIRED_FILES = {
198
+ extract: ["git-commits.json"],
199
+ curate: ["retro-page-data.json", "timeline-events.json"],
200
+ script: ["script.json"],
201
+ voices: [],
202
+ // checked dynamically — at least one .mp3 in audio/
203
+ "viz-timing": ["timing.json"],
204
+ "sound-design": ["audio-manifest.json"],
205
+ transform: ["viz-data.json"]
206
+ };
207
+ function validateProgressConsistency(progress, dataDir) {
208
+ const warnings = [];
209
+ const steps = progress.steps;
210
+ if (!steps) return warnings;
211
+ for (const [step, state] of Object.entries(steps)) {
212
+ if (state.status !== "complete") continue;
213
+ const requiredFiles = STEP_REQUIRED_FILES[step];
214
+ if (!requiredFiles) continue;
215
+ if (step === "voices") {
216
+ const audioDir = resolve(dataDir, "audio");
217
+ const hasMp3 = existsSync(audioDir) && readdirSync(audioDir).some((f) => f.endsWith(".mp3"));
218
+ if (!hasMp3) {
219
+ warnings.push(
220
+ `Step '${step}' marked complete but no audio files found \u2014 resetting to pending`
221
+ );
222
+ state.status = "pending";
223
+ }
224
+ continue;
225
+ }
226
+ for (const file of requiredFiles) {
227
+ if (!existsSync(resolve(dataDir, file))) {
228
+ warnings.push(`Step '${step}' marked complete but ${file} missing \u2014 resetting to pending`);
229
+ state.status = "pending";
230
+ break;
231
+ }
232
+ }
233
+ }
234
+ if (warnings.length > 0) {
235
+ const progressPath = resolve(dataDir, ".miriad-viz.json");
236
+ writeFileSync(progressPath, `${JSON.stringify(progress, null, 2)}
237
+ `);
238
+ }
239
+ return warnings;
240
+ }
241
+
242
+ export {
243
+ canSync,
244
+ getBoardSyncConfig,
245
+ syncTextFile,
246
+ syncStepOutputs,
247
+ pullFromBoard
248
+ };
@@ -1,15 +1,15 @@
1
1
  import {
2
2
  extractMentions,
3
3
  filterByDateRange
4
- } from "./chunk-R7QITZ76.js";
4
+ } from "./chunk-CUDVVNEZ.js";
5
5
  import {
6
6
  inferRole
7
- } from "./chunk-HFGJKQTB.js";
7
+ } from "./chunk-4CGFDD2G.js";
8
8
  import {
9
9
  markComplete,
10
10
  markInProgress,
11
11
  writeProgress
12
- } from "./chunk-GPVKAGZT.js";
12
+ } from "./chunk-IMD2MNLI.js";
13
13
 
14
14
  // src/cli/guided/steps/curate.ts
15
15
  import { existsSync, readFileSync, writeFileSync } from "fs";
@@ -27,8 +27,9 @@ function getDateRange(dataDir, chatInputPath) {
27
27
  } catch {
28
28
  }
29
29
  }
30
+ const messagesVizInDataDir = resolve(dataDir, "messages-viz.json");
30
31
  const messagesInDataDir = resolve(dataDir, "messages.json");
31
- const messagesPath = existsSync(messagesInDataDir) ? messagesInDataDir : chatInputPath && existsSync(chatInputPath) ? chatInputPath : null;
32
+ const messagesPath = existsSync(messagesVizInDataDir) ? messagesVizInDataDir : existsSync(messagesInDataDir) ? messagesInDataDir : chatInputPath && existsSync(chatInputPath) ? chatInputPath : null;
32
33
  if (messagesPath) {
33
34
  try {
34
35
  const raw = JSON.parse(readFileSync(messagesPath, "utf-8"));
@@ -95,11 +96,12 @@ function getAgentList(dataDir, chatInputPath) {
95
96
  } catch {
96
97
  }
97
98
  }
98
- const messagesInDataDir = resolve(dataDir, "messages.json");
99
- const messagesPath = existsSync(messagesInDataDir) ? messagesInDataDir : chatInputPath && existsSync(chatInputPath) ? chatInputPath : null;
100
- if (messagesPath) {
99
+ const messagesVizInDataDir2 = resolve(dataDir, "messages-viz.json");
100
+ const messagesInDataDir2 = resolve(dataDir, "messages.json");
101
+ const agentMessagesPath = existsSync(messagesVizInDataDir2) ? messagesVizInDataDir2 : existsSync(messagesInDataDir2) ? messagesInDataDir2 : chatInputPath && existsSync(chatInputPath) ? chatInputPath : null;
102
+ if (agentMessagesPath) {
101
103
  try {
102
- const raw = JSON.parse(readFileSync(messagesPath, "utf-8"));
104
+ const raw = JSON.parse(readFileSync(agentMessagesPath, "utf-8"));
103
105
  const messages = raw.messages || raw;
104
106
  for (const m of messages) agents.add(m.sender);
105
107
  } catch {
@@ -336,14 +338,35 @@ function generateTimelineEvents(dataDir, chatInputPath, dateRange) {
336
338
  }
337
339
  }
338
340
  const chatActivityPath = resolve(dataDir, "chat-activity.json");
341
+ const messagesVizPath = resolve(dataDir, "messages-viz.json");
339
342
  const messagesInDataDir = resolve(dataDir, "messages.json");
340
- const messagesPath = existsSync(chatActivityPath) ? chatActivityPath : existsSync(messagesInDataDir) ? messagesInDataDir : chatInputPath && existsSync(chatInputPath) ? chatInputPath : null;
343
+ let messagesPath = null;
344
+ let usePreExtractedMentions = false;
345
+ if (existsSync(chatActivityPath)) {
346
+ try {
347
+ const raw = JSON.parse(readFileSync(chatActivityPath, "utf-8"));
348
+ if (Array.isArray(raw.messages) || Array.isArray(raw)) {
349
+ messagesPath = chatActivityPath;
350
+ }
351
+ } catch {
352
+ }
353
+ }
354
+ if (!messagesPath && existsSync(messagesVizPath)) {
355
+ messagesPath = messagesVizPath;
356
+ usePreExtractedMentions = true;
357
+ }
358
+ if (!messagesPath && existsSync(messagesInDataDir)) {
359
+ messagesPath = messagesInDataDir;
360
+ }
361
+ if (!messagesPath && chatInputPath && existsSync(chatInputPath)) {
362
+ messagesPath = chatInputPath;
363
+ }
341
364
  if (messagesPath) {
342
365
  try {
343
366
  const raw = JSON.parse(readFileSync(messagesPath, "utf-8"));
344
367
  const messages = raw.messages || raw;
345
368
  for (const msg of messages) {
346
- const mentions = extractMentions(msg.content, msg.sender);
369
+ const mentions = usePreExtractedMentions && msg.mentions ? msg.mentions : msg.content ? extractMentions(msg.content, msg.sender) : [];
347
370
  if (mentions.length > 0) {
348
371
  events.push({
349
372
  type: "message",