@zauso-ai/capstan-cli 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1519 -0
- package/dist/index.js.map +1 -0
- package/package.json +59 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1519 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
4
|
+
import { join, resolve } from "node:path";
|
|
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";
|
|
13
|
+
async function main() {
|
|
14
|
+
const [command, ...args] = process.argv.slice(2);
|
|
15
|
+
switch (command) {
|
|
16
|
+
case "brief:check":
|
|
17
|
+
await runBriefCheck(args);
|
|
18
|
+
return;
|
|
19
|
+
case "brief:inspect":
|
|
20
|
+
await runBriefInspect(args);
|
|
21
|
+
return;
|
|
22
|
+
case "brief:graph":
|
|
23
|
+
await runBriefGraph(args);
|
|
24
|
+
return;
|
|
25
|
+
case "brief:scaffold":
|
|
26
|
+
await runBriefScaffold(args);
|
|
27
|
+
return;
|
|
28
|
+
case "graph:check":
|
|
29
|
+
await runGraphCheck(args);
|
|
30
|
+
return;
|
|
31
|
+
case "graph:scaffold":
|
|
32
|
+
await runGraphScaffold(args);
|
|
33
|
+
return;
|
|
34
|
+
case "graph:inspect":
|
|
35
|
+
await runGraphInspect(args);
|
|
36
|
+
return;
|
|
37
|
+
case "graph:diff":
|
|
38
|
+
await runGraphDiff(args);
|
|
39
|
+
return;
|
|
40
|
+
case "verify":
|
|
41
|
+
await runVerify(args, args.includes("--json"));
|
|
42
|
+
return;
|
|
43
|
+
case "release:plan":
|
|
44
|
+
await runReleasePlan(args);
|
|
45
|
+
return;
|
|
46
|
+
case "release:run":
|
|
47
|
+
await runReleaseRun(args);
|
|
48
|
+
return;
|
|
49
|
+
case "release:history":
|
|
50
|
+
await runReleaseHistory(args);
|
|
51
|
+
return;
|
|
52
|
+
case "release:rollback":
|
|
53
|
+
await runReleaseRollback(args);
|
|
54
|
+
return;
|
|
55
|
+
case "harness:start":
|
|
56
|
+
await runHarnessStart(args);
|
|
57
|
+
return;
|
|
58
|
+
case "harness:get":
|
|
59
|
+
await runHarnessGet(args);
|
|
60
|
+
return;
|
|
61
|
+
case "harness:list":
|
|
62
|
+
await runHarnessList(args);
|
|
63
|
+
return;
|
|
64
|
+
case "harness:pause":
|
|
65
|
+
await runHarnessPause(args);
|
|
66
|
+
return;
|
|
67
|
+
case "harness:resume":
|
|
68
|
+
await runHarnessResume(args);
|
|
69
|
+
return;
|
|
70
|
+
case "harness:request-approval":
|
|
71
|
+
await runHarnessRequestApproval(args);
|
|
72
|
+
return;
|
|
73
|
+
case "harness:approve":
|
|
74
|
+
await runHarnessApprove(args);
|
|
75
|
+
return;
|
|
76
|
+
case "harness:request-input":
|
|
77
|
+
await runHarnessRequestInput(args);
|
|
78
|
+
return;
|
|
79
|
+
case "harness:provide-input":
|
|
80
|
+
await runHarnessProvideInput(args);
|
|
81
|
+
return;
|
|
82
|
+
case "harness:complete":
|
|
83
|
+
await runHarnessComplete(args);
|
|
84
|
+
return;
|
|
85
|
+
case "harness:fail":
|
|
86
|
+
await runHarnessFail(args);
|
|
87
|
+
return;
|
|
88
|
+
case "harness:cancel":
|
|
89
|
+
await runHarnessCancel(args);
|
|
90
|
+
return;
|
|
91
|
+
case "harness:retry":
|
|
92
|
+
await runHarnessRetry(args);
|
|
93
|
+
return;
|
|
94
|
+
case "harness:events":
|
|
95
|
+
await runHarnessEvents(args);
|
|
96
|
+
return;
|
|
97
|
+
case "harness:replay":
|
|
98
|
+
await runHarnessReplay(args);
|
|
99
|
+
return;
|
|
100
|
+
case "harness:compact":
|
|
101
|
+
await runHarnessCompact(args);
|
|
102
|
+
return;
|
|
103
|
+
case "harness:summary":
|
|
104
|
+
await runHarnessSummary(args);
|
|
105
|
+
return;
|
|
106
|
+
case "harness:summaries":
|
|
107
|
+
await runHarnessSummaries(args);
|
|
108
|
+
return;
|
|
109
|
+
case "harness:memory":
|
|
110
|
+
await runHarnessMemory(args);
|
|
111
|
+
return;
|
|
112
|
+
case "harness:memories":
|
|
113
|
+
await runHarnessMemories(args);
|
|
114
|
+
return;
|
|
115
|
+
case "dev":
|
|
116
|
+
await runDev(args);
|
|
117
|
+
return;
|
|
118
|
+
case "build":
|
|
119
|
+
await runBuild();
|
|
120
|
+
return;
|
|
121
|
+
case "start":
|
|
122
|
+
await runStart(args);
|
|
123
|
+
return;
|
|
124
|
+
case "db:migrate":
|
|
125
|
+
await runDbMigrate(args);
|
|
126
|
+
return;
|
|
127
|
+
case "db:push":
|
|
128
|
+
await runDbPush();
|
|
129
|
+
return;
|
|
130
|
+
case "db:status":
|
|
131
|
+
await runDbStatus();
|
|
132
|
+
return;
|
|
133
|
+
case "mcp":
|
|
134
|
+
await runMcp();
|
|
135
|
+
return;
|
|
136
|
+
case "agent:manifest":
|
|
137
|
+
await runAgentManifest();
|
|
138
|
+
return;
|
|
139
|
+
case "agent:openapi":
|
|
140
|
+
await runAgentOpenapi();
|
|
141
|
+
return;
|
|
142
|
+
case "add":
|
|
143
|
+
await runAdd(args);
|
|
144
|
+
return;
|
|
145
|
+
case "help":
|
|
146
|
+
case "--help":
|
|
147
|
+
case "-h":
|
|
148
|
+
case undefined:
|
|
149
|
+
printHelp();
|
|
150
|
+
return;
|
|
151
|
+
default:
|
|
152
|
+
console.error(`Unknown command: ${command}`);
|
|
153
|
+
printHelp();
|
|
154
|
+
process.exitCode = 1;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
async function runBriefCheck(args) {
|
|
158
|
+
const target = args[0];
|
|
159
|
+
const packRegistryPath = readFlagValue(args, "--pack-registry");
|
|
160
|
+
if (!target) {
|
|
161
|
+
console.error("Usage: capstan brief:check <path-to-brief> [--pack-registry <path>]");
|
|
162
|
+
process.exitCode = 1;
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
const loaded = await loadBrief(target);
|
|
166
|
+
const validation = validateCapstanBrief(loaded.brief);
|
|
167
|
+
if (!validation.ok) {
|
|
168
|
+
console.error("Capstan brief validation failed:");
|
|
169
|
+
for (const issue of validation.issues) {
|
|
170
|
+
console.error(`- ${issue.path}: ${issue.message}`);
|
|
171
|
+
}
|
|
172
|
+
process.exitCode = 1;
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
const graph = await compileBriefWithPackDefinitions(loaded.brief, {
|
|
176
|
+
packDefinitions: loaded.packDefinitions,
|
|
177
|
+
...(packRegistryPath ? { packRegistryPath } : {})
|
|
178
|
+
});
|
|
179
|
+
const result = validateAppGraph(graph);
|
|
180
|
+
if (result.ok) {
|
|
181
|
+
console.log("Capstan brief is valid.");
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
console.error("Compiled App Graph validation failed:");
|
|
185
|
+
for (const issue of result.issues) {
|
|
186
|
+
console.error(`- ${issue.path}: ${issue.message}`);
|
|
187
|
+
}
|
|
188
|
+
process.exitCode = 1;
|
|
189
|
+
}
|
|
190
|
+
async function runBriefInspect(args) {
|
|
191
|
+
const target = args[0];
|
|
192
|
+
const packRegistryPath = readFlagValue(args, "--pack-registry");
|
|
193
|
+
if (!target) {
|
|
194
|
+
console.error("Usage: capstan brief:inspect <path-to-brief> [--pack-registry <path>]");
|
|
195
|
+
process.exitCode = 1;
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
const loaded = await loadBrief(target);
|
|
199
|
+
const graph = await compileBriefWithPackDefinitions(loaded.brief, {
|
|
200
|
+
packDefinitions: loaded.packDefinitions,
|
|
201
|
+
...(packRegistryPath ? { packRegistryPath } : {})
|
|
202
|
+
});
|
|
203
|
+
console.log(JSON.stringify({
|
|
204
|
+
brief: {
|
|
205
|
+
summary: summarizeCapstanBrief(loaded.brief, {
|
|
206
|
+
packDefinitions: loaded.packDefinitions
|
|
207
|
+
}),
|
|
208
|
+
validation: validateCapstanBrief(loaded.brief)
|
|
209
|
+
},
|
|
210
|
+
graph: introspectAppGraph(graph)
|
|
211
|
+
}, null, 2));
|
|
212
|
+
}
|
|
213
|
+
async function runBriefGraph(args) {
|
|
214
|
+
const target = args[0];
|
|
215
|
+
const packRegistryPath = readFlagValue(args, "--pack-registry");
|
|
216
|
+
if (!target) {
|
|
217
|
+
console.error("Usage: capstan brief:graph <path-to-brief> [--pack-registry <path>]");
|
|
218
|
+
process.exitCode = 1;
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
const loaded = await loadBrief(target);
|
|
222
|
+
const graph = await compileBriefWithPackDefinitions(loaded.brief, {
|
|
223
|
+
packDefinitions: loaded.packDefinitions,
|
|
224
|
+
...(packRegistryPath ? { packRegistryPath } : {})
|
|
225
|
+
});
|
|
226
|
+
console.log(JSON.stringify(graph, null, 2));
|
|
227
|
+
}
|
|
228
|
+
async function runBriefScaffold(args) {
|
|
229
|
+
const target = args[0];
|
|
230
|
+
const outputDir = args[1];
|
|
231
|
+
const force = args.includes("--force");
|
|
232
|
+
const packRegistryPath = readFlagValue(args, "--pack-registry");
|
|
233
|
+
if (!target || !outputDir) {
|
|
234
|
+
console.error("Usage: capstan brief:scaffold <path-to-brief> <output-dir> [--force] [--pack-registry <path>]");
|
|
235
|
+
process.exitCode = 1;
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
const loaded = await loadBrief(target);
|
|
239
|
+
const graph = await compileBriefWithPackDefinitions(loaded.brief, {
|
|
240
|
+
packDefinitions: loaded.packDefinitions,
|
|
241
|
+
...(packRegistryPath ? { packRegistryPath } : {})
|
|
242
|
+
});
|
|
243
|
+
const result = await scaffoldAppGraph(graph, resolve(process.cwd(), outputDir), {
|
|
244
|
+
force
|
|
245
|
+
});
|
|
246
|
+
console.log(`Scaffolded ${result.files.length} files into ${result.rootDir}`);
|
|
247
|
+
}
|
|
248
|
+
async function runGraphCheck(args) {
|
|
249
|
+
const target = args[0];
|
|
250
|
+
const packRegistryPath = readFlagValue(args, "--pack-registry");
|
|
251
|
+
if (!target) {
|
|
252
|
+
console.error("Usage: capstan graph:check <path-to-graph> [--pack-registry <path>]");
|
|
253
|
+
process.exitCode = 1;
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
const graph = await loadGraph(target, {
|
|
257
|
+
...(packRegistryPath ? { packRegistryPath } : {})
|
|
258
|
+
});
|
|
259
|
+
const result = validateAppGraph(graph);
|
|
260
|
+
if (result.ok) {
|
|
261
|
+
console.log("App Graph is valid.");
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
console.error("App Graph validation failed:");
|
|
265
|
+
for (const issue of result.issues) {
|
|
266
|
+
console.error(`- ${issue.path}: ${issue.message}`);
|
|
267
|
+
}
|
|
268
|
+
process.exitCode = 1;
|
|
269
|
+
}
|
|
270
|
+
async function runGraphScaffold(args) {
|
|
271
|
+
const target = args[0];
|
|
272
|
+
const outputDir = args[1];
|
|
273
|
+
const force = args.includes("--force");
|
|
274
|
+
const packRegistryPath = readFlagValue(args, "--pack-registry");
|
|
275
|
+
if (!target || !outputDir) {
|
|
276
|
+
console.error("Usage: capstan graph:scaffold <path-to-graph> <output-dir> [--force] [--pack-registry <path>]");
|
|
277
|
+
process.exitCode = 1;
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
const graph = await loadGraph(target, {
|
|
281
|
+
...(packRegistryPath ? { packRegistryPath } : {})
|
|
282
|
+
});
|
|
283
|
+
const result = await scaffoldAppGraph(graph, resolve(process.cwd(), outputDir), {
|
|
284
|
+
force
|
|
285
|
+
});
|
|
286
|
+
console.log(`Scaffolded ${result.files.length} files into ${result.rootDir}`);
|
|
287
|
+
}
|
|
288
|
+
async function runGraphInspect(args) {
|
|
289
|
+
const target = args[0];
|
|
290
|
+
const packRegistryPath = readFlagValue(args, "--pack-registry");
|
|
291
|
+
if (!target) {
|
|
292
|
+
console.error("Usage: capstan graph:inspect <path-to-graph> [--pack-registry <path>]");
|
|
293
|
+
process.exitCode = 1;
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
const graph = await loadGraph(target, {
|
|
297
|
+
...(packRegistryPath ? { packRegistryPath } : {})
|
|
298
|
+
});
|
|
299
|
+
console.log(JSON.stringify(introspectAppGraph(graph), null, 2));
|
|
300
|
+
}
|
|
301
|
+
async function runGraphDiff(args) {
|
|
302
|
+
const beforePath = args[0];
|
|
303
|
+
const afterPath = args[1];
|
|
304
|
+
const packRegistryPath = readFlagValue(args, "--pack-registry");
|
|
305
|
+
if (!beforePath || !afterPath) {
|
|
306
|
+
console.error("Usage: capstan graph:diff <before-graph> <after-graph> [--pack-registry <path>]");
|
|
307
|
+
process.exitCode = 1;
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
const before = await loadGraph(beforePath, {
|
|
311
|
+
...(packRegistryPath ? { packRegistryPath } : {})
|
|
312
|
+
});
|
|
313
|
+
const after = await loadGraph(afterPath, {
|
|
314
|
+
...(packRegistryPath ? { packRegistryPath } : {})
|
|
315
|
+
});
|
|
316
|
+
console.log(JSON.stringify(diffAppGraphs(before, after), null, 2));
|
|
317
|
+
}
|
|
318
|
+
async function runVerify(args, asJson) {
|
|
319
|
+
// Detect which verification system to use:
|
|
320
|
+
// - If the target (or cwd) has app/routes/, use the new runtime verifier
|
|
321
|
+
// - If the target has capstan.app.json, use the old compiler-based verifier
|
|
322
|
+
// Strip --json from args to get the positional target
|
|
323
|
+
const positional = args.filter((a) => a !== "--json");
|
|
324
|
+
const target = positional[0];
|
|
325
|
+
// For the new runtime verifier, target is optional (defaults to cwd)
|
|
326
|
+
const appRoot = target ? resolve(process.cwd(), target) : process.cwd();
|
|
327
|
+
const hasAppRoutes = existsSync(join(appRoot, "app", "routes"));
|
|
328
|
+
const hasOldAppJson = existsSync(join(appRoot, "capstan.app.json"));
|
|
329
|
+
if (hasAppRoutes && !hasOldAppJson) {
|
|
330
|
+
// New runtime framework — use @zauso-ai/capstan-core verifier
|
|
331
|
+
const { verifyCapstanApp, renderRuntimeVerifyText } = await import("@zauso-ai/capstan-core");
|
|
332
|
+
const report = await verifyCapstanApp(appRoot);
|
|
333
|
+
if (asJson) {
|
|
334
|
+
console.log(JSON.stringify(report, null, 2));
|
|
335
|
+
}
|
|
336
|
+
else {
|
|
337
|
+
process.stdout.write(renderRuntimeVerifyText(report));
|
|
338
|
+
}
|
|
339
|
+
if (report.status === "failed") {
|
|
340
|
+
process.exitCode = 1;
|
|
341
|
+
}
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
if (hasOldAppJson) {
|
|
345
|
+
// Old compiler-based framework — use @zauso-ai/capstan-feedback verifier
|
|
346
|
+
if (!target) {
|
|
347
|
+
console.error("Usage: capstan verify <generated-app-dir> [--json]");
|
|
348
|
+
process.exitCode = 1;
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
const report = await verifyGeneratedApp(resolve(process.cwd(), target));
|
|
352
|
+
if (asJson) {
|
|
353
|
+
console.log(JSON.stringify(report, null, 2));
|
|
354
|
+
}
|
|
355
|
+
else {
|
|
356
|
+
process.stdout.write(renderVerifyReportText(report));
|
|
357
|
+
}
|
|
358
|
+
if (report.status === "failed") {
|
|
359
|
+
process.exitCode = 1;
|
|
360
|
+
}
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
// Neither detected — try to give helpful guidance
|
|
364
|
+
console.error("Could not detect project type.");
|
|
365
|
+
console.error(" - For runtime apps: ensure app/routes/ directory exists.");
|
|
366
|
+
console.error(" - For generated apps: ensure capstan.app.json exists.");
|
|
367
|
+
if (target) {
|
|
368
|
+
console.error(` Looked in: ${appRoot}`);
|
|
369
|
+
}
|
|
370
|
+
else {
|
|
371
|
+
console.error(` Looked in: ${process.cwd()}`);
|
|
372
|
+
console.error(" Tip: run from your project root, or pass the directory as an argument.");
|
|
373
|
+
}
|
|
374
|
+
process.exitCode = 1;
|
|
375
|
+
}
|
|
376
|
+
async function runReleasePlan(args) {
|
|
377
|
+
const target = args[0];
|
|
378
|
+
const asJson = args.includes("--json");
|
|
379
|
+
const environmentPath = readFlagValue(args, "--env");
|
|
380
|
+
const migrationPath = readFlagValue(args, "--migrations");
|
|
381
|
+
if (!target) {
|
|
382
|
+
console.error("Usage: capstan release:plan <generated-app-dir> [--json] [--env <path>] [--migrations <path>]");
|
|
383
|
+
process.exitCode = 1;
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
const report = await createReleasePlan(resolve(process.cwd(), target), {
|
|
387
|
+
...(environmentPath ? { environmentPath } : {}),
|
|
388
|
+
...(migrationPath ? { migrationPath } : {})
|
|
389
|
+
});
|
|
390
|
+
if (asJson) {
|
|
391
|
+
console.log(JSON.stringify(report, null, 2));
|
|
392
|
+
}
|
|
393
|
+
else {
|
|
394
|
+
process.stdout.write(renderReleasePlanText(report));
|
|
395
|
+
}
|
|
396
|
+
if (report.status === "blocked") {
|
|
397
|
+
process.exitCode = 1;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
async function runReleaseRun(args) {
|
|
401
|
+
const target = args[0];
|
|
402
|
+
const mode = args[1];
|
|
403
|
+
const asJson = args.includes("--json");
|
|
404
|
+
const environmentPath = readFlagValue(args, "--env");
|
|
405
|
+
const migrationPath = readFlagValue(args, "--migrations");
|
|
406
|
+
if (!target || (mode !== "preview" && mode !== "release")) {
|
|
407
|
+
console.error("Usage: capstan release:run <generated-app-dir> <preview|release> [--json] [--env <path>] [--migrations <path>]");
|
|
408
|
+
process.exitCode = 1;
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
const report = await createReleaseRun(resolve(process.cwd(), target), mode, {
|
|
412
|
+
...(environmentPath ? { environmentPath } : {}),
|
|
413
|
+
...(migrationPath ? { migrationPath } : {})
|
|
414
|
+
});
|
|
415
|
+
if (asJson) {
|
|
416
|
+
console.log(JSON.stringify(report, null, 2));
|
|
417
|
+
}
|
|
418
|
+
else {
|
|
419
|
+
process.stdout.write(renderReleaseRunText(report));
|
|
420
|
+
}
|
|
421
|
+
if (report.status !== "completed") {
|
|
422
|
+
process.exitCode = 1;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
async function runReleaseHistory(args) {
|
|
426
|
+
const target = args[0];
|
|
427
|
+
const asJson = args.includes("--json");
|
|
428
|
+
if (!target) {
|
|
429
|
+
console.error("Usage: capstan release:history <generated-app-dir> [--json]");
|
|
430
|
+
process.exitCode = 1;
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
const report = await listReleaseRuns(resolve(process.cwd(), target));
|
|
434
|
+
if (asJson) {
|
|
435
|
+
console.log(JSON.stringify(report, null, 2));
|
|
436
|
+
}
|
|
437
|
+
else {
|
|
438
|
+
process.stdout.write(renderReleaseHistoryText(report));
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
async function runReleaseRollback(args) {
|
|
442
|
+
const target = args[0];
|
|
443
|
+
const asJson = args.includes("--json");
|
|
444
|
+
const tracePath = readFlagValue(args, "--trace");
|
|
445
|
+
if (!target) {
|
|
446
|
+
console.error("Usage: capstan release:rollback <generated-app-dir> [--json] [--trace <path>]");
|
|
447
|
+
process.exitCode = 1;
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
const report = await createRollbackRun(resolve(process.cwd(), target), {
|
|
451
|
+
...(tracePath ? { tracePath } : {})
|
|
452
|
+
});
|
|
453
|
+
if (asJson) {
|
|
454
|
+
console.log(JSON.stringify(report, null, 2));
|
|
455
|
+
}
|
|
456
|
+
else {
|
|
457
|
+
process.stdout.write(renderRollbackRunText(report));
|
|
458
|
+
}
|
|
459
|
+
if (report.status !== "completed") {
|
|
460
|
+
process.exitCode = 1;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
async function runHarnessStart(args) {
|
|
464
|
+
const appDir = args[0];
|
|
465
|
+
const taskKey = args[1];
|
|
466
|
+
const asJson = args.includes("--json");
|
|
467
|
+
const inputPath = readFlagValue(args, "--input");
|
|
468
|
+
const note = readFlagValue(args, "--note");
|
|
469
|
+
if (!appDir || !taskKey) {
|
|
470
|
+
console.error("Usage: capstan harness:start <generated-app-dir> <task-key> [--json] [--input <path>] [--note <text>]");
|
|
471
|
+
process.exitCode = 1;
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
const input = inputPath ? await loadJsonFile(inputPath) : {};
|
|
475
|
+
const run = await createHarnessRun(resolve(process.cwd(), appDir), taskKey, ensureRecord(input), {
|
|
476
|
+
...(note ? { note } : {})
|
|
477
|
+
});
|
|
478
|
+
if (asJson) {
|
|
479
|
+
console.log(JSON.stringify(run, null, 2));
|
|
480
|
+
}
|
|
481
|
+
else {
|
|
482
|
+
process.stdout.write(renderHarnessRunText(run));
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
async function runHarnessGet(args) {
|
|
486
|
+
const appDir = args[0];
|
|
487
|
+
const runId = args[1];
|
|
488
|
+
const asJson = args.includes("--json");
|
|
489
|
+
if (!appDir || !runId) {
|
|
490
|
+
console.error("Usage: capstan harness:get <generated-app-dir> <run-id> [--json]");
|
|
491
|
+
process.exitCode = 1;
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
const run = await getHarnessRun(resolve(process.cwd(), appDir), runId);
|
|
495
|
+
if (!run) {
|
|
496
|
+
console.error(`Unknown harness run "${runId}".`);
|
|
497
|
+
process.exitCode = 1;
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
if (asJson) {
|
|
501
|
+
console.log(JSON.stringify(run, null, 2));
|
|
502
|
+
}
|
|
503
|
+
else {
|
|
504
|
+
process.stdout.write(renderHarnessRunText(run));
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
async function runHarnessList(args) {
|
|
508
|
+
const appDir = args[0];
|
|
509
|
+
const asJson = args.includes("--json");
|
|
510
|
+
const taskKey = readFlagValue(args, "--task");
|
|
511
|
+
if (!appDir) {
|
|
512
|
+
console.error("Usage: capstan harness:list <generated-app-dir> [--json] [--task <task-key>]");
|
|
513
|
+
process.exitCode = 1;
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
const runs = await listHarnessRuns(resolve(process.cwd(), appDir), {
|
|
517
|
+
...(taskKey ? { taskKey } : {})
|
|
518
|
+
});
|
|
519
|
+
if (asJson) {
|
|
520
|
+
console.log(JSON.stringify(runs, null, 2));
|
|
521
|
+
}
|
|
522
|
+
else {
|
|
523
|
+
process.stdout.write(renderHarnessRunsText(runs));
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
async function runHarnessPause(args) {
|
|
527
|
+
await runHarnessMutation(args, "pause");
|
|
528
|
+
}
|
|
529
|
+
async function runHarnessResume(args) {
|
|
530
|
+
await runHarnessMutation(args, "resume");
|
|
531
|
+
}
|
|
532
|
+
async function runHarnessRequestApproval(args) {
|
|
533
|
+
await runHarnessMutation(args, "request-approval");
|
|
534
|
+
}
|
|
535
|
+
async function runHarnessApprove(args) {
|
|
536
|
+
await runHarnessMutation(args, "approve");
|
|
537
|
+
}
|
|
538
|
+
async function runHarnessRequestInput(args) {
|
|
539
|
+
await runHarnessMutation(args, "request-input");
|
|
540
|
+
}
|
|
541
|
+
async function runHarnessProvideInput(args) {
|
|
542
|
+
const appDir = args[0];
|
|
543
|
+
const runId = args[1];
|
|
544
|
+
const asJson = args.includes("--json");
|
|
545
|
+
const inputPath = readFlagValue(args, "--input");
|
|
546
|
+
const note = readFlagValue(args, "--note");
|
|
547
|
+
if (!appDir || !runId || !inputPath) {
|
|
548
|
+
console.error("Usage: capstan harness:provide-input <generated-app-dir> <run-id> --input <path> [--json] [--note <text>]");
|
|
549
|
+
process.exitCode = 1;
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
const input = ensureRecord(await loadJsonFile(inputPath));
|
|
553
|
+
const run = await provideHarnessInput(resolve(process.cwd(), appDir), runId, input, {
|
|
554
|
+
...(note ? { note } : {})
|
|
555
|
+
});
|
|
556
|
+
if (asJson) {
|
|
557
|
+
console.log(JSON.stringify(run, null, 2));
|
|
558
|
+
}
|
|
559
|
+
else {
|
|
560
|
+
process.stdout.write(renderHarnessRunText(run));
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
async function runHarnessComplete(args) {
|
|
564
|
+
const appDir = args[0];
|
|
565
|
+
const runId = args[1];
|
|
566
|
+
const asJson = args.includes("--json");
|
|
567
|
+
const outputPath = readFlagValue(args, "--output");
|
|
568
|
+
const note = readFlagValue(args, "--note");
|
|
569
|
+
if (!appDir || !runId) {
|
|
570
|
+
console.error("Usage: capstan harness:complete <generated-app-dir> <run-id> [--json] [--output <path>] [--note <text>]");
|
|
571
|
+
process.exitCode = 1;
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
const output = outputPath ? await loadJsonFile(outputPath) : {};
|
|
575
|
+
const run = await completeHarnessRun(resolve(process.cwd(), appDir), runId, output, {
|
|
576
|
+
...(note ? { note } : {})
|
|
577
|
+
});
|
|
578
|
+
if (asJson) {
|
|
579
|
+
console.log(JSON.stringify(run, null, 2));
|
|
580
|
+
}
|
|
581
|
+
else {
|
|
582
|
+
process.stdout.write(renderHarnessRunText(run));
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
async function runHarnessFail(args) {
|
|
586
|
+
const appDir = args[0];
|
|
587
|
+
const runId = args[1];
|
|
588
|
+
const asJson = args.includes("--json");
|
|
589
|
+
const message = readFlagValue(args, "--message");
|
|
590
|
+
if (!appDir || !runId || !message) {
|
|
591
|
+
console.error("Usage: capstan harness:fail <generated-app-dir> <run-id> --message <text> [--json]");
|
|
592
|
+
process.exitCode = 1;
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
const run = await failHarnessRun(resolve(process.cwd(), appDir), runId, message);
|
|
596
|
+
if (asJson) {
|
|
597
|
+
console.log(JSON.stringify(run, null, 2));
|
|
598
|
+
}
|
|
599
|
+
else {
|
|
600
|
+
process.stdout.write(renderHarnessRunText(run));
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
async function runHarnessCancel(args) {
|
|
604
|
+
await runHarnessMutation(args, "cancel");
|
|
605
|
+
}
|
|
606
|
+
async function runHarnessRetry(args) {
|
|
607
|
+
await runHarnessMutation(args, "retry");
|
|
608
|
+
}
|
|
609
|
+
async function runHarnessEvents(args) {
|
|
610
|
+
const appDir = args[0];
|
|
611
|
+
const asJson = args.includes("--json");
|
|
612
|
+
const runId = readFlagValue(args, "--run");
|
|
613
|
+
if (!appDir) {
|
|
614
|
+
console.error("Usage: capstan harness:events <generated-app-dir> [--json] [--run <run-id>]");
|
|
615
|
+
process.exitCode = 1;
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
const events = await listHarnessEvents(resolve(process.cwd(), appDir), {
|
|
619
|
+
...(runId ? { runId } : {})
|
|
620
|
+
});
|
|
621
|
+
if (asJson) {
|
|
622
|
+
console.log(JSON.stringify(events, null, 2));
|
|
623
|
+
}
|
|
624
|
+
else {
|
|
625
|
+
process.stdout.write(renderHarnessEventsText(events));
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
async function runHarnessReplay(args) {
|
|
629
|
+
const appDir = args[0];
|
|
630
|
+
const runId = args[1];
|
|
631
|
+
const asJson = args.includes("--json");
|
|
632
|
+
if (!appDir || !runId) {
|
|
633
|
+
console.error("Usage: capstan harness:replay <generated-app-dir> <run-id> [--json]");
|
|
634
|
+
process.exitCode = 1;
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
637
|
+
const report = await replayHarnessRun(resolve(process.cwd(), appDir), runId);
|
|
638
|
+
if (asJson) {
|
|
639
|
+
console.log(JSON.stringify(report, null, 2));
|
|
640
|
+
}
|
|
641
|
+
else {
|
|
642
|
+
process.stdout.write(renderHarnessReplayText(report));
|
|
643
|
+
}
|
|
644
|
+
if (!report.consistent) {
|
|
645
|
+
process.exitCode = 1;
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
async function runHarnessCompact(args) {
|
|
649
|
+
const appDir = args[0];
|
|
650
|
+
const runId = args[1];
|
|
651
|
+
const asJson = args.includes("--json");
|
|
652
|
+
const tailValue = readFlagValue(args, "--tail");
|
|
653
|
+
if (!appDir || !runId) {
|
|
654
|
+
console.error("Usage: capstan harness:compact <generated-app-dir> <run-id> [--json] [--tail <count>]");
|
|
655
|
+
process.exitCode = 1;
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
const tail = tailValue ? Number.parseInt(tailValue, 10) : undefined;
|
|
659
|
+
const summary = await compactHarnessRun(resolve(process.cwd(), appDir), runId, {
|
|
660
|
+
...(typeof tail === "number" && Number.isFinite(tail) ? { tail } : {})
|
|
661
|
+
});
|
|
662
|
+
if (asJson) {
|
|
663
|
+
console.log(JSON.stringify(summary, null, 2));
|
|
664
|
+
}
|
|
665
|
+
else {
|
|
666
|
+
process.stdout.write(renderHarnessCompactionText(summary));
|
|
667
|
+
}
|
|
668
|
+
if (!summary.consistent) {
|
|
669
|
+
process.exitCode = 1;
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
async function runHarnessSummary(args) {
|
|
673
|
+
const appDir = args[0];
|
|
674
|
+
const runId = args[1];
|
|
675
|
+
const asJson = args.includes("--json");
|
|
676
|
+
const refresh = args.includes("--refresh");
|
|
677
|
+
const tailValue = readFlagValue(args, "--tail");
|
|
678
|
+
if (!appDir || !runId) {
|
|
679
|
+
console.error("Usage: capstan harness:summary <generated-app-dir> <run-id> [--json] [--refresh] [--tail <count>]");
|
|
680
|
+
process.exitCode = 1;
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
const tail = tailValue ? Number.parseInt(tailValue, 10) : undefined;
|
|
684
|
+
const summary = await getHarnessSummary(resolve(process.cwd(), appDir), runId, {
|
|
685
|
+
refresh,
|
|
686
|
+
...(typeof tail === "number" && Number.isFinite(tail) ? { tail } : {})
|
|
687
|
+
});
|
|
688
|
+
if (asJson) {
|
|
689
|
+
console.log(JSON.stringify(summary, null, 2));
|
|
690
|
+
}
|
|
691
|
+
else {
|
|
692
|
+
process.stdout.write(renderHarnessCompactionText(summary));
|
|
693
|
+
}
|
|
694
|
+
if (!summary.consistent) {
|
|
695
|
+
process.exitCode = 1;
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
async function runHarnessSummaries(args) {
|
|
699
|
+
const appDir = args[0];
|
|
700
|
+
const asJson = args.includes("--json");
|
|
701
|
+
if (!appDir) {
|
|
702
|
+
console.error("Usage: capstan harness:summaries <generated-app-dir> [--json]");
|
|
703
|
+
process.exitCode = 1;
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
706
|
+
const summaries = await listHarnessSummaries(resolve(process.cwd(), appDir));
|
|
707
|
+
if (asJson) {
|
|
708
|
+
console.log(JSON.stringify(summaries, null, 2));
|
|
709
|
+
}
|
|
710
|
+
else {
|
|
711
|
+
process.stdout.write(renderHarnessSummariesText(summaries));
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
async function runHarnessMemory(args) {
|
|
715
|
+
const appDir = args[0];
|
|
716
|
+
const runId = args[1];
|
|
717
|
+
const asJson = args.includes("--json");
|
|
718
|
+
const refresh = args.includes("--refresh");
|
|
719
|
+
const tailValue = readFlagValue(args, "--tail");
|
|
720
|
+
if (!appDir || !runId) {
|
|
721
|
+
console.error("Usage: capstan harness:memory <generated-app-dir> <run-id> [--json] [--refresh] [--tail <count>]");
|
|
722
|
+
process.exitCode = 1;
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
const tail = tailValue ? Number.parseInt(tailValue, 10) : undefined;
|
|
726
|
+
const artifact = refresh
|
|
727
|
+
? await createHarnessMemory(resolve(process.cwd(), appDir), runId, {
|
|
728
|
+
refresh,
|
|
729
|
+
...(typeof tail === "number" && Number.isFinite(tail) ? { tail } : {})
|
|
730
|
+
})
|
|
731
|
+
: await getHarnessMemory(resolve(process.cwd(), appDir), runId, {
|
|
732
|
+
...(typeof tail === "number" && Number.isFinite(tail) ? { tail } : {})
|
|
733
|
+
});
|
|
734
|
+
if (asJson) {
|
|
735
|
+
console.log(JSON.stringify(artifact, null, 2));
|
|
736
|
+
}
|
|
737
|
+
else {
|
|
738
|
+
process.stdout.write(renderHarnessMemoryText(artifact));
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
async function runHarnessMemories(args) {
|
|
742
|
+
const appDir = args[0];
|
|
743
|
+
const asJson = args.includes("--json");
|
|
744
|
+
if (!appDir) {
|
|
745
|
+
console.error("Usage: capstan harness:memories <generated-app-dir> [--json]");
|
|
746
|
+
process.exitCode = 1;
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
749
|
+
const memories = await listHarnessMemories(resolve(process.cwd(), appDir));
|
|
750
|
+
if (asJson) {
|
|
751
|
+
console.log(JSON.stringify(memories, null, 2));
|
|
752
|
+
}
|
|
753
|
+
else {
|
|
754
|
+
process.stdout.write(renderHarnessMemoriesText(memories));
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
// ---------------------------------------------------------------------------
|
|
758
|
+
// Dev / Build / Start
|
|
759
|
+
// ---------------------------------------------------------------------------
|
|
760
|
+
async function runDev(args) {
|
|
761
|
+
const { createDevServer } = await import("@zauso-ai/capstan-dev");
|
|
762
|
+
const port = parseInt(readFlagValue(args, "--port") ?? "3000", 10);
|
|
763
|
+
const host = readFlagValue(args, "--host") ?? "localhost";
|
|
764
|
+
// Try to load capstan.config.ts / capstan.config.js for app metadata
|
|
765
|
+
let appName = "capstan-app";
|
|
766
|
+
let appDescription;
|
|
767
|
+
try {
|
|
768
|
+
const configPath = await resolveConfig();
|
|
769
|
+
if (configPath) {
|
|
770
|
+
const configUrl = pathToFileURL(configPath).href;
|
|
771
|
+
const mod = (await import(configUrl));
|
|
772
|
+
if (mod.default?.name)
|
|
773
|
+
appName = mod.default.name;
|
|
774
|
+
if (mod.default?.description)
|
|
775
|
+
appDescription = mod.default.description;
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
catch {
|
|
779
|
+
// Config file is optional — continue without it.
|
|
780
|
+
}
|
|
781
|
+
const server = await createDevServer({
|
|
782
|
+
rootDir: process.cwd(),
|
|
783
|
+
port,
|
|
784
|
+
host,
|
|
785
|
+
appName,
|
|
786
|
+
...(appDescription ? { appDescription } : {}),
|
|
787
|
+
});
|
|
788
|
+
await server.start();
|
|
789
|
+
}
|
|
790
|
+
async function runBuild() {
|
|
791
|
+
const { execFile } = await import("node:child_process");
|
|
792
|
+
const { promisify } = await import("node:util");
|
|
793
|
+
const exec = promisify(execFile);
|
|
794
|
+
console.log("Building project...");
|
|
795
|
+
await exec("npx", ["tsc", "-p", "tsconfig.json"], { cwd: process.cwd() });
|
|
796
|
+
console.log("TypeScript compilation complete.");
|
|
797
|
+
// Generate agent-manifest.json and openapi.json into dist/
|
|
798
|
+
const { mkdir, writeFile } = await import("node:fs/promises");
|
|
799
|
+
const { join } = await import("node:path");
|
|
800
|
+
const { scanRoutes } = await import("@zauso-ai/capstan-router");
|
|
801
|
+
const { generateAgentManifest, generateOpenApiSpec } = await import("@zauso-ai/capstan-agent");
|
|
802
|
+
let appName = "capstan-app";
|
|
803
|
+
let appDescription;
|
|
804
|
+
try {
|
|
805
|
+
const configPath = await resolveConfig();
|
|
806
|
+
if (configPath) {
|
|
807
|
+
const configUrl = pathToFileURL(configPath).href;
|
|
808
|
+
const mod = (await import(configUrl));
|
|
809
|
+
if (mod.default?.name)
|
|
810
|
+
appName = mod.default.name;
|
|
811
|
+
if (mod.default?.description)
|
|
812
|
+
appDescription = mod.default.description;
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
catch {
|
|
816
|
+
// Config is optional.
|
|
817
|
+
}
|
|
818
|
+
const routesDir = join(process.cwd(), "app", "routes");
|
|
819
|
+
const manifest = await scanRoutes(routesDir);
|
|
820
|
+
const registryEntries = manifest.routes
|
|
821
|
+
.filter((r) => r.type === "api")
|
|
822
|
+
.flatMap((r) => {
|
|
823
|
+
const methods = r.methods && r.methods.length > 0 ? r.methods : ["GET"];
|
|
824
|
+
return methods.map((m) => ({
|
|
825
|
+
method: m,
|
|
826
|
+
path: r.urlPattern,
|
|
827
|
+
}));
|
|
828
|
+
});
|
|
829
|
+
const agentConfig = { name: appName, ...(appDescription ? { description: appDescription } : {}) };
|
|
830
|
+
const agentManifest = generateAgentManifest(agentConfig, registryEntries);
|
|
831
|
+
const openApiSpec = generateOpenApiSpec(agentConfig, registryEntries);
|
|
832
|
+
const distDir = join(process.cwd(), "dist");
|
|
833
|
+
await mkdir(distDir, { recursive: true });
|
|
834
|
+
await writeFile(join(distDir, "agent-manifest.json"), JSON.stringify(agentManifest, null, 2));
|
|
835
|
+
await writeFile(join(distDir, "openapi.json"), JSON.stringify(openApiSpec, null, 2));
|
|
836
|
+
console.log("Generated dist/agent-manifest.json");
|
|
837
|
+
console.log("Generated dist/openapi.json");
|
|
838
|
+
console.log("Build complete.");
|
|
839
|
+
}
|
|
840
|
+
async function runStart(args) {
|
|
841
|
+
const { execFile } = await import("node:child_process");
|
|
842
|
+
const { promisify } = await import("node:util");
|
|
843
|
+
const exec = promisify(execFile);
|
|
844
|
+
const port = readFlagValue(args, "--port") ?? "3000";
|
|
845
|
+
console.log(`Starting production server on port ${port}...`);
|
|
846
|
+
await exec("node", ["dist/index.js"], {
|
|
847
|
+
cwd: process.cwd(),
|
|
848
|
+
env: { ...process.env, PORT: port },
|
|
849
|
+
});
|
|
850
|
+
}
|
|
851
|
+
// ---------------------------------------------------------------------------
|
|
852
|
+
// Database commands
|
|
853
|
+
// ---------------------------------------------------------------------------
|
|
854
|
+
async function runDbMigrate(args) {
|
|
855
|
+
const name = readFlagValue(args, "--name");
|
|
856
|
+
if (!name) {
|
|
857
|
+
console.error("Usage: capstan db:migrate --name <migration-name>");
|
|
858
|
+
process.exitCode = 1;
|
|
859
|
+
return;
|
|
860
|
+
}
|
|
861
|
+
const { mkdir, readdir, writeFile } = await import("node:fs/promises");
|
|
862
|
+
const { join } = await import("node:path");
|
|
863
|
+
const { generateMigration } = await import("@zauso-ai/capstan-db");
|
|
864
|
+
const migrationsDir = join(process.cwd(), "app", "migrations");
|
|
865
|
+
await mkdir(migrationsDir, { recursive: true });
|
|
866
|
+
// Collect existing model definitions from app/models/ if present
|
|
867
|
+
const modelsDir = join(process.cwd(), "app", "models");
|
|
868
|
+
let toModels = [];
|
|
869
|
+
try {
|
|
870
|
+
const modelFiles = await readdir(modelsDir);
|
|
871
|
+
for (const file of modelFiles) {
|
|
872
|
+
if (file.endsWith(".ts") || file.endsWith(".js")) {
|
|
873
|
+
const moduleUrl = pathToFileURL(join(modelsDir, file)).href;
|
|
874
|
+
const mod = (await import(moduleUrl));
|
|
875
|
+
// Look for exported model definitions
|
|
876
|
+
for (const value of Object.values(mod)) {
|
|
877
|
+
if (value &&
|
|
878
|
+
typeof value === "object" &&
|
|
879
|
+
"name" in value &&
|
|
880
|
+
"fields" in value) {
|
|
881
|
+
toModels.push(value);
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
catch {
|
|
888
|
+
// No models directory — generate an empty migration.
|
|
889
|
+
}
|
|
890
|
+
const statements = generateMigration([], toModels);
|
|
891
|
+
const timestamp = new Date().toISOString().replace(/[^0-9]/g, "").slice(0, 14);
|
|
892
|
+
const filename = `${timestamp}_${name}.sql`;
|
|
893
|
+
const content = statements.length > 0
|
|
894
|
+
? statements.join(";\n") + ";\n"
|
|
895
|
+
: "-- empty migration\n";
|
|
896
|
+
await writeFile(join(migrationsDir, filename), content);
|
|
897
|
+
console.log(`Created migration: app/migrations/${filename}`);
|
|
898
|
+
}
|
|
899
|
+
async function runDbPush() {
|
|
900
|
+
const { readdir, readFile: readMigrationFile } = await import("node:fs/promises");
|
|
901
|
+
const { join } = await import("node:path");
|
|
902
|
+
const { createDatabase } = await import("@zauso-ai/capstan-db");
|
|
903
|
+
const migrationsDir = join(process.cwd(), "app", "migrations");
|
|
904
|
+
let files;
|
|
905
|
+
try {
|
|
906
|
+
files = (await readdir(migrationsDir)).filter((f) => f.endsWith(".sql")).sort();
|
|
907
|
+
}
|
|
908
|
+
catch {
|
|
909
|
+
console.log("No migrations directory found at app/migrations/.");
|
|
910
|
+
return;
|
|
911
|
+
}
|
|
912
|
+
if (files.length === 0) {
|
|
913
|
+
console.log("No pending migrations.");
|
|
914
|
+
return;
|
|
915
|
+
}
|
|
916
|
+
const db = createDatabase({ provider: "sqlite", url: join(process.cwd(), "app", "data", "app.db") });
|
|
917
|
+
for (const file of files) {
|
|
918
|
+
const sql = await readMigrationFile(join(migrationsDir, file), "utf8");
|
|
919
|
+
const statements = sql
|
|
920
|
+
.split(";")
|
|
921
|
+
.map((s) => s.trim())
|
|
922
|
+
.filter((s) => s.length > 0 && !s.startsWith("--"));
|
|
923
|
+
if (statements.length > 0) {
|
|
924
|
+
const client = db.$client;
|
|
925
|
+
for (const stmt of statements) {
|
|
926
|
+
client.exec(stmt);
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
console.log(`Applied: ${file}`);
|
|
930
|
+
}
|
|
931
|
+
console.log("All migrations applied.");
|
|
932
|
+
}
|
|
933
|
+
async function runDbStatus() {
|
|
934
|
+
const { readdir } = await import("node:fs/promises");
|
|
935
|
+
const { join } = await import("node:path");
|
|
936
|
+
const migrationsDir = join(process.cwd(), "app", "migrations");
|
|
937
|
+
let files;
|
|
938
|
+
try {
|
|
939
|
+
files = (await readdir(migrationsDir)).filter((f) => f.endsWith(".sql")).sort();
|
|
940
|
+
}
|
|
941
|
+
catch {
|
|
942
|
+
console.log("No migrations directory found at app/migrations/.");
|
|
943
|
+
return;
|
|
944
|
+
}
|
|
945
|
+
if (files.length === 0) {
|
|
946
|
+
console.log("No migration files found.");
|
|
947
|
+
return;
|
|
948
|
+
}
|
|
949
|
+
console.log(`Found ${files.length} migration(s):`);
|
|
950
|
+
for (const file of files) {
|
|
951
|
+
console.log(` ${file}`);
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
// ---------------------------------------------------------------------------
|
|
955
|
+
// Agent / MCP commands
|
|
956
|
+
// ---------------------------------------------------------------------------
|
|
957
|
+
async function runMcp() {
|
|
958
|
+
const { createMcpServer, serveMcpStdio } = await import("@zauso-ai/capstan-agent");
|
|
959
|
+
const { scanRoutes } = await import("@zauso-ai/capstan-router");
|
|
960
|
+
const { join } = await import("node:path");
|
|
961
|
+
let appName = "capstan-app";
|
|
962
|
+
let appDescription;
|
|
963
|
+
try {
|
|
964
|
+
const configPath = await resolveConfig();
|
|
965
|
+
if (configPath) {
|
|
966
|
+
const configUrl = pathToFileURL(configPath).href;
|
|
967
|
+
const mod = (await import(configUrl));
|
|
968
|
+
if (mod.default?.name)
|
|
969
|
+
appName = mod.default.name;
|
|
970
|
+
if (mod.default?.description)
|
|
971
|
+
appDescription = mod.default.description;
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
catch {
|
|
975
|
+
// Config is optional.
|
|
976
|
+
}
|
|
977
|
+
const routesDir = join(process.cwd(), "app", "routes");
|
|
978
|
+
const manifest = await scanRoutes(routesDir);
|
|
979
|
+
const registryEntries = manifest.routes
|
|
980
|
+
.filter((r) => r.type === "api")
|
|
981
|
+
.flatMap((r) => {
|
|
982
|
+
const methods = r.methods && r.methods.length > 0 ? r.methods : ["GET"];
|
|
983
|
+
return methods.map((m) => ({
|
|
984
|
+
method: m,
|
|
985
|
+
path: r.urlPattern,
|
|
986
|
+
}));
|
|
987
|
+
});
|
|
988
|
+
// Build an executeRoute callback that loads handlers from disk and invokes
|
|
989
|
+
// them directly, so MCP tool calls actually run the real route logic.
|
|
990
|
+
const { loadApiHandlers } = await import("@zauso-ai/capstan-dev");
|
|
991
|
+
const executeRoute = async (method, urlPath, input) => {
|
|
992
|
+
try {
|
|
993
|
+
// Find the matching route file from the manifest.
|
|
994
|
+
const matchingRoutes = manifest.routes.filter((r) => r.type === "api" && r.urlPattern === urlPath);
|
|
995
|
+
if (matchingRoutes.length === 0) {
|
|
996
|
+
return { error: `No route found for ${method} ${urlPath}` };
|
|
997
|
+
}
|
|
998
|
+
const route = matchingRoutes[0];
|
|
999
|
+
const handlers = await loadApiHandlers(route.filePath);
|
|
1000
|
+
const handler = handlers[method];
|
|
1001
|
+
if (!handler || typeof handler !== "object" || !("handler" in handler)) {
|
|
1002
|
+
return { error: `No ${method} handler found at ${urlPath}` };
|
|
1003
|
+
}
|
|
1004
|
+
const apiDef = handler;
|
|
1005
|
+
const result = await apiDef.handler({
|
|
1006
|
+
input: input ?? {},
|
|
1007
|
+
ctx: {
|
|
1008
|
+
auth: {
|
|
1009
|
+
isAuthenticated: false,
|
|
1010
|
+
type: "anonymous",
|
|
1011
|
+
permissions: [],
|
|
1012
|
+
},
|
|
1013
|
+
request: new Request(`http://localhost${urlPath}`),
|
|
1014
|
+
env: process.env,
|
|
1015
|
+
honoCtx: {},
|
|
1016
|
+
},
|
|
1017
|
+
});
|
|
1018
|
+
return result;
|
|
1019
|
+
}
|
|
1020
|
+
catch (err) {
|
|
1021
|
+
return {
|
|
1022
|
+
error: err instanceof Error ? err.message : "Route execution failed",
|
|
1023
|
+
};
|
|
1024
|
+
}
|
|
1025
|
+
};
|
|
1026
|
+
const agentConfig = {
|
|
1027
|
+
name: appName,
|
|
1028
|
+
...(appDescription ? { description: appDescription } : {}),
|
|
1029
|
+
};
|
|
1030
|
+
const { server } = createMcpServer(agentConfig, registryEntries, executeRoute);
|
|
1031
|
+
await serveMcpStdio(server);
|
|
1032
|
+
}
|
|
1033
|
+
async function runAgentManifest() {
|
|
1034
|
+
const { generateAgentManifest } = await import("@zauso-ai/capstan-agent");
|
|
1035
|
+
const { scanRoutes } = await import("@zauso-ai/capstan-router");
|
|
1036
|
+
const { join } = await import("node:path");
|
|
1037
|
+
let appName = "capstan-app";
|
|
1038
|
+
let appDescription;
|
|
1039
|
+
try {
|
|
1040
|
+
const configPath = await resolveConfig();
|
|
1041
|
+
if (configPath) {
|
|
1042
|
+
const configUrl = pathToFileURL(configPath).href;
|
|
1043
|
+
const mod = (await import(configUrl));
|
|
1044
|
+
if (mod.default?.name)
|
|
1045
|
+
appName = mod.default.name;
|
|
1046
|
+
if (mod.default?.description)
|
|
1047
|
+
appDescription = mod.default.description;
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
catch {
|
|
1051
|
+
// Config is optional.
|
|
1052
|
+
}
|
|
1053
|
+
const routesDir = join(process.cwd(), "app", "routes");
|
|
1054
|
+
const manifest = await scanRoutes(routesDir);
|
|
1055
|
+
const registryEntries = manifest.routes
|
|
1056
|
+
.filter((r) => r.type === "api")
|
|
1057
|
+
.flatMap((r) => {
|
|
1058
|
+
const methods = r.methods && r.methods.length > 0 ? r.methods : ["GET"];
|
|
1059
|
+
return methods.map((m) => ({
|
|
1060
|
+
method: m,
|
|
1061
|
+
path: r.urlPattern,
|
|
1062
|
+
}));
|
|
1063
|
+
});
|
|
1064
|
+
const agentConfig = { name: appName, ...(appDescription ? { description: appDescription } : {}) };
|
|
1065
|
+
const agentManifest = generateAgentManifest(agentConfig, registryEntries);
|
|
1066
|
+
console.log(JSON.stringify(agentManifest, null, 2));
|
|
1067
|
+
}
|
|
1068
|
+
async function runAgentOpenapi() {
|
|
1069
|
+
const { generateOpenApiSpec } = await import("@zauso-ai/capstan-agent");
|
|
1070
|
+
const { scanRoutes } = await import("@zauso-ai/capstan-router");
|
|
1071
|
+
const { join } = await import("node:path");
|
|
1072
|
+
let appName = "capstan-app";
|
|
1073
|
+
let appDescription;
|
|
1074
|
+
try {
|
|
1075
|
+
const configPath = await resolveConfig();
|
|
1076
|
+
if (configPath) {
|
|
1077
|
+
const configUrl = pathToFileURL(configPath).href;
|
|
1078
|
+
const mod = (await import(configUrl));
|
|
1079
|
+
if (mod.default?.name)
|
|
1080
|
+
appName = mod.default.name;
|
|
1081
|
+
if (mod.default?.description)
|
|
1082
|
+
appDescription = mod.default.description;
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
catch {
|
|
1086
|
+
// Config is optional.
|
|
1087
|
+
}
|
|
1088
|
+
const routesDir = join(process.cwd(), "app", "routes");
|
|
1089
|
+
const manifest = await scanRoutes(routesDir);
|
|
1090
|
+
const registryEntries = manifest.routes
|
|
1091
|
+
.filter((r) => r.type === "api")
|
|
1092
|
+
.flatMap((r) => {
|
|
1093
|
+
const methods = r.methods && r.methods.length > 0 ? r.methods : ["GET"];
|
|
1094
|
+
return methods.map((m) => ({
|
|
1095
|
+
method: m,
|
|
1096
|
+
path: r.urlPattern,
|
|
1097
|
+
}));
|
|
1098
|
+
});
|
|
1099
|
+
const agentConfig = { name: appName, ...(appDescription ? { description: appDescription } : {}) };
|
|
1100
|
+
const spec = generateOpenApiSpec(agentConfig, registryEntries);
|
|
1101
|
+
console.log(JSON.stringify(spec, null, 2));
|
|
1102
|
+
}
|
|
1103
|
+
// ---------------------------------------------------------------------------
|
|
1104
|
+
// Config helpers
|
|
1105
|
+
// ---------------------------------------------------------------------------
|
|
1106
|
+
async function resolveConfig() {
|
|
1107
|
+
const { access } = await import("node:fs/promises");
|
|
1108
|
+
const candidates = [
|
|
1109
|
+
resolve(process.cwd(), "capstan.config.ts"),
|
|
1110
|
+
resolve(process.cwd(), "capstan.config.js"),
|
|
1111
|
+
];
|
|
1112
|
+
for (const candidate of candidates) {
|
|
1113
|
+
try {
|
|
1114
|
+
await access(candidate);
|
|
1115
|
+
return candidate;
|
|
1116
|
+
}
|
|
1117
|
+
catch {
|
|
1118
|
+
continue;
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
return null;
|
|
1122
|
+
}
|
|
1123
|
+
// ---------------------------------------------------------------------------
|
|
1124
|
+
// Existing helpers
|
|
1125
|
+
// ---------------------------------------------------------------------------
|
|
1126
|
+
function readFlagValue(args, flag) {
|
|
1127
|
+
const index = args.indexOf(flag);
|
|
1128
|
+
if (index === -1) {
|
|
1129
|
+
return undefined;
|
|
1130
|
+
}
|
|
1131
|
+
const value = args[index + 1];
|
|
1132
|
+
return value && !value.startsWith("--") ? value : undefined;
|
|
1133
|
+
}
|
|
1134
|
+
async function runHarnessMutation(args, mode) {
|
|
1135
|
+
const appDir = args[0];
|
|
1136
|
+
const runId = args[1];
|
|
1137
|
+
const asJson = args.includes("--json");
|
|
1138
|
+
const note = readFlagValue(args, "--note");
|
|
1139
|
+
if (!appDir || !runId) {
|
|
1140
|
+
console.error(`Usage: capstan harness:${mode} <generated-app-dir> <run-id> [--json] [--note <text>]`);
|
|
1141
|
+
process.exitCode = 1;
|
|
1142
|
+
return;
|
|
1143
|
+
}
|
|
1144
|
+
const root = resolve(process.cwd(), appDir);
|
|
1145
|
+
const run = mode === "pause"
|
|
1146
|
+
? await pauseHarnessRun(root, runId, { ...(note ? { note } : {}) })
|
|
1147
|
+
: mode === "resume"
|
|
1148
|
+
? await resumeHarnessRun(root, runId, { ...(note ? { note } : {}) })
|
|
1149
|
+
: mode === "request-approval"
|
|
1150
|
+
? await requestHarnessApproval(root, runId, { ...(note ? { note } : {}) })
|
|
1151
|
+
: mode === "approve"
|
|
1152
|
+
? await approveHarnessRun(root, runId, { ...(note ? { note } : {}) })
|
|
1153
|
+
: mode === "request-input"
|
|
1154
|
+
? await requestHarnessInput(root, runId, { ...(note ? { note } : {}) })
|
|
1155
|
+
: mode === "cancel"
|
|
1156
|
+
? await cancelHarnessRun(root, runId, { ...(note ? { note } : {}) })
|
|
1157
|
+
: await retryHarnessRun(root, runId, { ...(note ? { note } : {}) });
|
|
1158
|
+
if (asJson) {
|
|
1159
|
+
console.log(JSON.stringify(run, null, 2));
|
|
1160
|
+
}
|
|
1161
|
+
else {
|
|
1162
|
+
process.stdout.write(renderHarnessRunText(run));
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
async function loadJsonFile(target) {
|
|
1166
|
+
const source = await readFile(resolve(process.cwd(), target), "utf8");
|
|
1167
|
+
return JSON.parse(source);
|
|
1168
|
+
}
|
|
1169
|
+
function ensureRecord(value) {
|
|
1170
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
1171
|
+
throw new Error("Expected a JSON object.");
|
|
1172
|
+
}
|
|
1173
|
+
return value;
|
|
1174
|
+
}
|
|
1175
|
+
async function loadGraph(target, options = {}) {
|
|
1176
|
+
const resolved = resolve(process.cwd(), target);
|
|
1177
|
+
const externalPackDefinitions = await loadExternalPackDefinitions(options.packRegistryPath);
|
|
1178
|
+
if (resolved.endsWith(".json")) {
|
|
1179
|
+
const source = await readFile(resolved, "utf8");
|
|
1180
|
+
return applyGraphWithPackDefinitions(JSON.parse(source), externalPackDefinitions);
|
|
1181
|
+
}
|
|
1182
|
+
const moduleUrl = pathToFileURL(resolved).href;
|
|
1183
|
+
const loaded = (await import(moduleUrl));
|
|
1184
|
+
const graph = loaded.default ?? loaded.appGraph;
|
|
1185
|
+
if (!graph) {
|
|
1186
|
+
throw new Error(`Graph module "${target}" must export either a default AppGraph or a named "appGraph" export.`);
|
|
1187
|
+
}
|
|
1188
|
+
const modulePackDefinitions = normalizePackRegistryExport(loaded.packRegistry ?? loaded.packs);
|
|
1189
|
+
return applyGraphWithPackDefinitions(graph, mergeExtraPackDefinitions(modulePackDefinitions, externalPackDefinitions));
|
|
1190
|
+
}
|
|
1191
|
+
async function loadBrief(target) {
|
|
1192
|
+
const resolved = resolve(process.cwd(), target);
|
|
1193
|
+
if (resolved.endsWith(".json")) {
|
|
1194
|
+
const source = await readFile(resolved, "utf8");
|
|
1195
|
+
return {
|
|
1196
|
+
brief: JSON.parse(source),
|
|
1197
|
+
packDefinitions: []
|
|
1198
|
+
};
|
|
1199
|
+
}
|
|
1200
|
+
const moduleUrl = pathToFileURL(resolved).href;
|
|
1201
|
+
const loaded = (await import(moduleUrl));
|
|
1202
|
+
const brief = loaded.default ?? loaded.brief ?? loaded.capstanBrief;
|
|
1203
|
+
if (!brief) {
|
|
1204
|
+
throw new Error(`Brief module "${target}" must export either a default CapstanBrief or a named "brief" export.`);
|
|
1205
|
+
}
|
|
1206
|
+
return {
|
|
1207
|
+
brief,
|
|
1208
|
+
packDefinitions: normalizePackRegistryExport(loaded.packRegistry ?? loaded.packs)
|
|
1209
|
+
};
|
|
1210
|
+
}
|
|
1211
|
+
async function compileBriefWithPackDefinitions(brief, options = {}) {
|
|
1212
|
+
const externalPackDefinitions = await loadExternalPackDefinitions(options.packRegistryPath);
|
|
1213
|
+
const packDefinitions = mergeExtraPackDefinitions(options.packDefinitions ?? [], externalPackDefinitions);
|
|
1214
|
+
const compiled = compileCapstanBrief(brief, {
|
|
1215
|
+
packDefinitions
|
|
1216
|
+
});
|
|
1217
|
+
return applyGraphWithPackDefinitions(compiled, packDefinitions);
|
|
1218
|
+
}
|
|
1219
|
+
async function loadExternalPackDefinitions(target) {
|
|
1220
|
+
if (!target) {
|
|
1221
|
+
return [];
|
|
1222
|
+
}
|
|
1223
|
+
const resolved = resolve(process.cwd(), target);
|
|
1224
|
+
const moduleUrl = pathToFileURL(resolved).href;
|
|
1225
|
+
const loaded = (await import(moduleUrl));
|
|
1226
|
+
return normalizePackRegistryExport(loaded.packRegistry ?? loaded.packs ?? loaded.default);
|
|
1227
|
+
}
|
|
1228
|
+
function normalizePackRegistryExport(value) {
|
|
1229
|
+
if (!value) {
|
|
1230
|
+
return [];
|
|
1231
|
+
}
|
|
1232
|
+
if (Array.isArray(value)) {
|
|
1233
|
+
return [...value];
|
|
1234
|
+
}
|
|
1235
|
+
if (Array.isArray(value.packs)) {
|
|
1236
|
+
return [...value.packs];
|
|
1237
|
+
}
|
|
1238
|
+
throw new Error("Pack registry modules must export an array of pack definitions.");
|
|
1239
|
+
}
|
|
1240
|
+
function applyGraphWithPackDefinitions(graph, extraPackDefinitions) {
|
|
1241
|
+
if (!extraPackDefinitions.length) {
|
|
1242
|
+
return applyBuiltinAppGraphPacks(graph);
|
|
1243
|
+
}
|
|
1244
|
+
const builtinDefinitions = listBuiltinGraphPacks();
|
|
1245
|
+
const definitions = [...builtinDefinitions];
|
|
1246
|
+
const keys = new Set(definitions.map((definition) => definition.key));
|
|
1247
|
+
for (const definition of extraPackDefinitions) {
|
|
1248
|
+
if (keys.has(definition.key)) {
|
|
1249
|
+
throw new Error(`Duplicate pack definition "${definition.key}" in external pack registry.`);
|
|
1250
|
+
}
|
|
1251
|
+
keys.add(definition.key);
|
|
1252
|
+
definitions.push(definition);
|
|
1253
|
+
}
|
|
1254
|
+
return applyAppGraphPacks(graph, definitions);
|
|
1255
|
+
}
|
|
1256
|
+
function mergeExtraPackDefinitions(...groups) {
|
|
1257
|
+
const merged = [];
|
|
1258
|
+
const keys = new Set();
|
|
1259
|
+
for (const group of groups) {
|
|
1260
|
+
for (const definition of group) {
|
|
1261
|
+
if (keys.has(definition.key)) {
|
|
1262
|
+
throw new Error(`Duplicate pack definition "${definition.key}" in brief or graph pack registries.`);
|
|
1263
|
+
}
|
|
1264
|
+
keys.add(definition.key);
|
|
1265
|
+
merged.push(definition);
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
return merged;
|
|
1269
|
+
}
|
|
1270
|
+
// ---------------------------------------------------------------------------
|
|
1271
|
+
// capstan add
|
|
1272
|
+
// ---------------------------------------------------------------------------
|
|
1273
|
+
async function runAdd(args) {
|
|
1274
|
+
const subcommand = args[0];
|
|
1275
|
+
const name = args[1];
|
|
1276
|
+
if (!subcommand || !name) {
|
|
1277
|
+
console.error("Usage: capstan add <model|api|page|policy> <name>");
|
|
1278
|
+
process.exitCode = 1;
|
|
1279
|
+
return;
|
|
1280
|
+
}
|
|
1281
|
+
switch (subcommand) {
|
|
1282
|
+
case "model": {
|
|
1283
|
+
const filePath = join(process.cwd(), "app/models", `${name}.model.ts`);
|
|
1284
|
+
if (existsSync(filePath)) {
|
|
1285
|
+
console.error(`File already exists: app/models/${name}.model.ts`);
|
|
1286
|
+
process.exitCode = 1;
|
|
1287
|
+
return;
|
|
1288
|
+
}
|
|
1289
|
+
const pascalName = name.charAt(0).toUpperCase() + name.slice(1);
|
|
1290
|
+
const content = `import { defineModel, field } from "@zauso-ai/capstan-db";
|
|
1291
|
+
|
|
1292
|
+
export const ${pascalName} = defineModel("${name}", {
|
|
1293
|
+
fields: {
|
|
1294
|
+
id: field.id(),
|
|
1295
|
+
title: field.string({ required: true }),
|
|
1296
|
+
description: field.text(),
|
|
1297
|
+
createdAt: field.datetime({ default: "now" }),
|
|
1298
|
+
updatedAt: field.datetime({ updatedAt: true }),
|
|
1299
|
+
},
|
|
1300
|
+
});
|
|
1301
|
+
`;
|
|
1302
|
+
await mkdir(join(process.cwd(), "app/models"), { recursive: true });
|
|
1303
|
+
await writeFile(filePath, content, "utf-8");
|
|
1304
|
+
console.log(`\u2713 Created app/models/${name}.model.ts`);
|
|
1305
|
+
break;
|
|
1306
|
+
}
|
|
1307
|
+
case "api": {
|
|
1308
|
+
const dirPath = join(process.cwd(), "app/routes", name);
|
|
1309
|
+
const filePath = join(dirPath, "index.api.ts");
|
|
1310
|
+
if (existsSync(filePath)) {
|
|
1311
|
+
console.error(`File already exists: app/routes/${name}/index.api.ts`);
|
|
1312
|
+
process.exitCode = 1;
|
|
1313
|
+
return;
|
|
1314
|
+
}
|
|
1315
|
+
const content = `import { defineAPI } from "@zauso-ai/capstan-core";
|
|
1316
|
+
import { z } from "zod";
|
|
1317
|
+
|
|
1318
|
+
export const meta = {
|
|
1319
|
+
resource: "${name}",
|
|
1320
|
+
description: "Manage ${name}",
|
|
1321
|
+
};
|
|
1322
|
+
|
|
1323
|
+
export const GET = defineAPI({
|
|
1324
|
+
output: z.object({
|
|
1325
|
+
items: z.array(z.object({ id: z.string(), title: z.string() })),
|
|
1326
|
+
}),
|
|
1327
|
+
description: "List ${name}",
|
|
1328
|
+
capability: "read",
|
|
1329
|
+
resource: "${name}",
|
|
1330
|
+
async handler({ input, ctx }) {
|
|
1331
|
+
// TODO: Replace with real database query
|
|
1332
|
+
return { items: [] };
|
|
1333
|
+
},
|
|
1334
|
+
});
|
|
1335
|
+
|
|
1336
|
+
export const POST = defineAPI({
|
|
1337
|
+
input: z.object({
|
|
1338
|
+
title: z.string().min(1),
|
|
1339
|
+
}),
|
|
1340
|
+
output: z.object({
|
|
1341
|
+
id: z.string(),
|
|
1342
|
+
title: z.string(),
|
|
1343
|
+
}),
|
|
1344
|
+
description: "Create a ${name}",
|
|
1345
|
+
capability: "write",
|
|
1346
|
+
resource: "${name}",
|
|
1347
|
+
policy: "requireAuth",
|
|
1348
|
+
async handler({ input, ctx }) {
|
|
1349
|
+
// TODO: Replace with real database insert
|
|
1350
|
+
return {
|
|
1351
|
+
id: crypto.randomUUID(),
|
|
1352
|
+
title: input.title,
|
|
1353
|
+
};
|
|
1354
|
+
},
|
|
1355
|
+
});
|
|
1356
|
+
`;
|
|
1357
|
+
await mkdir(dirPath, { recursive: true });
|
|
1358
|
+
await writeFile(filePath, content, "utf-8");
|
|
1359
|
+
console.log(`\u2713 Created app/routes/${name}/index.api.ts`);
|
|
1360
|
+
break;
|
|
1361
|
+
}
|
|
1362
|
+
case "page": {
|
|
1363
|
+
const dirPath = join(process.cwd(), "app/routes", name);
|
|
1364
|
+
const filePath = join(dirPath, "index.page.tsx");
|
|
1365
|
+
if (existsSync(filePath)) {
|
|
1366
|
+
console.error(`File already exists: app/routes/${name}/index.page.tsx`);
|
|
1367
|
+
process.exitCode = 1;
|
|
1368
|
+
return;
|
|
1369
|
+
}
|
|
1370
|
+
const titleName = name.charAt(0).toUpperCase() + name.slice(1);
|
|
1371
|
+
const content = `export default function ${titleName}Page() {
|
|
1372
|
+
return (
|
|
1373
|
+
<main>
|
|
1374
|
+
<h1>${titleName}</h1>
|
|
1375
|
+
<p>This is the ${name} page.</p>
|
|
1376
|
+
</main>
|
|
1377
|
+
);
|
|
1378
|
+
}
|
|
1379
|
+
`;
|
|
1380
|
+
await mkdir(dirPath, { recursive: true });
|
|
1381
|
+
await writeFile(filePath, content, "utf-8");
|
|
1382
|
+
console.log(`\u2713 Created app/routes/${name}/index.page.tsx`);
|
|
1383
|
+
break;
|
|
1384
|
+
}
|
|
1385
|
+
case "policy": {
|
|
1386
|
+
const policiesDir = join(process.cwd(), "app/policies");
|
|
1387
|
+
const policiesFile = join(policiesDir, "index.ts");
|
|
1388
|
+
const camelName = name.charAt(0).toLowerCase() + name.slice(1);
|
|
1389
|
+
const titleName = name.charAt(0).toUpperCase() + name.slice(1);
|
|
1390
|
+
const policySnippet = `
|
|
1391
|
+
export const ${camelName} = definePolicy({
|
|
1392
|
+
key: "${camelName}",
|
|
1393
|
+
title: "${titleName}",
|
|
1394
|
+
effect: "deny",
|
|
1395
|
+
async check({ ctx }) {
|
|
1396
|
+
// TODO: Implement policy logic
|
|
1397
|
+
return { effect: "allow" };
|
|
1398
|
+
},
|
|
1399
|
+
});
|
|
1400
|
+
`;
|
|
1401
|
+
if (existsSync(policiesFile)) {
|
|
1402
|
+
// Append to existing policies file
|
|
1403
|
+
const existing = await readFile(policiesFile, "utf-8");
|
|
1404
|
+
await writeFile(policiesFile, existing + policySnippet, "utf-8");
|
|
1405
|
+
console.log(`\u2713 Appended policy "${camelName}" to app/policies/index.ts`);
|
|
1406
|
+
}
|
|
1407
|
+
else {
|
|
1408
|
+
// Create new policies file with import
|
|
1409
|
+
const content = `import { definePolicy } from "@zauso-ai/capstan-core";
|
|
1410
|
+
${policySnippet}`;
|
|
1411
|
+
await mkdir(policiesDir, { recursive: true });
|
|
1412
|
+
await writeFile(policiesFile, content, "utf-8");
|
|
1413
|
+
console.log(`\u2713 Created app/policies/index.ts with policy "${camelName}"`);
|
|
1414
|
+
}
|
|
1415
|
+
break;
|
|
1416
|
+
}
|
|
1417
|
+
default:
|
|
1418
|
+
console.error(`Unknown add subcommand: ${subcommand}`);
|
|
1419
|
+
console.error("Usage: capstan add <model|api|page|policy> <name>");
|
|
1420
|
+
process.exitCode = 1;
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
function printHelp() {
|
|
1424
|
+
console.log(`Capstan CLI
|
|
1425
|
+
|
|
1426
|
+
Commands:
|
|
1427
|
+
capstan dev [--port 3000] [--host localhost]
|
|
1428
|
+
Start the development server with HMR
|
|
1429
|
+
capstan build Build the project and generate agent manifests
|
|
1430
|
+
capstan start [--port 3000]
|
|
1431
|
+
Start the production server from built output
|
|
1432
|
+
|
|
1433
|
+
capstan add <model|api|page|policy> <name>
|
|
1434
|
+
Scaffold a model, API route, page, or policy
|
|
1435
|
+
|
|
1436
|
+
capstan db:migrate --name <name>
|
|
1437
|
+
Generate a new migration file in app/migrations/
|
|
1438
|
+
capstan db:push Apply pending migrations to the database
|
|
1439
|
+
capstan db:status Show migration status
|
|
1440
|
+
|
|
1441
|
+
capstan mcp Start an MCP server on stdio transport
|
|
1442
|
+
capstan agent:manifest Print the agent manifest JSON to stdout
|
|
1443
|
+
capstan agent:openapi Print the OpenAPI spec JSON to stdout
|
|
1444
|
+
|
|
1445
|
+
capstan brief:check <path> [--pack-registry <path>]
|
|
1446
|
+
Validate a Capstan brief and its compiled App Graph
|
|
1447
|
+
capstan brief:inspect <path> [--pack-registry <path>]
|
|
1448
|
+
Print brief summary plus compiled App Graph introspection
|
|
1449
|
+
capstan brief:graph <path> [--pack-registry <path>]
|
|
1450
|
+
Compile a Capstan brief into an App Graph JSON document
|
|
1451
|
+
capstan brief:scaffold <brief> <dir> [--force] [--pack-registry <path>]
|
|
1452
|
+
Generate a deterministic app skeleton directly from a Capstan brief
|
|
1453
|
+
capstan graph:check <path> [--pack-registry <path>]
|
|
1454
|
+
Validate an App Graph from a JSON or ESM module
|
|
1455
|
+
capstan graph:scaffold <graph> <dir> [--force] [--pack-registry <path>]
|
|
1456
|
+
Generate a deterministic app skeleton from an App Graph
|
|
1457
|
+
capstan graph:inspect <path> [--pack-registry <path>]
|
|
1458
|
+
Print graph metadata, summary, validation, and normalized output
|
|
1459
|
+
capstan graph:diff <before> <after> [--pack-registry <path>]
|
|
1460
|
+
Print a machine-readable diff between two App Graphs
|
|
1461
|
+
capstan verify [app-dir] [--json]
|
|
1462
|
+
Verify a Capstan app (auto-detects runtime vs generated)
|
|
1463
|
+
capstan release:plan <app-dir> [--json] [--env <path>] [--migrations <path>]
|
|
1464
|
+
Generate a machine-readable preview/release plan
|
|
1465
|
+
capstan release:run <app-dir> <preview|release> [--json] [--env <path>] [--migrations <path>]
|
|
1466
|
+
Execute a framework-managed preview/release run and emit a trace
|
|
1467
|
+
capstan release:history <app-dir> [--json]
|
|
1468
|
+
List persisted preview/release/rollback traces for an app
|
|
1469
|
+
capstan release:rollback <app-dir> [--json] [--trace <path>]
|
|
1470
|
+
Execute a framework-managed rollback from a persisted release run
|
|
1471
|
+
capstan harness:start <app-dir> <task-key> [--json] [--input <path>] [--note <text>]
|
|
1472
|
+
Start a durable harness run for a generated task
|
|
1473
|
+
capstan harness:get <app-dir> <run-id> [--json]
|
|
1474
|
+
Read a persisted harness run
|
|
1475
|
+
capstan harness:list <app-dir> [--json] [--task <task-key>]
|
|
1476
|
+
List persisted harness runs
|
|
1477
|
+
capstan harness:pause <app-dir> <run-id> [--json] [--note <text>]
|
|
1478
|
+
Pause a running harness run
|
|
1479
|
+
capstan harness:resume <app-dir> <run-id> [--json] [--note <text>]
|
|
1480
|
+
Resume a paused harness run
|
|
1481
|
+
capstan harness:request-approval <app-dir> <run-id> [--json] [--note <text>]
|
|
1482
|
+
Move a harness run into approval_required
|
|
1483
|
+
capstan harness:approve <app-dir> <run-id> [--json] [--note <text>]
|
|
1484
|
+
Approve a waiting harness run and resume execution
|
|
1485
|
+
capstan harness:request-input <app-dir> <run-id> [--json] [--note <text>]
|
|
1486
|
+
Move a harness run into input_required
|
|
1487
|
+
capstan harness:provide-input <app-dir> <run-id> --input <path> [--json] [--note <text>]
|
|
1488
|
+
Attach structured human input and resume execution
|
|
1489
|
+
capstan harness:complete <app-dir> <run-id> [--json] [--output <path>] [--note <text>]
|
|
1490
|
+
Complete a harness run with structured output
|
|
1491
|
+
capstan harness:fail <app-dir> <run-id> --message <text> [--json]
|
|
1492
|
+
Fail a harness run with an error message
|
|
1493
|
+
capstan harness:cancel <app-dir> <run-id> [--json] [--note <text>]
|
|
1494
|
+
Cancel a harness run while preserving durable history
|
|
1495
|
+
capstan harness:retry <app-dir> <run-id> [--json] [--note <text>]
|
|
1496
|
+
Retry a failed or cancelled harness run
|
|
1497
|
+
capstan harness:events <app-dir> [--json] [--run <run-id>]
|
|
1498
|
+
List persisted harness lifecycle events
|
|
1499
|
+
capstan harness:replay <app-dir> <run-id> [--json]
|
|
1500
|
+
Rebuild run state from persisted harness events
|
|
1501
|
+
capstan harness:compact <app-dir> <run-id> [--json] [--tail <count>]
|
|
1502
|
+
Persist a compact runtime summary for a harness run
|
|
1503
|
+
capstan harness:summary <app-dir> <run-id> [--json] [--refresh] [--tail <count>]
|
|
1504
|
+
Read or refresh a persisted compact summary for a harness run
|
|
1505
|
+
capstan harness:summaries <app-dir> [--json]
|
|
1506
|
+
List persisted harness summaries
|
|
1507
|
+
capstan harness:memory <app-dir> <run-id> [--json] [--refresh] [--tail <count>]
|
|
1508
|
+
Read or refresh a bounded runtime memory artifact for a harness run
|
|
1509
|
+
capstan harness:memories <app-dir> [--json]
|
|
1510
|
+
List persisted runtime memory artifacts
|
|
1511
|
+
capstan help Show this help message
|
|
1512
|
+
`);
|
|
1513
|
+
}
|
|
1514
|
+
void main().catch((error) => {
|
|
1515
|
+
const message = error instanceof Error ? error.stack ?? error.message : String(error);
|
|
1516
|
+
console.error(message);
|
|
1517
|
+
process.exitCode = 1;
|
|
1518
|
+
});
|
|
1519
|
+
//# sourceMappingURL=index.js.map
|