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,90 +1,190 @@
1
- import path from 'node:path';
2
- import { defaultProfileDir, FLOW_URL, readJson, resolvePath } from './paths.mjs';
3
- import { normalizeRequest, canSpend } from './request.mjs';
4
- import { closeFlowBrowser, launchFlowBrowser } from './browser.mjs';
5
- import { createRun, baseResult, saveResult } from './result.mjs';
6
- import { capture, ensureProjectEditor, fillPrompt, inspectFlowState, openFlow, submitGeneration, tryDownloadAssets } from './flow-ui.mjs';
7
- import { handleGenerationFollowups, waitForGenerationOutcome } from './flow-outcome.mjs';
8
- import { catalogAssets } from './media.mjs';
9
- import { createSettingsPlan } from './settings-plan.mjs';
10
-
11
- export async function generateFromRequest(args = {}) {
12
- if (!args.request) throw new Error('generate requires a request path.');
13
- const request = normalizeRequest(readJson(path.resolve(args.request)));
14
- const run = createRun(request, 'generate');
15
- const result = baseResult('blocked', run, {
16
- command: 'generate',
17
- kind: request.kind,
18
- prompt: request.prompt,
19
- flowUrl: FLOW_URL,
20
- settingsPlan: createSettingsPlan(request),
21
- });
22
- if (!canSpend(request)) {
23
- result.manualActions.push('Set request allowSpend=true or TAKOMI_FLOW_ALLOW_SPEND=true to submit a Flow generation.');
24
- saveResult(run, result);
25
- return result;
26
- }
27
- const browser = await launchFlowBrowser({
28
- profileDir: resolvePath(args.profileDir, defaultProfileDir()),
29
- downloadsDir: run.downloadsDir,
30
- browserChannel: args.browserChannel,
31
- cdpUrl: args.cdpUrl,
32
- headless: Boolean(args.headless),
33
- });
34
- try {
35
- await openFlow(browser.page);
36
- result.screenshots.push(await capture(browser.page, run, 'before-prompt'));
37
- const state = await inspectFlowState(browser.page);
38
- if (state.manualActions.length) {
39
- requireManual(result, state.manualActions);
40
- return result;
41
- }
42
- if (!(await ensureProjectEditor(browser.page))) {
43
- requireManual(result, ['New project button or project editor was not found. Open a Flow project manually, then retry with --cdp-url.']);
44
- return result;
45
- }
46
- result.projectUrl = browser.page.url();
47
- result.screenshots.push(await capture(browser.page, run, 'project-editor'));
48
- if (!(await fillPrompt(browser.page, request.prompt))) {
49
- requireManual(result, ['Prompt box was not found. Update Flow selectors or paste the prompt manually.']);
50
- return result;
51
- }
52
- result.screenshots.push(await capture(browser.page, run, 'prompt-filled'));
53
- if (!(await submitGeneration(browser.page))) {
54
- requireManual(result, ['Generate/Create button was not found. Submit manually in the opened browser.']);
55
- return result;
56
- }
57
- result.projectUrl = browser.page.url();
58
- await handleGenerationFollowups(browser.page, request);
59
- result.generationOutcome = await waitForGenerationOutcome(browser.page, request);
60
- result.projectUrl = browser.page.url();
61
- result.screenshots.push(await capture(browser.page, run, 'after-wait'));
62
- result.assets = await tryDownloadAssets(browser.page, run, request.variations);
63
- if (result.assets.length) {
64
- result.assetCatalogPath = (await catalogAssets(run, result.assets, { frames: request.extractFrames || 0 })).catalogPath;
65
- }
66
- result.status = result.assets.length ? 'downloaded' : 'manual_action_required';
67
- if (!result.assets.length) {
68
- result.manualActions.push(nextActionForOutcome(result.generationOutcome));
69
- }
70
- return result;
71
- } catch (error) {
72
- result.status = 'failed';
73
- result.errors.push(error.message);
74
- return result;
75
- } finally {
76
- await closeFlowBrowser(browser);
77
- saveResult(run, result);
78
- }
79
- }
80
-
81
- function requireManual(result, actions) {
82
- result.status = 'manual_action_required';
83
- result.manualActions.push(...actions);
84
- }
85
-
86
- function nextActionForOutcome(outcome = {}) {
87
- if (outcome.status === 'failed') return 'Flow reported generation failure. Inspect the browser/run screenshots.';
88
- if (outcome.status === 'timeout') return 'Generation did not finish before the wait timeout. Leave trusted Chrome open and run inspect/observe again.';
89
- return 'Generation may still be running or download controls changed. Inspect the browser/run screenshots.';
90
- }
1
+ import path from 'node:path';
2
+ import { defaultProfileDir, FLOW_URL, readJson, resolvePath } from './paths.mjs';
3
+ import { normalizeRequest, canSpend } from './request.mjs';
4
+ import { closeFlowBrowser, launchFlowBrowser } from './browser.mjs';
5
+ import { createRun, baseResult, saveResult } from './result.mjs';
6
+ import { capture, fillPrompt, hasText, submitGeneration, tryDownloadAssets } from './flow-ui.mjs';
7
+ import { handleGenerationFollowups, waitForGenerationOutcome } from './flow-outcome.mjs';
8
+ import { catalogAssets } from './media.mjs';
9
+ import { createSettingsPlan } from './settings-plan.mjs';
10
+ import { prepareProjectEditor, recoverFreshChat } from './flow-project-session.mjs';
11
+ import { listMediaIds } from './flow-media.mjs';
12
+
13
+ export async function generateFromRequest(args = {}) {
14
+ if (!args.request) throw new Error('generate requires a request path.');
15
+ const request = normalizeRequest({ ...readJson(path.resolve(args.request)), ...requestOverrides(args) });
16
+ const run = createRun(request, 'generate');
17
+ const result = baseResult('blocked', run, {
18
+ command: 'generate',
19
+ kind: request.kind,
20
+ prompt: request.prompt,
21
+ flowUrl: FLOW_URL,
22
+ settingsPlan: createSettingsPlan(request),
23
+ });
24
+ if (!canSpend(request)) {
25
+ result.manualActions.push('Set request allowSpend=true or TAKOMI_FLOW_ALLOW_SPEND=true to submit a Flow generation.');
26
+ saveResult(run, result);
27
+ return result;
28
+ }
29
+ const browser = await launchFlowBrowser({
30
+ profileDir: resolvePath(args.profileDir, defaultProfileDir()),
31
+ downloadsDir: run.downloadsDir,
32
+ browserChannel: args.browserChannel,
33
+ cdpUrl: args.cdpUrl,
34
+ headless: Boolean(args.headless),
35
+ });
36
+ try {
37
+ result.screenshots.push(await capture(browser.page, run, 'before-project-resolve'));
38
+ const project = await prepareProjectEditor(browser, request, result);
39
+ result.projectSession = project.diagnostics;
40
+ if (!project.ok) {
41
+ requireManual(result, project.manualActions);
42
+ return result;
43
+ }
44
+ result.projectUrl = project.diagnostics.projectUrl || browser.page.url();
45
+ result.screenshots.push(await capture(browser.page, run, 'project-editor'));
46
+ const maxAttempts = request.freshChatOnFailure === false ? 1 : 2;
47
+ result.generationAttempts = [];
48
+ if (request.freshChatOnFailure !== false && await hasText(browser.page, /failed|oops, something went wrong/i)) {
49
+ await recoverFreshChat(browser.page, request);
50
+ }
51
+ for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
52
+ const attemptResult = await runGenerationAttempt(browser.page, request, run, attempt);
53
+ result.generationAttempts.push({ attempt, outcome: attemptResult.outcome, recovered: attemptResult.recovered });
54
+ result.projectUrl = browser.page.url();
55
+ result.generationOutcome = attemptResult.outcome;
56
+ result.screenshots.push(await capture(browser.page, run, attempt === 1 ? 'after-wait' : `after-wait-${attempt}`));
57
+ result.assets = attemptResult.assets;
58
+ if (result.assets.length) break;
59
+ if (attempt < maxAttempts && shouldRetryOutcome(attemptResult.outcome)) {
60
+ const recovery = await recoverFreshChat(browser.page, request);
61
+ if (recovery.recovered) {
62
+ result.generationAttempts[result.generationAttempts.length - 1].recovered = true;
63
+ continue;
64
+ }
65
+ }
66
+ break;
67
+ }
68
+ if (result.assets.length) {
69
+ result.assetCatalogPath = (await catalogAssets(run, result.assets, { frames: request.extractFrames || 0 })).catalogPath;
70
+ }
71
+ result.status = result.assets.length ? 'downloaded' : 'manual_action_required';
72
+ if (!result.assets.length) result.manualActions.push(nextActionForOutcome(result.generationOutcome));
73
+ return result;
74
+ } catch (error) {
75
+ result.status = 'failed';
76
+ result.errors.push(error.message);
77
+ return result;
78
+ } finally {
79
+ await closeFlowBrowser(browser);
80
+ saveResult(run, result);
81
+ }
82
+ }
83
+
84
+ function requestOverrides(args) {
85
+ return Object.fromEntries([
86
+ ['projectUrl', args.projectUrl],
87
+ ['reuseCurrentProject', args.reuseCurrentProject],
88
+ ['allowNewProject', args.allowNewProject],
89
+ ['freshChatOnFailure', args.freshChatOnFailure],
90
+ ['editorWaitMs', args.editorWaitMs],
91
+ ].filter(([, value]) => value !== undefined));
92
+ }
93
+
94
+ async function fillPromptWithRecovery(page, request) {
95
+ if (await fillPrompt(page, request.prompt)) return true;
96
+ const recovery = await recoverFreshChat(page, request);
97
+ return recovery.recovered && await fillPrompt(page, request.prompt);
98
+ }
99
+
100
+ async function submitWithRecovery(page, request) {
101
+ if (await submitGeneration(page)) return true;
102
+ const recovery = await recoverFreshChat(page, request);
103
+ return recovery.recovered
104
+ && await fillPrompt(page, request.prompt)
105
+ && await submitGeneration(page);
106
+ }
107
+
108
+ async function runGenerationAttempt(page, request, run, attempt) {
109
+ const baselineMediaIds = await listMediaIds(page);
110
+ const pending = await pendingGenerationState(page, request);
111
+ if (pending.matchesRequest) {
112
+ await handleGenerationFollowups(page, request);
113
+ const outcome = await waitForGenerationOutcome(page, request, { baselineMediaIds });
114
+ const assets = await downloadAttemptAssets(page, run, request, outcome);
115
+ return { outcome, assets, recovered: false };
116
+ }
117
+ if (pending.exists) {
118
+ return {
119
+ outcome: { status: 'active_generation_pending' },
120
+ assets: [],
121
+ recovered: false,
122
+ };
123
+ }
124
+ if (!(await fillPromptWithRecovery(page, request))) {
125
+ return {
126
+ outcome: { status: 'prompt_not_found' },
127
+ assets: [],
128
+ recovered: false,
129
+ };
130
+ }
131
+ await capture(page, run, attempt === 1 ? 'prompt-filled' : `prompt-filled-${attempt}`);
132
+ if (!(await submitWithRecovery(page, request))) {
133
+ return {
134
+ outcome: { status: 'submit_not_found' },
135
+ assets: [],
136
+ recovered: false,
137
+ };
138
+ }
139
+ await handleGenerationFollowups(page, request);
140
+ const outcome = await waitForGenerationOutcome(page, request, { baselineMediaIds });
141
+ const assets = await downloadAttemptAssets(page, run, request, outcome);
142
+ return { outcome, assets, recovered: false };
143
+ }
144
+
145
+ async function downloadAttemptAssets(page, run, request, outcome) {
146
+ const assets = await tryDownloadAssets(page, run, request.variations, {
147
+ mediaIds: outcome.mediaIds || [],
148
+ allowRenderedImageFallback: request.kind !== 'video',
149
+ });
150
+ if (assets.length && !['download_ready', 'media_ready'].includes(outcome.status)) {
151
+ outcome.status = 'download_ready';
152
+ outcome.recoveredFromStaleText = true;
153
+ }
154
+ return assets;
155
+ }
156
+
157
+ function shouldRetryOutcome(outcome = {}) {
158
+ return ['failed', 'prompt_not_found', 'submit_not_found'].includes(outcome.status);
159
+ }
160
+
161
+ async function pendingGenerationState(page, request) {
162
+ const pendingPattern = /would you like me to kick off|costing\s+\d+\s+credits|currently in the queue|waiting in the queue|been scheduled|i['’]ve scheduled|ready for you shortly/i;
163
+ const text = await page.locator('body').innerText({ timeout: 5000 }).catch(() => '');
164
+ const exists = pendingPattern.test(text);
165
+ if (!exists) return { exists: false, matchesRequest: false };
166
+ const pageText = compactText(text).toLowerCase();
167
+ const promptText = compactText(request.prompt).toLowerCase();
168
+ const corePrompt = compactText(request.prompt.replace(/^create exactly one.*?not still images\.\s*/i, '')).toLowerCase();
169
+ const matchesRequest = pageText.includes(promptText.slice(0, 80))
170
+ || (corePrompt.length > 20 && pageText.includes(corePrompt.slice(0, 80)));
171
+ return { exists, matchesRequest };
172
+ }
173
+
174
+ function compactText(value) {
175
+ return String(value || '').replace(/\s+/g, ' ').trim();
176
+ }
177
+
178
+ function requireManual(result, actions) {
179
+ result.status = 'manual_action_required';
180
+ result.manualActions.push(...actions);
181
+ }
182
+
183
+ function nextActionForOutcome(outcome = {}) {
184
+ if (outcome.status === 'failed') return 'Flow reported generation failure. Inspect the browser/run screenshots.';
185
+ if (outcome.status === 'active_generation_pending') return 'A different Flow generation is already pending. Wait for it to finish before submitting another prompt.';
186
+ if (outcome.status === 'prompt_not_found') return 'Prompt box was not found. Update Flow selectors or paste the prompt manually.';
187
+ if (outcome.status === 'submit_not_found') return 'Generate/Create button was not found. Submit manually in the opened browser.';
188
+ if (outcome.status === 'timeout') return 'Generation did not finish before the wait timeout. Leave trusted Chrome open and run inspect/observe again.';
189
+ return 'Generation may still be running or download controls changed. Inspect the browser/run screenshots.';
190
+ }
@@ -1,81 +1,81 @@
1
- import fs from 'node:fs';
2
- import path from 'node:path';
3
- import { defaultRunsDir, readJson } from './paths.mjs';
4
-
5
- export function inspectRun(target) {
6
- const runPath = resolveRunJson(target);
7
- const data = readJson(runPath);
8
- return summarize(data, runPath);
9
- }
10
-
11
- export function latestRun(outputDir = defaultRunsDir()) {
12
- const root = path.resolve(outputDir);
13
- if (!fs.existsSync(root)) {
14
- throw new Error(`Output directory does not exist: ${root}`);
15
- }
16
- const candidates = fs.readdirSync(root, { withFileTypes: true })
17
- .filter(entry => entry.isDirectory())
18
- .map(entry => path.join(root, entry.name, 'run.json'))
19
- .filter(filePath => fs.existsSync(filePath))
20
- .sort();
21
- if (!candidates.length) {
22
- throw new Error(`No TakomiFlow runs found in: ${root}`);
23
- }
24
- return summarize(readJson(candidates[candidates.length - 1]), candidates[candidates.length - 1]);
25
- }
26
-
27
- export function listRuns(outputDir = defaultRunsDir(), limit = 20) {
28
- const root = path.resolve(outputDir);
29
- if (!fs.existsSync(root)) {
30
- return { schemaVersion: 1, outputDir: root, runs: [] };
31
- }
32
- const runFiles = fs.readdirSync(root, { withFileTypes: true })
33
- .filter(entry => entry.isDirectory())
34
- .map(entry => path.join(root, entry.name, 'run.json'))
35
- .filter(filePath => fs.existsSync(filePath))
36
- .sort()
37
- .reverse()
38
- .slice(0, Math.max(1, Number.parseInt(limit || '20', 10) || 20));
39
- return {
40
- schemaVersion: 1,
41
- outputDir: root,
42
- runs: runFiles.map(filePath => summarize(readJson(filePath), filePath)),
43
- };
44
- }
45
-
46
- function resolveRunJson(target) {
47
- if (!target) throw new Error('inspect requires --run <run.json|run-dir>');
48
- const resolved = path.resolve(target);
49
- if (fs.existsSync(resolved) && fs.statSync(resolved).isDirectory()) {
50
- return path.join(resolved, 'run.json');
51
- }
52
- return resolved;
53
- }
54
-
55
- export function resolveRun(target) {
56
- const runJsonPath = resolveRunJson(target);
57
- const data = readJson(runJsonPath);
58
- return {
59
- runId: data.runId || path.basename(path.dirname(runJsonPath)),
60
- runDir: path.dirname(runJsonPath),
61
- downloadsDir: path.join(path.dirname(runJsonPath), 'downloads'),
62
- screenshotsDir: path.join(path.dirname(runJsonPath), 'screenshots'),
63
- data,
64
- };
65
- }
66
-
67
- function summarize(data, runJsonPath) {
68
- return {
69
- status: data.status,
70
- runId: data.runId,
71
- command: data.command,
72
- kind: data.kind,
73
- projectUrl: data.projectUrl,
74
- flowUrl: data.flowUrl,
75
- assets: data.assets || [],
76
- screenshots: data.screenshots || [],
77
- errors: data.errors || [],
78
- manualActions: data.manualActions || [],
79
- metadataPath: data.metadataPath || runJsonPath,
80
- };
81
- }
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { defaultRunsDir, readJson } from './paths.mjs';
4
+
5
+ export function inspectRun(target) {
6
+ const runPath = resolveRunJson(target);
7
+ const data = readJson(runPath);
8
+ return summarize(data, runPath);
9
+ }
10
+
11
+ export function latestRun(outputDir = defaultRunsDir()) {
12
+ const root = path.resolve(outputDir);
13
+ if (!fs.existsSync(root)) {
14
+ throw new Error(`Output directory does not exist: ${root}`);
15
+ }
16
+ const candidates = fs.readdirSync(root, { withFileTypes: true })
17
+ .filter(entry => entry.isDirectory())
18
+ .map(entry => path.join(root, entry.name, 'run.json'))
19
+ .filter(filePath => fs.existsSync(filePath))
20
+ .sort();
21
+ if (!candidates.length) {
22
+ throw new Error(`No TakomiFlow runs found in: ${root}`);
23
+ }
24
+ return summarize(readJson(candidates[candidates.length - 1]), candidates[candidates.length - 1]);
25
+ }
26
+
27
+ export function listRuns(outputDir = defaultRunsDir(), limit = 20) {
28
+ const root = path.resolve(outputDir);
29
+ if (!fs.existsSync(root)) {
30
+ return { schemaVersion: 1, outputDir: root, runs: [] };
31
+ }
32
+ const runFiles = fs.readdirSync(root, { withFileTypes: true })
33
+ .filter(entry => entry.isDirectory())
34
+ .map(entry => path.join(root, entry.name, 'run.json'))
35
+ .filter(filePath => fs.existsSync(filePath))
36
+ .sort()
37
+ .reverse()
38
+ .slice(0, Math.max(1, Number.parseInt(limit || '20', 10) || 20));
39
+ return {
40
+ schemaVersion: 1,
41
+ outputDir: root,
42
+ runs: runFiles.map(filePath => summarize(readJson(filePath), filePath)),
43
+ };
44
+ }
45
+
46
+ function resolveRunJson(target) {
47
+ if (!target) throw new Error('inspect requires --run <run.json|run-dir>');
48
+ const resolved = path.resolve(target);
49
+ if (fs.existsSync(resolved) && fs.statSync(resolved).isDirectory()) {
50
+ return path.join(resolved, 'run.json');
51
+ }
52
+ return resolved;
53
+ }
54
+
55
+ export function resolveRun(target) {
56
+ const runJsonPath = resolveRunJson(target);
57
+ const data = readJson(runJsonPath);
58
+ return {
59
+ runId: data.runId || path.basename(path.dirname(runJsonPath)),
60
+ runDir: path.dirname(runJsonPath),
61
+ downloadsDir: path.join(path.dirname(runJsonPath), 'downloads'),
62
+ screenshotsDir: path.join(path.dirname(runJsonPath), 'screenshots'),
63
+ data,
64
+ };
65
+ }
66
+
67
+ function summarize(data, runJsonPath) {
68
+ return {
69
+ status: data.status,
70
+ runId: data.runId,
71
+ command: data.command,
72
+ kind: data.kind,
73
+ projectUrl: data.projectUrl,
74
+ flowUrl: data.flowUrl,
75
+ assets: data.assets || [],
76
+ screenshots: data.screenshots || [],
77
+ errors: data.errors || [],
78
+ manualActions: data.manualActions || [],
79
+ metadataPath: data.metadataPath || runJsonPath,
80
+ };
81
+ }
@@ -1,39 +1,39 @@
1
- import * as z from 'zod/v4';
2
- import { collectPrompt, imagePrompt, reviewPrompt, videoPrompt } from './prompts.mjs';
3
-
4
- export function registerPrompts(server) {
5
- server.registerPrompt('takomi_flow_video_workflow', {
6
- description: 'Guide an agent through a safe TakomiFlow video generation workflow.',
7
- argsSchema: {
8
- topic: z.string().optional(),
9
- aspectRatio: z.string().optional(),
10
- variations: z.string().optional(),
11
- },
12
- }, args => videoPrompt(args));
13
-
14
- server.registerPrompt('takomi_flow_image_workflow', {
15
- description: 'Guide an agent through a safe TakomiFlow image generation workflow.',
16
- argsSchema: {
17
- topic: z.string().optional(),
18
- aspectRatio: z.string().optional(),
19
- variations: z.string().optional(),
20
- },
21
- }, args => imagePrompt(args));
22
-
23
- server.registerPrompt('takomi_flow_review_workflow', {
24
- description: 'Guide an agent through reviewing TakomiFlow run results and assets.',
25
- argsSchema: {
26
- run: z.string().optional(),
27
- frames: z.string().optional(),
28
- },
29
- }, args => reviewPrompt(args));
30
-
31
- server.registerPrompt('takomi_flow_collect_workflow', {
32
- description: 'Guide an agent through collecting reviewed TakomiFlow outputs into a downstream folder.',
33
- argsSchema: {
34
- run: z.string().optional(),
35
- targetDir: z.string().optional(),
36
- includeFrames: z.string().optional(),
37
- },
38
- }, args => collectPrompt(args));
39
- }
1
+ import * as z from 'zod/v4';
2
+ import { collectPrompt, imagePrompt, reviewPrompt, videoPrompt } from './prompts.mjs';
3
+
4
+ export function registerPrompts(server) {
5
+ server.registerPrompt('takomi_flow_video_workflow', {
6
+ description: 'Guide an agent through a safe TakomiFlow video generation workflow.',
7
+ argsSchema: {
8
+ topic: z.string().optional(),
9
+ aspectRatio: z.string().optional(),
10
+ variations: z.string().optional(),
11
+ },
12
+ }, args => videoPrompt(args));
13
+
14
+ server.registerPrompt('takomi_flow_image_workflow', {
15
+ description: 'Guide an agent through a safe TakomiFlow image generation workflow.',
16
+ argsSchema: {
17
+ topic: z.string().optional(),
18
+ aspectRatio: z.string().optional(),
19
+ variations: z.string().optional(),
20
+ },
21
+ }, args => imagePrompt(args));
22
+
23
+ server.registerPrompt('takomi_flow_review_workflow', {
24
+ description: 'Guide an agent through reviewing TakomiFlow run results and assets.',
25
+ argsSchema: {
26
+ run: z.string().optional(),
27
+ frames: z.string().optional(),
28
+ },
29
+ }, args => reviewPrompt(args));
30
+
31
+ server.registerPrompt('takomi_flow_collect_workflow', {
32
+ description: 'Guide an agent through collecting reviewed TakomiFlow outputs into a downstream folder.',
33
+ argsSchema: {
34
+ run: z.string().optional(),
35
+ targetDir: z.string().optional(),
36
+ includeFrames: z.string().optional(),
37
+ },
38
+ }, args => collectPrompt(args));
39
+ }
@@ -1,16 +1,16 @@
1
- import { RESOURCES, readResourceFile } from './resource-files.mjs';
2
-
3
- export function registerResources(server) {
4
- for (const resource of RESOURCES) {
5
- server.registerResource(resource.name, resource.uri, {
6
- mimeType: resource.mimeType,
7
- description: resource.description,
8
- }, async () => ({
9
- contents: [{
10
- uri: resource.uri,
11
- mimeType: resource.mimeType,
12
- text: readResourceFile(resource.file),
13
- }],
14
- }));
15
- }
16
- }
1
+ import { RESOURCES, readResourceFile } from './resource-files.mjs';
2
+
3
+ export function registerResources(server) {
4
+ for (const resource of RESOURCES) {
5
+ server.registerResource(resource.name, resource.uri, {
6
+ mimeType: resource.mimeType,
7
+ description: resource.description,
8
+ }, async () => ({
9
+ contents: [{
10
+ uri: resource.uri,
11
+ mimeType: resource.mimeType,
12
+ text: readResourceFile(resource.file),
13
+ }],
14
+ }));
15
+ }
16
+ }