remotion-claude-agent-demo 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 (128) hide show
  1. package/README.md +160 -0
  2. package/apps/web/README.md +36 -0
  3. package/apps/web/env.example +20 -0
  4. package/apps/web/eslint.config.mjs +18 -0
  5. package/apps/web/next.config.ts +7 -0
  6. package/apps/web/package-lock.json +10348 -0
  7. package/apps/web/package.json +35 -0
  8. package/apps/web/postcss.config.mjs +7 -0
  9. package/apps/web/public/file.svg +1 -0
  10. package/apps/web/public/globe.svg +1 -0
  11. package/apps/web/public/next.svg +1 -0
  12. package/apps/web/public/vercel.svg +1 -0
  13. package/apps/web/public/window.svg +1 -0
  14. package/apps/web/src/app/.well-known/agent-card.json/route.ts +50 -0
  15. package/apps/web/src/app/background-tasks/[jobId]/cancel/route.ts +29 -0
  16. package/apps/web/src/app/events/stream/route.ts +58 -0
  17. package/apps/web/src/app/favicon.ico +0 -0
  18. package/apps/web/src/app/globals.css +174 -0
  19. package/apps/web/src/app/layout.tsx +34 -0
  20. package/apps/web/src/app/messages/answer/route.ts +57 -0
  21. package/apps/web/src/app/messages/stream/route.ts +381 -0
  22. package/apps/web/src/app/page.tsx +358 -0
  23. package/apps/web/src/app/tasks/[taskId]/cancel/route.ts +24 -0
  24. package/apps/web/src/app/tasks/[taskId]/route.ts +24 -0
  25. package/apps/web/src/app/tasks/route.ts +13 -0
  26. package/apps/web/src/components/chat/agent-blocks.tsx +111 -0
  27. package/apps/web/src/components/chat/ask-user-question-panel.tsx +172 -0
  28. package/apps/web/src/components/chat/session-sidebar.tsx +222 -0
  29. package/apps/web/src/components/chat/subagent-activity-sidebar.tsx +248 -0
  30. package/apps/web/src/components/chat/tool-blocks.tsx +550 -0
  31. package/apps/web/src/lib/a2a/activity-store.ts +150 -0
  32. package/apps/web/src/lib/a2a/client.ts +357 -0
  33. package/apps/web/src/lib/a2a/sse.ts +19 -0
  34. package/apps/web/src/lib/a2a/task-store.ts +111 -0
  35. package/apps/web/src/lib/a2a/types.ts +216 -0
  36. package/apps/web/src/lib/agent/answer-store.ts +109 -0
  37. package/apps/web/src/lib/agent/background-delivery.ts +343 -0
  38. package/apps/web/src/lib/agent/background-tool.ts +78 -0
  39. package/apps/web/src/lib/agent/background.ts +452 -0
  40. package/apps/web/src/lib/agent/chat.ts +543 -0
  41. package/apps/web/src/lib/agent/session-store.ts +26 -0
  42. package/apps/web/src/lib/chat/types.ts +44 -0
  43. package/apps/web/src/lib/env.ts +31 -0
  44. package/apps/web/src/lib/hooks/useA2AChat.ts +863 -0
  45. package/apps/web/src/lib/state/chat-atoms.ts +52 -0
  46. package/apps/web/src/lib/workspace.ts +9 -0
  47. package/apps/web/tsconfig.json +35 -0
  48. package/bin/remotion-agent.js +451 -0
  49. package/package.json +34 -0
  50. package/templates/.claude/CLAUDE.md +95 -0
  51. package/templates/.claude/README.md +129 -0
  52. package/templates/.claude/agents/composer-agent.md +188 -0
  53. package/templates/.claude/agents/crafter.md +181 -0
  54. package/templates/.claude/agents/creator.md +134 -0
  55. package/templates/.claude/agents/perceiver.md +92 -0
  56. package/templates/.claude/settings.json +36 -0
  57. package/templates/.claude/settings.local.json +39 -0
  58. package/templates/.claude/skills/agent-browser/SKILL.md +349 -0
  59. package/templates/.claude/skills/agent-browser/references/authentication.md +188 -0
  60. package/templates/.claude/skills/agent-browser/references/proxy-support.md +175 -0
  61. package/templates/.claude/skills/agent-browser/references/session-management.md +181 -0
  62. package/templates/.claude/skills/agent-browser/references/snapshot-refs.md +186 -0
  63. package/templates/.claude/skills/agent-browser/references/video-recording.md +162 -0
  64. package/templates/.claude/skills/agent-browser/templates/authenticated-session.sh +91 -0
  65. package/templates/.claude/skills/agent-browser/templates/capture-workflow.sh +68 -0
  66. package/templates/.claude/skills/agent-browser/templates/form-automation.sh +64 -0
  67. package/templates/.claude/skills/algorithmic-art/LICENSE.txt +202 -0
  68. package/templates/.claude/skills/algorithmic-art/SKILL.md +405 -0
  69. package/templates/.claude/skills/algorithmic-art/templates/generator_template.js +223 -0
  70. package/templates/.claude/skills/algorithmic-art/templates/viewer.html +599 -0
  71. package/templates/.claude/skills/asset-validator/SKILL.md +376 -0
  72. package/templates/.claude/skills/audio-video-sync/SKILL.md +219 -0
  73. package/templates/.claude/skills/bgm-manager/SKILL.md +334 -0
  74. package/templates/.claude/skills/remotion-best-practices/SKILL.md +45 -0
  75. package/templates/.claude/skills/remotion-best-practices/rules/3d.md +86 -0
  76. package/templates/.claude/skills/remotion-best-practices/rules/animations.md +29 -0
  77. package/templates/.claude/skills/remotion-best-practices/rules/assets/charts-bar-chart.tsx +173 -0
  78. package/templates/.claude/skills/remotion-best-practices/rules/assets/text-animations-typewriter.tsx +100 -0
  79. package/templates/.claude/skills/remotion-best-practices/rules/assets/text-animations-word-highlight.tsx +108 -0
  80. package/templates/.claude/skills/remotion-best-practices/rules/assets.md +78 -0
  81. package/templates/.claude/skills/remotion-best-practices/rules/audio.md +172 -0
  82. package/templates/.claude/skills/remotion-best-practices/rules/calculate-metadata.md +104 -0
  83. package/templates/.claude/skills/remotion-best-practices/rules/can-decode.md +75 -0
  84. package/templates/.claude/skills/remotion-best-practices/rules/charts.md +58 -0
  85. package/templates/.claude/skills/remotion-best-practices/rules/compositions.md +141 -0
  86. package/templates/.claude/skills/remotion-best-practices/rules/display-captions.md +126 -0
  87. package/templates/.claude/skills/remotion-best-practices/rules/extract-frames.md +229 -0
  88. package/templates/.claude/skills/remotion-best-practices/rules/fonts.md +152 -0
  89. package/templates/.claude/skills/remotion-best-practices/rules/get-audio-duration.md +58 -0
  90. package/templates/.claude/skills/remotion-best-practices/rules/get-video-dimensions.md +68 -0
  91. package/templates/.claude/skills/remotion-best-practices/rules/get-video-duration.md +58 -0
  92. package/templates/.claude/skills/remotion-best-practices/rules/gifs.md +138 -0
  93. package/templates/.claude/skills/remotion-best-practices/rules/images.md +130 -0
  94. package/templates/.claude/skills/remotion-best-practices/rules/import-srt-captions.md +67 -0
  95. package/templates/.claude/skills/remotion-best-practices/rules/lottie.md +68 -0
  96. package/templates/.claude/skills/remotion-best-practices/rules/maps.md +403 -0
  97. package/templates/.claude/skills/remotion-best-practices/rules/measuring-dom-nodes.md +35 -0
  98. package/templates/.claude/skills/remotion-best-practices/rules/measuring-text.md +143 -0
  99. package/templates/.claude/skills/remotion-best-practices/rules/parameters.md +98 -0
  100. package/templates/.claude/skills/remotion-best-practices/rules/sequencing.md +118 -0
  101. package/templates/.claude/skills/remotion-best-practices/rules/tailwind.md +11 -0
  102. package/templates/.claude/skills/remotion-best-practices/rules/text-animations.md +20 -0
  103. package/templates/.claude/skills/remotion-best-practices/rules/timing.md +179 -0
  104. package/templates/.claude/skills/remotion-best-practices/rules/transcribe-captions.md +19 -0
  105. package/templates/.claude/skills/remotion-best-practices/rules/transitions.md +122 -0
  106. package/templates/.claude/skills/remotion-best-practices/rules/trimming.md +53 -0
  107. package/templates/.claude/skills/remotion-best-practices/rules/videos.md +171 -0
  108. package/templates/.claude/skills/remotion-components/SKILL.md +453 -0
  109. package/templates/.claude/skills/render-config/SKILL.md +290 -0
  110. package/templates/.claude/skills/script-writer/SKILL.md +59 -0
  111. package/templates/.claude/skills/style-director/script-writer/SKILL.md +82 -0
  112. package/templates/.claude/skills/style-director/style-director/SKILL.md +287 -0
  113. package/templates/.claude/skills/style-director/style-director/references/audience-and-scenarios.md +43 -0
  114. package/templates/.claude/skills/style-director/style-director/references/interaction-innovation.md +26 -0
  115. package/templates/.claude/skills/style-director/style-director/references/motion-grammar.md +66 -0
  116. package/templates/.claude/skills/style-director/style-director/references/quality-checklist.md +29 -0
  117. package/templates/.claude/skills/style-director/style-director/references/scene-recipes.md +38 -0
  118. package/templates/.claude/skills/style-director/style-director/references/visual-style-system.md +148 -0
  119. package/templates/.claude/skills/subtitle-composer/SKILL.md +304 -0
  120. package/templates/.claude/skills/subtitle-processor/SKILL.md +308 -0
  121. package/templates/.claude/skills/timeline-generator/SKILL.md +253 -0
  122. package/templates/.claude/skills/video-preflight-check/SKILL.md +353 -0
  123. package/templates/.claude/skills/voice-synthesizer/SKILL.md +296 -0
  124. package/templates/.claude/skills/voice-synthesizer/scripts/synthesize_voice.py +315 -0
  125. package/templates/.claude/skills/voice-synthesizer/scripts/tts_cli.py +142 -0
  126. package/templates/.claude/skills/web-design-guidelines/SKILL.md +36 -0
  127. package/templates/.claude/skills/youtube-downloader/SKILL.md +99 -0
  128. package/templates/.claude/skills/youtube-downloader/scripts/download_video.py +145 -0
