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,58 +1,73 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { loadCapabilities } from './capabilities.mjs';
|
|
4
|
-
import { normalizeRequest } from './request.mjs';
|
|
5
|
-
|
|
6
|
-
export function validateRequestFile(requestPath) {
|
|
7
|
-
if (!requestPath) throw new Error('validate requires --request <path>');
|
|
8
|
-
const absolutePath = path.resolve(requestPath);
|
|
9
|
-
const raw = JSON.parse(fs.readFileSync(absolutePath, 'utf8'));
|
|
10
|
-
return validateRequest(raw, absolutePath);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function validateRequest(raw, requestPath = undefined) {
|
|
14
|
-
const capabilities = loadCapabilities();
|
|
15
|
-
const errors = [];
|
|
16
|
-
const warnings = [];
|
|
17
|
-
let normalized;
|
|
18
|
-
try {
|
|
19
|
-
normalized = normalizeRequest(raw);
|
|
20
|
-
} catch (error) {
|
|
21
|
-
return {
|
|
22
|
-
schemaVersion: 1,
|
|
23
|
-
status: 'failed',
|
|
24
|
-
requestPath,
|
|
25
|
-
errors: [error.message],
|
|
26
|
-
warnings,
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
if (!capabilities.kinds.includes(normalized.kind)) {
|
|
30
|
-
errors.push(`Unsupported kind: ${normalized.kind}`);
|
|
31
|
-
}
|
|
32
|
-
const allowedModes = capabilities.modes[normalized.kind] || [];
|
|
33
|
-
if (normalized.mode && !allowedModes.includes(normalized.mode)) {
|
|
34
|
-
warnings.push(`Mode "${normalized.mode}" is not listed for kind "${normalized.kind}".`);
|
|
35
|
-
}
|
|
36
|
-
if (normalized.variations > capabilities.variations.max) {
|
|
37
|
-
warnings.push(`Variations ${normalized.variations} exceeds listed max ${capabilities.variations.max}.`);
|
|
38
|
-
}
|
|
39
|
-
if (normalized.aspectRatio && !capabilities.aspectRatios.includes(normalized.aspectRatio)) {
|
|
40
|
-
warnings.push(`Aspect ratio "${normalized.aspectRatio}" is not in listed options.`);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { loadCapabilities } from './capabilities.mjs';
|
|
4
|
+
import { normalizeRequest } from './request.mjs';
|
|
5
|
+
|
|
6
|
+
export function validateRequestFile(requestPath) {
|
|
7
|
+
if (!requestPath) throw new Error('validate requires --request <path>');
|
|
8
|
+
const absolutePath = path.resolve(requestPath);
|
|
9
|
+
const raw = JSON.parse(fs.readFileSync(absolutePath, 'utf8'));
|
|
10
|
+
return validateRequest(raw, absolutePath);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function validateRequest(raw, requestPath = undefined) {
|
|
14
|
+
const capabilities = loadCapabilities();
|
|
15
|
+
const errors = [];
|
|
16
|
+
const warnings = [];
|
|
17
|
+
let normalized;
|
|
18
|
+
try {
|
|
19
|
+
normalized = normalizeRequest(raw);
|
|
20
|
+
} catch (error) {
|
|
21
|
+
return {
|
|
22
|
+
schemaVersion: 1,
|
|
23
|
+
status: 'failed',
|
|
24
|
+
requestPath,
|
|
25
|
+
errors: [error.message],
|
|
26
|
+
warnings,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
if (!capabilities.kinds.includes(normalized.kind)) {
|
|
30
|
+
errors.push(`Unsupported kind: ${normalized.kind}`);
|
|
31
|
+
}
|
|
32
|
+
const allowedModes = capabilities.modes[normalized.kind] || [];
|
|
33
|
+
if (normalized.mode && !allowedModes.includes(normalized.mode)) {
|
|
34
|
+
warnings.push(`Mode "${normalized.mode}" is not listed for kind "${normalized.kind}".`);
|
|
35
|
+
}
|
|
36
|
+
if (normalized.variations > capabilities.variations.max) {
|
|
37
|
+
warnings.push(`Variations ${normalized.variations} exceeds listed max ${capabilities.variations.max}.`);
|
|
38
|
+
}
|
|
39
|
+
if (normalized.aspectRatio && !capabilities.aspectRatios.includes(normalized.aspectRatio)) {
|
|
40
|
+
warnings.push(`Aspect ratio "${normalized.aspectRatio}" is not in listed options.`);
|
|
41
|
+
}
|
|
42
|
+
if (normalized.projectUrl && !isFlowProjectUrl(normalized.projectUrl)) {
|
|
43
|
+
errors.push(`Project URL must be a Google Flow project URL: ${normalized.projectUrl}`);
|
|
44
|
+
}
|
|
45
|
+
if (normalized.editorWaitMs !== undefined && normalized.editorWaitMs < 1000) {
|
|
46
|
+
errors.push('editorWaitMs must be at least 1000 milliseconds.');
|
|
47
|
+
}
|
|
48
|
+
for (const asset of normalized.sourceAssets) {
|
|
49
|
+
if (!fs.existsSync(path.resolve(asset))) {
|
|
50
|
+
errors.push(`Source asset does not exist: ${asset}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (!normalized.allowSpend) {
|
|
54
|
+
warnings.push('Spend guard is off. Generation will not submit until allowSpend=true or TAKOMI_FLOW_ALLOW_SPEND=true.');
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
schemaVersion: 1,
|
|
58
|
+
status: errors.length ? 'failed' : 'ok',
|
|
59
|
+
requestPath,
|
|
60
|
+
normalized,
|
|
61
|
+
errors,
|
|
62
|
+
warnings,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function isFlowProjectUrl(value) {
|
|
67
|
+
try {
|
|
68
|
+
const url = new URL(value);
|
|
69
|
+
return url.hostname === 'labs.google' && url.pathname.includes('/fx/tools/flow/project/');
|
|
70
|
+
} catch {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -1,55 +1,66 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
|
-
import { defaultRunsDir, ensureDir, timestampId, writeJson } from './paths.mjs';
|
|
3
|
-
|
|
4
|
-
const VALID_KINDS = new Set(['video', 'image']);
|
|
5
|
-
|
|
6
|
-
export function normalizeRequest(input) {
|
|
7
|
-
const kind = String(input.kind || 'video').toLowerCase();
|
|
8
|
-
if (!VALID_KINDS.has(kind)) {
|
|
9
|
-
throw new Error(`Invalid kind "${kind}". Expected video or image.`);
|
|
10
|
-
}
|
|
11
|
-
const prompt = String(input.prompt || '').trim();
|
|
12
|
-
if (!prompt) {
|
|
13
|
-
throw new Error('A non-empty prompt is required.');
|
|
14
|
-
}
|
|
15
|
-
const variations = Math.max(1, Number.parseInt(input.variations || '1', 10) || 1);
|
|
16
|
-
return {
|
|
17
|
-
schemaVersion: 1,
|
|
18
|
-
kind,
|
|
19
|
-
prompt,
|
|
20
|
-
variations,
|
|
21
|
-
aspectRatio: input.aspectRatio || input['aspect-ratio'] || undefined,
|
|
22
|
-
durationSeconds: toOptionalNumber(input.durationSeconds || input.duration),
|
|
23
|
-
mode: input.mode || (kind === 'video' ? 'text-to-video' : 'text-to-image'),
|
|
24
|
-
modelHint: input.modelHint || input.model || 'best-available',
|
|
25
|
-
outputDir: path.resolve(input.outputDir || input['output-dir'] || defaultRunsDir()),
|
|
26
|
-
allowSpend:
|
|
27
|
-
extractFrames: Math.max(0, Number.parseInt(input.extractFrames || input['extract-frames'] || '0', 10) || 0),
|
|
28
|
-
sourceAssets: normalizeList(input.sourceAssets || input.assets),
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { defaultRunsDir, ensureDir, timestampId, writeJson } from './paths.mjs';
|
|
3
|
+
|
|
4
|
+
const VALID_KINDS = new Set(['video', 'image']);
|
|
5
|
+
|
|
6
|
+
export function normalizeRequest(input) {
|
|
7
|
+
const kind = String(input.kind || 'video').toLowerCase();
|
|
8
|
+
if (!VALID_KINDS.has(kind)) {
|
|
9
|
+
throw new Error(`Invalid kind "${kind}". Expected video or image.`);
|
|
10
|
+
}
|
|
11
|
+
const prompt = String(input.prompt || '').trim();
|
|
12
|
+
if (!prompt) {
|
|
13
|
+
throw new Error('A non-empty prompt is required.');
|
|
14
|
+
}
|
|
15
|
+
const variations = Math.max(1, Number.parseInt(input.variations || '1', 10) || 1);
|
|
16
|
+
return {
|
|
17
|
+
schemaVersion: 1,
|
|
18
|
+
kind,
|
|
19
|
+
prompt,
|
|
20
|
+
variations,
|
|
21
|
+
aspectRatio: input.aspectRatio || input['aspect-ratio'] || undefined,
|
|
22
|
+
durationSeconds: toOptionalNumber(input.durationSeconds || input.duration),
|
|
23
|
+
mode: input.mode || (kind === 'video' ? 'text-to-video' : 'text-to-image'),
|
|
24
|
+
modelHint: input.modelHint || input.model || 'best-available',
|
|
25
|
+
outputDir: path.resolve(input.outputDir || input['output-dir'] || defaultRunsDir()),
|
|
26
|
+
allowSpend: toOptionalBoolean(input.allowSpend ?? input['allow-spend'], false),
|
|
27
|
+
extractFrames: Math.max(0, Number.parseInt(input.extractFrames || input['extract-frames'] || '0', 10) || 0),
|
|
28
|
+
sourceAssets: normalizeList(input.sourceAssets || input.assets),
|
|
29
|
+
projectUrl: input.projectUrl || input['project-url'] || undefined,
|
|
30
|
+
reuseCurrentProject: toOptionalBoolean(input.reuseCurrentProject ?? input['reuse-current-project'], true),
|
|
31
|
+
allowNewProject: toOptionalBoolean(input.allowNewProject ?? input['allow-new-project'], false),
|
|
32
|
+
freshChatOnFailure: toOptionalBoolean(input.freshChatOnFailure ?? input['fresh-chat-on-failure'], true),
|
|
33
|
+
editorWaitMs: toOptionalNumber(input.editorWaitMs || input['editor-wait-ms']),
|
|
34
|
+
notes: input.notes || undefined,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function createPreparedRequest(args) {
|
|
39
|
+
const request = normalizeRequest(args);
|
|
40
|
+
const requestDir = ensureDir(path.join(request.outputDir, 'requests'));
|
|
41
|
+
const requestPath = path.join(requestDir, `${timestampId('request')}.json`);
|
|
42
|
+
writeJson(requestPath, { ...request, status: 'prepared', requestPath });
|
|
43
|
+
return { request, requestPath };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function canSpend(request) {
|
|
47
|
+
return request.allowSpend === true || process.env.TAKOMI_FLOW_ALLOW_SPEND === 'true';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function toOptionalNumber(value) {
|
|
51
|
+
if (value === undefined || value === null || value === '') return undefined;
|
|
52
|
+
const parsed = Number(value);
|
|
53
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function toOptionalBoolean(value, defaultValue) {
|
|
57
|
+
if (value === undefined || value === null || value === '') return defaultValue;
|
|
58
|
+
if (typeof value === 'boolean') return value;
|
|
59
|
+
return ['1', 'true', 'yes', 'on'].includes(String(value).toLowerCase());
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function normalizeList(value) {
|
|
63
|
+
if (!value) return [];
|
|
64
|
+
if (Array.isArray(value)) return value;
|
|
65
|
+
return String(value).split(',').map(item => item.trim()).filter(Boolean);
|
|
66
|
+
}
|
|
@@ -1,69 +1,69 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { fileURLToPath } from 'node:url';
|
|
4
|
-
|
|
5
|
-
const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
6
|
-
const PLUGIN_ROOT = path.resolve(MODULE_DIR, '..', '..');
|
|
7
|
-
|
|
8
|
-
export const RESOURCES = [
|
|
9
|
-
{
|
|
10
|
-
name: 'takomi-flow-contract',
|
|
11
|
-
uri: 'takomi-flow://contract',
|
|
12
|
-
file: path.join(PLUGIN_ROOT, 'references', 'flow-provider-contract.md'),
|
|
13
|
-
mimeType: 'text/markdown',
|
|
14
|
-
description: 'TakomiFlow provider contract and agent workflow.',
|
|
15
|
-
},
|
|
16
|
-
{
|
|
17
|
-
name: 'takomi-flow-capabilities',
|
|
18
|
-
uri: 'takomi-flow://capabilities',
|
|
19
|
-
file: path.join(PLUGIN_ROOT, 'assets', 'capabilities.json'),
|
|
20
|
-
mimeType: 'application/json',
|
|
21
|
-
description: 'Machine-readable TakomiFlow capabilities.',
|
|
22
|
-
},
|
|
23
|
-
{
|
|
24
|
-
name: 'takomi-flow-examples',
|
|
25
|
-
uri: 'takomi-flow://examples',
|
|
26
|
-
file: path.join(PLUGIN_ROOT, 'assets', 'examples.json'),
|
|
27
|
-
mimeType: 'application/json',
|
|
28
|
-
description: 'Machine-readable TakomiFlow request and handoff examples.',
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
name: 'takomi-flow-request-schema',
|
|
32
|
-
uri: 'takomi-flow://schemas/request',
|
|
33
|
-
file: path.join(PLUGIN_ROOT, 'assets', 'request.schema.json'),
|
|
34
|
-
mimeType: 'application/schema+json',
|
|
35
|
-
description: 'JSON Schema for TakomiFlow request files.',
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
name: 'takomi-flow-result-schema',
|
|
39
|
-
uri: 'takomi-flow://schemas/result',
|
|
40
|
-
file: path.join(PLUGIN_ROOT, 'assets', 'result.schema.json'),
|
|
41
|
-
mimeType: 'application/schema+json',
|
|
42
|
-
description: 'JSON Schema for TakomiFlow run result metadata.',
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
name: 'takomi-flow-collection-schema',
|
|
46
|
-
uri: 'takomi-flow://schemas/collection',
|
|
47
|
-
file: path.join(PLUGIN_ROOT, 'assets', 'collection.schema.json'),
|
|
48
|
-
mimeType: 'application/schema+json',
|
|
49
|
-
description: 'JSON Schema for TakomiFlow collection manifests.',
|
|
50
|
-
},
|
|
51
|
-
{
|
|
52
|
-
name: 'takomi-flow-video-template',
|
|
53
|
-
uri: 'takomi-flow://templates/video',
|
|
54
|
-
file: path.join(PLUGIN_ROOT, 'assets', 'templates', 'video-request.json'),
|
|
55
|
-
mimeType: 'application/json',
|
|
56
|
-
description: 'Video request template.',
|
|
57
|
-
},
|
|
58
|
-
{
|
|
59
|
-
name: 'takomi-flow-image-template',
|
|
60
|
-
uri: 'takomi-flow://templates/image',
|
|
61
|
-
file: path.join(PLUGIN_ROOT, 'assets', 'templates', 'image-request.json'),
|
|
62
|
-
mimeType: 'application/json',
|
|
63
|
-
description: 'Image request template.',
|
|
64
|
-
},
|
|
65
|
-
];
|
|
66
|
-
|
|
67
|
-
export function readResourceFile(filePath) {
|
|
68
|
-
return fs.readFileSync(filePath, 'utf8');
|
|
69
|
-
}
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
|
|
5
|
+
const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
const PLUGIN_ROOT = path.resolve(MODULE_DIR, '..', '..');
|
|
7
|
+
|
|
8
|
+
export const RESOURCES = [
|
|
9
|
+
{
|
|
10
|
+
name: 'takomi-flow-contract',
|
|
11
|
+
uri: 'takomi-flow://contract',
|
|
12
|
+
file: path.join(PLUGIN_ROOT, 'references', 'flow-provider-contract.md'),
|
|
13
|
+
mimeType: 'text/markdown',
|
|
14
|
+
description: 'TakomiFlow provider contract and agent workflow.',
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
name: 'takomi-flow-capabilities',
|
|
18
|
+
uri: 'takomi-flow://capabilities',
|
|
19
|
+
file: path.join(PLUGIN_ROOT, 'assets', 'capabilities.json'),
|
|
20
|
+
mimeType: 'application/json',
|
|
21
|
+
description: 'Machine-readable TakomiFlow capabilities.',
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
name: 'takomi-flow-examples',
|
|
25
|
+
uri: 'takomi-flow://examples',
|
|
26
|
+
file: path.join(PLUGIN_ROOT, 'assets', 'examples.json'),
|
|
27
|
+
mimeType: 'application/json',
|
|
28
|
+
description: 'Machine-readable TakomiFlow request and handoff examples.',
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
name: 'takomi-flow-request-schema',
|
|
32
|
+
uri: 'takomi-flow://schemas/request',
|
|
33
|
+
file: path.join(PLUGIN_ROOT, 'assets', 'request.schema.json'),
|
|
34
|
+
mimeType: 'application/schema+json',
|
|
35
|
+
description: 'JSON Schema for TakomiFlow request files.',
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
name: 'takomi-flow-result-schema',
|
|
39
|
+
uri: 'takomi-flow://schemas/result',
|
|
40
|
+
file: path.join(PLUGIN_ROOT, 'assets', 'result.schema.json'),
|
|
41
|
+
mimeType: 'application/schema+json',
|
|
42
|
+
description: 'JSON Schema for TakomiFlow run result metadata.',
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: 'takomi-flow-collection-schema',
|
|
46
|
+
uri: 'takomi-flow://schemas/collection',
|
|
47
|
+
file: path.join(PLUGIN_ROOT, 'assets', 'collection.schema.json'),
|
|
48
|
+
mimeType: 'application/schema+json',
|
|
49
|
+
description: 'JSON Schema for TakomiFlow collection manifests.',
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
name: 'takomi-flow-video-template',
|
|
53
|
+
uri: 'takomi-flow://templates/video',
|
|
54
|
+
file: path.join(PLUGIN_ROOT, 'assets', 'templates', 'video-request.json'),
|
|
55
|
+
mimeType: 'application/json',
|
|
56
|
+
description: 'Video request template.',
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
name: 'takomi-flow-image-template',
|
|
60
|
+
uri: 'takomi-flow://templates/image',
|
|
61
|
+
file: path.join(PLUGIN_ROOT, 'assets', 'templates', 'image-request.json'),
|
|
62
|
+
mimeType: 'application/json',
|
|
63
|
+
description: 'Image request template.',
|
|
64
|
+
},
|
|
65
|
+
];
|
|
66
|
+
|
|
67
|
+
export function readResourceFile(filePath) {
|
|
68
|
+
return fs.readFileSync(filePath, 'utf8');
|
|
69
|
+
}
|
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
|
-
import { ensureDir, timestampId, writeJson } from './paths.mjs';
|
|
3
|
-
|
|
4
|
-
export function createRun(requestOrArgs, label = 'flow') {
|
|
5
|
-
const outputDir = path.resolve(requestOrArgs.outputDir || requestOrArgs['output-dir']);
|
|
6
|
-
const runId = timestampId(label);
|
|
7
|
-
const runDir = ensureDir(path.join(outputDir, runId));
|
|
8
|
-
const downloadsDir = ensureDir(path.join(runDir, 'downloads'));
|
|
9
|
-
const screenshotsDir = ensureDir(path.join(runDir, 'screenshots'));
|
|
10
|
-
return { runId, runDir, downloadsDir, screenshotsDir };
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function baseResult(status, run, data = {}) {
|
|
14
|
-
return {
|
|
15
|
-
schemaVersion: 1,
|
|
16
|
-
status,
|
|
17
|
-
runId: run.runId,
|
|
18
|
-
assets: [],
|
|
19
|
-
screenshots: [],
|
|
20
|
-
errors: [],
|
|
21
|
-
manualActions: [],
|
|
22
|
-
...data,
|
|
23
|
-
metadataPath: path.join(run.runDir, 'run.json'),
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export function saveResult(run, result) {
|
|
28
|
-
const finalResult = { ...result, metadataPath: path.join(run.runDir, 'run.json') };
|
|
29
|
-
writeJson(finalResult.metadataPath, finalResult);
|
|
30
|
-
return finalResult;
|
|
31
|
-
}
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { ensureDir, timestampId, writeJson } from './paths.mjs';
|
|
3
|
+
|
|
4
|
+
export function createRun(requestOrArgs, label = 'flow') {
|
|
5
|
+
const outputDir = path.resolve(requestOrArgs.outputDir || requestOrArgs['output-dir']);
|
|
6
|
+
const runId = timestampId(label);
|
|
7
|
+
const runDir = ensureDir(path.join(outputDir, runId));
|
|
8
|
+
const downloadsDir = ensureDir(path.join(runDir, 'downloads'));
|
|
9
|
+
const screenshotsDir = ensureDir(path.join(runDir, 'screenshots'));
|
|
10
|
+
return { runId, runDir, downloadsDir, screenshotsDir };
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function baseResult(status, run, data = {}) {
|
|
14
|
+
return {
|
|
15
|
+
schemaVersion: 1,
|
|
16
|
+
status,
|
|
17
|
+
runId: run.runId,
|
|
18
|
+
assets: [],
|
|
19
|
+
screenshots: [],
|
|
20
|
+
errors: [],
|
|
21
|
+
manualActions: [],
|
|
22
|
+
...data,
|
|
23
|
+
metadataPath: path.join(run.runDir, 'run.json'),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function saveResult(run, result) {
|
|
28
|
+
const finalResult = { ...result, metadataPath: path.join(run.runDir, 'run.json') };
|
|
29
|
+
writeJson(finalResult.metadataPath, finalResult);
|
|
30
|
+
return finalResult;
|
|
31
|
+
}
|
|
@@ -1,44 +1,44 @@
|
|
|
1
|
-
import { inspectRun, resolveRun } from './inspect.mjs';
|
|
2
|
-
import { catalogAssets } from './media.mjs';
|
|
3
|
-
import { createReport } from './report.mjs';
|
|
4
|
-
|
|
5
|
-
export async function reviewRun(args = {}) {
|
|
6
|
-
if (!args.run) throw new Error('review requires --run <run.json|run-dir>');
|
|
7
|
-
const summary = inspectRun(args.run);
|
|
8
|
-
const run = resolveRun(args.run);
|
|
9
|
-
const catalog = await catalogAssets(run, run.data.assets || [], {
|
|
10
|
-
frames: args.frames || 0,
|
|
11
|
-
});
|
|
12
|
-
const report = createReport({
|
|
13
|
-
run: args.run,
|
|
14
|
-
reportPath: args.reportPath,
|
|
15
|
-
});
|
|
16
|
-
return {
|
|
17
|
-
schemaVersion: 1,
|
|
18
|
-
status: reviewStatus(summary, catalog),
|
|
19
|
-
runId: summary.runId,
|
|
20
|
-
runDir: run.runDir,
|
|
21
|
-
summary,
|
|
22
|
-
catalog,
|
|
23
|
-
reportPath: report.reportPath,
|
|
24
|
-
nextActions: nextActions(summary, catalog),
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function reviewStatus(summary, catalog) {
|
|
29
|
-
if (summary.errors?.length) return 'attention_required';
|
|
30
|
-
if (summary.manualActions?.length) return 'manual_action_required';
|
|
31
|
-
if (catalog.assets?.some(asset => asset.errors?.length || !asset.exists)) return 'attention_required';
|
|
32
|
-
if (catalog.assets?.length) return 'review_ready';
|
|
33
|
-
return summary.status || 'unknown';
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function nextActions(summary, catalog) {
|
|
37
|
-
const actions = [];
|
|
38
|
-
if (summary.errors?.length) actions.push('Review run errors before reusing assets.');
|
|
39
|
-
if (summary.manualActions?.length) actions.push(...summary.manualActions);
|
|
40
|
-
if (catalog.assets?.some(asset => asset.frames?.length)) actions.push('Review extracted frames for video quality.');
|
|
41
|
-
if (catalog.assets?.length) actions.push('Use reportPath for the human handoff.');
|
|
42
|
-
if (!catalog.assets?.length) actions.push('No assets were cataloged; inspect screenshots and run metadata.');
|
|
43
|
-
return actions;
|
|
44
|
-
}
|
|
1
|
+
import { inspectRun, resolveRun } from './inspect.mjs';
|
|
2
|
+
import { catalogAssets } from './media.mjs';
|
|
3
|
+
import { createReport } from './report.mjs';
|
|
4
|
+
|
|
5
|
+
export async function reviewRun(args = {}) {
|
|
6
|
+
if (!args.run) throw new Error('review requires --run <run.json|run-dir>');
|
|
7
|
+
const summary = inspectRun(args.run);
|
|
8
|
+
const run = resolveRun(args.run);
|
|
9
|
+
const catalog = await catalogAssets(run, run.data.assets || [], {
|
|
10
|
+
frames: args.frames || 0,
|
|
11
|
+
});
|
|
12
|
+
const report = createReport({
|
|
13
|
+
run: args.run,
|
|
14
|
+
reportPath: args.reportPath,
|
|
15
|
+
});
|
|
16
|
+
return {
|
|
17
|
+
schemaVersion: 1,
|
|
18
|
+
status: reviewStatus(summary, catalog),
|
|
19
|
+
runId: summary.runId,
|
|
20
|
+
runDir: run.runDir,
|
|
21
|
+
summary,
|
|
22
|
+
catalog,
|
|
23
|
+
reportPath: report.reportPath,
|
|
24
|
+
nextActions: nextActions(summary, catalog),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function reviewStatus(summary, catalog) {
|
|
29
|
+
if (summary.errors?.length) return 'attention_required';
|
|
30
|
+
if (summary.manualActions?.length) return 'manual_action_required';
|
|
31
|
+
if (catalog.assets?.some(asset => asset.errors?.length || !asset.exists)) return 'attention_required';
|
|
32
|
+
if (catalog.assets?.length) return 'review_ready';
|
|
33
|
+
return summary.status || 'unknown';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function nextActions(summary, catalog) {
|
|
37
|
+
const actions = [];
|
|
38
|
+
if (summary.errors?.length) actions.push('Review run errors before reusing assets.');
|
|
39
|
+
if (summary.manualActions?.length) actions.push(...summary.manualActions);
|
|
40
|
+
if (catalog.assets?.some(asset => asset.frames?.length)) actions.push('Review extracted frames for video quality.');
|
|
41
|
+
if (catalog.assets?.length) actions.push('Use reportPath for the human handoff.');
|
|
42
|
+
if (!catalog.assets?.length) actions.push('No assets were cataloged; inspect screenshots and run metadata.');
|
|
43
|
+
return actions;
|
|
44
|
+
}
|