@remixmate/cli 0.1.0 → 0.1.2
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/README.md +100 -83
- package/README.zh-CN.md +183 -0
- package/dist/handlers/gen-digital-human.d.ts +12 -0
- package/dist/handlers/gen-digital-human.js +157 -0
- package/dist/handlers/gen-image.js +5 -41
- package/dist/handlers/gen-video.d.ts +18 -0
- package/dist/handlers/gen-video.js +153 -0
- package/dist/handlers/gen-voice.js +2 -12
- package/dist/handlers/index.js +4 -2
- package/dist/handlers/shared.d.ts +26 -0
- package/dist/handlers/shared.js +66 -0
- package/dist/http.d.ts +4 -2
- package/dist/http.js +5 -3
- package/dist/manifest.json +38 -24
- package/dist/registry.d.ts +5 -16
- package/dist/registry.js +11 -17
- package/dist/skill-schema.d.ts +65 -0
- package/dist/skill-schema.js +53 -0
- package/package.json +5 -4
- package/skills/export-jianying/scripts/gen_jianying_draft.py +4 -4
- package/skills/export-jianying/skill.json +1 -0
- package/skills/gen-digital-human/SKILL.md +23 -46
- package/skills/gen-digital-human/skill.json +4 -2
- package/skills/gen-digital-human/version.json +1 -1
- package/skills/gen-image/SKILL.md +2 -2
- package/skills/gen-image/skill.json +3 -1
- package/skills/gen-image/version.json +1 -1
- package/skills/gen-script/SKILL.md +13 -13
- package/skills/gen-script/scripts/gen_script.py +50 -12
- package/skills/gen-script/skill.json +3 -1
- package/skills/gen-video/SKILL.md +22 -33
- package/skills/gen-video/skill.json +4 -2
- package/skills/gen-video/version.json +1 -1
- package/skills/gen-voice/SKILL.md +1 -1
- package/skills/gen-voice/skill.json +1 -0
- package/skills/gen-voice/version.json +1 -1
- package/skills/prepare-video-assets/SKILL.md +2 -2
- package/skills/prepare-video-assets/skill.json +2 -1
- package/skills/render-video/scripts/render_video.py +8 -8
- package/skills/render-video/skill.json +1 -0
- package/skills/{template-bind → template-registry}/README.md +4 -4
- package/skills/{template-bind → template-registry}/SKILL.md +14 -12
- package/skills/template-registry/scripts/list_templates.py +214 -0
- package/skills/{template-bind → template-registry}/scripts/match_template.py +4 -3
- package/skills/{template-bind → template-registry}/scripts/registry_loader.py +1 -1
- package/skills/{template-bind → template-registry}/scripts/template_paths.py +7 -7
- package/skills/{template-bind → template-registry}/skill.json +8 -5
- package/skills/template-registry/version.json +7 -0
- package/skills/{template-bind → template-registry}/video_dsl/README.md +2 -2
- package/skills/{template-bind → template-registry}/video_dsl/runtime/__init__.py +1 -1
- package/skills/video-parser/SKILL.md +47 -160
- package/skills/video-parser/skill.json +4 -4
- package/skills/video-parser/version.json +1 -1
- package/skills/web-capture/SKILL.md +407 -116
- package/skills/web-capture/scripts/_media_screenshot/__init__.py +21 -0
- package/skills/web-capture/scripts/_media_screenshot/bootstrap.py +34 -0
- package/skills/web-capture/scripts/_media_screenshot/browser.py +86 -0
- package/skills/web-capture/scripts/_media_screenshot/cli_args.py +111 -0
- package/skills/web-capture/scripts/_media_screenshot/js/arrow.js +86 -0
- package/skills/web-capture/scripts/_media_screenshot/js/caption.js +27 -0
- package/skills/web-capture/scripts/_media_screenshot/js/clear_overlay.js +5 -0
- package/skills/web-capture/scripts/_media_screenshot/js/cursor_bootstrap.js +26 -0
- package/skills/web-capture/scripts/_media_screenshot/js/cursor_hide.js +6 -0
- package/skills/web-capture/scripts/_media_screenshot/js/cursor_move.js +12 -0
- package/skills/web-capture/scripts/_media_screenshot/js/cursor_ripple.js +25 -0
- package/skills/web-capture/scripts/_media_screenshot/js/element_zoom.js +72 -0
- package/skills/web-capture/scripts/_media_screenshot/js/element_zoom_restore.js +21 -0
- package/skills/web-capture/scripts/_media_screenshot/js/fade_out_overlays.js +10 -0
- package/skills/web-capture/scripts/_media_screenshot/js/focus.js +38 -0
- package/skills/web-capture/scripts/_media_screenshot/js/highlight.js +97 -0
- package/skills/web-capture/scripts/_media_screenshot/js/overlay_bootstrap.js +19 -0
- package/skills/web-capture/scripts/_media_screenshot/js/page_zoom_in.js +54 -0
- package/skills/web-capture/scripts/_media_screenshot/js/page_zoom_out.js +11 -0
- package/skills/web-capture/scripts/_media_screenshot/js/page_zoom_restore.js +13 -0
- package/skills/web-capture/scripts/_media_screenshot/js/redact.js +33 -0
- package/skills/web-capture/scripts/_media_screenshot/js/scroll.js +26 -0
- package/skills/web-capture/scripts/_media_screenshot/js/scroll_through.js +18 -0
- package/skills/web-capture/scripts/_media_screenshot/js/sequence.js +76 -0
- package/skills/web-capture/scripts/_media_screenshot/js/settled_check.js +15 -0
- package/skills/web-capture/scripts/_media_screenshot/js/title_card.js +71 -0
- package/skills/web-capture/scripts/_media_screenshot/js_loader.py +14 -0
- package/skills/web-capture/scripts/_media_screenshot/overlay.py +17 -0
- package/skills/web-capture/scripts/_media_screenshot/recording.py +100 -0
- package/skills/web-capture/scripts/_media_screenshot/scenes/__init__.py +39 -0
- package/skills/web-capture/scripts/_media_screenshot/scenes/arrow.py +50 -0
- package/skills/web-capture/scripts/_media_screenshot/scenes/caption.py +20 -0
- package/skills/web-capture/scripts/_media_screenshot/scenes/click.py +35 -0
- package/skills/web-capture/scripts/_media_screenshot/scenes/code_lines.py +54 -0
- package/skills/web-capture/scripts/_media_screenshot/scenes/focus.py +24 -0
- package/skills/web-capture/scripts/_media_screenshot/scenes/highlight.py +54 -0
- package/skills/web-capture/scripts/_media_screenshot/scenes/hover.py +28 -0
- package/skills/web-capture/scripts/_media_screenshot/scenes/redact.py +32 -0
- package/skills/web-capture/scripts/_media_screenshot/scenes/scroll.py +32 -0
- package/skills/web-capture/scripts/_media_screenshot/scenes/sequence.py +43 -0
- package/skills/web-capture/scripts/_media_screenshot/scenes/title_card.py +20 -0
- package/skills/web-capture/scripts/_media_screenshot/scenes/type.py +42 -0
- package/skills/web-capture/scripts/_media_screenshot/scenes/wait.py +9 -0
- package/skills/web-capture/scripts/_media_screenshot/scenes/zoom.py +72 -0
- package/skills/web-capture/scripts/_media_screenshot/screenshot.py +166 -0
- package/skills/web-capture/scripts/_media_screenshot/storyboard.py +88 -0
- package/skills/web-capture/scripts/_media_screenshot/template.py +119 -0
- package/skills/web-capture/scripts/_media_screenshot/trim.py +82 -0
- package/skills/web-capture/scripts/record.py +186 -0
- package/skills/web-capture/scripts/screenshot.py +94 -0
- package/skills/web-capture/skill.json +15 -14
- package/skills/web-capture/templates/github-code-tour.json +59 -0
- package/skills/web-capture/templates/github-readme-tour.json +77 -0
- package/skills/web-capture/templates/github-repo-intro.json +74 -0
- package/skills/web-capture/version.json +2 -2
- package/dist/handlers/template-bind.d.ts +0 -15
- package/dist/handlers/template-bind.js +0 -149
- package/skills/gen-digital-human/scripts/gen_digital_human_video.py +0 -574
- package/skills/gen-video/scripts/gen_video.py +0 -591
- package/skills/template-bind/version.json +0 -7
- package/skills/web-capture/scripts/web_capture.py +0 -461
- /package/skills/{template-bind → template-registry}/scripts/check_contracts.py +0 -0
- /package/skills/{template-bind → template-registry}/scripts/render_job_client.py +0 -0
- /package/skills/{template-bind → template-registry}/scripts/sync_registry.py +0 -0
- /package/skills/{template-bind → template-registry}/video_dsl/runtime/dsl_validator.py +0 -0
- /package/skills/{template-bind → template-registry}/video_dsl/runtime/prompt_enhancer.py +0 -0
- /package/skills/{template-bind → template-registry}/video_dsl/runtime/template_binder.py +0 -0
- /package/skills/{template-bind → template-registry}/video_dsl/runtime/timeline_compiler.py +0 -0
- /package/skills/{template-bind → template-registry}/video_dsl/schema/render-plan-v1alpha1.json +0 -0
- /package/skills/{template-bind → template-registry}/video_dsl/schema/template-binding-v1alpha1.json +0 -0
- /package/skills/{template-bind → template-registry}/video_dsl/schema/template-definition-v1alpha1.json +0 -0
- /package/skills/{template-bind → template-registry}/video_dsl/schema/video-dsl-v1alpha1.json +0 -0
|
@@ -9,10 +9,9 @@
|
|
|
9
9
|
* - Async response (generationId + status:'generating'/'pending') is polled
|
|
10
10
|
* against /model/getImgStatus until completed/failed/timeout
|
|
11
11
|
*/
|
|
12
|
-
import { readFileSync, existsSync, statSync } from 'node:fs';
|
|
13
|
-
import path from 'node:path';
|
|
14
12
|
import { mmPost, pollUntil, resolveHttpContext, SkillError } from '../http.js';
|
|
15
13
|
import { emitProgress } from '../progress.js';
|
|
14
|
+
import { isTrue, resolveImageInput, toNumber, toStringArray } from './shared.js';
|
|
16
15
|
const DEFAULT_IMAGE_MODEL = 'doubao/doubao-seedream-5-0-260128';
|
|
17
16
|
// Maps aspect-ratio shorthand → (width, height) for Seedream models.
|
|
18
17
|
const SIZE_PRESETS = {
|
|
@@ -23,13 +22,6 @@ const SIZE_PRESETS = {
|
|
|
23
22
|
'3:4': [1728, 2304],
|
|
24
23
|
'2:3': [1664, 2496],
|
|
25
24
|
};
|
|
26
|
-
const MIME_BY_EXT = {
|
|
27
|
-
'.png': 'image/png',
|
|
28
|
-
'.jpg': 'image/jpeg',
|
|
29
|
-
'.jpeg': 'image/jpeg',
|
|
30
|
-
'.webp': 'image/webp',
|
|
31
|
-
'.gif': 'image/gif',
|
|
32
|
-
};
|
|
33
25
|
// Per-model reference-image caps (matches ab-api validation).
|
|
34
26
|
const SEEDREAM_REF_LIMIT = 14;
|
|
35
27
|
const GEMINI_REF_LIMIT = 4;
|
|
@@ -37,38 +29,6 @@ function isSeedreamModel(model) {
|
|
|
37
29
|
const lower = model.toLowerCase();
|
|
38
30
|
return lower.includes('seedream') || lower.includes('seedance');
|
|
39
31
|
}
|
|
40
|
-
function resolveImageInput(pathOrUrl) {
|
|
41
|
-
const trimmed = pathOrUrl.trim();
|
|
42
|
-
if (!trimmed)
|
|
43
|
-
return '';
|
|
44
|
-
const lower = trimmed.toLowerCase();
|
|
45
|
-
if (lower.startsWith('http://') || lower.startsWith('https://') || lower.startsWith('data:')) {
|
|
46
|
-
return trimmed;
|
|
47
|
-
}
|
|
48
|
-
if (!existsSync(trimmed) || !statSync(trimmed).isFile()) {
|
|
49
|
-
throw new SkillError(`❌ image path does not exist or is not a file: ${trimmed}`);
|
|
50
|
-
}
|
|
51
|
-
const ext = path.extname(trimmed).toLowerCase();
|
|
52
|
-
const mime = MIME_BY_EXT[ext] ?? 'image/jpeg';
|
|
53
|
-
const b64 = readFileSync(trimmed).toString('base64');
|
|
54
|
-
return `data:${mime};base64,${b64}`;
|
|
55
|
-
}
|
|
56
|
-
/** Coerce a CLI flag value (string|string[]|boolean) into an array of strings. */
|
|
57
|
-
function toStringArray(value) {
|
|
58
|
-
if (value === undefined || value === null)
|
|
59
|
-
return [];
|
|
60
|
-
if (Array.isArray(value))
|
|
61
|
-
return value.map(String);
|
|
62
|
-
return [String(value)];
|
|
63
|
-
}
|
|
64
|
-
function toNumber(value, name) {
|
|
65
|
-
if (value === undefined || value === null)
|
|
66
|
-
return undefined;
|
|
67
|
-
const n = Number(value);
|
|
68
|
-
if (!Number.isFinite(n))
|
|
69
|
-
throw new SkillError(`❌ ${name} must be a number, received: ${value}`);
|
|
70
|
-
return n;
|
|
71
|
-
}
|
|
72
32
|
/** Resolve {width, height} for Seedream from --size (preset key or WxH). */
|
|
73
33
|
function resolveSeedreamSize(size) {
|
|
74
34
|
if (SIZE_PRESETS[size]) {
|
|
@@ -153,6 +113,10 @@ export async function genImage(input, ctxIn) {
|
|
|
153
113
|
if (imageUrls.length === 0) {
|
|
154
114
|
throw new SkillError(`❌ API response did not contain any image URL: ${JSON.stringify(data)}`);
|
|
155
115
|
}
|
|
116
|
+
if (isTrue(input.json_output)) {
|
|
117
|
+
process.stdout.write(JSON.stringify({ urls: imageUrls }) + '\n');
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
156
120
|
process.stdout.write(`\n📸 Generated ${imageUrls.length} image(s)\n`);
|
|
157
121
|
process.stdout.write(`\n🔗 Image URLs (renders directly, no download needed):\n`);
|
|
158
122
|
for (const url of imageUrls)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gen-video handler — POST ab-api /model/genVideo, then poll /model/getVideoStatus.
|
|
3
|
+
*
|
|
4
|
+
* Ported from the previous `scripts/gen_video.py` (entry.type python →
|
|
5
|
+
* entry.type http) so it shares auth / base-URL / business-code / polling with
|
|
6
|
+
* the other atomic generators via http.ts instead of re-implementing them.
|
|
7
|
+
*
|
|
8
|
+
* Model families (aligned with the ab-web AI_VIDEO_MODELS dropdown):
|
|
9
|
+
* - Seedance 1.5 Pro (Volcano): audio, first/last frame, fixed camera,
|
|
10
|
+
* adaptive ratio, duration 4–12s
|
|
11
|
+
* - Veo 3.1 / Veo 3.1 Fast (Google): first/last frame, reference images,
|
|
12
|
+
* negative prompt, 4K, duration 4/6/8s
|
|
13
|
+
*
|
|
14
|
+
* Local image paths for first/last/reference frames are read and base64-encoded
|
|
15
|
+
* into a data: URI; https / data: URIs pass through unchanged.
|
|
16
|
+
*/
|
|
17
|
+
import type { HandlerContext, HandlerInput } from './index.js';
|
|
18
|
+
export declare function genVideo(input: HandlerInput, ctxIn: HandlerContext): Promise<void>;
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gen-video handler — POST ab-api /model/genVideo, then poll /model/getVideoStatus.
|
|
3
|
+
*
|
|
4
|
+
* Ported from the previous `scripts/gen_video.py` (entry.type python →
|
|
5
|
+
* entry.type http) so it shares auth / base-URL / business-code / polling with
|
|
6
|
+
* the other atomic generators via http.ts instead of re-implementing them.
|
|
7
|
+
*
|
|
8
|
+
* Model families (aligned with the ab-web AI_VIDEO_MODELS dropdown):
|
|
9
|
+
* - Seedance 1.5 Pro (Volcano): audio, first/last frame, fixed camera,
|
|
10
|
+
* adaptive ratio, duration 4–12s
|
|
11
|
+
* - Veo 3.1 / Veo 3.1 Fast (Google): first/last frame, reference images,
|
|
12
|
+
* negative prompt, 4K, duration 4/6/8s
|
|
13
|
+
*
|
|
14
|
+
* Local image paths for first/last/reference frames are read and base64-encoded
|
|
15
|
+
* into a data: URI; https / data: URIs pass through unchanged.
|
|
16
|
+
*/
|
|
17
|
+
import { mmPost, pollUntil, resolveHttpContext, SkillError } from '../http.js';
|
|
18
|
+
import { emitProgress } from '../progress.js';
|
|
19
|
+
import { isTrue, resolveImageInput, toNumber, toStringArray } from './shared.js';
|
|
20
|
+
const DEFAULT_VIDEO_MODEL = 'doubao-seedance-1-5-pro-251215';
|
|
21
|
+
// CLI shorthand → full LiteLLM / gateway model id (matches the frontend dropdown).
|
|
22
|
+
const MODEL_ALIASES = {
|
|
23
|
+
seedance: 'doubao-seedance-1-5-pro-251215',
|
|
24
|
+
'seedance-1.5': 'doubao-seedance-1-5-pro-251215',
|
|
25
|
+
'seedance-1.5-pro': 'doubao-seedance-1-5-pro-251215',
|
|
26
|
+
veo: 'veo-3.1-generate-001',
|
|
27
|
+
'veo-3.1': 'veo-3.1-generate-001',
|
|
28
|
+
'veo-3.1-generate': 'veo-3.1-generate-001',
|
|
29
|
+
'veo-fast': 'veo-3.1-fast-generate-001',
|
|
30
|
+
'veo-3.1-fast': 'veo-3.1-fast-generate-001',
|
|
31
|
+
'veo-3.1-fast-generate': 'veo-3.1-fast-generate-001',
|
|
32
|
+
};
|
|
33
|
+
const SEEDANCE_RATIOS = new Set(['adaptive', '16:9', '4:3', '1:1', '3:4', '9:16', '21:9']);
|
|
34
|
+
const SEEDANCE_RESOLUTIONS = new Set(['480p', '720p', '1080p']);
|
|
35
|
+
const VEO_RATIOS = new Set(['16:9', '9:16']);
|
|
36
|
+
const VEO_RESOLUTIONS = new Set(['720p', '1080p', '4k']);
|
|
37
|
+
const VEO_DURATIONS = new Set([4, 6, 8]);
|
|
38
|
+
const VEO_MAX_REFERENCE_IMAGES = 3;
|
|
39
|
+
function normalizeModelId(model) {
|
|
40
|
+
const key = model.trim().toLowerCase();
|
|
41
|
+
return MODEL_ALIASES[key] ?? model.trim();
|
|
42
|
+
}
|
|
43
|
+
function isSeedanceModel(model) {
|
|
44
|
+
return model.toLowerCase().includes('seedance');
|
|
45
|
+
}
|
|
46
|
+
function isVeoModel(model) {
|
|
47
|
+
return model.toLowerCase().includes('veo');
|
|
48
|
+
}
|
|
49
|
+
function validateParamsForModel(model, duration, ratio, resolution, refCount) {
|
|
50
|
+
if (isSeedanceModel(model)) {
|
|
51
|
+
if (duration < 4 || duration > 12) {
|
|
52
|
+
throw new SkillError(`❌ Seedance duration must be 4–12 s, got: ${duration}`);
|
|
53
|
+
}
|
|
54
|
+
if (!SEEDANCE_RATIOS.has(ratio)) {
|
|
55
|
+
throw new SkillError(`❌ Seedance aspect ratio must be one of: ${[...SEEDANCE_RATIOS].join(', ')}; got: ${ratio}`);
|
|
56
|
+
}
|
|
57
|
+
if (!SEEDANCE_RESOLUTIONS.has(resolution)) {
|
|
58
|
+
throw new SkillError(`❌ Seedance resolution must be one of: ${[...SEEDANCE_RESOLUTIONS].join(', ')}; got: ${resolution}`);
|
|
59
|
+
}
|
|
60
|
+
if (refCount > 0) {
|
|
61
|
+
throw new SkillError('❌ Seedance 1.5 Pro does not support a reference-image array (first / last frame only); use --first-frame / --last-frame instead');
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
else if (isVeoModel(model)) {
|
|
65
|
+
if (!VEO_DURATIONS.has(duration)) {
|
|
66
|
+
throw new SkillError(`❌ Veo duration must be one of: ${[...VEO_DURATIONS].join(', ')} s; got: ${duration}`);
|
|
67
|
+
}
|
|
68
|
+
if (!VEO_RATIOS.has(ratio)) {
|
|
69
|
+
throw new SkillError(`❌ Veo aspect ratio must be one of: ${[...VEO_RATIOS].join(', ')}; got: ${ratio}`);
|
|
70
|
+
}
|
|
71
|
+
if (!VEO_RESOLUTIONS.has(resolution)) {
|
|
72
|
+
throw new SkillError(`❌ Veo resolution must be one of: ${[...VEO_RESOLUTIONS].join(', ')}; got: ${resolution}`);
|
|
73
|
+
}
|
|
74
|
+
if (refCount > VEO_MAX_REFERENCE_IMAGES) {
|
|
75
|
+
throw new SkillError(`❌ Veo accepts at most ${VEO_MAX_REFERENCE_IMAGES} reference images; got: ${refCount}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
async function pollVideoStatus(ctx, taskId) {
|
|
80
|
+
return pollUntil(async () => {
|
|
81
|
+
const data = await mmPost(ctx, '/model/getVideoStatus', { taskId }, { timeoutMs: 30_000 });
|
|
82
|
+
const status = data.status ?? 'unknown';
|
|
83
|
+
emitProgress({ phase: 'gen-video:poll', taskId, status });
|
|
84
|
+
if (status === 'succeeded' || status === 'completed') {
|
|
85
|
+
return data.videoUrl ?? '';
|
|
86
|
+
}
|
|
87
|
+
if (status === 'failed') {
|
|
88
|
+
const err = data.errorMsg ?? data.error ?? data.message ?? 'unknown error';
|
|
89
|
+
throw new SkillError(`❌ video generation failed: ${err}`);
|
|
90
|
+
}
|
|
91
|
+
return null;
|
|
92
|
+
}, { intervalMs: 10_000, timeoutMs: 300_000 });
|
|
93
|
+
}
|
|
94
|
+
export async function genVideo(input, ctxIn) {
|
|
95
|
+
const model = normalizeModelId(input.model ?? process.env.MM_VIDEO_MODEL ?? DEFAULT_VIDEO_MODEL);
|
|
96
|
+
const firstFrame = input.first_frame ? resolveImageInput(String(input.first_frame)) : '';
|
|
97
|
+
const lastFrame = input.last_frame ? resolveImageInput(String(input.last_frame)) : '';
|
|
98
|
+
const references = toStringArray(input.reference).map(resolveImageInput).filter(Boolean);
|
|
99
|
+
const prompt = (input.prompt ?? '').trim();
|
|
100
|
+
const hasVisual = Boolean(firstFrame || lastFrame || references.length > 0);
|
|
101
|
+
if (!prompt && !hasVisual) {
|
|
102
|
+
throw new SkillError('❌ Provide at least one of: a non-empty --prompt, or --first-frame / --last-frame / --reference');
|
|
103
|
+
}
|
|
104
|
+
const ratio = input.ratio ?? '16:9';
|
|
105
|
+
const resolution = input.resolution ?? '720p';
|
|
106
|
+
const duration = toNumber(input.duration, 'duration') ?? (isVeoModel(model) ? 8 : 5);
|
|
107
|
+
const seed = toNumber(input.seed, 'seed');
|
|
108
|
+
validateParamsForModel(model, duration, ratio, resolution, references.length);
|
|
109
|
+
const ctx = resolveHttpContext(ctxIn.skillName, {
|
|
110
|
+
apiBaseUrl: input.api_base_url,
|
|
111
|
+
privateToken: input.priv_token,
|
|
112
|
+
});
|
|
113
|
+
const payload = {
|
|
114
|
+
model,
|
|
115
|
+
duration,
|
|
116
|
+
ratio,
|
|
117
|
+
resolution,
|
|
118
|
+
generateAudio: isTrue(input.generate_audio),
|
|
119
|
+
cameraFixed: isTrue(input.camera_fixed),
|
|
120
|
+
};
|
|
121
|
+
if (prompt)
|
|
122
|
+
payload.prompt = prompt;
|
|
123
|
+
if (firstFrame)
|
|
124
|
+
payload.firstFrameImage = firstFrame;
|
|
125
|
+
if (lastFrame)
|
|
126
|
+
payload.lastFrameImage = lastFrame;
|
|
127
|
+
if (references.length > 0)
|
|
128
|
+
payload.referenceImages = references;
|
|
129
|
+
if (input.negative_prompt && String(input.negative_prompt).trim()) {
|
|
130
|
+
payload.negativePrompt = String(input.negative_prompt).trim();
|
|
131
|
+
}
|
|
132
|
+
if (seed !== undefined)
|
|
133
|
+
payload.seed = seed;
|
|
134
|
+
if (input.person_generation)
|
|
135
|
+
payload.personGeneration = String(input.person_generation);
|
|
136
|
+
emitProgress({ phase: 'gen-video:request', model, ratio, resolution, duration });
|
|
137
|
+
const data = await mmPost(ctx, '/model/genVideo', payload, { timeoutMs: 120_000 });
|
|
138
|
+
const taskId = data.taskId;
|
|
139
|
+
if (!taskId) {
|
|
140
|
+
throw new SkillError(`❌ could not parse the task id; API returned: ${JSON.stringify(data)}`);
|
|
141
|
+
}
|
|
142
|
+
emitProgress({ phase: 'gen-video:async', taskId });
|
|
143
|
+
const videoUrl = await pollVideoStatus(ctx, taskId);
|
|
144
|
+
if (!videoUrl) {
|
|
145
|
+
throw new SkillError(`❌ video generated but no URL was returned, task id: ${taskId}`);
|
|
146
|
+
}
|
|
147
|
+
if (isTrue(input.json_output)) {
|
|
148
|
+
process.stdout.write(JSON.stringify({ url: videoUrl }) + '\n');
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
process.stdout.write(`\n🔗 Video URL (renders directly, no download needed):\n${videoUrl}\n`);
|
|
152
|
+
process.stdout.write(`\n🎉 Video generation done!\n`);
|
|
153
|
+
}
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import { mmPost, resolveHttpContext, SkillError } from '../http.js';
|
|
16
16
|
import { emitProgress } from '../progress.js';
|
|
17
|
+
import { isTrue, toNumber } from './shared.js';
|
|
17
18
|
const DEFAULT_VOICE_ID = 'Chinese (Mandarin)_Male_Announcer';
|
|
18
19
|
/**
|
|
19
20
|
* Fallback voice catalog printed when `--list-voices --local` is passed.
|
|
@@ -27,17 +28,6 @@ const LOCAL_VOICE_CATALOG = [
|
|
|
27
28
|
// TODO: replace once gen-voice service confirms an English default voice id.
|
|
28
29
|
{ id: 'english-male-friendly-01', language: 'en', name: 'Friendly Male (English, TBD)' },
|
|
29
30
|
];
|
|
30
|
-
function toNumber(value, name, defaultValue) {
|
|
31
|
-
if (value === undefined || value === null || value === '')
|
|
32
|
-
return defaultValue;
|
|
33
|
-
const n = Number(value);
|
|
34
|
-
if (!Number.isFinite(n))
|
|
35
|
-
throw new SkillError(`❌ ${name} must be a number, received: ${value}`);
|
|
36
|
-
return n;
|
|
37
|
-
}
|
|
38
|
-
function isTrue(value) {
|
|
39
|
-
return value === true || value === 'true';
|
|
40
|
-
}
|
|
41
31
|
async function listVoicesRemote(ctx) {
|
|
42
32
|
const data = await mmPost(ctx, '/voice/page', { current: 1, pageSize: 100 }, { timeoutMs: 30_000 });
|
|
43
33
|
const voices = data.list ?? [];
|
|
@@ -75,7 +65,7 @@ export async function genVoice(input, ctxIn) {
|
|
|
75
65
|
throw new SkillError('❌ Please provide --text to specify the text to synthesize');
|
|
76
66
|
}
|
|
77
67
|
const voiceId = input.voice_id ?? DEFAULT_VOICE_ID;
|
|
78
|
-
const speed = toNumber(input.speed, 'speed'
|
|
68
|
+
const speed = toNumber(input.speed, 'speed') ?? 1.0;
|
|
79
69
|
if (speed < 0.5 || speed > 2.0) {
|
|
80
70
|
throw new SkillError('❌ speed must be in the range 0.5~2.0');
|
|
81
71
|
}
|
package/dist/handlers/index.js
CHANGED
|
@@ -6,9 +6,11 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { genImage } from './gen-image.js';
|
|
8
8
|
import { genVoice } from './gen-voice.js';
|
|
9
|
-
import {
|
|
9
|
+
import { genVideo } from './gen-video.js';
|
|
10
|
+
import { genDigitalHuman } from './gen-digital-human.js';
|
|
10
11
|
export const HANDLERS = {
|
|
11
12
|
'gen-image': genImage,
|
|
12
13
|
'gen-voice': genVoice,
|
|
13
|
-
'
|
|
14
|
+
'gen-video': genVideo,
|
|
15
|
+
'gen-digital-human': genDigitalHuman,
|
|
14
16
|
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared helpers for TS skill handlers.
|
|
3
|
+
*
|
|
4
|
+
* These were previously copy-pasted across gen-image / gen-video /
|
|
5
|
+
* gen-digital-human / gen-voice. Centralizing them keeps argument coercion and
|
|
6
|
+
* image-input resolution identical across every atomic generator.
|
|
7
|
+
*
|
|
8
|
+
* Lives under handlers/ (not src/ root) so the compiled `dist/handlers/shared.js`
|
|
9
|
+
* travels with the rest of the handler bundle when ab-agent copies `dist/`.
|
|
10
|
+
*/
|
|
11
|
+
/** Truthy check tolerant of CLI string values (`--flag` → true, `--flag true` → 'true'). */
|
|
12
|
+
export declare function isTrue(value: unknown): boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Coerce a flag value into a finite number, or `undefined` when absent/empty.
|
|
15
|
+
* Throws SkillError on a non-numeric value so callers fail fast with a clear msg.
|
|
16
|
+
*/
|
|
17
|
+
export declare function toNumber(value: unknown, name: string): number | undefined;
|
|
18
|
+
/** Coerce a repeatable flag (string | string[] | undefined) into a string array. */
|
|
19
|
+
export declare function toStringArray(value: unknown): string[];
|
|
20
|
+
/**
|
|
21
|
+
* Resolve an image reference into a value the backend accepts:
|
|
22
|
+
* - http(s) URLs and data: URIs pass through unchanged
|
|
23
|
+
* - local file paths are read and base64-encoded into a `data:<mime>;base64,…` URI
|
|
24
|
+
* Throws SkillError when a local path does not exist or is not a file.
|
|
25
|
+
*/
|
|
26
|
+
export declare function resolveImageInput(pathOrUrl: string): string;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared helpers for TS skill handlers.
|
|
3
|
+
*
|
|
4
|
+
* These were previously copy-pasted across gen-image / gen-video /
|
|
5
|
+
* gen-digital-human / gen-voice. Centralizing them keeps argument coercion and
|
|
6
|
+
* image-input resolution identical across every atomic generator.
|
|
7
|
+
*
|
|
8
|
+
* Lives under handlers/ (not src/ root) so the compiled `dist/handlers/shared.js`
|
|
9
|
+
* travels with the rest of the handler bundle when ab-agent copies `dist/`.
|
|
10
|
+
*/
|
|
11
|
+
import { readFileSync, existsSync, statSync } from 'node:fs';
|
|
12
|
+
import path from 'node:path';
|
|
13
|
+
import { SkillError } from '../http.js';
|
|
14
|
+
const MIME_BY_EXT = {
|
|
15
|
+
'.png': 'image/png',
|
|
16
|
+
'.jpg': 'image/jpeg',
|
|
17
|
+
'.jpeg': 'image/jpeg',
|
|
18
|
+
'.webp': 'image/webp',
|
|
19
|
+
'.gif': 'image/gif',
|
|
20
|
+
};
|
|
21
|
+
/** Truthy check tolerant of CLI string values (`--flag` → true, `--flag true` → 'true'). */
|
|
22
|
+
export function isTrue(value) {
|
|
23
|
+
return value === true || value === 'true';
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Coerce a flag value into a finite number, or `undefined` when absent/empty.
|
|
27
|
+
* Throws SkillError on a non-numeric value so callers fail fast with a clear msg.
|
|
28
|
+
*/
|
|
29
|
+
export function toNumber(value, name) {
|
|
30
|
+
if (value === undefined || value === null || value === '')
|
|
31
|
+
return undefined;
|
|
32
|
+
const n = Number(value);
|
|
33
|
+
if (!Number.isFinite(n))
|
|
34
|
+
throw new SkillError(`❌ ${name} must be a number, received: ${value}`);
|
|
35
|
+
return n;
|
|
36
|
+
}
|
|
37
|
+
/** Coerce a repeatable flag (string | string[] | undefined) into a string array. */
|
|
38
|
+
export function toStringArray(value) {
|
|
39
|
+
if (value === undefined || value === null)
|
|
40
|
+
return [];
|
|
41
|
+
if (Array.isArray(value))
|
|
42
|
+
return value.map(String);
|
|
43
|
+
return [String(value)];
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Resolve an image reference into a value the backend accepts:
|
|
47
|
+
* - http(s) URLs and data: URIs pass through unchanged
|
|
48
|
+
* - local file paths are read and base64-encoded into a `data:<mime>;base64,…` URI
|
|
49
|
+
* Throws SkillError when a local path does not exist or is not a file.
|
|
50
|
+
*/
|
|
51
|
+
export function resolveImageInput(pathOrUrl) {
|
|
52
|
+
const trimmed = pathOrUrl.trim();
|
|
53
|
+
if (!trimmed)
|
|
54
|
+
return '';
|
|
55
|
+
const lower = trimmed.toLowerCase();
|
|
56
|
+
if (lower.startsWith('http://') || lower.startsWith('https://') || lower.startsWith('data:')) {
|
|
57
|
+
return trimmed;
|
|
58
|
+
}
|
|
59
|
+
if (!existsSync(trimmed) || !statSync(trimmed).isFile()) {
|
|
60
|
+
throw new SkillError(`❌ image path does not exist or is not a file: ${trimmed}`);
|
|
61
|
+
}
|
|
62
|
+
const ext = path.extname(trimmed).toLowerCase();
|
|
63
|
+
const mime = MIME_BY_EXT[ext] ?? 'image/jpeg';
|
|
64
|
+
const b64 = readFileSync(trimmed).toString('base64');
|
|
65
|
+
return `data:${mime};base64,${b64}`;
|
|
66
|
+
}
|
package/dist/http.d.ts
CHANGED
|
@@ -43,8 +43,10 @@ export declare function mmPost<T = unknown>(ctx: HttpContext, pathOrUrl: string,
|
|
|
43
43
|
timeoutMs?: number;
|
|
44
44
|
}): Promise<T>;
|
|
45
45
|
/**
|
|
46
|
-
* Generic polling helper.
|
|
47
|
-
* until it returns a non-null value or `timeoutMs` elapses.
|
|
46
|
+
* Generic polling helper. Invokes `probe()` immediately, then every
|
|
47
|
+
* `intervalMs`, until it returns a non-null value or `timeoutMs` elapses.
|
|
48
|
+
* Probing first (rather than sleeping first) avoids a wasted initial interval
|
|
49
|
+
* when the backend job is already complete.
|
|
48
50
|
*/
|
|
49
51
|
export declare function pollUntil<T>(probe: () => Promise<T | null>, opts?: {
|
|
50
52
|
intervalMs?: number;
|
package/dist/http.js
CHANGED
|
@@ -93,18 +93,20 @@ export async function mmPost(ctx, pathOrUrl, body, opts = {}) {
|
|
|
93
93
|
return parsed.data ?? undefined;
|
|
94
94
|
}
|
|
95
95
|
/**
|
|
96
|
-
* Generic polling helper.
|
|
97
|
-
* until it returns a non-null value or `timeoutMs` elapses.
|
|
96
|
+
* Generic polling helper. Invokes `probe()` immediately, then every
|
|
97
|
+
* `intervalMs`, until it returns a non-null value or `timeoutMs` elapses.
|
|
98
|
+
* Probing first (rather than sleeping first) avoids a wasted initial interval
|
|
99
|
+
* when the backend job is already complete.
|
|
98
100
|
*/
|
|
99
101
|
export async function pollUntil(probe, opts = {}) {
|
|
100
102
|
const intervalMs = opts.intervalMs ?? 5_000;
|
|
101
103
|
const timeoutMs = opts.timeoutMs ?? 120_000;
|
|
102
104
|
const start = Date.now();
|
|
103
105
|
while (Date.now() - start < timeoutMs) {
|
|
104
|
-
await new Promise((r) => setTimeout(r, intervalMs));
|
|
105
106
|
const result = await probe();
|
|
106
107
|
if (result !== null)
|
|
107
108
|
return result;
|
|
109
|
+
await new Promise((r) => setTimeout(r, intervalMs));
|
|
108
110
|
}
|
|
109
111
|
throw new SkillError(`❌ polling timed out (${Math.round(timeoutMs / 1000)}s)`);
|
|
110
112
|
}
|
package/dist/manifest.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"generatedAt": "2026-06-
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"generatedAt": "2026-06-08T08:37:23.147Z",
|
|
5
5
|
"skills": [
|
|
6
6
|
{
|
|
7
7
|
"id": "export-jianying",
|
|
8
8
|
"tier": "orchestration",
|
|
9
|
+
"category": "consuming",
|
|
9
10
|
"title": "Jianying (CapCut) Draft Export",
|
|
10
11
|
"summary": "Jianying (CapCut) draft export: package asset URLs into a draft ZIP that Jianying can import. Supports automatic conversion from a RenderPlan.",
|
|
11
12
|
"triggers": [
|
|
@@ -28,6 +29,7 @@
|
|
|
28
29
|
{
|
|
29
30
|
"id": "gen-digital-human",
|
|
30
31
|
"tier": "atomic",
|
|
32
|
+
"category": "asset",
|
|
31
33
|
"title": "Digital-Human Talking-Head",
|
|
32
34
|
"summary": "Digital-human video: list available avatars; produce a talking-head video from text via TTS, or drive an avatar from an existing audio URL.",
|
|
33
35
|
"triggers": [
|
|
@@ -37,10 +39,10 @@
|
|
|
37
39
|
"Use Jimeng or HiFly to drive an avatar"
|
|
38
40
|
],
|
|
39
41
|
"entry": {
|
|
40
|
-
"type": "
|
|
41
|
-
"
|
|
42
|
+
"type": "http",
|
|
43
|
+
"handler": "gen-digital-human"
|
|
42
44
|
},
|
|
43
|
-
"runtime": "
|
|
45
|
+
"runtime": "node",
|
|
44
46
|
"envVars": [
|
|
45
47
|
"PRIV_TOKEN",
|
|
46
48
|
"MM_API_BASE_URL",
|
|
@@ -50,6 +52,7 @@
|
|
|
50
52
|
{
|
|
51
53
|
"id": "gen-image",
|
|
52
54
|
"tier": "atomic",
|
|
55
|
+
"category": "asset",
|
|
53
56
|
"title": "AI Image Generation",
|
|
54
57
|
"summary": "AI image generation: produce an image from a text prompt. Supports Seedream and Gemini models, plus image-to-image with reference images.",
|
|
55
58
|
"triggers": [
|
|
@@ -73,6 +76,7 @@
|
|
|
73
76
|
{
|
|
74
77
|
"id": "gen-script",
|
|
75
78
|
"tier": "orchestration",
|
|
79
|
+
"category": "authoring",
|
|
76
80
|
"title": "Video Script Generation",
|
|
77
81
|
"summary": "Video script generation: turn a topic into a structured Video DSL (JSON) that describes the full video — scene structure, asset requirements, and narrative flow.",
|
|
78
82
|
"triggers": [
|
|
@@ -95,6 +99,7 @@
|
|
|
95
99
|
{
|
|
96
100
|
"id": "gen-video",
|
|
97
101
|
"tier": "atomic",
|
|
102
|
+
"category": "asset",
|
|
98
103
|
"title": "AI Video Generation",
|
|
99
104
|
"summary": "AI video generation: produce a short video clip from a text prompt. Supports Seedance and Veo models, plus first/last frame and reference images.",
|
|
100
105
|
"triggers": [
|
|
@@ -103,10 +108,10 @@
|
|
|
103
108
|
"Image-to-video, first-frame / last-frame, reference-image-to-video"
|
|
104
109
|
],
|
|
105
110
|
"entry": {
|
|
106
|
-
"type": "
|
|
107
|
-
"
|
|
111
|
+
"type": "http",
|
|
112
|
+
"handler": "gen-video"
|
|
108
113
|
},
|
|
109
|
-
"runtime": "
|
|
114
|
+
"runtime": "node",
|
|
110
115
|
"envVars": [
|
|
111
116
|
"PRIV_TOKEN",
|
|
112
117
|
"MM_API_BASE_URL",
|
|
@@ -117,6 +122,7 @@
|
|
|
117
122
|
{
|
|
118
123
|
"id": "gen-voice",
|
|
119
124
|
"tier": "atomic",
|
|
125
|
+
"category": "asset",
|
|
120
126
|
"title": "Text-to-Speech (Minimax)",
|
|
121
127
|
"summary": "Text-to-speech (TTS): synthesize narration audio from text via the Minimax TTS model. Returns the persisted audio URL — no download needed.",
|
|
122
128
|
"triggers": [
|
|
@@ -138,6 +144,7 @@
|
|
|
138
144
|
{
|
|
139
145
|
"id": "prepare-video-assets",
|
|
140
146
|
"tier": "orchestration",
|
|
147
|
+
"category": "authoring",
|
|
141
148
|
"title": "Video Asset Preparation",
|
|
142
149
|
"summary": "Resolves and generates every asset (image / audio / video) referenced by a Video DSL, persists a RenderPlan to the database, and returns a job_id for the subsequent render_video call. This is Phase 1 of the two-phase video pipeline; Phase 3 (Remotion render) lives in render_video.",
|
|
143
150
|
"triggers": [
|
|
@@ -165,6 +172,7 @@
|
|
|
165
172
|
{
|
|
166
173
|
"id": "render-video",
|
|
167
174
|
"tier": "orchestration",
|
|
175
|
+
"category": "authoring",
|
|
168
176
|
"title": "Remotion Video Renderer",
|
|
169
177
|
"summary": "Loads a persisted RenderPlan by job_id and drives the Remotion engine to produce the final video. Assets must already be generated via prepare_video_assets — this skill never resolves or regenerates assets.",
|
|
170
178
|
"triggers": [
|
|
@@ -190,20 +198,19 @@
|
|
|
190
198
|
]
|
|
191
199
|
},
|
|
192
200
|
{
|
|
193
|
-
"id": "template-
|
|
201
|
+
"id": "template-registry",
|
|
194
202
|
"tier": "orchestration",
|
|
195
|
-
"
|
|
203
|
+
"category": "authoring",
|
|
204
|
+
"title": "Video Template Registry",
|
|
196
205
|
"summary": "List all available video templates (templateId / name / aspect ratio / style tags). Template-to-DSL binding is no longer exposed as a separate step — once prepare_video_assets receives a template_id it runs the full DSL→RenderPlan pipeline internally.",
|
|
197
206
|
"triggers": [
|
|
198
|
-
"View available templates / list every template"
|
|
199
|
-
"Bind a template to a DSL (a template-id must be supplied)",
|
|
200
|
-
"Agent has a Video DSL and needs a TemplateBinding"
|
|
207
|
+
"View available templates / list every template"
|
|
201
208
|
],
|
|
202
209
|
"entry": {
|
|
203
|
-
"type": "
|
|
204
|
-
"
|
|
210
|
+
"type": "python",
|
|
211
|
+
"scriptPath": "scripts/list_templates.py"
|
|
205
212
|
},
|
|
206
|
-
"runtime": "
|
|
213
|
+
"runtime": "python",
|
|
207
214
|
"envVars": [
|
|
208
215
|
"PRIV_TOKEN",
|
|
209
216
|
"VIDEO_TEMPLATE_REGISTRY",
|
|
@@ -214,6 +221,7 @@
|
|
|
214
221
|
{
|
|
215
222
|
"id": "video-parser",
|
|
216
223
|
"tier": "tool",
|
|
224
|
+
"category": "consuming",
|
|
217
225
|
"title": "Video Deconstruction",
|
|
218
226
|
"summary": "Video deconstruction: split a video into reusable assets (audio extraction, ASR transcript, keyframe extraction, scene segmentation).",
|
|
219
227
|
"triggers": [
|
|
@@ -227,25 +235,31 @@
|
|
|
227
235
|
},
|
|
228
236
|
"runtime": "python",
|
|
229
237
|
"envVars": [
|
|
238
|
+
"RENDER_API_URL",
|
|
230
239
|
"PRIV_TOKEN",
|
|
231
|
-
"
|
|
232
|
-
"AGENT_NAME",
|
|
233
|
-
"RENDER_API_URL"
|
|
240
|
+
"CONVERSATION_ID"
|
|
234
241
|
]
|
|
235
242
|
},
|
|
236
243
|
{
|
|
237
244
|
"id": "web-capture",
|
|
238
245
|
"tier": "tool",
|
|
246
|
+
"category": "consuming",
|
|
239
247
|
"title": "Web Page Capture",
|
|
240
|
-
"summary": "Drive a headless browser (Playwright) to capture
|
|
248
|
+
"summary": "Drive a headless browser (Playwright Python) to capture any URL to local files: full-page / element / region screenshots, fixed-duration / condition-triggered / auto-scroll recordings, and storyboard videos (highlight / focus / zoom / scroll / virtual-cursor click / caption / title-card / arrow / numbered sequence / redact / code-line highlight). Two entry scripts: scripts/screenshot.py (stills) and scripts/record.py (webm video, storyboards, templates).",
|
|
241
249
|
"triggers": [
|
|
242
|
-
"
|
|
243
|
-
"
|
|
244
|
-
"
|
|
250
|
+
"网页截图、网页截屏、整页截图、长截图、full page screenshot",
|
|
251
|
+
"截某个元素 / 区域、局部截屏、focus 某个区域",
|
|
252
|
+
"录屏、网页录制、录制视频、录一段操作、生成 webm",
|
|
253
|
+
"滚动录屏、页面从头划到尾的视频",
|
|
254
|
+
"多场景视频、分镜视频、storyboard、按时间线编排",
|
|
255
|
+
"演示点击 / 输入 / 悬停(虚拟鼠标 + 涟漪 / 打字机 / 触发 tooltip)",
|
|
256
|
+
"加箭头、加字幕条、加标题卡 / 片头片尾、按顺序编号高亮多个区域",
|
|
257
|
+
"截图前隐藏元素 / 涂盖打码、给静态截图加注释(红框 / 箭头 / 标签)",
|
|
258
|
+
"移动端 / 设备模拟、带 cookie / 登录态截屏 / 录屏"
|
|
245
259
|
],
|
|
246
260
|
"entry": {
|
|
247
261
|
"type": "python",
|
|
248
|
-
"scriptPath": "scripts/
|
|
262
|
+
"scriptPath": "scripts/screenshot.py"
|
|
249
263
|
},
|
|
250
264
|
"runtime": "python",
|
|
251
265
|
"envVars": [
|
package/dist/registry.d.ts
CHANGED
|
@@ -1,24 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Skill registry — scan skills/ and load skill.json definitions.
|
|
3
3
|
*
|
|
4
|
-
* skill.
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* `entry` is the dispatcher hint. Missing entry falls back to the legacy
|
|
8
|
-
* top-level scriptPath, treated as { type:'python', scriptPath } so older
|
|
9
|
-
* skills keep working without a touch.
|
|
4
|
+
* Validation and entry-normalization live in `skill-schema.ts` (shared with
|
|
5
|
+
* the build-time manifest generator and smoke test). A record that fails
|
|
6
|
+
* validation is skipped with a warning rather than crashing the whole CLI.
|
|
10
7
|
*/
|
|
8
|
+
import { type SkillEntry } from './skill-schema.js';
|
|
11
9
|
export declare const SKILLS_DIR: string;
|
|
12
|
-
export type SkillEntry
|
|
13
|
-
type: 'python';
|
|
14
|
-
scriptPath: string;
|
|
15
|
-
} | {
|
|
16
|
-
type: 'http';
|
|
17
|
-
handler: string;
|
|
18
|
-
} | {
|
|
19
|
-
type: 'builtin';
|
|
20
|
-
handler: string;
|
|
21
|
-
};
|
|
10
|
+
export type { SkillEntry } from './skill-schema.js';
|
|
22
11
|
export interface SkillDef {
|
|
23
12
|
name: string;
|
|
24
13
|
toolName: string;
|