@zauso-ai/capstan-cli 0.1.4 → 1.0.0-beta.1

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/dist/index.js CHANGED
@@ -3,13 +3,22 @@ import { existsSync } from "node:fs";
3
3
  import { mkdir, readFile, writeFile } from "node:fs/promises";
4
4
  import { join, resolve } from "node:path";
5
5
  import { pathToFileURL } from "node:url";
6
- import { diffAppGraphs, introspectAppGraph, validateAppGraph } from "@zauso-ai/capstan-app-graph";
7
- import { compileCapstanBrief, summarizeCapstanBrief, validateCapstanBrief } from "@zauso-ai/capstan-brief";
8
- import { applyAppGraphPacks, applyBuiltinAppGraphPacks, listBuiltinGraphPacks } from "@zauso-ai/capstan-packs-core";
9
- import { scaffoldAppGraph } from "@zauso-ai/capstan-compiler";
10
- import { renderVerifyReportText, verifyGeneratedApp } from "@zauso-ai/capstan-feedback";
11
- import { approveHarnessRun, cancelHarnessRun, compactHarnessRun, completeHarnessRun, createHarnessMemory, createHarnessRun, failHarnessRun, getHarnessRun, getHarnessMemory, getHarnessSummary, listHarnessEvents, listHarnessMemories, listHarnessRuns, listHarnessSummaries, renderHarnessMemoriesText, renderHarnessMemoryText, renderHarnessEventsText, renderHarnessCompactionText, renderHarnessReplayText, renderHarnessRunText, renderHarnessRunsText, renderHarnessSummariesText, replayHarnessRun, provideHarnessInput, requestHarnessApproval, requestHarnessInput, resumeHarnessRun, retryHarnessRun, pauseHarnessRun } from "@zauso-ai/capstan-harness";
12
- import { createReleasePlan, createReleaseRun, createRollbackRun, listReleaseRuns, renderReleaseHistoryText, renderReleasePlanText, renderRollbackRunText, renderReleaseRunText } from "@zauso-ai/capstan-release";
6
+ const LEGACY_INSTALL_HINT = "Legacy compiler packages are not installed.\n" +
7
+ "Install them with: npm install @zauso-ai/capstan-app-graph @zauso-ai/capstan-packs-core @zauso-ai/capstan-brief @zauso-ai/capstan-surface-web @zauso-ai/capstan-surface-agent @zauso-ai/capstan-compiler @zauso-ai/capstan-feedback @zauso-ai/capstan-release @zauso-ai/capstan-harness";
8
+ /**
9
+ * Dynamically import a legacy package, providing a clear error when it is
10
+ * not installed.
11
+ */
12
+ async function requireLegacy(pkg) {
13
+ try {
14
+ return (await import(pkg));
15
+ }
16
+ catch {
17
+ console.error(`The legacy package "${pkg}" is required for this command but is not installed.\n\n${LEGACY_INSTALL_HINT}`);
18
+ process.exitCode = 1;
19
+ throw new Error(`Missing legacy package: ${pkg}`);
20
+ }
21
+ }
13
22
  async function main() {
14
23
  const [command, ...args] = process.argv.slice(2);
15
24
  switch (command) {
@@ -162,6 +171,8 @@ async function runBriefCheck(args) {
162
171
  process.exitCode = 1;
163
172
  return;
164
173
  }
174
+ const { validateCapstanBrief } = await requireLegacy("@zauso-ai/capstan-brief");
175
+ const { validateAppGraph } = await requireLegacy("@zauso-ai/capstan-app-graph");
165
176
  const loaded = await loadBrief(target);
166
177
  const validation = validateCapstanBrief(loaded.brief);
167
178
  if (!validation.ok) {
@@ -195,6 +206,8 @@ async function runBriefInspect(args) {
195
206
  process.exitCode = 1;
196
207
  return;
197
208
  }
209
+ const { summarizeCapstanBrief, validateCapstanBrief } = await requireLegacy("@zauso-ai/capstan-brief");
210
+ const { introspectAppGraph } = await requireLegacy("@zauso-ai/capstan-app-graph");
198
211
  const loaded = await loadBrief(target);
199
212
  const graph = await compileBriefWithPackDefinitions(loaded.brief, {
200
213
  packDefinitions: loaded.packDefinitions,
@@ -235,6 +248,7 @@ async function runBriefScaffold(args) {
235
248
  process.exitCode = 1;
236
249
  return;
237
250
  }
251
+ const { scaffoldAppGraph } = await requireLegacy("@zauso-ai/capstan-compiler");
238
252
  const loaded = await loadBrief(target);
239
253
  const graph = await compileBriefWithPackDefinitions(loaded.brief, {
240
254
  packDefinitions: loaded.packDefinitions,
@@ -253,6 +267,7 @@ async function runGraphCheck(args) {
253
267
  process.exitCode = 1;
254
268
  return;
255
269
  }
270
+ const { validateAppGraph } = await requireLegacy("@zauso-ai/capstan-app-graph");
256
271
  const graph = await loadGraph(target, {
257
272
  ...(packRegistryPath ? { packRegistryPath } : {})
258
273
  });
@@ -277,6 +292,7 @@ async function runGraphScaffold(args) {
277
292
  process.exitCode = 1;
278
293
  return;
279
294
  }
295
+ const { scaffoldAppGraph } = await requireLegacy("@zauso-ai/capstan-compiler");
280
296
  const graph = await loadGraph(target, {
281
297
  ...(packRegistryPath ? { packRegistryPath } : {})
282
298
  });
@@ -293,6 +309,7 @@ async function runGraphInspect(args) {
293
309
  process.exitCode = 1;
294
310
  return;
295
311
  }
312
+ const { introspectAppGraph } = await requireLegacy("@zauso-ai/capstan-app-graph");
296
313
  const graph = await loadGraph(target, {
297
314
  ...(packRegistryPath ? { packRegistryPath } : {})
298
315
  });
@@ -307,6 +324,7 @@ async function runGraphDiff(args) {
307
324
  process.exitCode = 1;
308
325
  return;
309
326
  }
327
+ const { diffAppGraphs } = await requireLegacy("@zauso-ai/capstan-app-graph");
310
328
  const before = await loadGraph(beforePath, {
311
329
  ...(packRegistryPath ? { packRegistryPath } : {})
312
330
  });
@@ -348,6 +366,7 @@ async function runVerify(args, asJson) {
348
366
  process.exitCode = 1;
349
367
  return;
350
368
  }
369
+ const { verifyGeneratedApp, renderVerifyReportText } = await requireLegacy("@zauso-ai/capstan-feedback");
351
370
  const report = await verifyGeneratedApp(resolve(process.cwd(), target));
352
371
  if (asJson) {
353
372
  console.log(JSON.stringify(report, null, 2));
@@ -383,6 +402,7 @@ async function runReleasePlan(args) {
383
402
  process.exitCode = 1;
384
403
  return;
385
404
  }
405
+ const { createReleasePlan, renderReleasePlanText } = await requireLegacy("@zauso-ai/capstan-release");
386
406
  const report = await createReleasePlan(resolve(process.cwd(), target), {
387
407
  ...(environmentPath ? { environmentPath } : {}),
388
408
  ...(migrationPath ? { migrationPath } : {})
@@ -408,6 +428,7 @@ async function runReleaseRun(args) {
408
428
  process.exitCode = 1;
409
429
  return;
410
430
  }
431
+ const { createReleaseRun, renderReleaseRunText } = await requireLegacy("@zauso-ai/capstan-release");
411
432
  const report = await createReleaseRun(resolve(process.cwd(), target), mode, {
412
433
  ...(environmentPath ? { environmentPath } : {}),
413
434
  ...(migrationPath ? { migrationPath } : {})
@@ -430,6 +451,7 @@ async function runReleaseHistory(args) {
430
451
  process.exitCode = 1;
431
452
  return;
432
453
  }
454
+ const { listReleaseRuns, renderReleaseHistoryText } = await requireLegacy("@zauso-ai/capstan-release");
433
455
  const report = await listReleaseRuns(resolve(process.cwd(), target));
434
456
  if (asJson) {
435
457
  console.log(JSON.stringify(report, null, 2));
@@ -447,6 +469,7 @@ async function runReleaseRollback(args) {
447
469
  process.exitCode = 1;
448
470
  return;
449
471
  }
472
+ const { createRollbackRun, renderRollbackRunText } = await requireLegacy("@zauso-ai/capstan-release");
450
473
  const report = await createRollbackRun(resolve(process.cwd(), target), {
451
474
  ...(tracePath ? { tracePath } : {})
452
475
  });
@@ -471,6 +494,7 @@ async function runHarnessStart(args) {
471
494
  process.exitCode = 1;
472
495
  return;
473
496
  }
497
+ const { createHarnessRun, renderHarnessRunText } = await requireLegacy("@zauso-ai/capstan-harness");
474
498
  const input = inputPath ? await loadJsonFile(inputPath) : {};
475
499
  const run = await createHarnessRun(resolve(process.cwd(), appDir), taskKey, ensureRecord(input), {
476
500
  ...(note ? { note } : {})
@@ -491,6 +515,7 @@ async function runHarnessGet(args) {
491
515
  process.exitCode = 1;
492
516
  return;
493
517
  }
518
+ const { getHarnessRun, renderHarnessRunText } = await requireLegacy("@zauso-ai/capstan-harness");
494
519
  const run = await getHarnessRun(resolve(process.cwd(), appDir), runId);
495
520
  if (!run) {
496
521
  console.error(`Unknown harness run "${runId}".`);
@@ -513,6 +538,7 @@ async function runHarnessList(args) {
513
538
  process.exitCode = 1;
514
539
  return;
515
540
  }
541
+ const { listHarnessRuns, renderHarnessRunsText } = await requireLegacy("@zauso-ai/capstan-harness");
516
542
  const runs = await listHarnessRuns(resolve(process.cwd(), appDir), {
517
543
  ...(taskKey ? { taskKey } : {})
518
544
  });
@@ -549,6 +575,7 @@ async function runHarnessProvideInput(args) {
549
575
  process.exitCode = 1;
550
576
  return;
551
577
  }
578
+ const { provideHarnessInput, renderHarnessRunText } = await requireLegacy("@zauso-ai/capstan-harness");
552
579
  const input = ensureRecord(await loadJsonFile(inputPath));
553
580
  const run = await provideHarnessInput(resolve(process.cwd(), appDir), runId, input, {
554
581
  ...(note ? { note } : {})
@@ -571,6 +598,7 @@ async function runHarnessComplete(args) {
571
598
  process.exitCode = 1;
572
599
  return;
573
600
  }
601
+ const { completeHarnessRun, renderHarnessRunText } = await requireLegacy("@zauso-ai/capstan-harness");
574
602
  const output = outputPath ? await loadJsonFile(outputPath) : {};
575
603
  const run = await completeHarnessRun(resolve(process.cwd(), appDir), runId, output, {
576
604
  ...(note ? { note } : {})
@@ -592,6 +620,7 @@ async function runHarnessFail(args) {
592
620
  process.exitCode = 1;
593
621
  return;
594
622
  }
623
+ const { failHarnessRun, renderHarnessRunText } = await requireLegacy("@zauso-ai/capstan-harness");
595
624
  const run = await failHarnessRun(resolve(process.cwd(), appDir), runId, message);
596
625
  if (asJson) {
597
626
  console.log(JSON.stringify(run, null, 2));
@@ -615,6 +644,7 @@ async function runHarnessEvents(args) {
615
644
  process.exitCode = 1;
616
645
  return;
617
646
  }
647
+ const { listHarnessEvents, renderHarnessEventsText } = await requireLegacy("@zauso-ai/capstan-harness");
618
648
  const events = await listHarnessEvents(resolve(process.cwd(), appDir), {
619
649
  ...(runId ? { runId } : {})
620
650
  });
@@ -634,6 +664,7 @@ async function runHarnessReplay(args) {
634
664
  process.exitCode = 1;
635
665
  return;
636
666
  }
667
+ const { replayHarnessRun, renderHarnessReplayText } = await requireLegacy("@zauso-ai/capstan-harness");
637
668
  const report = await replayHarnessRun(resolve(process.cwd(), appDir), runId);
638
669
  if (asJson) {
639
670
  console.log(JSON.stringify(report, null, 2));
@@ -655,6 +686,7 @@ async function runHarnessCompact(args) {
655
686
  process.exitCode = 1;
656
687
  return;
657
688
  }
689
+ const { compactHarnessRun, renderHarnessCompactionText } = await requireLegacy("@zauso-ai/capstan-harness");
658
690
  const tail = tailValue ? Number.parseInt(tailValue, 10) : undefined;
659
691
  const summary = await compactHarnessRun(resolve(process.cwd(), appDir), runId, {
660
692
  ...(typeof tail === "number" && Number.isFinite(tail) ? { tail } : {})
@@ -680,6 +712,7 @@ async function runHarnessSummary(args) {
680
712
  process.exitCode = 1;
681
713
  return;
682
714
  }
715
+ const { getHarnessSummary, renderHarnessCompactionText } = await requireLegacy("@zauso-ai/capstan-harness");
683
716
  const tail = tailValue ? Number.parseInt(tailValue, 10) : undefined;
684
717
  const summary = await getHarnessSummary(resolve(process.cwd(), appDir), runId, {
685
718
  refresh,
@@ -703,6 +736,7 @@ async function runHarnessSummaries(args) {
703
736
  process.exitCode = 1;
704
737
  return;
705
738
  }
739
+ const { listHarnessSummaries, renderHarnessSummariesText } = await requireLegacy("@zauso-ai/capstan-harness");
706
740
  const summaries = await listHarnessSummaries(resolve(process.cwd(), appDir));
707
741
  if (asJson) {
708
742
  console.log(JSON.stringify(summaries, null, 2));
@@ -722,6 +756,7 @@ async function runHarnessMemory(args) {
722
756
  process.exitCode = 1;
723
757
  return;
724
758
  }
759
+ const { createHarnessMemory, getHarnessMemory, renderHarnessMemoryText } = await requireLegacy("@zauso-ai/capstan-harness");
725
760
  const tail = tailValue ? Number.parseInt(tailValue, 10) : undefined;
726
761
  const artifact = refresh
727
762
  ? await createHarnessMemory(resolve(process.cwd(), appDir), runId, {
@@ -746,6 +781,7 @@ async function runHarnessMemories(args) {
746
781
  process.exitCode = 1;
747
782
  return;
748
783
  }
784
+ const { listHarnessMemories, renderHarnessMemoriesText } = await requireLegacy("@zauso-ai/capstan-harness");
749
785
  const memories = await listHarnessMemories(resolve(process.cwd(), appDir));
750
786
  if (asJson) {
751
787
  console.log(JSON.stringify(memories, null, 2));
@@ -821,14 +857,25 @@ async function runBuild() {
821
857
  const { execFile } = await import("node:child_process");
822
858
  const { promisify } = await import("node:util");
823
859
  const exec = promisify(execFile);
824
- console.log("Building project...");
825
- await exec("npx", ["tsc", "-p", "tsconfig.json"], { cwd: process.cwd() });
826
- console.log("TypeScript compilation complete.");
827
- // Generate agent-manifest.json and openapi.json into dist/
828
- const { mkdir, writeFile } = await import("node:fs/promises");
860
+ const { mkdir, writeFile, cp, access } = await import("node:fs/promises");
829
861
  const { join } = await import("node:path");
830
862
  const { scanRoutes } = await import("@zauso-ai/capstan-router");
831
863
  const { generateAgentManifest, generateOpenApiSpec } = await import("@zauso-ai/capstan-agent");
864
+ const cwd = process.cwd();
865
+ const distDir = join(cwd, "dist");
866
+ // Step 1: TypeScript compilation
867
+ console.log("[capstan] Compiling TypeScript...");
868
+ try {
869
+ await exec("npx", ["tsc", "-p", "tsconfig.json"], { cwd });
870
+ }
871
+ catch (err) {
872
+ const message = err instanceof Error ? err.message : String(err);
873
+ console.error(`[capstan] TypeScript compilation failed:\n${message}`);
874
+ process.exitCode = 1;
875
+ return;
876
+ }
877
+ console.log("[capstan] TypeScript compilation complete.");
878
+ // Step 2: Load app config
832
879
  let appName = "capstan-app";
833
880
  let appDescription;
834
881
  try {
@@ -836,17 +883,42 @@ async function runBuild() {
836
883
  if (configPath) {
837
884
  const configUrl = pathToFileURL(configPath).href;
838
885
  const mod = (await import(configUrl));
839
- if (mod.default?.name)
886
+ if (mod.default?.app?.name)
887
+ appName = mod.default.app.name;
888
+ else if (mod.default?.name)
840
889
  appName = mod.default.name;
841
- if (mod.default?.description)
890
+ if (mod.default?.app?.description)
891
+ appDescription = mod.default.app.description;
892
+ else if (mod.default?.description)
842
893
  appDescription = mod.default.description;
843
894
  }
844
895
  }
845
896
  catch {
846
897
  // Config is optional.
847
898
  }
848
- const routesDir = join(process.cwd(), "app", "routes");
899
+ // Step 3: Scan routes and build manifest with compiled paths
900
+ const routesDir = join(cwd, "app", "routes");
901
+ console.log("[capstan] Scanning routes...");
849
902
  const manifest = await scanRoutes(routesDir);
903
+ // Rewrite file paths from source .ts/.tsx to compiled .js/.jsx
904
+ // and make them relative to the dist directory
905
+ const rewrittenManifest = {
906
+ ...manifest,
907
+ rootDir: join(distDir, "app", "routes"),
908
+ routes: manifest.routes.map((route) => ({
909
+ ...route,
910
+ filePath: route.filePath
911
+ .replace(cwd, distDir)
912
+ .replace(/\.tsx$/, ".jsx")
913
+ .replace(/\.ts$/, ".js"),
914
+ layouts: route.layouts.map((l) => l.replace(cwd, distDir).replace(/\.tsx$/, ".jsx").replace(/\.ts$/, ".js")),
915
+ middlewares: route.middlewares.map((m) => m.replace(cwd, distDir).replace(/\.tsx$/, ".jsx").replace(/\.ts$/, ".js")),
916
+ })),
917
+ };
918
+ await mkdir(distDir, { recursive: true });
919
+ await writeFile(join(distDir, "_capstan_manifest.json"), JSON.stringify(rewrittenManifest, null, 2));
920
+ console.log("[capstan] Generated dist/_capstan_manifest.json");
921
+ // Step 4: Generate agent-manifest.json and openapi.json
850
922
  const registryEntries = manifest.routes
851
923
  .filter((r) => r.type === "api")
852
924
  .flatMap((r) => {
@@ -859,24 +931,544 @@ async function runBuild() {
859
931
  const agentConfig = { name: appName, ...(appDescription ? { description: appDescription } : {}) };
860
932
  const agentManifest = generateAgentManifest(agentConfig, registryEntries);
861
933
  const openApiSpec = generateOpenApiSpec(agentConfig, registryEntries);
862
- const distDir = join(process.cwd(), "dist");
863
- await mkdir(distDir, { recursive: true });
864
934
  await writeFile(join(distDir, "agent-manifest.json"), JSON.stringify(agentManifest, null, 2));
865
935
  await writeFile(join(distDir, "openapi.json"), JSON.stringify(openApiSpec, null, 2));
866
- console.log("Generated dist/agent-manifest.json");
867
- console.log("Generated dist/openapi.json");
868
- console.log("Build complete.");
936
+ console.log("[capstan] Generated dist/agent-manifest.json");
937
+ console.log("[capstan] Generated dist/openapi.json");
938
+ // Step 5: Copy public/ assets to dist/public/ (if the directory exists)
939
+ const publicDir = join(cwd, "app", "public");
940
+ try {
941
+ await access(publicDir);
942
+ await cp(publicDir, join(distDir, "public"), { recursive: true });
943
+ console.log("[capstan] Copied app/public/ to dist/public/");
944
+ }
945
+ catch {
946
+ // No public directory — skip.
947
+ }
948
+ // Step 6: Generate the production server entry file
949
+ const serverEntry = `import { createServer } from "node:http";
950
+ import { readFileSync, existsSync } from "node:fs";
951
+ import { join, resolve } from "node:path";
952
+ import { pathToFileURL } from "node:url";
953
+ import { Hono } from "hono";
954
+ import { cors } from "hono/cors";
955
+ import { serveStatic } from "@hono/node-server/serve-static";
956
+
957
+ const cwd = process.cwd();
958
+ const distDir = resolve(cwd, "dist");
959
+
960
+ // Read the pre-built route manifest
961
+ const manifestPath = join(distDir, "_capstan_manifest.json");
962
+ const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
963
+
964
+ const port = parseInt(process.env.CAPSTAN_PORT ?? process.env.PORT ?? "3000", 10);
965
+ const host = process.env.CAPSTAN_HOST ?? "0.0.0.0";
966
+ const MAX_BODY_SIZE = parseInt(process.env.CAPSTAN_MAX_BODY_SIZE ?? "1048576", 10);
967
+
968
+ async function main() {
969
+ const app = new Hono();
970
+ app.use("*", cors());
971
+
972
+ // --- Auth middleware -------------------------------------------------------
973
+ // Load config from the compiled capstan.config.js to obtain auth settings.
974
+ // If auth config exists, create a real auth resolver via @zauso-ai/capstan-auth
975
+ // so that session cookies and API keys are verified on every request.
976
+
977
+ let resolveAuth = null;
978
+ let appConfig = null;
979
+
980
+ const configCandidates = [
981
+ join(distDir, "capstan.config.js"),
982
+ join(cwd, "capstan.config.js"),
983
+ ];
984
+
985
+ for (const candidate of configCandidates) {
986
+ if (existsSync(candidate)) {
987
+ try {
988
+ const configUrl = pathToFileURL(candidate).href;
989
+ const configMod = await import(configUrl);
990
+ appConfig = configMod.default ?? configMod;
991
+ break;
992
+ } catch (err) {
993
+ console.warn("[capstan] Failed to load config from " + candidate + ":", err?.message ?? err);
994
+ }
995
+ }
996
+ }
997
+
998
+ // Derive auth config: support both CapstanConfig shape (auth.session) and
999
+ // the flat AuthConfig shape ({ session: { secret } }).
1000
+ const authCfg = appConfig?.auth ?? null;
1001
+ const authSessionConfig = authCfg?.session ?? null;
1002
+
1003
+ if (authSessionConfig && authSessionConfig.secret) {
1004
+ try {
1005
+ const authPkg = await import("@zauso-ai/capstan-auth");
1006
+ resolveAuth = authPkg.createAuthMiddleware(
1007
+ {
1008
+ session: {
1009
+ secret: authSessionConfig.secret,
1010
+ maxAge: authSessionConfig.maxAge,
1011
+ },
1012
+ apiKeys: authCfg.apiKeys ?? undefined,
1013
+ },
1014
+ {
1015
+ findAgentByKeyPrefix: appConfig?.findAgentByKeyPrefix ?? undefined,
1016
+ },
1017
+ );
1018
+ console.log("[capstan] Auth middleware enabled (session + API key verification).");
1019
+ } catch (err) {
1020
+ console.warn("[capstan] @zauso-ai/capstan-auth not available. Auth middleware disabled.", err?.message ?? "");
1021
+ }
1022
+ } else {
1023
+ console.warn("[capstan] No auth config found. All requests will be treated as anonymous.");
1024
+ }
1025
+
1026
+ // Hono middleware: resolve auth for every request and store on context.
1027
+ app.use("*", async (c, next) => {
1028
+ if (resolveAuth) {
1029
+ try {
1030
+ const authCtx = await resolveAuth(c.req.raw);
1031
+ c.set("capstanAuth", authCtx);
1032
+ } catch (err) {
1033
+ console.error("[capstan] Auth resolution error:", err?.message ?? err);
1034
+ c.set("capstanAuth", { isAuthenticated: false, type: "anonymous", permissions: [] });
1035
+ }
1036
+ }
1037
+ await next();
1038
+ });
1039
+
1040
+ // Helper: build a CapstanContext from Hono context, using resolved auth.
1041
+ function buildCtx(c) {
1042
+ const authFromMiddleware = c.get("capstanAuth");
1043
+ return {
1044
+ auth: authFromMiddleware ?? { isAuthenticated: false, type: "anonymous", permissions: [] },
1045
+ request: c.req.raw,
1046
+ env: process.env,
1047
+ honoCtx: c,
1048
+ };
1049
+ }
1050
+
1051
+ // --- Policy loading --------------------------------------------------------
1052
+ // Load user-defined policies from dist/app/policies/index.js (if present).
1053
+ // This mirrors enforcePolicies from @zauso-ai/capstan-core so that custom
1054
+ // policies (beyond "requireAuth") are enforced in production.
1055
+
1056
+ const policyRegistry = new Map();
1057
+ let enforcePoliciesFn = null;
1058
+
1059
+ try {
1060
+ const corePkg = await import("@zauso-ai/capstan-core");
1061
+ if (typeof corePkg.enforcePolicies === "function") {
1062
+ enforcePoliciesFn = corePkg.enforcePolicies;
1063
+ }
1064
+ } catch {
1065
+ // @zauso-ai/capstan-core not available.
1066
+ }
1067
+
1068
+ const policiesIndexPath = join(distDir, "app", "policies", "index.js");
1069
+ if (existsSync(policiesIndexPath)) {
1070
+ try {
1071
+ const policiesMod = await import(pathToFileURL(policiesIndexPath).href);
1072
+ const exports = policiesMod.default ?? policiesMod;
1073
+ if (exports && typeof exports === "object") {
1074
+ for (const [key, value] of Object.entries(exports)) {
1075
+ if (value && typeof value === "object" && "check" in value) {
1076
+ policyRegistry.set(value.key ?? key, value);
1077
+ }
1078
+ }
1079
+ }
1080
+ if (policyRegistry.size > 0) {
1081
+ console.log("[capstan] Loaded " + policyRegistry.size + " custom policies from app/policies/index.js");
1082
+ }
1083
+ } catch (err) {
1084
+ console.warn("[capstan] Failed to load policies from " + policiesIndexPath + ":", err?.message ?? err);
1085
+ }
1086
+ }
1087
+
1088
+ // Built-in requireAuth policy used when no custom override exists.
1089
+ const builtinRequireAuth = {
1090
+ key: "requireAuth",
1091
+ title: "Require Authentication",
1092
+ effect: "deny",
1093
+ check: async ({ ctx }) => {
1094
+ if (ctx.auth.isAuthenticated) return { effect: "allow" };
1095
+ return { effect: "deny", reason: "Authentication required" };
1096
+ },
1097
+ };
1098
+
1099
+ // Enforce all policies for a handler. Returns null if allowed, or a Response
1100
+ // if the request should be blocked/deferred.
1101
+ async function enforceHandlerPolicy(c, ctx, handler, input) {
1102
+ if (!handler.policy) return null;
1103
+
1104
+ const policyName = handler.policy;
1105
+ const policyDef = policyRegistry.get(policyName)
1106
+ ?? (policyName === "requireAuth" ? builtinRequireAuth : null);
1107
+
1108
+ if (!policyDef) {
1109
+ // Unknown policy in production: deny by default (fail closed).
1110
+ console.warn("[capstan] Unknown policy: " + policyName + ". Denying request (fail closed).");
1111
+ return c.json({ error: "Forbidden", reason: "Unknown policy: " + policyName }, 403);
1112
+ }
1113
+
1114
+ let result;
1115
+ if (enforcePoliciesFn) {
1116
+ result = await enforcePoliciesFn([policyDef], ctx, input);
1117
+ } else {
1118
+ result = await policyDef.check({ ctx, input });
1119
+ }
1120
+
1121
+ if (result.effect === "deny") {
1122
+ return c.json(
1123
+ { error: "Forbidden", reason: result.reason ?? "Policy denied", policy: policyName },
1124
+ 403,
1125
+ );
1126
+ }
1127
+
1128
+ if (result.effect === "approve") {
1129
+ try {
1130
+ const corePkg = await import("@zauso-ai/capstan-core");
1131
+ if (typeof corePkg.createApproval === "function") {
1132
+ const approval = corePkg.createApproval({
1133
+ method: c.req.method,
1134
+ path: c.req.path,
1135
+ input,
1136
+ policy: policyName,
1137
+ reason: result.reason ?? "This action requires approval",
1138
+ });
1139
+ return c.json(
1140
+ {
1141
+ status: "approval_required",
1142
+ approvalId: approval.id,
1143
+ reason: result.reason ?? "This action requires approval",
1144
+ pollUrl: "/capstan/approvals/" + approval.id,
1145
+ },
1146
+ 202,
1147
+ );
1148
+ }
1149
+ } catch {}
1150
+ return c.json(
1151
+ { error: "Forbidden", reason: "Approval required but approval system unavailable", policy: policyName },
1152
+ 403,
1153
+ );
1154
+ }
1155
+
1156
+ return null;
1157
+ }
1158
+
1159
+ // Serve static assets from dist/public/ if present
1160
+ try {
1161
+ app.use("/public/*", serveStatic({ root: distDir }));
1162
+ } catch {
1163
+ // serveStatic not available or dist/public/ does not exist
1164
+ }
1165
+
1166
+ // Route metadata for framework endpoints
1167
+ const routeRegistry = [];
1168
+
1169
+ let apiRouteCount = 0;
1170
+ let pageRouteCount = 0;
1171
+
1172
+ // Register API routes from the manifest
1173
+ for (const route of manifest.routes) {
1174
+ if (route.type !== "api") continue;
1175
+
1176
+ let handlers;
1177
+ try {
1178
+ const moduleUrl = pathToFileURL(route.filePath).href;
1179
+ handlers = await import(moduleUrl);
1180
+ } catch (err) {
1181
+ console.error("[capstan] Failed to load API route " + route.filePath + ":", err?.message ?? err);
1182
+ continue;
1183
+ }
1184
+
1185
+ const methods = ["GET", "POST", "PUT", "DELETE", "PATCH"];
1186
+ for (const method of methods) {
1187
+ const handler = handlers[method];
1188
+ if (handler === undefined) continue;
1189
+
1190
+ apiRouteCount++;
1191
+
1192
+ const isApiDef = handler !== null && typeof handler === "object" && "handler" in handler && typeof handler.handler === "function";
1193
+ const meta = { method, path: route.urlPattern };
1194
+ if (isApiDef && handler.description) meta.description = handler.description;
1195
+ if (isApiDef && handler.capability) meta.capability = handler.capability;
1196
+ routeRegistry.push(meta);
1197
+
1198
+ const honoMethod = method.toLowerCase();
1199
+ app[honoMethod](route.urlPattern, async (c) => {
1200
+ let input;
1201
+ try {
1202
+ if (method === "GET") {
1203
+ input = Object.fromEntries(new URL(c.req.url).searchParams);
1204
+ } else {
1205
+ const ct = c.req.header("content-type") ?? "";
1206
+ if (ct.includes("application/json")) {
1207
+ input = await c.req.json();
1208
+ } else {
1209
+ input = {};
1210
+ }
1211
+ }
1212
+ } catch {
1213
+ input = {};
1214
+ }
1215
+
1216
+ const ctx = buildCtx(c);
1217
+
1218
+ try {
1219
+ if (isApiDef) {
1220
+ // Policy enforcement using auth-resolved ctx and loaded policies.
1221
+ const policyResponse = await enforceHandlerPolicy(c, ctx, handler, input);
1222
+ if (policyResponse !== null) return policyResponse;
1223
+
1224
+ const result = await handler.handler({ input, ctx });
1225
+ return c.json(result);
1226
+ }
1227
+ if (typeof handler === "function") {
1228
+ const result = await handler({ input, ctx });
1229
+ return c.json(result);
1230
+ }
1231
+ return c.json({ error: "Invalid handler export" }, 500);
1232
+ } catch (err) {
1233
+ if (err && typeof err === "object" && "issues" in err && Array.isArray(err.issues)) {
1234
+ return c.json({ error: "Validation Error", issues: err.issues }, 400);
1235
+ }
1236
+ console.error("[capstan] Request error:", err);
1237
+ const message = "Internal Server Error";
1238
+ return c.json({ error: message }, 500);
1239
+ }
1240
+ });
1241
+ }
1242
+ }
1243
+
1244
+ // Register page routes from the manifest
1245
+ for (const route of manifest.routes) {
1246
+ if (route.type !== "page") continue;
1247
+
1248
+ let pageModule;
1249
+ try {
1250
+ const moduleUrl = pathToFileURL(route.filePath).href;
1251
+ pageModule = await import(moduleUrl);
1252
+ } catch (err) {
1253
+ console.error("[capstan] Failed to load page " + route.filePath + ":", err?.message ?? err);
1254
+ continue;
1255
+ }
1256
+
1257
+ if (!pageModule.default) continue;
1258
+ pageRouteCount++;
1259
+
1260
+ app.get(route.urlPattern, async (c) => {
1261
+ const params = {};
1262
+ for (const name of route.params) {
1263
+ const value = c.req.param(name);
1264
+ if (value !== undefined) params[name] = value;
1265
+ }
1266
+
1267
+ const ctx = buildCtx(c);
1268
+
1269
+ let loaderData = null;
1270
+ if (typeof pageModule.loader === "function") {
1271
+ try {
1272
+ loaderData = await pageModule.loader({
1273
+ params,
1274
+ request: c.req.raw,
1275
+ ctx: { auth: ctx.auth },
1276
+ fetch: { get: async () => null, post: async () => null, put: async () => null, delete: async () => null },
1277
+ });
1278
+ } catch (err) {
1279
+ console.error("[capstan] Loader error in " + route.filePath + ":", err?.message ?? err);
1280
+ }
1281
+ }
1282
+
1283
+ // Attempt SSR via @zauso-ai/capstan-react
1284
+ try {
1285
+ const reactPkg = await import("@zauso-ai/capstan-react");
1286
+ const result = await reactPkg.renderPage({
1287
+ pageModule: { default: pageModule.default, loader: pageModule.loader },
1288
+ layouts: [],
1289
+ params,
1290
+ request: c.req.raw,
1291
+ loaderArgs: {
1292
+ params,
1293
+ request: c.req.raw,
1294
+ ctx: { auth: ctx.auth },
1295
+ fetch: { get: async () => null, post: async () => null, put: async () => null, delete: async () => null },
1296
+ },
1297
+ });
1298
+ return c.html(result.html, result.statusCode);
1299
+ } catch {
1300
+ // Fallback minimal HTML
1301
+ const html = \`<!DOCTYPE html>
1302
+ <html lang="en">
1303
+ <head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${appName.replace(/"/g, "&quot;")}</title></head>
1304
+ <body>
1305
+ <div id="capstan-root"><p>Page: \${route.urlPattern}</p></div>
1306
+ <script>window.__CAPSTAN_DATA__ = \${JSON.stringify({ loaderData, params }).replace(/</g, '\\\\u003c').replace(/>/g, '\\\\u003e')}</script>
1307
+ </body>
1308
+ </html>\`;
1309
+ return c.html(html);
1310
+ }
1311
+ });
1312
+ }
1313
+
1314
+ // Read pre-built agent-manifest.json and openapi.json
1315
+ let agentManifestJson = null;
1316
+ let openApiJson = null;
1317
+ try { agentManifestJson = JSON.parse(readFileSync(join(distDir, "agent-manifest.json"), "utf-8")); } catch {}
1318
+ try { openApiJson = JSON.parse(readFileSync(join(distDir, "openapi.json"), "utf-8")); } catch {}
1319
+
1320
+ // Framework endpoints
1321
+ app.get("/.well-known/capstan.json", (c) => {
1322
+ if (agentManifestJson) return c.json(agentManifestJson);
1323
+ return c.json({ error: "Agent manifest not found" }, 404);
1324
+ });
1325
+ app.get("/openapi.json", (c) => {
1326
+ if (openApiJson) return c.json(openApiJson);
1327
+ return c.json({ error: "OpenAPI spec not found" }, 404);
1328
+ });
1329
+ app.get("/health", (c) => {
1330
+ return c.json({ status: "ok", uptime: process.uptime(), timestamp: new Date().toISOString() });
1331
+ });
1332
+
1333
+ // Approval management endpoints (if @zauso-ai/capstan-core is available)
1334
+ try {
1335
+ const corePkg = await import("@zauso-ai/capstan-core");
1336
+ if (typeof corePkg.listApprovals === "function") {
1337
+ app.get("/capstan/approvals", (c) => {
1338
+ const status = new URL(c.req.url).searchParams.get("status") ?? undefined;
1339
+ const approvals = corePkg.listApprovals(status);
1340
+ return c.json({ approvals });
1341
+ });
1342
+ app.get("/capstan/approvals/:id", (c) => {
1343
+ const approval = corePkg.getApproval(c.req.param("id"));
1344
+ if (!approval) return c.json({ error: "Approval not found" }, 404);
1345
+ return c.json(approval);
1346
+ });
1347
+ app.post("/capstan/approvals/:id/resolve", async (c) => {
1348
+ let body;
1349
+ try { body = await c.req.json(); } catch { body = {}; }
1350
+ const decision = body.decision === "approved" ? "approved" : "denied";
1351
+ const approval = corePkg.resolveApproval(c.req.param("id"), decision, body.resolvedBy);
1352
+ if (!approval) return c.json({ error: "Approval not found" }, 404);
1353
+ return c.json(approval);
1354
+ });
1355
+ }
1356
+ } catch {
1357
+ // Approval endpoints not available.
1358
+ }
1359
+
1360
+ // Start HTTP server
1361
+ const server = createServer(async (req, res) => {
1362
+ try {
1363
+ const url = new URL(req.url ?? "/", "http://" + (req.headers.host ?? host + ":" + port));
1364
+ const headers = new Headers();
1365
+ for (const [key, value] of Object.entries(req.headers)) {
1366
+ if (value === undefined) continue;
1367
+ if (Array.isArray(value)) { for (const v of value) headers.append(key, v); }
1368
+ else headers.set(key, value);
1369
+ }
1370
+
1371
+ const hasBody = req.method !== "GET" && req.method !== "HEAD";
1372
+ let body;
1373
+ if (hasBody) {
1374
+ body = await new Promise((resolve, reject) => {
1375
+ const chunks = [];
1376
+ let received = 0;
1377
+ req.on("data", (c) => {
1378
+ received += c.length;
1379
+ if (received > MAX_BODY_SIZE) {
1380
+ req.destroy();
1381
+ const err = new Error("Request body exceeds maximum allowed size of " + MAX_BODY_SIZE + " bytes");
1382
+ err.statusCode = 413;
1383
+ reject(err);
1384
+ return;
1385
+ }
1386
+ chunks.push(c);
1387
+ });
1388
+ req.on("error", reject);
1389
+ req.on("end", () => {
1390
+ const raw = Buffer.concat(chunks).toString("utf-8");
1391
+ resolve(raw.length > 0 ? raw : undefined);
1392
+ });
1393
+ });
1394
+ }
1395
+
1396
+ const init = { method: req.method ?? "GET", headers };
1397
+ if (body !== undefined) init.body = body;
1398
+
1399
+ const request = new Request(url.toString(), init);
1400
+ const response = await app.fetch(request);
1401
+
1402
+ res.writeHead(response.status, Object.fromEntries(response.headers.entries()));
1403
+ const responseBody = await response.text();
1404
+ res.end(responseBody);
1405
+ } catch (err) {
1406
+ if (err && err.statusCode === 413) {
1407
+ if (!res.headersSent) res.writeHead(413, { "Content-Type": "application/json" });
1408
+ res.end(JSON.stringify({ error: "Payload Too Large" }));
1409
+ return;
1410
+ }
1411
+ console.error("[capstan] Unhandled request error:", err);
1412
+ if (!res.headersSent) res.writeHead(500, { "Content-Type": "application/json" });
1413
+ res.end(JSON.stringify({ error: "Internal Server Error" }));
1414
+ }
1415
+ });
1416
+
1417
+ server.listen(port, host, () => {
1418
+ console.log("");
1419
+ console.log(" Capstan production server running");
1420
+ console.log(" Local: http://" + (host === "0.0.0.0" ? "localhost" : host) + ":" + port);
1421
+ console.log(" Routes: " + (apiRouteCount + pageRouteCount) + " total (" + apiRouteCount + " API, " + pageRouteCount + " pages)");
1422
+ if (resolveAuth) console.log(" Auth: enabled");
1423
+ else console.log(" Auth: disabled (no auth config)");
1424
+ if (policyRegistry.size > 0) console.log(" Policies: " + policyRegistry.size + " custom policies loaded");
1425
+ console.log("");
1426
+ });
1427
+ }
1428
+
1429
+ main().catch((err) => {
1430
+ console.error("[capstan] Fatal error starting production server:", err);
1431
+ process.exit(1);
1432
+ });
1433
+ `;
1434
+ await writeFile(join(distDir, "_capstan_server.js"), serverEntry);
1435
+ console.log("[capstan] Generated dist/_capstan_server.js");
1436
+ console.log("[capstan] Build complete.");
869
1437
  }
870
1438
  async function runStart(args) {
871
- const { execFile } = await import("node:child_process");
872
- const { promisify } = await import("node:util");
873
- const exec = promisify(execFile);
1439
+ const { spawn } = await import("node:child_process");
1440
+ const { access } = await import("node:fs/promises");
1441
+ const { join } = await import("node:path");
1442
+ const cwd = process.cwd();
1443
+ const serverEntry = join(cwd, "dist", "_capstan_server.js");
1444
+ // Verify the production build exists
1445
+ try {
1446
+ await access(serverEntry);
1447
+ }
1448
+ catch {
1449
+ console.error("[capstan] dist/_capstan_server.js not found.");
1450
+ console.error("[capstan] Run `capstan build` first to compile the project.");
1451
+ process.exitCode = 1;
1452
+ return;
1453
+ }
874
1454
  const port = readFlagValue(args, "--port") ?? "3000";
875
- console.log(`Starting production server on port ${port}...`);
876
- await exec("node", ["dist/index.js"], {
877
- cwd: process.cwd(),
878
- env: { ...process.env, PORT: port },
1455
+ const host = readFlagValue(args, "--host") ?? "0.0.0.0";
1456
+ const child = spawn(process.execPath, [serverEntry], {
1457
+ cwd,
1458
+ stdio: "inherit",
1459
+ env: {
1460
+ ...process.env,
1461
+ CAPSTAN_PORT: port,
1462
+ CAPSTAN_HOST: host,
1463
+ },
1464
+ });
1465
+ child.on("exit", (code) => {
1466
+ process.exit(code ?? 0);
879
1467
  });
1468
+ // Forward SIGINT / SIGTERM to child
1469
+ for (const sig of ["SIGINT", "SIGTERM"]) {
1470
+ process.on(sig, () => child.kill(sig));
1471
+ }
880
1472
  }
881
1473
  // ---------------------------------------------------------------------------
882
1474
  // Database commands
@@ -926,10 +1518,31 @@ async function runDbMigrate(args) {
926
1518
  await writeFile(join(migrationsDir, filename), content);
927
1519
  console.log(`Created migration: app/migrations/${filename}`);
928
1520
  }
1521
+ async function loadDbConfig() {
1522
+ let provider = "sqlite";
1523
+ let url = join(process.cwd(), "app", "data", "app.db");
1524
+ const configPath = await resolveConfig();
1525
+ if (configPath) {
1526
+ try {
1527
+ const configUrl = pathToFileURL(configPath).href;
1528
+ const configMod = (await import(configUrl));
1529
+ if (configMod.default?.database?.provider) {
1530
+ provider = configMod.default.database.provider;
1531
+ }
1532
+ if (configMod.default?.database?.url) {
1533
+ url = configMod.default.database.url;
1534
+ }
1535
+ }
1536
+ catch {
1537
+ // Config load failed — use defaults.
1538
+ }
1539
+ }
1540
+ return { provider, url };
1541
+ }
929
1542
  async function runDbPush() {
930
- const { readdir, readFile: readMigrationFile } = await import("node:fs/promises");
931
- const { join } = await import("node:path");
932
- const { createDatabase } = await import("@zauso-ai/capstan-db");
1543
+ const { readdir, readFile: readMigrationFile, mkdir: mkdirFs } = await import("node:fs/promises");
1544
+ const { dirname } = await import("node:path");
1545
+ const { createDatabase, applyTrackedMigrations } = await import("@zauso-ai/capstan-db");
933
1546
  const migrationsDir = join(process.cwd(), "app", "migrations");
934
1547
  let files;
935
1548
  try {
@@ -940,29 +1553,37 @@ async function runDbPush() {
940
1553
  return;
941
1554
  }
942
1555
  if (files.length === 0) {
943
- console.log("No pending migrations.");
1556
+ console.log("No migration files found.");
944
1557
  return;
945
1558
  }
946
- const db = createDatabase({ provider: "sqlite", url: join(process.cwd(), "app", "data", "app.db") });
1559
+ const { provider, url } = await loadDbConfig();
1560
+ // Ensure directory exists for SQLite file-based databases
1561
+ if (provider === "sqlite" && url !== ":memory:") {
1562
+ await mkdirFs(dirname(url), { recursive: true });
1563
+ }
1564
+ const dbInstance = createDatabase({ provider, url });
1565
+ // Access the underlying driver client from the Drizzle instance
1566
+ const client = dbInstance.db.$client;
1567
+ // Load all migration file contents
1568
+ const migrations = [];
947
1569
  for (const file of files) {
948
1570
  const sql = await readMigrationFile(join(migrationsDir, file), "utf8");
949
- const statements = sql
950
- .split(";")
951
- .map((s) => s.trim())
952
- .filter((s) => s.length > 0 && !s.startsWith("--"));
953
- if (statements.length > 0) {
954
- const client = db.$client;
955
- for (const stmt of statements) {
956
- client.exec(stmt);
957
- }
1571
+ migrations.push({ name: file, sql });
1572
+ }
1573
+ const executed = applyTrackedMigrations(client, migrations, provider);
1574
+ if (executed.length === 0) {
1575
+ console.log("No pending migrations. Database is up to date.");
1576
+ }
1577
+ else {
1578
+ for (const name of executed) {
1579
+ console.log(`Applied: ${name}`);
958
1580
  }
959
- console.log(`Applied: ${file}`);
1581
+ console.log(`\n${executed.length} migration(s) applied.`);
960
1582
  }
961
- console.log("All migrations applied.");
962
1583
  }
963
1584
  async function runDbStatus() {
964
1585
  const { readdir } = await import("node:fs/promises");
965
- const { join } = await import("node:path");
1586
+ const { createDatabase, getMigrationStatus } = await import("@zauso-ai/capstan-db");
966
1587
  const migrationsDir = join(process.cwd(), "app", "migrations");
967
1588
  let files;
968
1589
  try {
@@ -976,9 +1597,37 @@ async function runDbStatus() {
976
1597
  console.log("No migration files found.");
977
1598
  return;
978
1599
  }
979
- console.log(`Found ${files.length} migration(s):`);
980
- for (const file of files) {
981
- console.log(` ${file}`);
1600
+ const { provider, url } = await loadDbConfig();
1601
+ let status;
1602
+ try {
1603
+ const dbInstance = createDatabase({ provider, url });
1604
+ const client = dbInstance.db.$client;
1605
+ status = getMigrationStatus(client, files, provider);
1606
+ }
1607
+ catch {
1608
+ // Database may not exist yet — treat everything as pending
1609
+ status = {
1610
+ applied: [],
1611
+ pending: files,
1612
+ };
1613
+ }
1614
+ console.log(`Migration status (${provider}):\n`);
1615
+ if (status.applied.length > 0) {
1616
+ console.log(`Applied (${status.applied.length}):`);
1617
+ for (const m of status.applied) {
1618
+ console.log(` ✓ ${m.name} (${m.appliedAt})`);
1619
+ }
1620
+ }
1621
+ if (status.pending.length > 0) {
1622
+ if (status.applied.length > 0)
1623
+ console.log("");
1624
+ console.log(`Pending (${status.pending.length}):`);
1625
+ for (const name of status.pending) {
1626
+ console.log(` • ${name}`);
1627
+ }
1628
+ }
1629
+ if (status.applied.length > 0 && status.pending.length === 0) {
1630
+ console.log("\nDatabase is up to date.");
982
1631
  }
983
1632
  }
984
1633
  // ---------------------------------------------------------------------------
@@ -1171,25 +1820,26 @@ async function runHarnessMutation(args, mode) {
1171
1820
  process.exitCode = 1;
1172
1821
  return;
1173
1822
  }
1823
+ const harness = await requireLegacy("@zauso-ai/capstan-harness");
1174
1824
  const root = resolve(process.cwd(), appDir);
1175
1825
  const run = mode === "pause"
1176
- ? await pauseHarnessRun(root, runId, { ...(note ? { note } : {}) })
1826
+ ? await harness.pauseHarnessRun(root, runId, { ...(note ? { note } : {}) })
1177
1827
  : mode === "resume"
1178
- ? await resumeHarnessRun(root, runId, { ...(note ? { note } : {}) })
1828
+ ? await harness.resumeHarnessRun(root, runId, { ...(note ? { note } : {}) })
1179
1829
  : mode === "request-approval"
1180
- ? await requestHarnessApproval(root, runId, { ...(note ? { note } : {}) })
1830
+ ? await harness.requestHarnessApproval(root, runId, { ...(note ? { note } : {}) })
1181
1831
  : mode === "approve"
1182
- ? await approveHarnessRun(root, runId, { ...(note ? { note } : {}) })
1832
+ ? await harness.approveHarnessRun(root, runId, { ...(note ? { note } : {}) })
1183
1833
  : mode === "request-input"
1184
- ? await requestHarnessInput(root, runId, { ...(note ? { note } : {}) })
1834
+ ? await harness.requestHarnessInput(root, runId, { ...(note ? { note } : {}) })
1185
1835
  : mode === "cancel"
1186
- ? await cancelHarnessRun(root, runId, { ...(note ? { note } : {}) })
1187
- : await retryHarnessRun(root, runId, { ...(note ? { note } : {}) });
1836
+ ? await harness.cancelHarnessRun(root, runId, { ...(note ? { note } : {}) })
1837
+ : await harness.retryHarnessRun(root, runId, { ...(note ? { note } : {}) });
1188
1838
  if (asJson) {
1189
1839
  console.log(JSON.stringify(run, null, 2));
1190
1840
  }
1191
1841
  else {
1192
- process.stdout.write(renderHarnessRunText(run));
1842
+ process.stdout.write(harness.renderHarnessRunText(run));
1193
1843
  }
1194
1844
  }
1195
1845
  async function loadJsonFile(target) {
@@ -1241,6 +1891,7 @@ async function loadBrief(target) {
1241
1891
  async function compileBriefWithPackDefinitions(brief, options = {}) {
1242
1892
  const externalPackDefinitions = await loadExternalPackDefinitions(options.packRegistryPath);
1243
1893
  const packDefinitions = mergeExtraPackDefinitions(options.packDefinitions ?? [], externalPackDefinitions);
1894
+ const { compileCapstanBrief } = await requireLegacy("@zauso-ai/capstan-brief");
1244
1895
  const compiled = compileCapstanBrief(brief, {
1245
1896
  packDefinitions
1246
1897
  });
@@ -1267,7 +1918,8 @@ function normalizePackRegistryExport(value) {
1267
1918
  }
1268
1919
  throw new Error("Pack registry modules must export an array of pack definitions.");
1269
1920
  }
1270
- function applyGraphWithPackDefinitions(graph, extraPackDefinitions) {
1921
+ async function applyGraphWithPackDefinitions(graph, extraPackDefinitions) {
1922
+ const { applyAppGraphPacks, applyBuiltinAppGraphPacks, listBuiltinGraphPacks, } = await requireLegacy("@zauso-ai/capstan-packs-core");
1271
1923
  if (!extraPackDefinitions.length) {
1272
1924
  return applyBuiltinAppGraphPacks(graph);
1273
1925
  }
@@ -1456,8 +2108,8 @@ function printHelp() {
1456
2108
  Commands:
1457
2109
  capstan dev [--port 3000] [--host localhost]
1458
2110
  Start the development server with HMR
1459
- capstan build Build the project and generate agent manifests
1460
- capstan start [--port 3000]
2111
+ capstan build Build the project (tsc + route manifest + server entry)
2112
+ capstan start [--port 3000] [--host 0.0.0.0]
1461
2113
  Start the production server from built output
1462
2114
 
1463
2115
  capstan add <model|api|page|policy> <name>