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,153 +1,153 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { execFile } from 'node:child_process';
|
|
4
|
-
import { promisify } from 'node:util';
|
|
5
|
-
import { defaultRunsDir, ensureDir, readJson, writeJson } from './paths.mjs';
|
|
6
|
-
import { runDoctor } from './doctor.mjs';
|
|
7
|
-
import { loadCapabilities } from './capabilities.mjs';
|
|
8
|
-
import { loadTemplate } from './templates.mjs';
|
|
9
|
-
import { createPreparedRequest } from './request.mjs';
|
|
10
|
-
import { validateRequestFile } from './request-validator.mjs';
|
|
11
|
-
import { createRun, saveResult, baseResult } from './result.mjs';
|
|
12
|
-
import { catalogAssets } from './media.mjs';
|
|
13
|
-
import { createReport } from './report.mjs';
|
|
14
|
-
import { runWorkflow } from './workflow.mjs';
|
|
15
|
-
import { reviewRun } from './review.mjs';
|
|
16
|
-
import { runAudit } from './audit.mjs';
|
|
17
|
-
import { collectRun } from './collect.mjs';
|
|
18
|
-
import { loadExamples } from './examples.mjs';
|
|
19
|
-
import { createAgentPlan } from './agent-plan.mjs';
|
|
20
|
-
|
|
21
|
-
const execFileAsync = promisify(execFile);
|
|
22
|
-
|
|
23
|
-
export async function runSelfTest(options = {}) {
|
|
24
|
-
const outputDir = path.resolve(options.outputDir || defaultRunsDir(), 'selftest');
|
|
25
|
-
ensureDir(outputDir);
|
|
26
|
-
const steps = [];
|
|
27
|
-
steps.push(await step('doctor', () => runDoctor({ outputDir })));
|
|
28
|
-
steps.push(await step('audit', () => runAudit({ outputDir })));
|
|
29
|
-
steps.push(await step('capabilities', () => loadCapabilities()));
|
|
30
|
-
steps.push(await step('examples', () => loadExamples('cinematic-video')));
|
|
31
|
-
steps.push(await step('agentPlan', () => createAgentPlan({
|
|
32
|
-
kind: 'video',
|
|
33
|
-
prompt: 'TakomiFlow self-test planned video prompt',
|
|
34
|
-
variations: 2,
|
|
35
|
-
aspectRatio: '16:9',
|
|
36
|
-
durationSeconds: 8,
|
|
37
|
-
extractFrames: 2,
|
|
38
|
-
})));
|
|
39
|
-
steps.push(await step('videoTemplate', () => loadTemplate('video')));
|
|
40
|
-
steps.push(await step('imageTemplate', () => loadTemplate('image')));
|
|
41
|
-
const prepared = await step('prepareAndValidate', () => prepareAndValidate(outputDir));
|
|
42
|
-
steps.push(prepared);
|
|
43
|
-
steps.push(await step('workflow', () => noSpendWorkflow(outputDir)));
|
|
44
|
-
steps.push(await step('settingsPlan', () => verifySettingsPlan(outputDir)));
|
|
45
|
-
steps.push(await step('guardedGenerate', () => guardedGenerate(prepared.data?.requestPath)));
|
|
46
|
-
const catalog = await step('assetCatalog', () => syntheticAssetCatalog(outputDir));
|
|
47
|
-
steps.push(catalog);
|
|
48
|
-
steps.push(await step('report', () => selftestReport(catalog.data?.runDir)));
|
|
49
|
-
steps.push(await step('review', () => selftestReview(catalog.data?.runDir)));
|
|
50
|
-
steps.push(await step('collect', () => selftestCollect(catalog.data?.runDir, outputDir)));
|
|
51
|
-
const failed = steps.some(item => item.status !== 'ok');
|
|
52
|
-
return {
|
|
53
|
-
schemaVersion: 1,
|
|
54
|
-
status: failed ? 'failed' : 'ok',
|
|
55
|
-
outputDir,
|
|
56
|
-
steps,
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
async function step(name, fn) {
|
|
61
|
-
try {
|
|
62
|
-
return { name, status: 'ok', data: await fn() };
|
|
63
|
-
} catch (error) {
|
|
64
|
-
return { name, status: 'failed', error: error.message };
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function prepareAndValidate(outputDir) {
|
|
69
|
-
const { requestPath, request } = createPreparedRequest({
|
|
70
|
-
kind: 'video',
|
|
71
|
-
prompt: 'TakomiFlow self-test video prompt',
|
|
72
|
-
variations: 1,
|
|
73
|
-
'output-dir': outputDir,
|
|
74
|
-
'extract-frames': 2,
|
|
75
|
-
});
|
|
76
|
-
return { requestPath, request, validation: validateRequestFile(requestPath) };
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function guardedGenerate(requestPath) {
|
|
80
|
-
if (!requestPath) throw new Error('No request path available.');
|
|
81
|
-
const request = readJson(requestPath);
|
|
82
|
-
if (request.allowSpend) throw new Error('Self-test request unexpectedly allows spend.');
|
|
83
|
-
return {
|
|
84
|
-
requestPath,
|
|
85
|
-
wouldSubmit: false,
|
|
86
|
-
reason: 'allowSpend is false, so generate must be blocked by the spend guard.',
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
async function noSpendWorkflow(outputDir) {
|
|
91
|
-
return runWorkflow({
|
|
92
|
-
kind: 'image',
|
|
93
|
-
prompt: 'TakomiFlow self-test image prompt',
|
|
94
|
-
variations: 1,
|
|
95
|
-
outputDir,
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
async function verifySettingsPlan(outputDir) {
|
|
100
|
-
const result = await runWorkflow({
|
|
101
|
-
kind: 'video',
|
|
102
|
-
prompt: 'TakomiFlow settings plan prompt',
|
|
103
|
-
variations: 2,
|
|
104
|
-
aspectRatio: '16:9',
|
|
105
|
-
durationSeconds: 8,
|
|
106
|
-
outputDir,
|
|
107
|
-
});
|
|
108
|
-
if (!result.settingsPlan?.selectorDependent?.length) {
|
|
109
|
-
throw new Error('Expected selector-dependent settings in workflow result.');
|
|
110
|
-
}
|
|
111
|
-
return result.settingsPlan;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
async function syntheticAssetCatalog(outputDir) {
|
|
115
|
-
const run = createRun({ outputDir }, 'selftest-asset');
|
|
116
|
-
const videoPath = path.join(run.downloadsDir, 'sample.mp4');
|
|
117
|
-
await execFileAsync('ffmpeg', [
|
|
118
|
-
'-y',
|
|
119
|
-
'-f', 'lavfi',
|
|
120
|
-
'-i', 'testsrc=duration=2:size=320x180:rate=24',
|
|
121
|
-
'-c:v', 'libx264',
|
|
122
|
-
'-pix_fmt', 'yuv420p',
|
|
123
|
-
videoPath,
|
|
124
|
-
]);
|
|
125
|
-
const result = baseResult('downloaded', run, {
|
|
126
|
-
command: 'selftest',
|
|
127
|
-
kind: 'video',
|
|
128
|
-
assets: [videoPath],
|
|
129
|
-
});
|
|
130
|
-
saveResult(run, result);
|
|
131
|
-
const catalog = await catalogAssets(run, [videoPath], { frames: 2 });
|
|
132
|
-
return { ...catalog, runDir: run.runDir };
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
function selftestReport(runDir) {
|
|
136
|
-
if (!runDir) throw new Error('No self-test run directory available.');
|
|
137
|
-
return createReport({ run: runDir });
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
async function selftestReview(runDir) {
|
|
141
|
-
if (!runDir) throw new Error('No self-test run directory available.');
|
|
142
|
-
return reviewRun({ run: runDir, frames: 2 });
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
async function selftestCollect(runDir, outputDir) {
|
|
146
|
-
if (!runDir) throw new Error('No self-test run directory available.');
|
|
147
|
-
return collectRun({
|
|
148
|
-
run: runDir,
|
|
149
|
-
targetDir: path.join(outputDir, 'collected'),
|
|
150
|
-
frames: 2,
|
|
151
|
-
includeFrames: true,
|
|
152
|
-
});
|
|
153
|
-
}
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { execFile } from 'node:child_process';
|
|
4
|
+
import { promisify } from 'node:util';
|
|
5
|
+
import { defaultRunsDir, ensureDir, readJson, writeJson } from './paths.mjs';
|
|
6
|
+
import { runDoctor } from './doctor.mjs';
|
|
7
|
+
import { loadCapabilities } from './capabilities.mjs';
|
|
8
|
+
import { loadTemplate } from './templates.mjs';
|
|
9
|
+
import { createPreparedRequest } from './request.mjs';
|
|
10
|
+
import { validateRequestFile } from './request-validator.mjs';
|
|
11
|
+
import { createRun, saveResult, baseResult } from './result.mjs';
|
|
12
|
+
import { catalogAssets } from './media.mjs';
|
|
13
|
+
import { createReport } from './report.mjs';
|
|
14
|
+
import { runWorkflow } from './workflow.mjs';
|
|
15
|
+
import { reviewRun } from './review.mjs';
|
|
16
|
+
import { runAudit } from './audit.mjs';
|
|
17
|
+
import { collectRun } from './collect.mjs';
|
|
18
|
+
import { loadExamples } from './examples.mjs';
|
|
19
|
+
import { createAgentPlan } from './agent-plan.mjs';
|
|
20
|
+
|
|
21
|
+
const execFileAsync = promisify(execFile);
|
|
22
|
+
|
|
23
|
+
export async function runSelfTest(options = {}) {
|
|
24
|
+
const outputDir = path.resolve(options.outputDir || defaultRunsDir(), 'selftest');
|
|
25
|
+
ensureDir(outputDir);
|
|
26
|
+
const steps = [];
|
|
27
|
+
steps.push(await step('doctor', () => runDoctor({ outputDir })));
|
|
28
|
+
steps.push(await step('audit', () => runAudit({ outputDir })));
|
|
29
|
+
steps.push(await step('capabilities', () => loadCapabilities()));
|
|
30
|
+
steps.push(await step('examples', () => loadExamples('cinematic-video')));
|
|
31
|
+
steps.push(await step('agentPlan', () => createAgentPlan({
|
|
32
|
+
kind: 'video',
|
|
33
|
+
prompt: 'TakomiFlow self-test planned video prompt',
|
|
34
|
+
variations: 2,
|
|
35
|
+
aspectRatio: '16:9',
|
|
36
|
+
durationSeconds: 8,
|
|
37
|
+
extractFrames: 2,
|
|
38
|
+
})));
|
|
39
|
+
steps.push(await step('videoTemplate', () => loadTemplate('video')));
|
|
40
|
+
steps.push(await step('imageTemplate', () => loadTemplate('image')));
|
|
41
|
+
const prepared = await step('prepareAndValidate', () => prepareAndValidate(outputDir));
|
|
42
|
+
steps.push(prepared);
|
|
43
|
+
steps.push(await step('workflow', () => noSpendWorkflow(outputDir)));
|
|
44
|
+
steps.push(await step('settingsPlan', () => verifySettingsPlan(outputDir)));
|
|
45
|
+
steps.push(await step('guardedGenerate', () => guardedGenerate(prepared.data?.requestPath)));
|
|
46
|
+
const catalog = await step('assetCatalog', () => syntheticAssetCatalog(outputDir));
|
|
47
|
+
steps.push(catalog);
|
|
48
|
+
steps.push(await step('report', () => selftestReport(catalog.data?.runDir)));
|
|
49
|
+
steps.push(await step('review', () => selftestReview(catalog.data?.runDir)));
|
|
50
|
+
steps.push(await step('collect', () => selftestCollect(catalog.data?.runDir, outputDir)));
|
|
51
|
+
const failed = steps.some(item => item.status !== 'ok');
|
|
52
|
+
return {
|
|
53
|
+
schemaVersion: 1,
|
|
54
|
+
status: failed ? 'failed' : 'ok',
|
|
55
|
+
outputDir,
|
|
56
|
+
steps,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function step(name, fn) {
|
|
61
|
+
try {
|
|
62
|
+
return { name, status: 'ok', data: await fn() };
|
|
63
|
+
} catch (error) {
|
|
64
|
+
return { name, status: 'failed', error: error.message };
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function prepareAndValidate(outputDir) {
|
|
69
|
+
const { requestPath, request } = createPreparedRequest({
|
|
70
|
+
kind: 'video',
|
|
71
|
+
prompt: 'TakomiFlow self-test video prompt',
|
|
72
|
+
variations: 1,
|
|
73
|
+
'output-dir': outputDir,
|
|
74
|
+
'extract-frames': 2,
|
|
75
|
+
});
|
|
76
|
+
return { requestPath, request, validation: validateRequestFile(requestPath) };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function guardedGenerate(requestPath) {
|
|
80
|
+
if (!requestPath) throw new Error('No request path available.');
|
|
81
|
+
const request = readJson(requestPath);
|
|
82
|
+
if (request.allowSpend) throw new Error('Self-test request unexpectedly allows spend.');
|
|
83
|
+
return {
|
|
84
|
+
requestPath,
|
|
85
|
+
wouldSubmit: false,
|
|
86
|
+
reason: 'allowSpend is false, so generate must be blocked by the spend guard.',
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function noSpendWorkflow(outputDir) {
|
|
91
|
+
return runWorkflow({
|
|
92
|
+
kind: 'image',
|
|
93
|
+
prompt: 'TakomiFlow self-test image prompt',
|
|
94
|
+
variations: 1,
|
|
95
|
+
outputDir,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function verifySettingsPlan(outputDir) {
|
|
100
|
+
const result = await runWorkflow({
|
|
101
|
+
kind: 'video',
|
|
102
|
+
prompt: 'TakomiFlow settings plan prompt',
|
|
103
|
+
variations: 2,
|
|
104
|
+
aspectRatio: '16:9',
|
|
105
|
+
durationSeconds: 8,
|
|
106
|
+
outputDir,
|
|
107
|
+
});
|
|
108
|
+
if (!result.settingsPlan?.selectorDependent?.length) {
|
|
109
|
+
throw new Error('Expected selector-dependent settings in workflow result.');
|
|
110
|
+
}
|
|
111
|
+
return result.settingsPlan;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async function syntheticAssetCatalog(outputDir) {
|
|
115
|
+
const run = createRun({ outputDir }, 'selftest-asset');
|
|
116
|
+
const videoPath = path.join(run.downloadsDir, 'sample.mp4');
|
|
117
|
+
await execFileAsync('ffmpeg', [
|
|
118
|
+
'-y',
|
|
119
|
+
'-f', 'lavfi',
|
|
120
|
+
'-i', 'testsrc=duration=2:size=320x180:rate=24',
|
|
121
|
+
'-c:v', 'libx264',
|
|
122
|
+
'-pix_fmt', 'yuv420p',
|
|
123
|
+
videoPath,
|
|
124
|
+
]);
|
|
125
|
+
const result = baseResult('downloaded', run, {
|
|
126
|
+
command: 'selftest',
|
|
127
|
+
kind: 'video',
|
|
128
|
+
assets: [videoPath],
|
|
129
|
+
});
|
|
130
|
+
saveResult(run, result);
|
|
131
|
+
const catalog = await catalogAssets(run, [videoPath], { frames: 2 });
|
|
132
|
+
return { ...catalog, runDir: run.runDir };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function selftestReport(runDir) {
|
|
136
|
+
if (!runDir) throw new Error('No self-test run directory available.');
|
|
137
|
+
return createReport({ run: runDir });
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async function selftestReview(runDir) {
|
|
141
|
+
if (!runDir) throw new Error('No self-test run directory available.');
|
|
142
|
+
return reviewRun({ run: runDir, frames: 2 });
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async function selftestCollect(runDir, outputDir) {
|
|
146
|
+
if (!runDir) throw new Error('No self-test run directory available.');
|
|
147
|
+
return collectRun({
|
|
148
|
+
run: runDir,
|
|
149
|
+
targetDir: path.join(outputDir, 'collected'),
|
|
150
|
+
frames: 2,
|
|
151
|
+
includeFrames: true,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
@@ -1,39 +1,44 @@
|
|
|
1
|
-
export function createSettingsPlan(request) {
|
|
2
|
-
const requested = {
|
|
3
|
-
kind: request.kind,
|
|
4
|
-
mode: request.mode,
|
|
5
|
-
variations: request.variations,
|
|
6
|
-
aspectRatio: request.aspectRatio,
|
|
7
|
-
durationSeconds: request.durationSeconds,
|
|
8
|
-
modelHint: request.modelHint,
|
|
9
|
-
extractFrames: request.extractFrames,
|
|
10
|
-
sourceAssets: request.sourceAssets || [],
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
if (request.
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
1
|
+
export function createSettingsPlan(request) {
|
|
2
|
+
const requested = {
|
|
3
|
+
kind: request.kind,
|
|
4
|
+
mode: request.mode,
|
|
5
|
+
variations: request.variations,
|
|
6
|
+
aspectRatio: request.aspectRatio,
|
|
7
|
+
durationSeconds: request.durationSeconds,
|
|
8
|
+
modelHint: request.modelHint,
|
|
9
|
+
extractFrames: request.extractFrames,
|
|
10
|
+
sourceAssets: request.sourceAssets || [],
|
|
11
|
+
projectUrl: request.projectUrl,
|
|
12
|
+
reuseCurrentProject: request.reuseCurrentProject,
|
|
13
|
+
allowNewProject: request.allowNewProject,
|
|
14
|
+
freshChatOnFailure: request.freshChatOnFailure,
|
|
15
|
+
editorWaitMs: request.editorWaitMs,
|
|
16
|
+
};
|
|
17
|
+
const automatic = ['prompt', 'project reuse', 'same-project chat recovery', 'download folder', 'metadata', 'asset catalog'];
|
|
18
|
+
if (request.extractFrames > 0) automatic.push('video review frame extraction after download');
|
|
19
|
+
return {
|
|
20
|
+
schemaVersion: 1,
|
|
21
|
+
requested,
|
|
22
|
+
automatic,
|
|
23
|
+
selectorDependent: selectorDependent(request),
|
|
24
|
+
notes: [
|
|
25
|
+
'Prompt submission and downloads are automated through public Flow UI selectors.',
|
|
26
|
+
'Visual settings remain explicit in metadata until live Flow controls are observed and selector coverage is tuned.',
|
|
27
|
+
],
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function selectorDependent(request) {
|
|
32
|
+
const items = [];
|
|
33
|
+
if (request.mode) items.push({ field: 'mode', value: request.mode });
|
|
34
|
+
if (request.aspectRatio) items.push({ field: 'aspectRatio', value: request.aspectRatio });
|
|
35
|
+
if (request.durationSeconds) items.push({ field: 'durationSeconds', value: request.durationSeconds });
|
|
36
|
+
if (request.modelHint && request.modelHint !== 'best-available') {
|
|
37
|
+
items.push({ field: 'modelHint', value: request.modelHint });
|
|
38
|
+
}
|
|
39
|
+
if (request.sourceAssets?.length) {
|
|
40
|
+
items.push({ field: 'sourceAssets', value: request.sourceAssets });
|
|
41
|
+
}
|
|
42
|
+
if (request.variations > 1) items.push({ field: 'variations', value: request.variations });
|
|
43
|
+
return items;
|
|
44
|
+
}
|
|
@@ -1,37 +1,37 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { fileURLToPath } from 'node:url';
|
|
4
|
-
import { ensureDir, writeJson } from './paths.mjs';
|
|
5
|
-
|
|
6
|
-
const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
-
const PLUGIN_ROOT = path.resolve(MODULE_DIR, '..', '..');
|
|
8
|
-
const TEMPLATE_DIR = path.join(PLUGIN_ROOT, 'assets', 'templates');
|
|
9
|
-
const TEMPLATES = {
|
|
10
|
-
video: 'video-request.json',
|
|
11
|
-
image: 'image-request.json',
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
export function listTemplates() {
|
|
15
|
-
return Object.keys(TEMPLATES).map(kind => ({ kind, path: templatePath(kind) }));
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function loadTemplate(kind) {
|
|
19
|
-
const filePath = templatePath(kind);
|
|
20
|
-
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function writeTemplate(kind, outputDir) {
|
|
24
|
-
const template = loadTemplate(kind);
|
|
25
|
-
const destinationDir = ensureDir(path.resolve(outputDir));
|
|
26
|
-
const destination = path.join(destinationDir, TEMPLATES[kind]);
|
|
27
|
-
writeJson(destination, template);
|
|
28
|
-
return { kind, destination, template };
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function templatePath(kind) {
|
|
32
|
-
const fileName = TEMPLATES[String(kind || '').toLowerCase()];
|
|
33
|
-
if (!fileName) {
|
|
34
|
-
throw new Error(`Unknown template "${kind}". Expected image or video.`);
|
|
35
|
-
}
|
|
36
|
-
return path.join(TEMPLATE_DIR, fileName);
|
|
37
|
-
}
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { ensureDir, writeJson } from './paths.mjs';
|
|
5
|
+
|
|
6
|
+
const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const PLUGIN_ROOT = path.resolve(MODULE_DIR, '..', '..');
|
|
8
|
+
const TEMPLATE_DIR = path.join(PLUGIN_ROOT, 'assets', 'templates');
|
|
9
|
+
const TEMPLATES = {
|
|
10
|
+
video: 'video-request.json',
|
|
11
|
+
image: 'image-request.json',
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export function listTemplates() {
|
|
15
|
+
return Object.keys(TEMPLATES).map(kind => ({ kind, path: templatePath(kind) }));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function loadTemplate(kind) {
|
|
19
|
+
const filePath = templatePath(kind);
|
|
20
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function writeTemplate(kind, outputDir) {
|
|
24
|
+
const template = loadTemplate(kind);
|
|
25
|
+
const destinationDir = ensureDir(path.resolve(outputDir));
|
|
26
|
+
const destination = path.join(destinationDir, TEMPLATES[kind]);
|
|
27
|
+
writeJson(destination, template);
|
|
28
|
+
return { kind, destination, template };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function templatePath(kind) {
|
|
32
|
+
const fileName = TEMPLATES[String(kind || '').toLowerCase()];
|
|
33
|
+
if (!fileName) {
|
|
34
|
+
throw new Error(`Unknown template "${kind}". Expected image or video.`);
|
|
35
|
+
}
|
|
36
|
+
return path.join(TEMPLATE_DIR, fileName);
|
|
37
|
+
}
|
|
@@ -1,67 +1,67 @@
|
|
|
1
|
-
import os from 'node:os';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import fs from 'node:fs';
|
|
4
|
-
import { spawn } from 'node:child_process';
|
|
5
|
-
import { ensureDir, FLOW_URL } from './paths.mjs';
|
|
6
|
-
|
|
7
|
-
export function defaultTrustedProfileDir() {
|
|
8
|
-
return path.join(os.homedir(), '.takomi-flow', 'trusted-chrome-profile');
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function launchTrustedChrome(args = {}) {
|
|
12
|
-
const port = Number.parseInt(args.port || '9222', 10) || 9222;
|
|
13
|
-
const userDataDir = ensureDir(path.resolve(args.profileDir || defaultTrustedProfileDir()));
|
|
14
|
-
const executable = args.chromePath || findChromeExecutable();
|
|
15
|
-
const chromeArgs = [
|
|
16
|
-
`--remote-debugging-port=${port}`,
|
|
17
|
-
`--user-data-dir=${userDataDir}`,
|
|
18
|
-
'--no-first-run',
|
|
19
|
-
'--new-window',
|
|
20
|
-
args.url || FLOW_URL,
|
|
21
|
-
];
|
|
22
|
-
if (args.printCommand) {
|
|
23
|
-
return result({ executable, chromeArgs, port, userDataDir, launched: false });
|
|
24
|
-
}
|
|
25
|
-
const child = spawn(executable, chromeArgs, {
|
|
26
|
-
detached: true,
|
|
27
|
-
stdio: 'ignore',
|
|
28
|
-
windowsHide: false,
|
|
29
|
-
});
|
|
30
|
-
child.unref();
|
|
31
|
-
return result({ executable, chromeArgs, port, userDataDir, launched: true, processId: child.pid });
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function result(data) {
|
|
35
|
-
return {
|
|
36
|
-
schemaVersion: 1,
|
|
37
|
-
status: 'ok',
|
|
38
|
-
cdpUrl: `http://127.0.0.1:${data.port}`,
|
|
39
|
-
loginUrl: data.chromeArgs[data.chromeArgs.length - 1],
|
|
40
|
-
userDataDir: data.userDataDir,
|
|
41
|
-
executable: data.executable,
|
|
42
|
-
args: data.chromeArgs,
|
|
43
|
-
launched: data.launched,
|
|
44
|
-
processId: data.processId,
|
|
45
|
-
nextActions: [
|
|
46
|
-
'Log into Google Flow manually in the opened Chrome window.',
|
|
47
|
-
`After login, use --cdp-url http://127.0.0.1:${data.port} with observe, smoke, workflow, or generate.`,
|
|
48
|
-
'Keep this Chrome window open while TakomiFlow attaches to it.',
|
|
49
|
-
],
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function findChromeExecutable() {
|
|
54
|
-
const candidates = process.platform === 'win32' ? [
|
|
55
|
-
path.join(process.env.PROGRAMFILES || 'C:\\Program Files', 'Google', 'Chrome', 'Application', 'chrome.exe'),
|
|
56
|
-
path.join(process.env['PROGRAMFILES(X86)'] || 'C:\\Program Files (x86)', 'Google', 'Chrome', 'Application', 'chrome.exe'),
|
|
57
|
-
path.join(process.env.LOCALAPPDATA || '', 'Google', 'Chrome', 'Application', 'chrome.exe'),
|
|
58
|
-
] : [
|
|
59
|
-
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
|
|
60
|
-
'/usr/bin/google-chrome',
|
|
61
|
-
'/usr/bin/google-chrome-stable',
|
|
62
|
-
'/usr/bin/chromium-browser',
|
|
63
|
-
];
|
|
64
|
-
const found = candidates.find(candidate => candidate && fs.existsSync(candidate));
|
|
65
|
-
if (!found) throw new Error('Could not find Chrome. Pass --chrome-path <path-to-chrome>.');
|
|
66
|
-
return found;
|
|
67
|
-
}
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import { spawn } from 'node:child_process';
|
|
5
|
+
import { ensureDir, FLOW_URL } from './paths.mjs';
|
|
6
|
+
|
|
7
|
+
export function defaultTrustedProfileDir() {
|
|
8
|
+
return path.join(os.homedir(), '.takomi-flow', 'trusted-chrome-profile');
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function launchTrustedChrome(args = {}) {
|
|
12
|
+
const port = Number.parseInt(args.port || '9222', 10) || 9222;
|
|
13
|
+
const userDataDir = ensureDir(path.resolve(args.profileDir || defaultTrustedProfileDir()));
|
|
14
|
+
const executable = args.chromePath || findChromeExecutable();
|
|
15
|
+
const chromeArgs = [
|
|
16
|
+
`--remote-debugging-port=${port}`,
|
|
17
|
+
`--user-data-dir=${userDataDir}`,
|
|
18
|
+
'--no-first-run',
|
|
19
|
+
'--new-window',
|
|
20
|
+
args.url || FLOW_URL,
|
|
21
|
+
];
|
|
22
|
+
if (args.printCommand) {
|
|
23
|
+
return result({ executable, chromeArgs, port, userDataDir, launched: false });
|
|
24
|
+
}
|
|
25
|
+
const child = spawn(executable, chromeArgs, {
|
|
26
|
+
detached: true,
|
|
27
|
+
stdio: 'ignore',
|
|
28
|
+
windowsHide: false,
|
|
29
|
+
});
|
|
30
|
+
child.unref();
|
|
31
|
+
return result({ executable, chromeArgs, port, userDataDir, launched: true, processId: child.pid });
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function result(data) {
|
|
35
|
+
return {
|
|
36
|
+
schemaVersion: 1,
|
|
37
|
+
status: 'ok',
|
|
38
|
+
cdpUrl: `http://127.0.0.1:${data.port}`,
|
|
39
|
+
loginUrl: data.chromeArgs[data.chromeArgs.length - 1],
|
|
40
|
+
userDataDir: data.userDataDir,
|
|
41
|
+
executable: data.executable,
|
|
42
|
+
args: data.chromeArgs,
|
|
43
|
+
launched: data.launched,
|
|
44
|
+
processId: data.processId,
|
|
45
|
+
nextActions: [
|
|
46
|
+
'Log into Google Flow manually in the opened Chrome window.',
|
|
47
|
+
`After login, use --cdp-url http://127.0.0.1:${data.port} with observe, smoke, workflow, or generate.`,
|
|
48
|
+
'Keep this Chrome window open while TakomiFlow attaches to it.',
|
|
49
|
+
],
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function findChromeExecutable() {
|
|
54
|
+
const candidates = process.platform === 'win32' ? [
|
|
55
|
+
path.join(process.env.PROGRAMFILES || 'C:\\Program Files', 'Google', 'Chrome', 'Application', 'chrome.exe'),
|
|
56
|
+
path.join(process.env['PROGRAMFILES(X86)'] || 'C:\\Program Files (x86)', 'Google', 'Chrome', 'Application', 'chrome.exe'),
|
|
57
|
+
path.join(process.env.LOCALAPPDATA || '', 'Google', 'Chrome', 'Application', 'chrome.exe'),
|
|
58
|
+
] : [
|
|
59
|
+
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
|
|
60
|
+
'/usr/bin/google-chrome',
|
|
61
|
+
'/usr/bin/google-chrome-stable',
|
|
62
|
+
'/usr/bin/chromium-browser',
|
|
63
|
+
];
|
|
64
|
+
const found = candidates.find(candidate => candidate && fs.existsSync(candidate));
|
|
65
|
+
if (!found) throw new Error('Could not find Chrome. Pass --chrome-path <path-to-chrome>.');
|
|
66
|
+
return found;
|
|
67
|
+
}
|