@@ -0,0 +1,52 @@
1
+ "use client";
2
+
3
+ import { atom } from "jotai";
4
+ import { atomWithStorage, createJSONStorage } from "jotai/utils";
5
+ import type { ChatItem, PendingQuestion, SubagentActivity } from "@/lib/chat/types";
6
+
7
+ export type ChatSession = {
8
+ id: string;
9
+ title: string;
10
+ createdAt: number;
11
+ updatedAt: number;
12
+ contextId?: string;
13
+ sessionId?: string;
14
+ items: ChatItem[];
15
+ isStreaming: boolean;
16
+ error: string | null;
17
+ activeTaskId?: string;
18
+ pendingQuestion: PendingQuestion | null;
19
+ subagentActivities: SubagentActivity[];
20
+ inputDraft: string;
21
+ };
22
+
23
+ const fallbackStorage: Storage = {
24
+ getItem: () => null,
25
+ setItem: () => undefined,
26
+ removeItem: () => undefined,
27
+ clear: () => undefined,
28
+ key: () => null,
29
+ length: 0,
30
+ };
31
+
32
+ const getBrowserStorage = (): Storage =>
33
+ typeof window === "undefined" ? fallbackStorage : window.localStorage;
34
+
35
+ const sessionsStorage = createJSONStorage<ChatSession[]>(getBrowserStorage);
36
+ const idStorage = createJSONStorage<string | null>(getBrowserStorage);
37
+
38
+ export const sessionsAtom = atomWithStorage<ChatSession[]>(
39
+ "a2a.sessions.v1",
40
+ [],
41
+ sessionsStorage,
42
+ { getOnInit: true },
43
+ );
44
+ export const currentSessionIdAtom = atomWithStorage<string | null>(
45
+ "a2a.currentSessionId.v1",
46
+ null,
47
+ idStorage,
48
+ { getOnInit: true },
49
+ );
50
+
51
+ export const leftSidebarOpenAtom = atom(true);
52
+ export const rightSidebarOpenAtom = atom(false);
@@ -0,0 +1,9 @@
1
+ import path from "path";
2
+
3
+ export function getWorkspaceDir(): string {
4
+ const fromEnv = process.env.WORKSPACE_DIR;
5
+ if (fromEnv && fromEnv.trim().length > 0) {
6
+ return path.resolve(fromEnv);
7
+ }
8
+ return path.resolve(process.cwd(), "..", "..", "workspace");
9
+ }
@@ -0,0 +1,35 @@
1
+ {
2
+ "compilerOptions": {
3
+ "baseUrl": ".",
4
+ "target": "ES2017",
5
+ "lib": ["dom", "dom.iterable", "esnext"],
6
+ "allowJs": true,
7
+ "skipLibCheck": true,
8
+ "strict": true,
9
+ "noEmit": true,
10
+ "esModuleInterop": true,
11
+ "module": "esnext",
12
+ "moduleResolution": "bundler",
13
+ "resolveJsonModule": true,
14
+ "isolatedModules": true,
15
+ "jsx": "react-jsx",
16
+ "incremental": true,
17
+ "plugins": [
18
+ {
19
+ "name": "next"
20
+ }
21
+ ],
22
+ "paths": {
23
+ "@/*": ["./src/*"]
24
+ }
25
+ },
26
+ "include": [
27
+ "next-env.d.ts",
28
+ "**/*.ts",
29
+ "**/*.tsx",
30
+ ".next/types/**/*.ts",
31
+ ".next/dev/types/**/*.ts",
32
+ "**/*.mts"
33
+ ],
34
+ "exclude": ["node_modules"]
35
+ }
@@ -0,0 +1,451 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ const fs = require("fs");
5
+ const os = require("os");
6
+ const path = require("path");
7
+ const { spawn } = require("child_process");
8
+ const readline = require("readline");
9
+
10
+ const args = process.argv.slice(2);
11
+ const packageRoot = path.resolve(__dirname, "..");
12
+
13
+ function printHelp(exitCode = 0) {
14
+ const text = `remotion-agent - Remotion Claude Agent CLI
15
+
16
+ Usage:
17
+ remotion-agent --help
18
+ remotion-agent config set workspace <path>
19
+ remotion-agent config show
20
+ remotion-agent env set <KEY> <VALUE>
21
+ remotion-agent env unset <KEY>
22
+ remotion-agent env list
23
+ remotion-agent init
24
+ remotion-agent web start [--workspace <path>] [--port <port>]
25
+
26
+ Notes:
27
+ - 配置文件位于用户主目录下的 .remotion-claude-agent/config.json
28
+ - 默认 workspace 为当前命令执行目录 (pwd)
29
+ - 首次运行 web start 时会在终端引导输入 env
30
+ `;
31
+ process.stdout.write(text);
32
+ process.exit(exitCode);
33
+ }
34
+
35
+ function isObject(value) {
36
+ return !!value && typeof value === "object" && !Array.isArray(value);
37
+ }
38
+
39
+ function getConfigPath() {
40
+ return path.join(os.homedir(), ".remotion-claude-agent", "config.json");
41
+ }
42
+
43
+ function ensureConfigDir() {
44
+ fs.mkdirSync(path.dirname(getConfigPath()), { recursive: true });
45
+ }
46
+
47
+ function readConfig() {
48
+ const configPath = getConfigPath();
49
+ if (!fs.existsSync(configPath)) {
50
+ return { env: {} };
51
+ }
52
+ try {
53
+ const raw = fs.readFileSync(configPath, "utf8");
54
+ const parsed = JSON.parse(raw);
55
+ return {
56
+ workspace: typeof parsed.workspace === "string" ? parsed.workspace : undefined,
57
+ env: isObject(parsed.env) ? parsed.env : {},
58
+ };
59
+ } catch (error) {
60
+ process.stderr.write(
61
+ `Failed to read config at ${configPath}: ${error instanceof Error ? error.message : String(error)}\n`,
62
+ );
63
+ process.exit(1);
64
+ }
65
+ }
66
+
67
+ function writeConfig(config) {
68
+ ensureConfigDir();
69
+ const configPath = getConfigPath();
70
+ const data = {
71
+ ...(config.workspace ? { workspace: config.workspace } : {}),
72
+ env: config.env ?? {},
73
+ };
74
+ fs.writeFileSync(configPath, JSON.stringify(data, null, 2));
75
+ }
76
+
77
+ function promptInitialConfig() {
78
+ if (!process.stdin.isTTY) {
79
+ process.stderr.write(
80
+ "未检测到配置文件,且当前终端不可交互。请先运行 remotion-agent env set <KEY> <VALUE>。\n",
81
+ );
82
+ process.exit(1);
83
+ }
84
+
85
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
86
+ const ask = (question) =>
87
+ new Promise((resolve) => {
88
+ rl.question(question, (answer) => resolve(answer));
89
+ });
90
+
91
+ const result = { workspace: "", env: {} };
92
+
93
+ return (async () => {
94
+ const wsInput = String(
95
+ await ask("首次运行:请输入 workspace 路径(回车使用当前目录):"),
96
+ ).trim();
97
+ result.workspace = wsInput ? path.resolve(wsInput) : process.cwd();
98
+
99
+ const envKeys = [
100
+ "ANTHROPIC_API_KEY",
101
+ "ANTHROPIC_BASE_URL",
102
+ "ANTHROPIC_DEFAULT_HAIKU_MODEL",
103
+ "ANTHROPIC_DEFAULT_OPUS_MODEL",
104
+ "ANTHROPIC_DEFAULT_SONNET_MODEL",
105
+ "ANTHROPIC_MODEL",
106
+ "ANTHROPIC_REASONING_MODEL",
107
+ ];
108
+
109
+ process.stdout.write("请输入环境变量(逐项输入,直接回车跳过):\n");
110
+ for (const key of envKeys) {
111
+ const value = String(await ask(`${key}=`)).trim();
112
+ if (value) {
113
+ result.env[key] = value;
114
+ }
115
+ }
116
+
117
+ rl.close();
118
+
119
+ if (Object.keys(result.env).length === 0) {
120
+ process.stderr.write("未设置任何环境变量,已取消启动。请先设置 env。\n");
121
+ process.exit(1);
122
+ }
123
+
124
+ return result;
125
+ })();
126
+ }
127
+
128
+ function resolveWorkspace(input, config) {
129
+ const candidate = input || config.workspace || process.cwd();
130
+ return path.resolve(candidate);
131
+ }
132
+
133
+ function parseFlags(rest) {
134
+ const flags = {};
135
+ const positional = [];
136
+ for (let i = 0; i < rest.length; i += 1) {
137
+ const token = rest[i];
138
+ if (token === "--workspace") {
139
+ const value = rest[i + 1];
140
+ if (!value) {
141
+ process.stderr.write("--workspace requires a value\n");
142
+ process.exit(1);
143
+ }
144
+ flags.workspace = value;
145
+ i += 1;
146
+ continue;
147
+ }
148
+ if (token === "--port") {
149
+ const value = rest[i + 1];
150
+ if (!value) {
151
+ process.stderr.write("--port requires a value\n");
152
+ process.exit(1);
153
+ }
154
+ flags.port = value;
155
+ i += 1;
156
+ continue;
157
+ }
158
+ if (token === "--help" || token === "-h") {
159
+ printHelp(0);
160
+ }
161
+ positional.push(token);
162
+ }
163
+ return { flags, positional };
164
+ }
165
+
166
+ function copyDir(src, dest, options = {}) {
167
+ const shouldIgnore = options.shouldIgnore;
168
+ fs.mkdirSync(dest, { recursive: true });
169
+ const entries = fs.readdirSync(src, { withFileTypes: true });
170
+ for (const entry of entries) {
171
+ const srcPath = path.join(src, entry.name);
172
+ const destPath = path.join(dest, entry.name);
173
+ if (shouldIgnore && shouldIgnore(srcPath, entry)) {
174
+ continue;
175
+ }
176
+ if (entry.isDirectory()) {
177
+ copyDir(srcPath, destPath, options);
178
+ } else if (entry.isSymbolicLink()) {
179
+ const link = fs.readlinkSync(srcPath);
180
+ fs.symlinkSync(link, destPath);
181
+ } else {
182
+ fs.copyFileSync(srcPath, destPath);
183
+ }
184
+ }
185
+ }
186
+
187
+ function makeReadOnly(targetPath) {
188
+ let stats;
189
+ try {
190
+ stats = fs.lstatSync(targetPath);
191
+ } catch {
192
+ return;
193
+ }
194
+ if (stats.isSymbolicLink()) return;
195
+ const mode = stats.mode & ~0o222;
196
+ try {
197
+ fs.chmodSync(targetPath, mode);
198
+ } catch {
199
+ // Ignore permission errors on platforms that do not fully support chmod
200
+ }
201
+ }
202
+
203
+ function setReadOnlyRecursive(targetPath) {
204
+ let stats;
205
+ try {
206
+ stats = fs.lstatSync(targetPath);
207
+ } catch {
208
+ return;
209
+ }
210
+ if (stats.isSymbolicLink()) return;
211
+ if (stats.isDirectory()) {
212
+ const entries = fs.readdirSync(targetPath);
213
+ for (const entry of entries) {
214
+ setReadOnlyRecursive(path.join(targetPath, entry));
215
+ }
216
+ }
217
+ makeReadOnly(targetPath);
218
+ }
219
+
220
+ function ensureClaudeWorkspace(workspaceDir) {
221
+ fs.mkdirSync(workspaceDir, { recursive: true });
222
+ const dest = path.join(workspaceDir, ".claude");
223
+ if (fs.existsSync(dest)) {
224
+ setReadOnlyRecursive(dest);
225
+ return;
226
+ }
227
+ const src = path.join(packageRoot, "templates", ".claude");
228
+ if (!fs.existsSync(src)) {
229
+ process.stderr.write(`Template not found: ${src}\n`);
230
+ process.exit(1);
231
+ }
232
+ copyDir(src, dest);
233
+ setReadOnlyRecursive(dest);
234
+ }
235
+
236
+ function getPackageVersion() {
237
+ try {
238
+ const pkg = JSON.parse(fs.readFileSync(path.join(packageRoot, "package.json"), "utf8"));
239
+ return typeof pkg.version === "string" ? pkg.version : "unknown";
240
+ } catch {
241
+ return "unknown";
242
+ }
243
+ }
244
+
245
+ function prepareWebRuntimeDir() {
246
+ const cacheRoot = path.join(os.homedir(), ".remotion-claude-agent");
247
+ const runtimeDir = path.join(cacheRoot, "web");
248
+ const marker = path.join(runtimeDir, ".version");
249
+ const version = getPackageVersion();
250
+ let needsCopy = true;
251
+ try {
252
+ if (fs.existsSync(marker)) {
253
+ const current = fs.readFileSync(marker, "utf8").trim();
254
+ if (current === version) {
255
+ needsCopy = false;
256
+ }
257
+ }
258
+ } catch {
259
+ needsCopy = true;
260
+ }
261
+
262
+ if (needsCopy) {
263
+ fs.rmSync(runtimeDir, { recursive: true, force: true });
264
+ fs.mkdirSync(runtimeDir, { recursive: true });
265
+ const src = path.join(packageRoot, "apps", "web");
266
+ if (!fs.existsSync(src)) {
267
+ process.stderr.write(`Web app not found: ${src}\n`);
268
+ process.exit(1);
269
+ }
270
+ copyDir(src, runtimeDir, {
271
+ shouldIgnore: (_srcPath, entry) => entry.name === "node_modules" || entry.name === ".next",
272
+ });
273
+ fs.writeFileSync(marker, version);
274
+ }
275
+
276
+ return runtimeDir;
277
+ }
278
+
279
+ function handleConfig(subcommand, rest) {
280
+ if (subcommand === "show") {
281
+ const config = readConfig();
282
+ process.stdout.write(`${JSON.stringify(config, null, 2)}\n`);
283
+ return;
284
+ }
285
+ if (subcommand === "set") {
286
+ if (rest[0] !== "workspace") {
287
+ process.stderr.write("Usage: remotion-agent config set workspace <path>\n");
288
+ process.exit(1);
289
+ }
290
+ const value = rest[1];
291
+ if (!value) {
292
+ process.stderr.write("Workspace path is required\n");
293
+ process.exit(1);
294
+ }
295
+ const config = readConfig();
296
+ config.workspace = path.resolve(value);
297
+ writeConfig(config);
298
+ process.stdout.write(`Workspace set to ${config.workspace}\n`);
299
+ return;
300
+ }
301
+ process.stderr.write("Usage: remotion-agent config set workspace <path>\n");
302
+ process.exit(1);
303
+ }
304
+
305
+ function handleEnv(subcommand, rest) {
306
+ const config = readConfig();
307
+ if (subcommand === "list") {
308
+ process.stdout.write(`${JSON.stringify(config.env ?? {}, null, 2)}\n`);
309
+ return;
310
+ }
311
+ if (subcommand === "set") {
312
+ const key = rest[0];
313
+ const value = rest[1];
314
+ if (!key || !value) {
315
+ process.stderr.write("Usage: remotion-agent env set <KEY> <VALUE>\n");
316
+ process.exit(1);
317
+ }
318
+ config.env = config.env ?? {};
319
+ config.env[String(key)] = String(value);
320
+ writeConfig(config);
321
+ process.stdout.write(`Environment variable ${key} saved\n`);
322
+ return;
323
+ }
324
+ if (subcommand === "unset") {
325
+ const key = rest[0];
326
+ if (!key) {
327
+ process.stderr.write("Usage: remotion-agent env unset <KEY>\n");
328
+ process.exit(1);
329
+ }
330
+ if (config.env && Object.prototype.hasOwnProperty.call(config.env, key)) {
331
+ delete config.env[key];
332
+ writeConfig(config);
333
+ }
334
+ process.stdout.write(`Environment variable ${key} removed\n`);
335
+ return;
336
+ }
337
+ process.stderr.write("Usage: remotion-agent env set <KEY> <VALUE>\n");
338
+ process.exit(1);
339
+ }
340
+
341
+ async function handleWeb(subcommand, rest) {
342
+ if (subcommand !== "start") {
343
+ process.stderr.write("Usage: remotion-agent web start [--workspace <path>] [--port <port>]\n");
344
+ process.exit(1);
345
+ }
346
+ const parsed = parseFlags(rest);
347
+ if (parsed.positional.length > 0) {
348
+ process.stderr.write("Unknown arguments: " + parsed.positional.join(" ") + "\n");
349
+ process.exit(1);
350
+ }
351
+ const configPath = getConfigPath();
352
+ let config = readConfig();
353
+ if (!fs.existsSync(configPath)) {
354
+ const init = await promptInitialConfig();
355
+ config = { workspace: init.workspace, env: init.env };
356
+ writeConfig(config);
357
+ }
358
+ const workspaceDir = resolveWorkspace(parsed.flags.workspace, config);
359
+ ensureClaudeWorkspace(workspaceDir);
360
+
361
+ const webDir = prepareWebRuntimeDir();
362
+
363
+ let nextBin;
364
+ try {
365
+ nextBin = require.resolve("next/dist/bin/next", { paths: [packageRoot] });
366
+ } catch (error) {
367
+ process.stderr.write("Next.js not found. Please reinstall the package.\n");
368
+ process.exit(1);
369
+ }
370
+
371
+ const portArgs = parsed.flags.port ? ["--port", String(parsed.flags.port)] : [];
372
+ const nodePathParts = [path.join(packageRoot, "node_modules"), process.env.NODE_PATH]
373
+ .filter(Boolean)
374
+ .join(path.delimiter);
375
+
376
+ const child = spawn(process.execPath, [nextBin, "dev", ...portArgs], {
377
+ cwd: webDir,
378
+ stdio: "inherit",
379
+ env: {
380
+ ...process.env,
381
+ ...(config.env ?? {}),
382
+ WORKSPACE_DIR: workspaceDir,
383
+ NODE_PATH: nodePathParts,
384
+ NEXT_TELEMETRY_DISABLED: "1",
385
+ },
386
+ });
387
+
388
+ child.on("exit", (code) => {
389
+ process.exit(code ?? 0);
390
+ });
391
+ }
392
+
393
+ async function handleInit() {
394
+ const configPath = getConfigPath();
395
+ if (fs.existsSync(configPath)) {
396
+ if (!process.stdin.isTTY) {
397
+ process.stderr.write(
398
+ "配置文件已存在,且当前终端不可交互。请手动编辑或删除后重试。\n",
399
+ );
400
+ process.exit(1);
401
+ }
402
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
403
+ const answer = await new Promise((resolve) =>
404
+ rl.question("配置文件已存在,是否覆盖?(y/N): ", resolve),
405
+ );
406
+ rl.close();
407
+ if (!String(answer).trim().toLowerCase().startsWith("y")) {
408
+ process.stdout.write("已取消。\n");
409
+ process.exit(0);
410
+ }
411
+ }
412
+
413
+ const init = await promptInitialConfig();
414
+ const config = { workspace: init.workspace, env: init.env };
415
+ writeConfig(config);
416
+ process.stdout.write(`已写入配置: ${getConfigPath()}\n`);
417
+ }
418
+
419
+ async function main() {
420
+ if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
421
+ printHelp(0);
422
+ }
423
+
424
+ const [command, subcommand, ...rest] = args;
425
+ if (!command) {
426
+ printHelp(0);
427
+ }
428
+
429
+ switch (command) {
430
+ case "config":
431
+ handleConfig(subcommand, rest);
432
+ break;
433
+ case "env":
434
+ handleEnv(subcommand, rest);
435
+ break;
436
+ case "init":
437
+ await handleInit();
438
+ break;
439
+ case "web":
440
+ await handleWeb(subcommand, rest);
441
+ break;
442
+ default:
443
+ process.stderr.write(`Unknown command: ${command}\n`);
444
+ printHelp(1);
445
+ }
446
+ }
447
+
448
+ main().catch((error) => {
449
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
450
+ process.exit(1);
451
+ });
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "remotion-claude-agent-demo",
3
+ "version": "0.1.0",
4
+ "description": "Remotion + Claude Agent local web UI",
5
+ "license": "MIT",
6
+ "bin": {
7
+ "remotion-agent": "bin/remotion-agent.js"
8
+ },
9
+ "engines": {
10
+ "node": ">=18"
11
+ },
12
+ "files": [
13
+ "bin/",
14
+ "apps/web/",
15
+ "templates/.claude/",
16
+ "README.md"
17
+ ],
18
+ "dependencies": {
19
+ "@anthropic-ai/claude-agent-sdk": "^0.2.19",
20
+ "@tailwindcss/postcss": "^4",
21
+ "@streamdown/cjk": "^1.0.1",
22
+ "@streamdown/code": "^1.0.1",
23
+ "@streamdown/math": "^1.0.1",
24
+ "@streamdown/mermaid": "^1.0.1",
25
+ "jotai": "^2.10.0",
26
+ "lucide-react": "^0.475.0",
27
+ "next": "16.1.4",
28
+ "react": "19.2.3",
29
+ "react-dom": "19.2.3",
30
+ "streamdown": "^2.1.0",
31
+ "tailwindcss": "^4",
32
+ "zod": "^4.3.6"
33
+ }
34
+ }
@@ -0,0 +1,95 @@
1
+ # 主对话代理编排
2
+
3
+ <project_definition>
4
+ Role: 视频创作系统主协调者
5
+ Mission: 编排三个核心阶段——感知 → 创作 → 执行,理解用户意图、澄清需求、在关键节点请求确认,并委派子代理执行具体任务。
6
+ Scope: 产品教程 / Demo 演示 / SaaS 介绍类视频。
7
+ </project_definition>
8
+
9
+ <available_tools>
10
+
11
+ - `Read`: 读取脚本、分镜、配置等
12
+ - `Edit` / `Write`: 编写或调整委派说明、阶段产出
13
+ - `AskUserQuestion`: 在关键节点请求用户确认
14
+ - `Skill`: 技能与子代理(Perceiver / Creator / Crafter)
15
+ - `mcp__background_tasks__start`: 后台任务(独立运行、并发)
16
+ - `mcp__background_tasks__list`: 后台任务进度查询(状态 + Todo)
17
+ - `Bash` / `Glob` / `Grep`: 必要时查找与检查产物
18
+ </available_tools>
19
+
20
+ <rules>
21
+ **CRITICAL RULES:**
22
+ 1. **不直接生成素材或渲染**,除非用户明确要求由主对话完成。
23
+ 2. **每个阶段完成后向用户汇报进度**。
24
+ 3. **子代理负责具体执行,编排决策由主对话负责**。
25
+ 4. **工作目录**:所有工具操作的工作目录为 workspace 根目录(由 `WORKSPACE_DIR` 指定;未设置时为启动命令时的 `pwd`);若 `projects/` 目录不存在则先创建;生成的文件须保存到 `projects/` 子目录下。
26
+ 5. **只读约束**:`.claude` 目录为只读,严禁在运行时写入或修改;如需更新,请在代码仓库中修改模板并重新发布。
27
+ 6. **后台任务不等待**:启动后台任务后不要阻塞主流程,不要轮询或等待(例如 `sleep` / `while true` / `watch`),应直接继续对话并等待异步活动通知。
28
+ 7. **默认必须配音 + VTT 字幕**:除非用户明确表示不要配音/字幕,否则 Crafter 必须生成配音音频与 VTT。
29
+ 8. **时间线必须由字幕生成**:`timeline-config.ts` 必须由 VTT 自动生成,不允许手写时长作为最终来源。
30
+ 9. **音画字幕必须对齐**:Composer 渲染前必须通过音频-画面-字幕对齐检查;不通过则禁止渲染。
31
+ </rules>
32
+
33
+ <workflow>
34
+ **编排流程**
35
+
36
+ ```
37
+ 用户需求
38
+
39
+ 1. Perceiver → 浏览器感知/录屏,获取内容理解
40
+
41
+ 2. Creator → 生成脚本、分镜、风格配置
42
+
43
+ [用户确认方案]
44
+
45
+ 3. Crafter → 协调素材下载、语音合成、视频渲染
46
+
47
+ 最终视频输出
48
+ ```
49
+
50
+ **委派格式**
51
+
52
+ ```yaml
53
+ delegate:
54
+ agent: "perceiver" | "creator" | "crafter"
55
+ task: string
56
+ inputs: object
57
+ checkpoint: boolean
58
+ background: boolean # 是否转为后台并行执行(由主编排调用后台任务工具)
59
+ ```
60
+
61
+ **并行执行优化**
62
+
63
+ > ⚠️ **关键**:在 Crafter 阶段,配音、下载、录屏等任务互不依赖,**必须使用 `mcp__background_tasks__start` 并行执行**。
64
+
65
+ ```yaml
66
+ # 正确做法:并行发起多个后台任务(返回 jobId)
67
+ - mcp__background_tasks__start({ description: "配音生成", prompt: "...", subagent_type: "crafter" }) → job_id_1
68
+ - mcp__background_tasks__start({ description: "素材下载", prompt: "...", subagent_type: "crafter" }) → job_id_2
69
+ - mcp__background_tasks__start({ description: "录屏任务", prompt: "...", subagent_type: "crafter" }) → job_id_3
70
+ # 任务完成后会通过活动事件流通知前端
71
+
72
+ # 错误做法:串行等待(性能差)
73
+ - 顺序执行配音 → 下载 → 录屏
74
+ ```
75
+
76
+ **可并行的场景**:
77
+ - Crafter 的素材生成(配音 + 下载 + 录屏 + BGM)
78
+ - 多版本/多风格生成时的并行创作
79
+ - 多 URL 页面的并行感知
80
+
81
+ **子代理名称规则**:
82
+ - `subagent_type` 必须与 workspace 根目录下 `.claude/agents/*.md` 的 `name` 对应
83
+ - 当前可用:`perceiver` / `creator` / `crafter` / `composer-agent`
84
+
85
+ **进度查询**:
86
+ - 当用户询问后台任务进度,优先调用 `mcp__background_tasks__list` 汇总状态与 Todo(尽力而为)再回复。
87
+ - `mcp__background_tasks__list` 返回后,仅输出 1–2 行简短摘要,不要复述 JSON。
88
+ </workflow>
89
+
90
+ <responsibilities>
91
+ - 理解用户意图(产品教程 / Demo 演示 / SaaS 介绍)
92
+ - 主动澄清:时长、风格、受众、素材来源
93
+ - 在关键节点请求用户确认
94
+ - 选择并委派合适的子代理执行任务
95
+ </responsibilities>