autocrew 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 (165) hide show
  1. package/HAMLETDEER.md +562 -0
  2. package/LICENSE +21 -0
  3. package/README.md +190 -0
  4. package/README_CN.md +190 -0
  5. package/adapters/openclaw/index.ts +68 -0
  6. package/bin/autocrew.mjs +23 -0
  7. package/bin/autocrew.ts +13 -0
  8. package/openclaw.plugin.json +36 -0
  9. package/package.json +74 -0
  10. package/skills/_writing-style/SKILL.md +68 -0
  11. package/skills/audience-profiler/SKILL.md +241 -0
  12. package/skills/content-attribution/SKILL.md +128 -0
  13. package/skills/content-review/SKILL.md +257 -0
  14. package/skills/cover-generator/SKILL.md +93 -0
  15. package/skills/humanizer-zh/SKILL.md +75 -0
  16. package/skills/intel-digest/SKILL.md +57 -0
  17. package/skills/intel-pull/SKILL.md +74 -0
  18. package/skills/manage-pipeline/SKILL.md +63 -0
  19. package/skills/memory-distill/SKILL.md +89 -0
  20. package/skills/onboarding/SKILL.md +117 -0
  21. package/skills/pipeline-status/SKILL.md +51 -0
  22. package/skills/platform-rewrite/SKILL.md +125 -0
  23. package/skills/pre-publish/SKILL.md +142 -0
  24. package/skills/publish-content/SKILL.md +500 -0
  25. package/skills/remix-content/SKILL.md +77 -0
  26. package/skills/research/SKILL.md +127 -0
  27. package/skills/setup/SKILL.md +353 -0
  28. package/skills/spawn-batch-writer/SKILL.md +66 -0
  29. package/skills/spawn-planner/SKILL.md +72 -0
  30. package/skills/spawn-writer/SKILL.md +60 -0
  31. package/skills/teardown/SKILL.md +144 -0
  32. package/skills/title-craft/SKILL.md +234 -0
  33. package/skills/topic-ideas/SKILL.md +105 -0
  34. package/skills/video-timeline/SKILL.md +117 -0
  35. package/skills/write-script/SKILL.md +232 -0
  36. package/skills/xhs-cover-review/SKILL.md +48 -0
  37. package/src/adapters/browser/browser-cdp.ts +260 -0
  38. package/src/adapters/browser/browser-relay.ts +236 -0
  39. package/src/adapters/browser/gateway-client.ts +148 -0
  40. package/src/adapters/browser/types.ts +36 -0
  41. package/src/adapters/image/gemini.ts +219 -0
  42. package/src/adapters/research/tikhub.ts +19 -0
  43. package/src/cli/banner.ts +18 -0
  44. package/src/cli/bootstrap.ts +33 -0
  45. package/src/cli/commands/adapt.ts +28 -0
  46. package/src/cli/commands/advance.ts +28 -0
  47. package/src/cli/commands/assets.ts +24 -0
  48. package/src/cli/commands/audit.ts +18 -0
  49. package/src/cli/commands/contents.ts +18 -0
  50. package/src/cli/commands/cover.ts +58 -0
  51. package/src/cli/commands/events.ts +17 -0
  52. package/src/cli/commands/humanize.ts +27 -0
  53. package/src/cli/commands/index.ts +80 -0
  54. package/src/cli/commands/init.ts +28 -0
  55. package/src/cli/commands/intel.ts +55 -0
  56. package/src/cli/commands/learn.ts +34 -0
  57. package/src/cli/commands/memory.ts +18 -0
  58. package/src/cli/commands/migrate.ts +24 -0
  59. package/src/cli/commands/open.ts +21 -0
  60. package/src/cli/commands/pipelines.ts +18 -0
  61. package/src/cli/commands/pre-publish.ts +27 -0
  62. package/src/cli/commands/profile.ts +31 -0
  63. package/src/cli/commands/research.ts +36 -0
  64. package/src/cli/commands/restore.ts +28 -0
  65. package/src/cli/commands/review.ts +61 -0
  66. package/src/cli/commands/start.ts +28 -0
  67. package/src/cli/commands/status.ts +14 -0
  68. package/src/cli/commands/templates.ts +15 -0
  69. package/src/cli/commands/topics.ts +18 -0
  70. package/src/cli/commands/trash.ts +28 -0
  71. package/src/cli/commands/upgrade.ts +48 -0
  72. package/src/cli/commands/versions.ts +24 -0
  73. package/src/cli/index.ts +40 -0
  74. package/src/data/sensitive-words-builtin.json +114 -0
  75. package/src/data/source-presets.yaml +54 -0
  76. package/src/e2e.test.ts +596 -0
  77. package/src/modules/auth/cookie-manager.ts +113 -0
  78. package/src/modules/cards/template-engine.ts +74 -0
  79. package/src/modules/cards/templates/comparison-table.ts +71 -0
  80. package/src/modules/cards/templates/data-chart.ts +76 -0
  81. package/src/modules/cards/templates/flow-chart.ts +49 -0
  82. package/src/modules/cards/templates/key-points.ts +59 -0
  83. package/src/modules/cover/prompt-builder.test.ts +157 -0
  84. package/src/modules/cover/prompt-builder.ts +212 -0
  85. package/src/modules/cover/ratio-adapter.test.ts +122 -0
  86. package/src/modules/cover/ratio-adapter.ts +104 -0
  87. package/src/modules/filter/sensitive-words.test.ts +72 -0
  88. package/src/modules/filter/sensitive-words.ts +212 -0
  89. package/src/modules/humanizer/zh.test.ts +75 -0
  90. package/src/modules/humanizer/zh.ts +175 -0
  91. package/src/modules/intel/collector.ts +19 -0
  92. package/src/modules/intel/collectors/competitor.test.ts +71 -0
  93. package/src/modules/intel/collectors/competitor.ts +65 -0
  94. package/src/modules/intel/collectors/rss.test.ts +56 -0
  95. package/src/modules/intel/collectors/rss.ts +70 -0
  96. package/src/modules/intel/collectors/trends.test.ts +80 -0
  97. package/src/modules/intel/collectors/trends.ts +107 -0
  98. package/src/modules/intel/collectors/web-search.test.ts +85 -0
  99. package/src/modules/intel/collectors/web-search.ts +81 -0
  100. package/src/modules/intel/integration.test.ts +203 -0
  101. package/src/modules/intel/intel-engine.test.ts +103 -0
  102. package/src/modules/intel/intel-engine.ts +96 -0
  103. package/src/modules/intel/source-config.test.ts +113 -0
  104. package/src/modules/intel/source-config.ts +131 -0
  105. package/src/modules/learnings/diff-tracker.test.ts +144 -0
  106. package/src/modules/learnings/diff-tracker.ts +189 -0
  107. package/src/modules/learnings/rule-distiller.ts +141 -0
  108. package/src/modules/memory/distill.ts +208 -0
  109. package/src/modules/migrate/legacy-migrate.test.ts +169 -0
  110. package/src/modules/migrate/legacy-migrate.ts +229 -0
  111. package/src/modules/pro/api-client.ts +192 -0
  112. package/src/modules/pro/gate.test.ts +110 -0
  113. package/src/modules/pro/gate.ts +104 -0
  114. package/src/modules/profile/creator-profile.test.ts +178 -0
  115. package/src/modules/profile/creator-profile.ts +248 -0
  116. package/src/modules/publish/douyin-api.ts +34 -0
  117. package/src/modules/publish/wechat-mp.ts +320 -0
  118. package/src/modules/publish/xiaohongshu-api.ts +127 -0
  119. package/src/modules/research/free-engine.ts +360 -0
  120. package/src/modules/timeline/markup-generator.ts +63 -0
  121. package/src/modules/timeline/parser.ts +275 -0
  122. package/src/modules/workflow/templates.ts +124 -0
  123. package/src/modules/writing/platform-rewrite.ts +190 -0
  124. package/src/modules/writing/title-hashtag.ts +385 -0
  125. package/src/runtime/context.test.ts +97 -0
  126. package/src/runtime/context.ts +129 -0
  127. package/src/runtime/events.test.ts +83 -0
  128. package/src/runtime/events.ts +104 -0
  129. package/src/runtime/hooks.ts +174 -0
  130. package/src/runtime/tool-runner.test.ts +204 -0
  131. package/src/runtime/tool-runner.ts +282 -0
  132. package/src/runtime/workflow-engine.test.ts +455 -0
  133. package/src/runtime/workflow-engine.ts +391 -0
  134. package/src/server/index.ts +409 -0
  135. package/src/server/start.ts +39 -0
  136. package/src/storage/local-store.test.ts +304 -0
  137. package/src/storage/local-store.ts +704 -0
  138. package/src/storage/pipeline-store.test.ts +363 -0
  139. package/src/storage/pipeline-store.ts +698 -0
  140. package/src/tools/asset.ts +96 -0
  141. package/src/tools/content-save.ts +276 -0
  142. package/src/tools/cover-review.ts +221 -0
  143. package/src/tools/humanize.ts +54 -0
  144. package/src/tools/init.ts +133 -0
  145. package/src/tools/intel.ts +92 -0
  146. package/src/tools/memory.ts +76 -0
  147. package/src/tools/pipeline-ops.ts +109 -0
  148. package/src/tools/pipeline.ts +168 -0
  149. package/src/tools/pre-publish.ts +232 -0
  150. package/src/tools/publish.ts +183 -0
  151. package/src/tools/registry.ts +198 -0
  152. package/src/tools/research.ts +304 -0
  153. package/src/tools/review.ts +305 -0
  154. package/src/tools/rewrite.ts +165 -0
  155. package/src/tools/status.ts +30 -0
  156. package/src/tools/timeline.ts +234 -0
  157. package/src/tools/topic-create.ts +50 -0
  158. package/src/types/providers.ts +69 -0
  159. package/src/types/timeline.test.ts +147 -0
  160. package/src/types/timeline.ts +83 -0
  161. package/src/utils/retry.test.ts +97 -0
  162. package/src/utils/retry.ts +85 -0
  163. package/templates/AGENTS.md +99 -0
  164. package/templates/SOUL.md +31 -0
  165. package/templates/TOOLS.md +76 -0
