takomi 2.1.39 → 2.1.41

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 (68) hide show
  1. package/.agents/plugins/marketplace.json +22 -22
  2. package/README.md +217 -422
  3. package/assets/.agent/skills/remotion/rules/assets/charts-bar-chart.tsx +173 -173
  4. package/assets/.agent/skills/remotion/rules/assets/text-animations-typewriter.tsx +100 -100
  5. package/assets/.agent/skills/remotion/rules/text-animations.md +20 -20
  6. package/assets/.agent/skills/remotion/rules/videos.md +171 -171
  7. package/assets/.agent/skills/remotion-real-ui-video/SKILL.md +264 -0
  8. package/assets/.agent/skills/remotion-real-ui-video/references/anti-patterns.md +114 -0
  9. package/assets/.agent/skills/remotion-real-ui-video/references/completion-checklist.md +75 -0
  10. package/assets/.agent/skills/remotion-real-ui-video/references/real-ui-bridge-example.md +93 -0
  11. package/assets/.agent/skills/takomi-flow/SKILL.md +244 -235
  12. package/docs/features/TakomiFlow_Portable_Plugin.md +82 -78
  13. package/docs/takomi-flow-onboarding.md +86 -76
  14. package/docs/takomi-flow-public-distribution.md +94 -94
  15. package/package.json +1 -1
  16. package/plugins/takomi-flow/.mcp.json +11 -11
  17. package/plugins/takomi-flow/assets/capabilities.json +203 -171
  18. package/plugins/takomi-flow/assets/collection.schema.json +29 -29
  19. package/plugins/takomi-flow/assets/examples.json +94 -94
  20. package/plugins/takomi-flow/assets/request.schema.json +91 -67
  21. package/plugins/takomi-flow/assets/result.schema.json +46 -45
  22. package/plugins/takomi-flow/assets/templates/image-request.json +17 -13
  23. package/plugins/takomi-flow/assets/templates/video-request.json +19 -15
  24. package/plugins/takomi-flow/package.json +38 -38
  25. package/plugins/takomi-flow/pnpm-lock.yaml +813 -813
  26. package/plugins/takomi-flow/references/flow-provider-contract.md +349 -326
  27. package/plugins/takomi-flow/scripts/lib/agent-plan.mjs +140 -130
  28. package/plugins/takomi-flow/scripts/lib/api.mjs +113 -113
  29. package/plugins/takomi-flow/scripts/lib/args.mjs +66 -57
  30. package/plugins/takomi-flow/scripts/lib/audit.mjs +65 -65
  31. package/plugins/takomi-flow/scripts/lib/browser.mjs +78 -39
  32. package/plugins/takomi-flow/scripts/lib/capabilities.mjs +11 -11
  33. package/plugins/takomi-flow/scripts/lib/collect.mjs +53 -53
  34. package/plugins/takomi-flow/scripts/lib/commands.mjs +192 -175
  35. package/plugins/takomi-flow/scripts/lib/doctor.mjs +77 -77
  36. package/plugins/takomi-flow/scripts/lib/examples.mjs +17 -17
  37. package/plugins/takomi-flow/scripts/lib/flow-media.mjs +39 -0
  38. package/plugins/takomi-flow/scripts/lib/flow-outcome.mjs +115 -90
  39. package/plugins/takomi-flow/scripts/lib/flow-project-session.mjs +153 -0
  40. package/plugins/takomi-flow/scripts/lib/flow-ui.mjs +204 -165
  41. package/plugins/takomi-flow/scripts/lib/generation.mjs +190 -90
  42. package/plugins/takomi-flow/scripts/lib/inspect.mjs +81 -81
  43. package/plugins/takomi-flow/scripts/lib/mcp-prompts.mjs +39 -39
  44. package/plugins/takomi-flow/scripts/lib/mcp-resources.mjs +16 -16
  45. package/plugins/takomi-flow/scripts/lib/mcp-tools.mjs +165 -150
  46. package/plugins/takomi-flow/scripts/lib/media.mjs +78 -78
  47. package/plugins/takomi-flow/scripts/lib/observe.mjs +54 -54
  48. package/plugins/takomi-flow/scripts/lib/paths.mjs +37 -37
  49. package/plugins/takomi-flow/scripts/lib/playwright-loader.mjs +23 -23
  50. package/plugins/takomi-flow/scripts/lib/prompts.mjs +72 -72
  51. package/plugins/takomi-flow/scripts/lib/report.mjs +141 -141
  52. package/plugins/takomi-flow/scripts/lib/request-validator.mjs +73 -58
  53. package/plugins/takomi-flow/scripts/lib/request.mjs +66 -55
  54. package/plugins/takomi-flow/scripts/lib/resource-files.mjs +69 -69
  55. package/plugins/takomi-flow/scripts/lib/result.mjs +31 -31
  56. package/plugins/takomi-flow/scripts/lib/review.mjs +44 -44
  57. package/plugins/takomi-flow/scripts/lib/selftest.mjs +153 -153
  58. package/plugins/takomi-flow/scripts/lib/settings-plan.mjs +44 -39
  59. package/plugins/takomi-flow/scripts/lib/templates.mjs +37 -37
  60. package/plugins/takomi-flow/scripts/lib/trusted-chrome.mjs +67 -67
  61. package/plugins/takomi-flow/scripts/lib/workflow.mjs +56 -56
  62. package/plugins/takomi-flow/scripts/mcp-server.mjs +18 -18
  63. package/plugins/takomi-flow/scripts/mcp-smoke.mjs +144 -142
  64. package/plugins/takomi-flow/scripts/takomi-flow.mjs +17 -17
  65. package/plugins/takomi-flow/skills/takomi-flow/SKILL.md +244 -235
  66. package/plugins/takomi-flow/skills/takomi-flow/agents/openai.yaml +7 -7
  67. package/scripts/install-takomi-flow.ps1 +85 -85
  68. package/src/skills-catalog.js +10 -9
