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.
- package/.agents/plugins/marketplace.json +22 -22
- package/README.md +217 -422
- package/assets/.agent/skills/remotion/rules/assets/charts-bar-chart.tsx +173 -173
- package/assets/.agent/skills/remotion/rules/assets/text-animations-typewriter.tsx +100 -100
- package/assets/.agent/skills/remotion/rules/text-animations.md +20 -20
- package/assets/.agent/skills/remotion/rules/videos.md +171 -171
- package/assets/.agent/skills/remotion-real-ui-video/SKILL.md +264 -0
- package/assets/.agent/skills/remotion-real-ui-video/references/anti-patterns.md +114 -0
- package/assets/.agent/skills/remotion-real-ui-video/references/completion-checklist.md +75 -0
- package/assets/.agent/skills/remotion-real-ui-video/references/real-ui-bridge-example.md +93 -0
- package/assets/.agent/skills/takomi-flow/SKILL.md +244 -235
- package/docs/features/TakomiFlow_Portable_Plugin.md +82 -78
- package/docs/takomi-flow-onboarding.md +86 -76
- package/docs/takomi-flow-public-distribution.md +94 -94
- package/package.json +1 -1
- package/plugins/takomi-flow/.mcp.json +11 -11
- package/plugins/takomi-flow/assets/capabilities.json +203 -171
- package/plugins/takomi-flow/assets/collection.schema.json +29 -29
- package/plugins/takomi-flow/assets/examples.json +94 -94
- package/plugins/takomi-flow/assets/request.schema.json +91 -67
- package/plugins/takomi-flow/assets/result.schema.json +46 -45
- package/plugins/takomi-flow/assets/templates/image-request.json +17 -13
- package/plugins/takomi-flow/assets/templates/video-request.json +19 -15
- package/plugins/takomi-flow/package.json +38 -38
- package/plugins/takomi-flow/pnpm-lock.yaml +813 -813
- package/plugins/takomi-flow/references/flow-provider-contract.md +349 -326
- package/plugins/takomi-flow/scripts/lib/agent-plan.mjs +140 -130
- package/plugins/takomi-flow/scripts/lib/api.mjs +113 -113
- package/plugins/takomi-flow/scripts/lib/args.mjs +66 -57
- package/plugins/takomi-flow/scripts/lib/audit.mjs +65 -65
- package/plugins/takomi-flow/scripts/lib/browser.mjs +78 -39
- package/plugins/takomi-flow/scripts/lib/capabilities.mjs +11 -11
- package/plugins/takomi-flow/scripts/lib/collect.mjs +53 -53
- package/plugins/takomi-flow/scripts/lib/commands.mjs +192 -175
- package/plugins/takomi-flow/scripts/lib/doctor.mjs +77 -77
- package/plugins/takomi-flow/scripts/lib/examples.mjs +17 -17
- package/plugins/takomi-flow/scripts/lib/flow-media.mjs +39 -0
- package/plugins/takomi-flow/scripts/lib/flow-outcome.mjs +115 -90
- package/plugins/takomi-flow/scripts/lib/flow-project-session.mjs +153 -0
- package/plugins/takomi-flow/scripts/lib/flow-ui.mjs +204 -165
- package/plugins/takomi-flow/scripts/lib/generation.mjs +190 -90
- package/plugins/takomi-flow/scripts/lib/inspect.mjs +81 -81
- package/plugins/takomi-flow/scripts/lib/mcp-prompts.mjs +39 -39
- package/plugins/takomi-flow/scripts/lib/mcp-resources.mjs +16 -16
- package/plugins/takomi-flow/scripts/lib/mcp-tools.mjs +165 -150
- package/plugins/takomi-flow/scripts/lib/media.mjs +78 -78
- package/plugins/takomi-flow/scripts/lib/observe.mjs +54 -54
- package/plugins/takomi-flow/scripts/lib/paths.mjs +37 -37
- package/plugins/takomi-flow/scripts/lib/playwright-loader.mjs +23 -23
- package/plugins/takomi-flow/scripts/lib/prompts.mjs +72 -72
- package/plugins/takomi-flow/scripts/lib/report.mjs +141 -141
- package/plugins/takomi-flow/scripts/lib/request-validator.mjs +73 -58
- package/plugins/takomi-flow/scripts/lib/request.mjs +66 -55
- package/plugins/takomi-flow/scripts/lib/resource-files.mjs +69 -69
- package/plugins/takomi-flow/scripts/lib/result.mjs +31 -31
- package/plugins/takomi-flow/scripts/lib/review.mjs +44 -44
- package/plugins/takomi-flow/scripts/lib/selftest.mjs +153 -153
- package/plugins/takomi-flow/scripts/lib/settings-plan.mjs +44 -39
- package/plugins/takomi-flow/scripts/lib/templates.mjs +37 -37
- package/plugins/takomi-flow/scripts/lib/trusted-chrome.mjs +67 -67
- package/plugins/takomi-flow/scripts/lib/workflow.mjs +56 -56
- package/plugins/takomi-flow/scripts/mcp-server.mjs +18 -18
- package/plugins/takomi-flow/scripts/mcp-smoke.mjs +144 -142
- package/plugins/takomi-flow/scripts/takomi-flow.mjs +17 -17
- package/plugins/takomi-flow/skills/takomi-flow/SKILL.md +244 -235
- package/plugins/takomi-flow/skills/takomi-flow/agents/openai.yaml +7 -7
- package/scripts/install-takomi-flow.ps1 +85 -85
- 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,
|
|
7
|
-
import { handleGenerationFollowups, waitForGenerationOutcome } from './flow-outcome.mjs';
|
|
8
|
-
import { catalogAssets } from './media.mjs';
|
|
9
|
-
import { createSettingsPlan } from './settings-plan.mjs';
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
result.
|
|
48
|
-
if (
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
result.
|
|
73
|
-
result
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
+
}
|