@@ -0,0 +1,409 @@
1
+ /**
2
+ * AutoCrew HTTP Server — lightweight local dashboard backend.
3
+ *
4
+ * Wraps ToolRunner as REST API + EventBus as SSE stream.
5
+ * Start with: autocrew serve --port 3000
6
+ */
7
+ import { Hono } from "hono";
8
+ import { cors } from "hono/cors";
9
+ import { serveStatic } from "@hono/node-server/serve-static";
10
+ import { streamSSE } from "hono/streaming";
11
+ import type { ToolRunner } from "../runtime/tool-runner.js";
12
+ import type { EventBus } from "../runtime/events.js";
13
+ import type { WorkflowEngine } from "../runtime/workflow-engine.js";
14
+
15
+ export interface ServerDeps {
16
+ runner: ToolRunner;
17
+ eventBus: EventBus;
18
+ workflowEngine: WorkflowEngine;
19
+ }
20
+
21
+ export function createApp(deps: ServerDeps): Hono {
22
+ const { runner, eventBus, workflowEngine } = deps;
23
+ const app = new Hono();
24
+
25
+ // --- CORS (allow all origins for local dev) ---
26
+ app.use("*", cors());
27
+
28
+ // --- Health ---
29
+ app.get("/api/health", (c) => {
30
+ return c.json({ ok: true, version: "0.1.0" });
31
+ });
32
+
33
+ // --- Status ---
34
+ app.get("/api/status", async (c) => {
35
+ try {
36
+ const result = await runner.execute("autocrew_status", {});
37
+ return c.json(result);
38
+ } catch (err) {
39
+ return c.json({ ok: false, error: String(err) }, 500);
40
+ }
41
+ });
42
+
43
+ // --- Tool API ---
44
+ app.get("/api/tools", (c) => {
45
+ const tools = runner.getTools().map((t) => ({
46
+ name: t.name,
47
+ description: t.description,
48
+ parameters: t.parameters,
49
+ }));
50
+ return c.json({ ok: true, tools });
51
+ });
52
+
53
+ app.post("/api/tools/:name", async (c) => {
54
+ const toolName = c.req.param("name");
55
+ const def = runner.getTool(toolName);
56
+ if (!def) {
57
+ return c.json({ ok: false, error: `Unknown tool: ${toolName}` }, 404);
58
+ }
59
+ try {
60
+ const params = await c.req.json();
61
+ const result = await runner.execute(toolName, params);
62
+ return c.json(result);
63
+ } catch (err) {
64
+ return c.json({ ok: false, error: String(err) }, 500);
65
+ }
66
+ });
67
+
68
+ // --- Content API (convenience wrappers) ---
69
+ app.get("/api/contents", async (c) => {
70
+ try {
71
+ const result = await runner.execute("autocrew_content", { action: "list" });
72
+ return c.json(result);
73
+ } catch (err) {
74
+ return c.json({ ok: false, error: String(err) }, 500);
75
+ }
76
+ });
77
+
78
+ app.post("/api/contents", async (c) => {
79
+ try {
80
+ const body = await c.req.json();
81
+
82
+ // If topicId provided, fetch topic title first
83
+ let title = body.title || "新内容";
84
+ if (body.topicId) {
85
+ try {
86
+ const topicResult = await runner.execute("autocrew_topic", { action: "list" });
87
+ const topics = (topicResult as { topics?: { id: string; title: string }[] }).topics ?? [];
88
+ const topic = topics.find((t) => t.id === body.topicId);
89
+ if (topic) title = topic.title;
90
+ } catch {
91
+ // Use default title if topic lookup fails
92
+ }
93
+ }
94
+
95
+ const result = await runner.execute("autocrew_content", {
96
+ action: "save",
97
+ title,
98
+ body: body.body || `# ${title}\n\n(稿件待编辑)`,
99
+ topicId: body.topicId,
100
+ status: "draft",
101
+ });
102
+ return c.json(result, 201);
103
+ } catch (err) {
104
+ return c.json({ ok: false, error: String(err) }, 500);
105
+ }
106
+ });
107
+
108
+ app.get("/api/contents/:id", async (c) => {
109
+ try {
110
+ const result = await runner.execute("autocrew_content", {
111
+ action: "get",
112
+ id: c.req.param("id"),
113
+ });
114
+ return c.json(result);
115
+ } catch (err) {
116
+ return c.json({ ok: false, error: String(err) }, 500);
117
+ }
118
+ });
119
+
120
+ app.patch("/api/contents/:id", async (c) => {
121
+ try {
122
+ const body = await c.req.json();
123
+ const result = await runner.execute("autocrew_content", {
124
+ action: "update",
125
+ id: c.req.param("id"),
126
+ title: body.title,
127
+ body: body.body,
128
+ status: body.status,
129
+ });
130
+ return c.json(result);
131
+ } catch (err) {
132
+ return c.json({ ok: false, error: String(err) }, 500);
133
+ }
134
+ });
135
+
136
+ // --- Timeline API ---
137
+ app.get("/api/contents/:id/timeline", async (c) => {
138
+ try {
139
+ const result = await runner.execute("autocrew_timeline", {
140
+ action: "get",
141
+ content_id: c.req.param("id"),
142
+ });
143
+ if (!result.ok) return c.json(result, 404);
144
+ return c.json(result);
145
+ } catch (err) {
146
+ return c.json({ ok: false, error: String(err) }, 500);
147
+ }
148
+ });
149
+
150
+ app.post("/api/contents/:id/timeline", async (c) => {
151
+ try {
152
+ const body = await c.req.json();
153
+ const result = await runner.execute("autocrew_timeline", {
154
+ action: "generate",
155
+ content_id: c.req.param("id"),
156
+ preset: body.preset || "knowledge-explainer",
157
+ aspect_ratio: body.aspectRatio || "9:16",
158
+ });
159
+ return c.json(result);
160
+ } catch (err) {
161
+ return c.json({ ok: false, error: String(err) }, 500);
162
+ }
163
+ });
164
+
165
+ app.patch("/api/contents/:id/timeline/segments/:segId", async (c) => {
166
+ try {
167
+ const body = await c.req.json();
168
+ const result = await runner.execute("autocrew_timeline", {
169
+ action: "update_segment",
170
+ content_id: c.req.param("id"),
171
+ segment_id: c.req.param("segId"),
172
+ status: body.status,
173
+ asset_path: body.assetPath,
174
+ text: body.text,
175
+ });
176
+ if (!result.ok) return c.json(result, 400);
177
+ return c.json(result);
178
+ } catch (err) {
179
+ return c.json({ ok: false, error: String(err) }, 500);
180
+ }
181
+ });
182
+
183
+ app.post("/api/contents/:id/timeline/confirm-all", async (c) => {
184
+ try {
185
+ const result = await runner.execute("autocrew_timeline", {
186
+ action: "confirm_all",
187
+ content_id: c.req.param("id"),
188
+ });
189
+ return c.json(result);
190
+ } catch (err) {
191
+ return c.json({ ok: false, error: String(err) }, 500);
192
+ }
193
+ });
194
+
195
+ // --- Card Preview ---
196
+ const VALID_CARD_TEMPLATES = new Set([
197
+ "comparison-table",
198
+ "key-points",
199
+ "flow-chart",
200
+ "data-chart",
201
+ ]);
202
+ const VALID_ASPECT_RATIOS = new Set(["9:16", "16:9", "3:4", "1:1", "4:3"]);
203
+
204
+ app.get("/api/cards/preview", async (c) => {
205
+ const template = c.req.query("template");
206
+ if (!template || !VALID_CARD_TEMPLATES.has(template)) {
207
+ return c.json(
208
+ {
209
+ ok: false,
210
+ error: `Invalid template. Must be one of: ${[...VALID_CARD_TEMPLATES].join(", ")}`,
211
+ },
212
+ 400,
213
+ );
214
+ }
215
+
216
+ const aspectRatio = c.req.query("aspectRatio") || "9:16";
217
+ if (!VALID_ASPECT_RATIOS.has(aspectRatio)) {
218
+ return c.json(
219
+ {
220
+ ok: false,
221
+ error: `Invalid aspectRatio. Must be one of: ${[...VALID_ASPECT_RATIOS].join(", ")}`,
222
+ },
223
+ 400,
224
+ );
225
+ }
226
+
227
+ let data: Record<string, unknown>;
228
+ try {
229
+ data = JSON.parse(c.req.query("data") || "{}");
230
+ } catch {
231
+ return c.json({ ok: false, error: "Invalid JSON in data parameter" }, 400);
232
+ }
233
+
234
+ try {
235
+ const { renderCard } = await import("../modules/cards/template-engine.js");
236
+ const html = renderCard(template as any, data, {
237
+ aspectRatio: aspectRatio as any,
238
+ });
239
+ return c.html(html);
240
+ } catch (err) {
241
+ return c.json({ ok: false, error: String(err) }, 500);
242
+ }
243
+ });
244
+
245
+ app.get("/api/topics", async (c) => {
246
+ try {
247
+ const result = await runner.execute("autocrew_topic", { action: "list" });
248
+ return c.json(result);
249
+ } catch (err) {
250
+ return c.json({ ok: false, error: String(err) }, 500);
251
+ }
252
+ });
253
+
254
+ // --- Workflow API ---
255
+ app.get("/api/workflows/templates", async (c) => {
256
+ try {
257
+ const result = await runner.execute("autocrew_pipeline", { action: "templates" });
258
+ return c.json(result);
259
+ } catch (err) {
260
+ return c.json({ ok: false, error: String(err) }, 500);
261
+ }
262
+ });
263
+
264
+ app.get("/api/workflows", async (c) => {
265
+ try {
266
+ const result = await runner.execute("autocrew_pipeline", { action: "list" });
267
+ return c.json(result);
268
+ } catch (err) {
269
+ return c.json({ ok: false, error: String(err) }, 500);
270
+ }
271
+ });
272
+
273
+ app.post("/api/workflows", async (c) => {
274
+ try {
275
+ const body = await c.req.json();
276
+ const result = await runner.execute("autocrew_pipeline", {
277
+ action: "create",
278
+ template: body.template,
279
+ params: body.params,
280
+ });
281
+ return c.json(result, result.ok === false ? 400 : 201);
282
+ } catch (err) {
283
+ return c.json({ ok: false, error: String(err) }, 500);
284
+ }
285
+ });
286
+
287
+ app.post("/api/workflows/:id/start", async (c) => {
288
+ try {
289
+ const result = await runner.execute("autocrew_pipeline", {
290
+ action: "start",
291
+ id: c.req.param("id"),
292
+ });
293
+ return c.json(result);
294
+ } catch (err) {
295
+ return c.json({ ok: false, error: String(err) }, 500);
296
+ }
297
+ });
298
+
299
+ app.post("/api/workflows/:id/approve", async (c) => {
300
+ try {
301
+ const result = await runner.execute("autocrew_pipeline", {
302
+ action: "approve",
303
+ id: c.req.param("id"),
304
+ });
305
+ return c.json(result);
306
+ } catch (err) {
307
+ return c.json({ ok: false, error: String(err) }, 500);
308
+ }
309
+ });
310
+
311
+ app.post("/api/workflows/:id/cancel", async (c) => {
312
+ try {
313
+ const result = await runner.execute("autocrew_pipeline", {
314
+ action: "cancel",
315
+ id: c.req.param("id"),
316
+ });
317
+ return c.json(result);
318
+ } catch (err) {
319
+ return c.json({ ok: false, error: String(err) }, 500);
320
+ }
321
+ });
322
+
323
+ app.get("/api/workflows/:id", async (c) => {
324
+ try {
325
+ const result = await runner.execute("autocrew_pipeline", {
326
+ action: "status",
327
+ id: c.req.param("id"),
328
+ });
329
+ return c.json(result);
330
+ } catch (err) {
331
+ return c.json({ ok: false, error: String(err) }, 500);
332
+ }
333
+ });
334
+
335
+ // --- Studio Export/Render ---
336
+ app.post("/api/contents/:id/export/jianying", async (c) => {
337
+ try {
338
+ const contentId = c.req.param("id");
339
+ const timelineResult = await runner.execute("autocrew_timeline", {
340
+ action: "get",
341
+ content_id: contentId,
342
+ });
343
+ if (!timelineResult.ok) {
344
+ return c.json({ ok: false, error: "Timeline not found" }, 404);
345
+ }
346
+
347
+ const { join } = await import("node:path");
348
+ const { JianyingExporter } = await import("../../packages/studio/src/providers/compositor/jianying/exporter.js");
349
+ const exporter = new JianyingExporter();
350
+ const homeDir = process.env.HOME || "~";
351
+ const outputDir = join(homeDir, ".autocrew", "contents", contentId, "export");
352
+ const result = await exporter.export(timelineResult.timeline, outputDir);
353
+ return c.json({ ok: true, path: result.path, format: result.format });
354
+ } catch (err) {
355
+ return c.json({ ok: false, error: String(err) }, 500);
356
+ }
357
+ });
358
+
359
+ app.post("/api/contents/:id/render", async (c) => {
360
+ try {
361
+ return c.json({ ok: false, error: "视频渲染需要配置豆包 TTS。请先在设置页填入 API Key。" }, 400);
362
+ } catch (err) {
363
+ return c.json({ ok: false, error: String(err) }, 500);
364
+ }
365
+ });
366
+
367
+ // --- Events SSE ---
368
+ app.get("/api/events", (c) => {
369
+ return streamSSE(c, async (stream) => {
370
+ const subId = eventBus.on("*", (event) => {
371
+ stream.writeSSE({
372
+ event: event.type,
373
+ data: JSON.stringify(event),
374
+ }).catch(() => {});
375
+ });
376
+
377
+ // Heartbeat every 30s
378
+ const heartbeat = setInterval(() => {
379
+ stream.writeSSE({ event: "heartbeat", data: "" }).catch(() => {
380
+ clearInterval(heartbeat);
381
+ });
382
+ }, 30_000);
383
+
384
+ // Keep connection open until client disconnects
385
+ stream.onAbort(() => {
386
+ eventBus.off(subId);
387
+ clearInterval(heartbeat);
388
+ });
389
+
390
+ // Block until aborted
391
+ await new Promise<void>((resolve) => {
392
+ stream.onAbort(() => resolve());
393
+ });
394
+ });
395
+ });
396
+
397
+ // --- Static files (frontend build) ---
398
+ app.use(
399
+ "/*",
400
+ serveStatic({
401
+ root: "./web/dist",
402
+ }),
403
+ );
404
+
405
+ // SPA fallback: serve index.html for unmatched routes
406
+ app.get("*", serveStatic({ root: "./web/dist", path: "index.html" }));
407
+
408
+ return app;
409
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * AutoCrew Dashboard — standalone entry point.
3
+ *
4
+ * Usage: node --loader ts-node/esm src/server/start.ts [--port 3000]
5
+ */
6
+ import { serve } from "@hono/node-server";
7
+ import { bootstrap } from "../cli/bootstrap.js";
8
+ import { createApp } from "./index.js";
9
+
10
+ // --- Parse CLI args ---
11
+
12
+ function parsePort(args: string[]): number {
13
+ const idx = args.indexOf("--port");
14
+ if (idx !== -1 && args[idx + 1]) {
15
+ const port = Number(args[idx + 1]);
16
+ if (!Number.isNaN(port) && port > 0 && port < 65536) return port;
17
+ }
18
+ return 3000;
19
+ }
20
+
21
+ // --- Main ---
22
+
23
+ async function main(): Promise<void> {
24
+ const port = parsePort(process.argv);
25
+ const { runner, eventBus, workflowEngine } = bootstrap();
26
+ const app = createApp({ runner, eventBus, workflowEngine });
27
+
28
+ console.log(`AutoCrew Dashboard: http://localhost:${port}`);
29
+
30
+ serve({
31
+ fetch: app.fetch,
32
+ port,
33
+ });
34
+ }
35
+
36
+ main().catch((err) => {
37
+ console.error("Failed to start AutoCrew server:", err);
38
+ process.exit(1);
39
+ });