@@ -1,37 +1,37 @@
1
- import os from 'node:os';
2
- import path from 'node:path';
3
- import fs from 'node:fs';
4
-
5
- export const FLOW_URL = process.env.TAKOMI_FLOW_URL || 'https://labs.google/fx/tools/flow';
6
-
7
- export function defaultProfileDir() {
8
- return path.join(os.homedir(), '.takomi-flow', 'browser-profile');
9
- }
10
-
11
- export function defaultRunsDir() {
12
- return path.join(os.homedir(), '.takomi-flow', 'runs');
13
- }
14
-
15
- export function timestampId(label = 'flow') {
16
- const stamp = new Date().toISOString().replace(/[-:]/g, '').replace('.', '-').replace('T', '-').replace('Z', '');
17
- return `${stamp}-${label}`;
18
- }
19
-
20
- export function ensureDir(dirPath) {
21
- fs.mkdirSync(dirPath, { recursive: true });
22
- return dirPath;
23
- }
24
-
25
- export function resolvePath(value, fallback) {
26
- return path.resolve(value || fallback);
27
- }
28
-
29
- export function writeJson(filePath, data) {
30
- ensureDir(path.dirname(filePath));
31
- fs.writeFileSync(filePath, `${JSON.stringify(data, null, 2)}\n`);
32
- return filePath;
33
- }
34
-
35
- export function readJson(filePath) {
36
- return JSON.parse(fs.readFileSync(filePath, 'utf8'));
37
- }
1
+ import os from 'node:os';
2
+ import path from 'node:path';
3
+ import fs from 'node:fs';
4
+
5
+ export const FLOW_URL = process.env.TAKOMI_FLOW_URL || 'https://labs.google/fx/tools/flow';
6
+
7
+ export function defaultProfileDir() {
8
+ return path.join(os.homedir(), '.takomi-flow', 'browser-profile');
9
+ }
10
+
11
+ export function defaultRunsDir() {
12
+ return path.join(os.homedir(), '.takomi-flow', 'runs');
13
+ }
14
+
15
+ export function timestampId(label = 'flow') {
16
+ const stamp = new Date().toISOString().replace(/[-:]/g, '').replace('.', '-').replace('T', '-').replace('Z', '');
17
+ return `${stamp}-${label}`;
18
+ }
19
+
20
+ export function ensureDir(dirPath) {
21
+ fs.mkdirSync(dirPath, { recursive: true });
22
+ return dirPath;
23
+ }
24
+
25
+ export function resolvePath(value, fallback) {
26
+ return path.resolve(value || fallback);
27
+ }
28
+
29
+ export function writeJson(filePath, data) {
30
+ ensureDir(path.dirname(filePath));
31
+ fs.writeFileSync(filePath, `${JSON.stringify(data, null, 2)}\n`);
32
+ return filePath;
33
+ }
34
+
35
+ export function readJson(filePath) {
36
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
37
+ }
@@ -1,23 +1,23 @@
1
- import { createRequire } from 'node:module';
2
- import path from 'node:path';
3
-
4
- export async function loadPlaywright() {
5
- try {
6
- return await import('playwright');
7
- } catch (firstError) {
8
- try {
9
- const requireFromCwd = createRequire(path.join(process.cwd(), 'package.json'));
10
- return requireFromCwd('playwright');
11
- } catch {
12
- const message = [
13
- 'Playwright is required for TakomiFlow browser automation.',
14
- 'Install it in the plugin folder with:',
15
- ' cd C:\\Users\\johno\\plugins\\takomi-flow',
16
- ' pnpm install',
17
- '',
18
- `Original import error: ${firstError.message}`,
19
- ].join('\n');
20
- throw new Error(message);
21
- }
22
- }
23
- }
1
+ import { createRequire } from 'node:module';
2
+ import path from 'node:path';
3
+
4
+ export async function loadPlaywright() {
5
+ try {
6
+ return await import('playwright');
7
+ } catch (firstError) {
8
+ try {
9
+ const requireFromCwd = createRequire(path.join(process.cwd(), 'package.json'));
10
+ return requireFromCwd('playwright');
11
+ } catch {
12
+ const message = [
13
+ 'Playwright is required for TakomiFlow browser automation.',
14
+ 'Install it in the plugin folder with:',
15
+ ' cd C:\\Users\\johno\\plugins\\takomi-flow',
16
+ ' pnpm install',
17
+ '',
18
+ `Original import error: ${firstError.message}`,
19
+ ].join('\n');
20
+ throw new Error(message);
21
+ }
22
+ }
23
+ }
@@ -1,72 +1,72 @@
1
- export function videoPrompt(args = {}) {
2
- const variations = normalizeVariations(args.variations);
3
- return promptResult('TakomiFlow video workflow', [
4
- 'Use TakomiFlow to prepare a Google Flow video request.',
5
- `Topic or prompt: ${args.topic || '<ask the user for the video prompt>'}`,
6
- `Aspect ratio: ${args.aspectRatio || '16:9'}`,
7
- `Variations: ${variations}`,
8
- 'Read takomi-flow://contract and takomi-flow://templates/video if available.',
9
- 'Run capabilities, prepare, validate, then generate only with explicit browser and spend approval.',
10
- 'After generation, inspect the run and catalog assets with frame extraction.',
11
- ]);
12
- }
13
-
14
- export function imagePrompt(args = {}) {
15
- const variations = normalizeVariations(args.variations);
16
- return promptResult('TakomiFlow image workflow', [
17
- 'Use TakomiFlow to prepare a Google Flow image request.',
18
- `Topic or prompt: ${args.topic || '<ask the user for the image prompt>'}`,
19
- `Aspect ratio: ${args.aspectRatio || '1:1'}`,
20
- `Variations: ${variations}`,
21
- 'Read takomi-flow://contract and takomi-flow://templates/image if available.',
22
- 'Run capabilities, prepare, validate, then generate only with explicit browser and spend approval.',
23
- 'After generation, inspect the run and catalog downloaded assets.',
24
- ]);
25
- }
26
-
27
- export function reviewPrompt(args = {}) {
28
- return promptResult('TakomiFlow review workflow', [
29
- 'Use TakomiFlow to review a completed or partial Flow run.',
30
- `Run path: ${args.run || '<ask for the run.json path or run directory>'}`,
31
- `Review frames: ${normalizeFrames(args.frames)}`,
32
- 'Run inspect or latest if the user did not provide a run path.',
33
- 'Run review with frame extraction to produce assets.json, report.md, and next actions.',
34
- 'If downloaded assets are present and a downstream project needs them, run collect after review.',
35
- 'Report run status, errors, manual actions, asset paths, frame paths, and reportPath.',
36
- ]);
37
- }
38
-
39
- export function collectPrompt(args = {}) {
40
- return promptResult('TakomiFlow collect workflow', [
41
- 'Use TakomiFlow to package reviewed Flow outputs for another project or pipeline.',
42
- `Run path: ${args.run || '<ask for the run.json path or run directory>'}`,
43
- `Target folder: ${args.targetDir || '<ask for the downstream target folder>'}`,
44
- `Include frames: ${args.includeFrames === 'false' ? 'false' : 'true'}`,
45
- 'Run review first when asset quality or errors are unknown.',
46
- 'Run collect with a target directory and include frames when video review frames are useful downstream.',
47
- 'Return the copied asset paths, copied frame paths, report path, and takomi-flow-collection.json manifest path.',
48
- ]);
49
- }
50
-
51
- function normalizeVariations(value) {
52
- const parsed = Number.parseInt(value || '1', 10);
53
- return Number.isFinite(parsed) && parsed > 0 ? parsed : 1;
54
- }
55
-
56
- function normalizeFrames(value) {
57
- const parsed = Number.parseInt(value || '4', 10);
58
- return Number.isFinite(parsed) && parsed >= 0 ? parsed : 4;
59
- }
60
-
61
- function promptResult(description, lines) {
62
- return {
63
- description,
64
- messages: [{
65
- role: 'user',
66
- content: {
67
- type: 'text',
68
- text: lines.join('\n'),
69
- },
70
- }],
71
- };
72
- }
1
+ export function videoPrompt(args = {}) {
2
+ const variations = normalizeVariations(args.variations);
3
+ return promptResult('TakomiFlow video workflow', [
4
+ 'Use TakomiFlow to prepare a Google Flow video request.',
5
+ `Topic or prompt: ${args.topic || '<ask the user for the video prompt>'}`,
6
+ `Aspect ratio: ${args.aspectRatio || '16:9'}`,
7
+ `Variations: ${variations}`,
8
+ 'Read takomi-flow://contract and takomi-flow://templates/video if available.',
9
+ 'Run capabilities, prepare, validate, then generate only with explicit browser and spend approval.',
10
+ 'After generation, inspect the run and catalog assets with frame extraction.',
11
+ ]);
12
+ }
13
+
14
+ export function imagePrompt(args = {}) {
15
+ const variations = normalizeVariations(args.variations);
16
+ return promptResult('TakomiFlow image workflow', [
17
+ 'Use TakomiFlow to prepare a Google Flow image request.',
18
+ `Topic or prompt: ${args.topic || '<ask the user for the image prompt>'}`,
19
+ `Aspect ratio: ${args.aspectRatio || '1:1'}`,
20
+ `Variations: ${variations}`,
21
+ 'Read takomi-flow://contract and takomi-flow://templates/image if available.',
22
+ 'Run capabilities, prepare, validate, then generate only with explicit browser and spend approval.',
23
+ 'After generation, inspect the run and catalog downloaded assets.',
24
+ ]);
25
+ }
26
+
27
+ export function reviewPrompt(args = {}) {
28
+ return promptResult('TakomiFlow review workflow', [
29
+ 'Use TakomiFlow to review a completed or partial Flow run.',
30
+ `Run path: ${args.run || '<ask for the run.json path or run directory>'}`,
31
+ `Review frames: ${normalizeFrames(args.frames)}`,
32
+ 'Run inspect or latest if the user did not provide a run path.',
33
+ 'Run review with frame extraction to produce assets.json, report.md, and next actions.',
34
+ 'If downloaded assets are present and a downstream project needs them, run collect after review.',
35
+ 'Report run status, errors, manual actions, asset paths, frame paths, and reportPath.',
36
+ ]);
37
+ }
38
+
39
+ export function collectPrompt(args = {}) {
40
+ return promptResult('TakomiFlow collect workflow', [
41
+ 'Use TakomiFlow to package reviewed Flow outputs for another project or pipeline.',
42
+ `Run path: ${args.run || '<ask for the run.json path or run directory>'}`,
43
+ `Target folder: ${args.targetDir || '<ask for the downstream target folder>'}`,
44
+ `Include frames: ${args.includeFrames === 'false' ? 'false' : 'true'}`,
45
+ 'Run review first when asset quality or errors are unknown.',
46
+ 'Run collect with a target directory and include frames when video review frames are useful downstream.',
47
+ 'Return the copied asset paths, copied frame paths, report path, and takomi-flow-collection.json manifest path.',
48
+ ]);
49
+ }
50
+
51
+ function normalizeVariations(value) {
52
+ const parsed = Number.parseInt(value || '1', 10);
53
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : 1;
54
+ }
55
+
56
+ function normalizeFrames(value) {
57
+ const parsed = Number.parseInt(value || '4', 10);
58
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : 4;
59
+ }
60
+
61
+ function promptResult(description, lines) {
62
+ return {
63
+ description,
64
+ messages: [{
65
+ role: 'user',
66
+ content: {
67
+ type: 'text',
68
+ text: lines.join('\n'),
69
+ },
70
+ }],
71
+ };
72
+ }
@@ -1,141 +1,141 @@
1
- import fs from 'node:fs';
2
- import path from 'node:path';
3
- import { defaultRunsDir, ensureDir, resolvePath } from './paths.mjs';
4
- import { inspectRun, listRuns, resolveRun } from './inspect.mjs';
5
-
6
- export function createReport(args = {}) {
7
- if (args.run) return createRunReport(args);
8
- return createHistoryReport(args);
9
- }
10
-
11
- function createRunReport(args) {
12
- const run = resolveRun(args.run);
13
- const summary = inspectRun(args.run);
14
- const catalog = readOptionalJson(path.join(run.runDir, 'assets.json'));
15
- const markdown = renderRunReport(summary, run.runDir, catalog);
16
- const reportPath = resolvePath(args.reportPath, path.join(run.runDir, 'report.md'));
17
- writeText(reportPath, markdown);
18
- return {
19
- schemaVersion: 1,
20
- status: 'ok',
21
- mode: 'run',
22
- runId: summary.runId,
23
- reportPath,
24
- markdown,
25
- };
26
- }
27
-
28
- function createHistoryReport(args) {
29
- const outputDir = resolvePath(args.outputDir, defaultRunsDir());
30
- const history = listRuns(outputDir, args.limit || 10);
31
- const markdown = renderHistoryReport(history);
32
- const reportPath = resolvePath(args.reportPath, path.join(outputDir, 'takomi-flow-report.md'));
33
- writeText(reportPath, markdown);
34
- return {
35
- schemaVersion: 1,
36
- status: 'ok',
37
- mode: 'history',
38
- outputDir,
39
- reportPath,
40
- runCount: history.runs.length,
41
- markdown,
42
- };
43
- }
44
-
45
- function renderRunReport(summary, runDir, catalog) {
46
- const lines = [
47
- '# TakomiFlow Run Report',
48
- '',
49
- `Generated: ${new Date().toISOString()}`,
50
- `Run: ${summary.runId || 'unknown'}`,
51
- `Status: ${summary.status || 'unknown'}`,
52
- `Command: ${summary.command || 'unknown'}`,
53
- `Kind: ${summary.kind || 'unknown'}`,
54
- `Project URL: ${summary.projectUrl || 'unknown'}`,
55
- `Run directory: ${runDir}`,
56
- `Metadata: ${summary.metadataPath || 'unknown'}`,
57
- '',
58
- ];
59
- pushList(lines, 'Manual Actions', summary.manualActions);
60
- pushList(lines, 'Errors', summary.errors);
61
- pushList(lines, 'Screenshots', summary.screenshots);
62
- pushList(lines, 'Assets', summary.assets);
63
- if (catalog) pushCatalog(lines, catalog);
64
- return `${lines.join('\n')}\n`;
65
- }
66
-
67
- function renderHistoryReport(history) {
68
- const lines = [
69
- '# TakomiFlow Run History',
70
- '',
71
- `Generated: ${new Date().toISOString()}`,
72
- `Output directory: ${history.outputDir}`,
73
- `Runs: ${history.runs.length}`,
74
- '',
75
- ];
76
- if (!history.runs.length) {
77
- lines.push('No TakomiFlow runs were found.', '');
78
- return `${lines.join('\n')}\n`;
79
- }
80
- lines.push('| Run | Status | Command | Kind | Assets | Manual Actions | Errors |');
81
- lines.push('| --- | --- | --- | --- | ---: | ---: | ---: |');
82
- for (const run of history.runs) {
83
- lines.push([
84
- cell(run.runId),
85
- cell(run.status),
86
- cell(run.command),
87
- cell(run.kind),
88
- run.assets?.length || 0,
89
- run.manualActions?.length || 0,
90
- run.errors?.length || 0,
91
- ].join(' | ').replace(/^/, '| ').replace(/$/, ' |'));
92
- }
93
- lines.push('');
94
- lines.push('## Latest Details', '');
95
- for (const run of history.runs.slice(0, 5)) {
96
- lines.push(`### ${run.runId || 'unknown run'}`, '');
97
- lines.push(`- Status: ${run.status || 'unknown'}`);
98
- if (run.projectUrl) lines.push(`- Project URL: ${run.projectUrl}`);
99
- lines.push(`- Metadata: ${run.metadataPath || 'unknown'}`);
100
- if (run.errors?.length) lines.push(`- Errors: ${run.errors.join('; ')}`);
101
- if (run.manualActions?.length) lines.push(`- Manual actions: ${run.manualActions.join('; ')}`);
102
- lines.push('');
103
- }
104
- return `${lines.join('\n')}\n`;
105
- }
106
-
107
- function pushList(lines, title, values = []) {
108
- lines.push(`## ${title}`, '');
109
- if (!values.length) {
110
- lines.push('None.', '');
111
- return;
112
- }
113
- for (const value of values) lines.push(`- ${value}`);
114
- lines.push('');
115
- }
116
-
117
- function pushCatalog(lines, catalog) {
118
- lines.push('## Asset Catalog', '');
119
- lines.push(`Catalog: ${catalog.assetCatalogPath || catalog.catalogPath || 'unknown'}`);
120
- lines.push(`Assets cataloged: ${catalog.assets?.length || 0}`);
121
- const frames = (catalog.assets || []).flatMap(asset => asset.frames || []);
122
- if (frames.length) {
123
- lines.push('', '### Review Frames', '');
124
- for (const frame of frames) lines.push(`- ${frame}`);
125
- }
126
- lines.push('');
127
- }
128
-
129
- function readOptionalJson(filePath) {
130
- if (!fs.existsSync(filePath)) return null;
131
- return { catalogPath: filePath, ...JSON.parse(fs.readFileSync(filePath, 'utf8')) };
132
- }
133
-
134
- function writeText(filePath, text) {
135
- ensureDir(path.dirname(filePath));
136
- fs.writeFileSync(filePath, text);
137
- }
138
-
139
- function cell(value) {
140
- return String(value || '').replace(/\|/g, '\\|');
141
- }
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { defaultRunsDir, ensureDir, resolvePath } from './paths.mjs';
4
+ import { inspectRun, listRuns, resolveRun } from './inspect.mjs';
5
+
6
+ export function createReport(args = {}) {
7
+ if (args.run) return createRunReport(args);
8
+ return createHistoryReport(args);
9
+ }
10
+
11
+ function createRunReport(args) {
12
+ const run = resolveRun(args.run);
13
+ const summary = inspectRun(args.run);
14
+ const catalog = readOptionalJson(path.join(run.runDir, 'assets.json'));
15
+ const markdown = renderRunReport(summary, run.runDir, catalog);
16
+ const reportPath = resolvePath(args.reportPath, path.join(run.runDir, 'report.md'));
17
+ writeText(reportPath, markdown);
18
+ return {
19
+ schemaVersion: 1,
20
+ status: 'ok',
21
+ mode: 'run',
22
+ runId: summary.runId,
23
+ reportPath,
24
+ markdown,
25
+ };
26
+ }
27
+
28
+ function createHistoryReport(args) {
29
+ const outputDir = resolvePath(args.outputDir, defaultRunsDir());
30
+ const history = listRuns(outputDir, args.limit || 10);
31
+ const markdown = renderHistoryReport(history);
32
+ const reportPath = resolvePath(args.reportPath, path.join(outputDir, 'takomi-flow-report.md'));
33
+ writeText(reportPath, markdown);
34
+ return {
35
+ schemaVersion: 1,
36
+ status: 'ok',
37
+ mode: 'history',
38
+ outputDir,
39
+ reportPath,
40
+ runCount: history.runs.length,
41
+ markdown,
42
+ };
43
+ }
44
+
45
+ function renderRunReport(summary, runDir, catalog) {
46
+ const lines = [
47
+ '# TakomiFlow Run Report',
48
+ '',
49
+ `Generated: ${new Date().toISOString()}`,
50
+ `Run: ${summary.runId || 'unknown'}`,
51
+ `Status: ${summary.status || 'unknown'}`,
52
+ `Command: ${summary.command || 'unknown'}`,
53
+ `Kind: ${summary.kind || 'unknown'}`,
54
+ `Project URL: ${summary.projectUrl || 'unknown'}`,
55
+ `Run directory: ${runDir}`,
56
+ `Metadata: ${summary.metadataPath || 'unknown'}`,
57
+ '',
58
+ ];
59
+ pushList(lines, 'Manual Actions', summary.manualActions);
60
+ pushList(lines, 'Errors', summary.errors);
61
+ pushList(lines, 'Screenshots', summary.screenshots);
62
+ pushList(lines, 'Assets', summary.assets);
63
+ if (catalog) pushCatalog(lines, catalog);
64
+ return `${lines.join('\n')}\n`;
65
+ }
66
+
67
+ function renderHistoryReport(history) {
68
+ const lines = [
69
+ '# TakomiFlow Run History',
70
+ '',
71
+ `Generated: ${new Date().toISOString()}`,
72
+ `Output directory: ${history.outputDir}`,
73
+ `Runs: ${history.runs.length}`,
74
+ '',
75
+ ];
76
+ if (!history.runs.length) {
77
+ lines.push('No TakomiFlow runs were found.', '');
78
+ return `${lines.join('\n')}\n`;
79
+ }
80
+ lines.push('| Run | Status | Command | Kind | Assets | Manual Actions | Errors |');
81
+ lines.push('| --- | --- | --- | --- | ---: | ---: | ---: |');
82
+ for (const run of history.runs) {
83
+ lines.push([
84
+ cell(run.runId),
85
+ cell(run.status),
86
+ cell(run.command),
87
+ cell(run.kind),
88
+ run.assets?.length || 0,
89
+ run.manualActions?.length || 0,
90
+ run.errors?.length || 0,
91
+ ].join(' | ').replace(/^/, '| ').replace(/$/, ' |'));
92
+ }
93
+ lines.push('');
94
+ lines.push('## Latest Details', '');
95
+ for (const run of history.runs.slice(0, 5)) {
96
+ lines.push(`### ${run.runId || 'unknown run'}`, '');
97
+ lines.push(`- Status: ${run.status || 'unknown'}`);
98
+ if (run.projectUrl) lines.push(`- Project URL: ${run.projectUrl}`);
99
+ lines.push(`- Metadata: ${run.metadataPath || 'unknown'}`);
100
+ if (run.errors?.length) lines.push(`- Errors: ${run.errors.join('; ')}`);
101
+ if (run.manualActions?.length) lines.push(`- Manual actions: ${run.manualActions.join('; ')}`);
102
+ lines.push('');
103
+ }
104
+ return `${lines.join('\n')}\n`;
105
+ }
106
+
107
+ function pushList(lines, title, values = []) {
108
+ lines.push(`## ${title}`, '');
109
+ if (!values.length) {
110
+ lines.push('None.', '');
111
+ return;
112
+ }
113
+ for (const value of values) lines.push(`- ${value}`);
114
+ lines.push('');
115
+ }
116
+
117
+ function pushCatalog(lines, catalog) {
118
+ lines.push('## Asset Catalog', '');
119
+ lines.push(`Catalog: ${catalog.assetCatalogPath || catalog.catalogPath || 'unknown'}`);
120
+ lines.push(`Assets cataloged: ${catalog.assets?.length || 0}`);
121
+ const frames = (catalog.assets || []).flatMap(asset => asset.frames || []);
122
+ if (frames.length) {
123
+ lines.push('', '### Review Frames', '');
124
+ for (const frame of frames) lines.push(`- ${frame}`);
125
+ }
126
+ lines.push('');
127
+ }
128
+
129
+ function readOptionalJson(filePath) {
130
+ if (!fs.existsSync(filePath)) return null;
131
+ return { catalogPath: filePath, ...JSON.parse(fs.readFileSync(filePath, 'utf8')) };
132
+ }
133
+
134
+ function writeText(filePath, text) {
135
+ ensureDir(path.dirname(filePath));
136
+ fs.writeFileSync(filePath, text);
137
+ }
138
+
139
+ function cell(value) {
140
+ return String(value || '').replace(/\|/g, '\\|');
141
+ }