@remixmate/cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +166 -0
  3. package/dist/argv.d.ts +15 -0
  4. package/dist/argv.js +51 -0
  5. package/dist/cli.d.ts +13 -0
  6. package/dist/cli.js +83 -0
  7. package/dist/handlers/gen-image.d.ts +13 -0
  8. package/dist/handlers/gen-image.js +161 -0
  9. package/dist/handlers/gen-voice.d.ts +16 -0
  10. package/dist/handlers/gen-voice.js +110 -0
  11. package/dist/handlers/index.d.ts +13 -0
  12. package/dist/handlers/index.js +14 -0
  13. package/dist/handlers/template-bind.d.ts +15 -0
  14. package/dist/handlers/template-bind.js +149 -0
  15. package/dist/http.d.ts +52 -0
  16. package/dist/http.js +110 -0
  17. package/dist/index.d.ts +9 -0
  18. package/dist/index.js +8 -0
  19. package/dist/manifest.json +260 -0
  20. package/dist/progress.d.ts +23 -0
  21. package/dist/progress.js +15 -0
  22. package/dist/registry.d.ts +33 -0
  23. package/dist/registry.js +72 -0
  24. package/dist/runner.d.ts +17 -0
  25. package/dist/runner.js +63 -0
  26. package/package.json +54 -0
  27. package/skills/export-jianying/SKILL.md +251 -0
  28. package/skills/export-jianying/scripts/gen_jianying_draft.py +1099 -0
  29. package/skills/export-jianying/skill.json +24 -0
  30. package/skills/export-jianying/version.json +7 -0
  31. package/skills/gen-digital-human/SKILL.md +211 -0
  32. package/skills/gen-digital-human/scripts/gen_digital_human_video.py +574 -0
  33. package/skills/gen-digital-human/skill.json +23 -0
  34. package/skills/gen-digital-human/version.json +7 -0
  35. package/skills/gen-image/SKILL.md +130 -0
  36. package/skills/gen-image/skill.json +22 -0
  37. package/skills/gen-image/version.json +7 -0
  38. package/skills/gen-script/SKILL.md +283 -0
  39. package/skills/gen-script/scripts/gen_script.py +1208 -0
  40. package/skills/gen-script/skill.json +41 -0
  41. package/skills/gen-script/version.json +7 -0
  42. package/skills/gen-video/SKILL.md +170 -0
  43. package/skills/gen-video/scripts/gen_video.py +591 -0
  44. package/skills/gen-video/skill.json +24 -0
  45. package/skills/gen-video/version.json +7 -0
  46. package/skills/gen-voice/SKILL.md +104 -0
  47. package/skills/gen-voice/skill.json +21 -0
  48. package/skills/gen-voice/version.json +7 -0
  49. package/skills/prepare-video-assets/SKILL.md +192 -0
  50. package/skills/prepare-video-assets/scripts/prepare_video_assets.py +57 -0
  51. package/skills/prepare-video-assets/skill.json +23 -0
  52. package/skills/prepare-video-assets/version.json +7 -0
  53. package/skills/render-video/SKILL.md +187 -0
  54. package/skills/render-video/scripts/_chrome_vendor.py +305 -0
  55. package/skills/render-video/scripts/_video_probe.py +174 -0
  56. package/skills/render-video/scripts/_vod_polling.py +86 -0
  57. package/skills/render-video/scripts/remote_renderer_client.py +236 -0
  58. package/skills/render-video/scripts/render_video.py +2288 -0
  59. package/skills/render-video/scripts/upload_video.py +193 -0
  60. package/skills/render-video/skill.json +21 -0
  61. package/skills/render-video/version.json +7 -0
  62. package/skills/template-bind/README.md +63 -0
  63. package/skills/template-bind/SKILL.md +234 -0
  64. package/skills/template-bind/scripts/check_contracts.py +196 -0
  65. package/skills/template-bind/scripts/match_template.py +215 -0
  66. package/skills/template-bind/scripts/registry_loader.py +379 -0
  67. package/skills/template-bind/scripts/render_job_client.py +162 -0
  68. package/skills/template-bind/scripts/sync_registry.py +44 -0
  69. package/skills/template-bind/scripts/template_paths.py +72 -0
  70. package/skills/template-bind/skill.json +20 -0
  71. package/skills/template-bind/version.json +7 -0
  72. package/skills/template-bind/video_dsl/README.md +97 -0
  73. package/skills/template-bind/video_dsl/runtime/__init__.py +11 -0
  74. package/skills/template-bind/video_dsl/runtime/dsl_validator.py +495 -0
  75. package/skills/template-bind/video_dsl/runtime/prompt_enhancer.py +274 -0
  76. package/skills/template-bind/video_dsl/runtime/template_binder.py +87 -0
  77. package/skills/template-bind/video_dsl/runtime/timeline_compiler.py +312 -0
  78. package/skills/template-bind/video_dsl/schema/render-plan-v1alpha1.json +193 -0
  79. package/skills/template-bind/video_dsl/schema/template-binding-v1alpha1.json +122 -0
  80. package/skills/template-bind/video_dsl/schema/template-definition-v1alpha1.json +247 -0
  81. package/skills/template-bind/video_dsl/schema/video-dsl-v1alpha1.json +378 -0
  82. package/skills/video-parser/SKILL.md +238 -0
  83. package/skills/video-parser/scripts/analyze_video.py +1038 -0
  84. package/skills/video-parser/scripts/deconstruct_video.py +602 -0
  85. package/skills/video-parser/scripts/parse_via_render.py +148 -0
  86. package/skills/video-parser/skill.json +21 -0
  87. package/skills/video-parser/version.json +7 -0
  88. package/skills/web-capture/SKILL.md +183 -0
  89. package/skills/web-capture/scripts/web_capture.py +461 -0
  90. package/skills/web-capture/skill.json +29 -0
  91. package/skills/web-capture/version.json +7 -0
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Handler registry — maps `entry.handler` strings to their implementation.
3
+ *
4
+ * Only `entry.type` in ('http' | 'builtin') uses this registry; python
5
+ * skills go through spawn() in runner.ts and bypass handlers entirely.
6
+ */
7
+ import { genImage } from './gen-image.js';
8
+ import { genVoice } from './gen-voice.js';
9
+ import { templateBind } from './template-bind.js';
10
+ export const HANDLERS = {
11
+ 'gen-image': genImage,
12
+ 'gen-voice': genVoice,
13
+ 'template-bind': templateBind,
14
+ };
@@ -0,0 +1,15 @@
1
+ /**
2
+ * template-bind handler — list available video templates.
3
+ *
4
+ * The DSL → TemplateBinding logic is intentionally NOT exposed via the CLI;
5
+ * it stays inside `skills/template-bind/scripts/match_template.py` and is
6
+ * imported in-process by `render-video`. The skill's only CLI surface is
7
+ * `--list-templates`, which we re-implement in TS here.
8
+ *
9
+ * Registry resolution (mirrors skills/template-bind/scripts/registry_loader.py):
10
+ * 1. VIDEO_TEMPLATE_REGISTRY — explicit local file
11
+ * 2. VIDEO_TEMPLATE_REGISTRY_URL — HTTP API (POST by default, with X-Priv-Token)
12
+ * 3. <repo>/template-library/packages/metadata/registry.json — monorepo dev fallback
13
+ */
14
+ import type { HandlerContext, HandlerInput } from './index.js';
15
+ export declare function templateBind(input: HandlerInput, _ctx: HandlerContext): Promise<void>;
@@ -0,0 +1,149 @@
1
+ /**
2
+ * template-bind handler — list available video templates.
3
+ *
4
+ * The DSL → TemplateBinding logic is intentionally NOT exposed via the CLI;
5
+ * it stays inside `skills/template-bind/scripts/match_template.py` and is
6
+ * imported in-process by `render-video`. The skill's only CLI surface is
7
+ * `--list-templates`, which we re-implement in TS here.
8
+ *
9
+ * Registry resolution (mirrors skills/template-bind/scripts/registry_loader.py):
10
+ * 1. VIDEO_TEMPLATE_REGISTRY — explicit local file
11
+ * 2. VIDEO_TEMPLATE_REGISTRY_URL — HTTP API (POST by default, with X-Priv-Token)
12
+ * 3. <repo>/template-library/packages/metadata/registry.json — monorepo dev fallback
13
+ */
14
+ import { existsSync, readFileSync } from 'node:fs';
15
+ import path from 'node:path';
16
+ import { fileURLToPath } from 'node:url';
17
+ import { SkillError } from '../http.js';
18
+ const __filename = fileURLToPath(import.meta.url);
19
+ function monorepoFallbackPath() {
20
+ // <ab-skill>/dist/handlers/ → up 4 → repo root
21
+ const here = path.dirname(__filename);
22
+ return path.resolve(here, '..', '..', '..', '..', 'template-library', 'packages', 'metadata', 'registry.json');
23
+ }
24
+ function loadFromFile(filePath) {
25
+ const raw = readFileSync(filePath, 'utf-8');
26
+ return JSON.parse(raw);
27
+ }
28
+ async function loadFromHttp(url) {
29
+ const method = (process.env.VIDEO_TEMPLATE_REGISTRY_HTTP_METHOD ?? 'POST').toUpperCase();
30
+ const headers = { Accept: 'application/json' };
31
+ if (method === 'POST')
32
+ headers['Content-Type'] = 'application/json';
33
+ const token = (process.env.PRIV_TOKEN ?? '').trim();
34
+ if (token)
35
+ headers['X-Priv-Token'] = token;
36
+ const body = method === 'POST' ? '{}' : undefined;
37
+ const resp = await fetch(url, { method, headers, body });
38
+ if (!resp.ok) {
39
+ throw new SkillError(`registry HTTP failed: ${resp.status} ${await resp.text()}`);
40
+ }
41
+ const parsed = (await resp.json());
42
+ if ('code' in parsed && 'data' in parsed) {
43
+ if (parsed.code !== 0 && parsed.code !== '0') {
44
+ throw new SkillError(`registry HTTP business error: code=${parsed.code} msg=${parsed.msg ?? ''}`);
45
+ }
46
+ return parsed.data;
47
+ }
48
+ return parsed;
49
+ }
50
+ async function loadRegistry() {
51
+ const explicit = (process.env.VIDEO_TEMPLATE_REGISTRY ?? '').trim();
52
+ if (explicit) {
53
+ process.stderr.write(`[template-bind] using explicit file: ${explicit}\n`);
54
+ return loadFromFile(explicit);
55
+ }
56
+ const url = (process.env.VIDEO_TEMPLATE_REGISTRY_URL ?? '').trim();
57
+ if (url) {
58
+ process.stderr.write(`[template-bind] using HTTP registry: ${url}\n`);
59
+ return loadFromHttp(url);
60
+ }
61
+ const fallback = monorepoFallbackPath();
62
+ if (existsSync(fallback)) {
63
+ process.stderr.write(`[template-bind] using monorepo registry (fallback): ${fallback}\n`);
64
+ return loadFromFile(fallback);
65
+ }
66
+ throw new SkillError('template registry not found. Set VIDEO_TEMPLATE_REGISTRY (local file) or VIDEO_TEMPLATE_REGISTRY_URL (HTTP API).');
67
+ }
68
+ function statusOf(tpl) {
69
+ const raw = (tpl.status ?? '').trim().toLowerCase();
70
+ return raw || 'stable';
71
+ }
72
+ function isTruthyEnv(name) {
73
+ const v = (process.env[name] ?? '').trim().toLowerCase();
74
+ return v === '1' || v === 'true' || v === 'yes' || v === 'on';
75
+ }
76
+ function visibleStatuses() {
77
+ const allowed = new Set(['stable']);
78
+ if (isTruthyEnv('ENABLE_BETA_TEMPLATES'))
79
+ allowed.add('beta');
80
+ return allowed;
81
+ }
82
+ function matchesFilter(tpl, opts) {
83
+ if (opts.tag) {
84
+ const needle = opts.tag.toLowerCase();
85
+ const tags = (tpl.styleTags ?? []).map((t) => String(t).toLowerCase());
86
+ if (!tags.some((t) => t.includes(needle) || needle.includes(t)))
87
+ return false;
88
+ }
89
+ if (opts.aspect) {
90
+ const want = opts.aspect.toLowerCase();
91
+ const ratios = (tpl.supportedAspectRatios ?? []).map((r) => String(r).toLowerCase());
92
+ if (!ratios.includes(want))
93
+ return false;
94
+ }
95
+ if (opts.language) {
96
+ // Unknown language (absent/empty) is never excluded by a language filter.
97
+ const langs = (tpl.contentLanguage ?? []).map((l) => String(l).toLowerCase());
98
+ if (langs.length > 0 && !langs.includes(opts.language.toLowerCase()))
99
+ return false;
100
+ }
101
+ return true;
102
+ }
103
+ export async function templateBind(input, _ctx) {
104
+ // The skill currently exposes exactly one CLI verb. Treat missing flag as
105
+ // list-templates too, matching match_template.py's "no args == list" behavior.
106
+ const wantsList = input.list_templates === true || input.list_templates === 'true' || Object.keys(input).length === 0 ||
107
+ typeof input.filter_tag === 'string' || typeof input.filter_aspect === 'string' ||
108
+ typeof input.filter_language === 'string' ||
109
+ input.include_beta === true || input.include_beta === 'true';
110
+ if (!wantsList) {
111
+ throw new SkillError('template-bind CLI only supports --list-templates [--filter-tag X] [--filter-aspect X] [--filter-language zh|en] [--include-beta]; binding logic is invoked in-process by render-video and is not exposed as a CLI verb.');
112
+ }
113
+ // P2.2: status gating mirrors registry_loader.py — only stable by default,
114
+ // beta unlocked by env var or explicit --include-beta flag.
115
+ const allowed = visibleStatuses();
116
+ if (input.include_beta === true || input.include_beta === 'true')
117
+ allowed.add('beta');
118
+ const tagFilter = typeof input.filter_tag === 'string' ? input.filter_tag : undefined;
119
+ const aspectFilter = typeof input.filter_aspect === 'string' ? input.filter_aspect : undefined;
120
+ const languageFilter = typeof input.filter_language === 'string' ? input.filter_language : undefined;
121
+ const registry = await loadRegistry();
122
+ const all = registry.templates ?? [];
123
+ const visible = all.filter((t) => allowed.has(statusOf(t)) && matchesFilter(t, { tag: tagFilter, aspect: aspectFilter, language: languageFilter }));
124
+ if (visible.length === 0) {
125
+ const hiddenCount = all.length - visible.length;
126
+ process.stdout.write(`⚠️ No templates match the current filters (${hiddenCount} hidden by status/filter; check --include-beta or relax --filter-*).\n`);
127
+ return;
128
+ }
129
+ process.stdout.write(`\n${'Template ID'.padEnd(22)} ${'Name'.padEnd(18)} ${'Aspect'.padEnd(12)} ${'Lang'.padEnd(8)} ${'Status'.padEnd(8)} Tags / LLM hint\n`);
130
+ process.stdout.write(`${'-'.repeat(22)} ${'-'.repeat(18)} ${'-'.repeat(12)} ${'-'.repeat(8)} ${'-'.repeat(8)} ${'-'.repeat(60)}\n`);
131
+ for (const tpl of visible) {
132
+ const tid = (tpl.templateId ?? '').padEnd(22);
133
+ const name = (tpl.name ?? '').padEnd(18);
134
+ const ratios = (tpl.supportedAspectRatios ?? []).join(',').padEnd(12);
135
+ const lang = ((tpl.contentLanguage ?? []).join(',') || '-').padEnd(8);
136
+ const st = statusOf(tpl).padEnd(8);
137
+ const tags = (tpl.styleTags ?? []).slice(0, 5).join(', ');
138
+ process.stdout.write(`${tid} ${name} ${ratios} ${lang} ${st} ${tags}\n`);
139
+ if (tpl.llmHint) {
140
+ // Indent the hint so it visually belongs to the row above. Truncated to
141
+ // keep `--list-templates` scannable at 100+ templates.
142
+ const hint = tpl.llmHint.length > 200 ? tpl.llmHint.slice(0, 197) + '...' : tpl.llmHint;
143
+ process.stdout.write(`${' '.repeat(22)} ↳ ${hint}\n`);
144
+ }
145
+ }
146
+ const hidden = all.length - visible.length;
147
+ const hiddenNote = hidden > 0 ? ` (${hidden} hidden by status/filter)` : '';
148
+ process.stdout.write(`\n${visible.length} template(s) shown${hiddenNote}\n`);
149
+ }
package/dist/http.d.ts ADDED
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Shared HTTP helpers for ab-api endpoints.
3
+ *
4
+ * Centralizes the auth contract (X-Priv-Token + x-invoke-skill / x-invoke-agent /
5
+ * x-conversation-id), business-code error handling (result.code != 0), and the
6
+ * env var resolution order that every Python skill used to duplicate.
7
+ *
8
+ * Token resolution order (high → low):
9
+ * 1. opts.privateToken — caller override
10
+ * 2. process.env.PRIV_TOKEN — process env
11
+ * 3. throws SkillError — non-interactive failure
12
+ *
13
+ * Base URL resolution:
14
+ * 1. opts.apiBaseUrl — caller override
15
+ * 2. process.env.MM_API_BASE_URL — process env
16
+ * 3. http://localhost:2999/api — local dev fallback
17
+ */
18
+ export declare class SkillError extends Error {
19
+ readonly exitCode: number;
20
+ constructor(message: string, exitCode?: number);
21
+ }
22
+ export interface HttpContext {
23
+ apiBaseUrl: string;
24
+ privateToken: string;
25
+ skillName: string;
26
+ agentName?: string;
27
+ conversationId?: string;
28
+ }
29
+ export declare function resolveHttpContext(skillName: string, opts?: {
30
+ apiBaseUrl?: string;
31
+ privateToken?: string;
32
+ }): HttpContext;
33
+ export interface MmResponse<T = unknown> {
34
+ code: number;
35
+ msg?: string;
36
+ data?: T;
37
+ }
38
+ /**
39
+ * POST JSON to ab-api and return the parsed business payload.
40
+ * Throws SkillError on HTTP errors or business code != 0.
41
+ */
42
+ export declare function mmPost<T = unknown>(ctx: HttpContext, pathOrUrl: string, body: unknown, opts?: {
43
+ timeoutMs?: number;
44
+ }): Promise<T>;
45
+ /**
46
+ * Generic polling helper. Repeatedly invokes `probe()` every `intervalMs`
47
+ * until it returns a non-null value or `timeoutMs` elapses.
48
+ */
49
+ export declare function pollUntil<T>(probe: () => Promise<T | null>, opts?: {
50
+ intervalMs?: number;
51
+ timeoutMs?: number;
52
+ }): Promise<T>;
package/dist/http.js ADDED
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Shared HTTP helpers for ab-api endpoints.
3
+ *
4
+ * Centralizes the auth contract (X-Priv-Token + x-invoke-skill / x-invoke-agent /
5
+ * x-conversation-id), business-code error handling (result.code != 0), and the
6
+ * env var resolution order that every Python skill used to duplicate.
7
+ *
8
+ * Token resolution order (high → low):
9
+ * 1. opts.privateToken — caller override
10
+ * 2. process.env.PRIV_TOKEN — process env
11
+ * 3. throws SkillError — non-interactive failure
12
+ *
13
+ * Base URL resolution:
14
+ * 1. opts.apiBaseUrl — caller override
15
+ * 2. process.env.MM_API_BASE_URL — process env
16
+ * 3. http://localhost:2999/api — local dev fallback
17
+ */
18
+ const DEFAULT_API_BASE_URL = 'http://localhost:2999/api';
19
+ export class SkillError extends Error {
20
+ exitCode;
21
+ constructor(message, exitCode = 1) {
22
+ super(message);
23
+ this.exitCode = exitCode;
24
+ this.name = 'SkillError';
25
+ }
26
+ }
27
+ export function resolveHttpContext(skillName, opts = {}) {
28
+ const apiBaseUrl = (opts.apiBaseUrl ?? process.env.MM_API_BASE_URL ?? DEFAULT_API_BASE_URL).replace(/\/+$/, '');
29
+ const privateToken = opts.privateToken ?? process.env.PRIV_TOKEN ?? '';
30
+ if (!privateToken) {
31
+ throw new SkillError('❌ PRIV_TOKEN is not configured (set the env var or pass --priv-token)');
32
+ }
33
+ return {
34
+ apiBaseUrl,
35
+ privateToken,
36
+ skillName,
37
+ agentName: process.env.AGENT_NAME || undefined,
38
+ conversationId: process.env.CONVERSATION_ID || undefined,
39
+ };
40
+ }
41
+ function buildHeaders(ctx, extra) {
42
+ const headers = {
43
+ 'Content-Type': 'application/json',
44
+ 'X-Priv-Token': ctx.privateToken,
45
+ 'x-invoke-skill': ctx.skillName,
46
+ };
47
+ if (ctx.agentName)
48
+ headers['x-invoke-agent'] = ctx.agentName;
49
+ if (ctx.conversationId)
50
+ headers['x-conversation-id'] = ctx.conversationId;
51
+ return { ...headers, ...extra };
52
+ }
53
+ /**
54
+ * POST JSON to ab-api and return the parsed business payload.
55
+ * Throws SkillError on HTTP errors or business code != 0.
56
+ */
57
+ export async function mmPost(ctx, pathOrUrl, body, opts = {}) {
58
+ const url = pathOrUrl.startsWith('http') ? pathOrUrl : `${ctx.apiBaseUrl}${pathOrUrl}`;
59
+ const controller = new AbortController();
60
+ const timeout = setTimeout(() => controller.abort(), opts.timeoutMs ?? 120_000);
61
+ let resp;
62
+ try {
63
+ resp = await fetch(url, {
64
+ method: 'POST',
65
+ headers: buildHeaders(ctx),
66
+ body: JSON.stringify(body),
67
+ signal: controller.signal,
68
+ });
69
+ }
70
+ catch (err) {
71
+ if (err instanceof Error && err.name === 'AbortError') {
72
+ throw new SkillError(`❌ request timed out: ${url}`);
73
+ }
74
+ throw new SkillError(`❌ network error: ${err.message}`);
75
+ }
76
+ finally {
77
+ clearTimeout(timeout);
78
+ }
79
+ const text = await resp.text();
80
+ if (!resp.ok) {
81
+ throw new SkillError(`❌ API request failed (HTTP ${resp.status}): ${text}`);
82
+ }
83
+ let parsed;
84
+ try {
85
+ parsed = JSON.parse(text);
86
+ }
87
+ catch {
88
+ throw new SkillError(`❌ failed to parse response, body is not JSON: ${text.slice(0, 200)}`);
89
+ }
90
+ if (parsed.code !== 0) {
91
+ throw new SkillError(`❌ API returned a business error: ${parsed.msg ?? 'unknown error'} (code=${parsed.code})`);
92
+ }
93
+ return parsed.data ?? undefined;
94
+ }
95
+ /**
96
+ * Generic polling helper. Repeatedly invokes `probe()` every `intervalMs`
97
+ * until it returns a non-null value or `timeoutMs` elapses.
98
+ */
99
+ export async function pollUntil(probe, opts = {}) {
100
+ const intervalMs = opts.intervalMs ?? 5_000;
101
+ const timeoutMs = opts.timeoutMs ?? 120_000;
102
+ const start = Date.now();
103
+ while (Date.now() - start < timeoutMs) {
104
+ await new Promise((r) => setTimeout(r, intervalMs));
105
+ const result = await probe();
106
+ if (result !== null)
107
+ return result;
108
+ }
109
+ throw new SkillError(`❌ polling timed out (${Math.round(timeoutMs / 1000)}s)`);
110
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * remixmate — public library exports.
3
+ *
4
+ * The CLI entrypoint lives in `cli.ts` (`bin: remixmate`). This file is the
5
+ * library surface for type-sharing consumers; the published npm package
6
+ * exposes it via `main: dist/index.js` / `types: dist/index.d.ts`.
7
+ */
8
+ export { loadSkills, findSkill, SKILLS_DIR } from './registry.js';
9
+ export type { SkillDef, SkillEntry } from './registry.js';
package/dist/index.js ADDED
@@ -0,0 +1,8 @@
1
+ /**
2
+ * remixmate — public library exports.
3
+ *
4
+ * The CLI entrypoint lives in `cli.ts` (`bin: remixmate`). This file is the
5
+ * library surface for type-sharing consumers; the published npm package
6
+ * exposes it via `main: dist/index.js` / `types: dist/index.d.ts`.
7
+ */
8
+ export { loadSkills, findSkill, SKILLS_DIR } from './registry.js';
@@ -0,0 +1,260 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "version": "0.1.0",
4
+ "generatedAt": "2026-06-06T15:02:44.292Z",
5
+ "skills": [
6
+ {
7
+ "id": "export-jianying",
8
+ "tier": "orchestration",
9
+ "title": "Jianying (CapCut) Draft Export",
10
+ "summary": "Jianying (CapCut) draft export: package asset URLs into a draft ZIP that Jianying can import. Supports automatic conversion from a RenderPlan.",
11
+ "triggers": [
12
+ "Export to Jianying, Jianying draft, package for Jianying, import into Jianying",
13
+ "Export materials to the Jianying format, generate a Jianying project",
14
+ "Bundle video / image / audio into a Jianying draft",
15
+ "Export a Jianying draft from a RenderPlan"
16
+ ],
17
+ "entry": {
18
+ "type": "python",
19
+ "scriptPath": "scripts/gen_jianying_draft.py"
20
+ },
21
+ "runtime": "python",
22
+ "envVars": [
23
+ "PRIV_TOKEN",
24
+ "MM_API_BASE_URL",
25
+ "AGENT_NAME"
26
+ ]
27
+ },
28
+ {
29
+ "id": "gen-digital-human",
30
+ "tier": "atomic",
31
+ "title": "Digital-Human Talking-Head",
32
+ "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
+ "triggers": [
34
+ "Digital human, talking-head video, AI presenter, virtual host",
35
+ "Make a talking-head video out of this script / this audio file",
36
+ "List or browse digital-human avatars",
37
+ "Use Jimeng or HiFly to drive an avatar"
38
+ ],
39
+ "entry": {
40
+ "type": "python",
41
+ "scriptPath": "scripts/gen_digital_human_video.py"
42
+ },
43
+ "runtime": "python",
44
+ "envVars": [
45
+ "PRIV_TOKEN",
46
+ "MM_API_BASE_URL",
47
+ "AGENT_NAME"
48
+ ]
49
+ },
50
+ {
51
+ "id": "gen-image",
52
+ "tier": "atomic",
53
+ "title": "AI Image Generation",
54
+ "summary": "AI image generation: produce an image from a text prompt. Supports Seedream and Gemini models, plus image-to-image with reference images.",
55
+ "triggers": [
56
+ "AI image generation, text-to-image, \"draw me ...\", \"generate an image of ...\"",
57
+ "Image-to-image, reference image, style transfer, image variation",
58
+ "Generate an image with Doubao / Seedream / Gemini",
59
+ "Provide a prompt and ask for an image"
60
+ ],
61
+ "entry": {
62
+ "type": "http",
63
+ "handler": "gen-image"
64
+ },
65
+ "runtime": "node",
66
+ "envVars": [
67
+ "PRIV_TOKEN",
68
+ "MM_API_BASE_URL",
69
+ "AGENT_NAME",
70
+ "MM_IMAGE_MODEL"
71
+ ]
72
+ },
73
+ {
74
+ "id": "gen-script",
75
+ "tier": "orchestration",
76
+ "title": "Video Script Generation",
77
+ "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
+ "triggers": [
79
+ "Write a video script, generate a video script, plan a video, write storyboards",
80
+ "Create a short video, plan video content, generate a Video DSL",
81
+ "Turn a topic into a video structure / video content plan"
82
+ ],
83
+ "entry": {
84
+ "type": "python",
85
+ "scriptPath": "scripts/gen_script.py"
86
+ },
87
+ "runtime": "python",
88
+ "envVars": [
89
+ "DEFAULT_IMAGE_MODEL",
90
+ "DEFAULT_VIDEO_MODEL",
91
+ "STUB_IMAGE_URL",
92
+ "STUB_VIDEO_URL"
93
+ ]
94
+ },
95
+ {
96
+ "id": "gen-video",
97
+ "tier": "atomic",
98
+ "title": "AI Video Generation",
99
+ "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
+ "triggers": [
101
+ "Text-to-video, AI-generated clip, \"make a short video of ...\"",
102
+ "Generate video with Doubao / Seedance / Veo / Google",
103
+ "Image-to-video, first-frame / last-frame, reference-image-to-video"
104
+ ],
105
+ "entry": {
106
+ "type": "python",
107
+ "scriptPath": "scripts/gen_video.py"
108
+ },
109
+ "runtime": "python",
110
+ "envVars": [
111
+ "PRIV_TOKEN",
112
+ "MM_API_BASE_URL",
113
+ "AGENT_NAME",
114
+ "MM_VIDEO_MODEL"
115
+ ]
116
+ },
117
+ {
118
+ "id": "gen-voice",
119
+ "tier": "atomic",
120
+ "title": "Text-to-Speech (Minimax)",
121
+ "summary": "Text-to-speech (TTS): synthesize narration audio from text via the Minimax TTS model. Returns the persisted audio URL — no download needed.",
122
+ "triggers": [
123
+ "AI voice-over, TTS, text-to-speech, generate narration audio",
124
+ "Use Minimax for speech synthesis",
125
+ "Read a piece of text aloud / produce an audio file from text"
126
+ ],
127
+ "entry": {
128
+ "type": "http",
129
+ "handler": "gen-voice"
130
+ },
131
+ "runtime": "node",
132
+ "envVars": [
133
+ "PRIV_TOKEN",
134
+ "MM_API_BASE_URL",
135
+ "AGENT_NAME"
136
+ ]
137
+ },
138
+ {
139
+ "id": "prepare-video-assets",
140
+ "tier": "orchestration",
141
+ "title": "Video Asset Preparation",
142
+ "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
+ "triggers": [
144
+ "Generate / prepare video assets",
145
+ "Resolve missing assets in a DSL",
146
+ "User confirmed the script and the agent needs to prepare assets for review",
147
+ "User asks to regenerate a specific scene's image or narration"
148
+ ],
149
+ "entry": {
150
+ "type": "python",
151
+ "scriptPath": "scripts/prepare_video_assets.py"
152
+ },
153
+ "runtime": "python",
154
+ "envVars": [
155
+ "PRIV_TOKEN",
156
+ "MM_API_BASE_URL",
157
+ "MM_BACKEND_API_URL",
158
+ "AGENT_NAME",
159
+ "REMOTION_RENDER_API_URL",
160
+ "REMOTION_RENDER_MODE",
161
+ "REMOTION_OUTPUT_DIR",
162
+ "ASSET_CACHE_DIR"
163
+ ]
164
+ },
165
+ {
166
+ "id": "render-video",
167
+ "tier": "orchestration",
168
+ "title": "Remotion Video Renderer",
169
+ "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
+ "triggers": [
171
+ "Render the video, composite the video, export the video",
172
+ "Turn the prepared assets into the final clip",
173
+ "Render with Remotion",
174
+ "Agent has a job_id from prepare_video_assets and needs the final video"
175
+ ],
176
+ "entry": {
177
+ "type": "python",
178
+ "scriptPath": "scripts/render_video.py"
179
+ },
180
+ "runtime": "python",
181
+ "envVars": [
182
+ "PRIV_TOKEN",
183
+ "MM_API_BASE_URL",
184
+ "MM_BACKEND_API_URL",
185
+ "AGENT_NAME",
186
+ "REMOTION_RENDER_API_URL",
187
+ "REMOTION_RENDER_MODE",
188
+ "REMOTION_OUTPUT_DIR",
189
+ "ASSET_CACHE_DIR"
190
+ ]
191
+ },
192
+ {
193
+ "id": "template-bind",
194
+ "tier": "orchestration",
195
+ "title": "Template Registry (List-only)",
196
+ "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
+ "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"
201
+ ],
202
+ "entry": {
203
+ "type": "builtin",
204
+ "handler": "template-bind"
205
+ },
206
+ "runtime": "node",
207
+ "envVars": [
208
+ "PRIV_TOKEN",
209
+ "VIDEO_TEMPLATE_REGISTRY",
210
+ "VIDEO_TEMPLATE_REGISTRY_URL",
211
+ "VIDEO_TEMPLATE_REGISTRY_HTTP_METHOD"
212
+ ]
213
+ },
214
+ {
215
+ "id": "video-parser",
216
+ "tier": "tool",
217
+ "title": "Video Deconstruction",
218
+ "summary": "Video deconstruction: split a video into reusable assets (audio extraction, ASR transcript, keyframe extraction, scene segmentation).",
219
+ "triggers": [
220
+ "Deconstruct / split / analyze / parse a video",
221
+ "Extract keyframes, extract the script from a video, transcribe a video",
222
+ "Pull content assets or raw material out of a video"
223
+ ],
224
+ "entry": {
225
+ "type": "python",
226
+ "scriptPath": "scripts/parse_via_render.py"
227
+ },
228
+ "runtime": "python",
229
+ "envVars": [
230
+ "PRIV_TOKEN",
231
+ "MM_API_BASE_URL",
232
+ "AGENT_NAME",
233
+ "RENDER_API_URL"
234
+ ]
235
+ },
236
+ {
237
+ "id": "web-capture",
238
+ "tier": "tool",
239
+ "title": "Web Page Capture",
240
+ "summary": "Drive a headless browser (Playwright) to capture a web page: viewport/full-page screenshots, or a smooth top-to-bottom scrolling video. Produces PNG / MP4 assets that feed the screen-walkthrough template.",
241
+ "triggers": [
242
+ "Screenshot a web page / website / a specific URL, full-page screenshot",
243
+ "Record a page scrolling top-to-bottom into a video, \"scroll video\" of a site",
244
+ "Produce screen-recording / walkthrough footage of a GitHub repo, landing page, or product page"
245
+ ],
246
+ "entry": {
247
+ "type": "python",
248
+ "scriptPath": "scripts/web_capture.py"
249
+ },
250
+ "runtime": "python",
251
+ "envVars": [
252
+ "WEB_CAPTURE_BROWSER",
253
+ "WEB_CAPTURE_OUTPUT_BASE",
254
+ "WEB_CAPTURE_NO_SANDBOX",
255
+ "WEB_CAPTURE_ALLOW_PRIVATE_HOSTS",
256
+ "PLAYWRIGHT_BROWSERS_PATH"
257
+ ]
258
+ }
259
+ ]
260
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Progress reporting — emits __progress__ JSON lines that ab-agent's
3
+ * executor parses (see apps/ab-agent/src/skills/executor.ts).
4
+ *
5
+ * Always writes to stdout because the consumer reads stdout line-by-line.
6
+ * Each event is a single JSON object on its own line, with the marker
7
+ * field `__progress__: true` so non-progress stdout content is ignored.
8
+ *
9
+ * The consumer flattens the entire parsed JSON into `extra`, so any
10
+ * additional context (model id, generationId, ...) can be passed as
11
+ * top-level keys and will reach the SSE client unchanged.
12
+ */
13
+ export interface ProgressEvent {
14
+ /** Pipeline phase, e.g. `gen-image:request`. */
15
+ phase?: string;
16
+ /** 0..1 progress fraction. */
17
+ progress?: number;
18
+ etaSeconds?: number | null;
19
+ elapsedSeconds?: number | null;
20
+ /** Free-form context — flattened into the parsed JSON line. */
21
+ [key: string]: unknown;
22
+ }
23
+ export declare function emitProgress(ev: ProgressEvent): void;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Progress reporting — emits __progress__ JSON lines that ab-agent's
3
+ * executor parses (see apps/ab-agent/src/skills/executor.ts).
4
+ *
5
+ * Always writes to stdout because the consumer reads stdout line-by-line.
6
+ * Each event is a single JSON object on its own line, with the marker
7
+ * field `__progress__: true` so non-progress stdout content is ignored.
8
+ *
9
+ * The consumer flattens the entire parsed JSON into `extra`, so any
10
+ * additional context (model id, generationId, ...) can be passed as
11
+ * top-level keys and will reach the SSE client unchanged.
12
+ */
13
+ export function emitProgress(ev) {
14
+ process.stdout.write(JSON.stringify({ __progress__: true, ...ev }) + '\n');
15
+ }