@usegraft/cli 0.0.0-canary-20260831153011

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.
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ findConfig,
4
+ loadConfig,
5
+ loadProjectEnv,
6
+ requireDatabaseUrl
7
+ } from "./chunk-2NH6KYTL.js";
8
+
9
+ // src/commands/db.ts
10
+ import { GraftError } from "@usegraft/contracts";
11
+ async function dbMigrateCommand(options) {
12
+ loadProjectEnv(options.cwd);
13
+ const config = await loadConfig(findConfig(options.cwd));
14
+ if (config.index.driver === "static") {
15
+ throw new GraftError({
16
+ code: "NEEDS_DATABASE",
17
+ message: "This project's content index is static (a SQLite artifact), so there is no database schema to migrate.",
18
+ fix: 'Static projects need no migrations \u2014 just `graft compile`. To move to the Postgres tier: set `export const index = "postgres"` in graft.config.ts and DATABASE_URL in .env, then re-run `graft db migrate` followed by `graft compile`.',
19
+ details: { index: config.index.driver }
20
+ });
21
+ }
22
+ const url = requireDatabaseUrl();
23
+ const { runMigrations } = await import("@usegraft/db");
24
+ return runMigrations(url, { dryRun: options.dryRun });
25
+ }
26
+ function formatMigrateResult(result) {
27
+ if (result.dryRun) {
28
+ return result.pending.length === 0 ? "Schema is up to date \u2014 no migrations pending." : [
29
+ `${result.pending.length} migration(s) pending:`,
30
+ ...result.pending.map((tag) => ` ${tag}`),
31
+ "",
32
+ "Run `graft db migrate` (without --dry-run) to apply them."
33
+ ].join("\n");
34
+ }
35
+ if (result.applied.length === 0) {
36
+ return "Schema is up to date \u2014 nothing to apply.";
37
+ }
38
+ return [
39
+ `Applied ${result.applied.length} migration(s):`,
40
+ ...result.applied.map((tag) => ` ${tag}`),
41
+ "",
42
+ "Next: `graft compile` to project your content into the index."
43
+ ].join("\n");
44
+ }
45
+ export {
46
+ dbMigrateCommand,
47
+ formatMigrateResult
48
+ };
@@ -0,0 +1,124 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ assertNoStaticBranch
4
+ } from "./chunk-GDTOV3DU.js";
5
+ import {
6
+ formatCompileResult,
7
+ printGraftError
8
+ } from "./chunk-GPA66AP5.js";
9
+ import {
10
+ CONFIG_FILENAMES,
11
+ findConfig,
12
+ loadConfig,
13
+ loadProjectEnv,
14
+ requireDatabaseUrl
15
+ } from "./chunk-2NH6KYTL.js";
16
+
17
+ // src/commands/dev.ts
18
+ import { existsSync, watch } from "fs";
19
+ import { basename, relative } from "path";
20
+ import { GraftError } from "@usegraft/contracts";
21
+ var DEBOUNCE_MS = 150;
22
+ async function devCommand(options) {
23
+ loadProjectEnv(options.cwd);
24
+ const configPath = findConfig(options.cwd);
25
+ let config = await loadConfig(configPath);
26
+ if (!existsSync(config.contentDir)) {
27
+ throw new GraftError({
28
+ code: "CONTENT_DIR_NOT_FOUND",
29
+ message: `Content directory ${config.contentDir} does not exist.`,
30
+ fix: "Create it (documents live at <contentDir>/<collection>/<slug>.mdx) or fix the `contentDir` export in graft.config.ts, then restart `graft dev`."
31
+ });
32
+ }
33
+ const isStatic = config.index.driver === "static";
34
+ if (isStatic) assertNoStaticBranch(options.branchId);
35
+ const url = isStatic ? void 0 : requireDatabaseUrl();
36
+ const [compiler, { createDb, resolveBranchHandle, scopeWriteBranch }] = await Promise.all([
37
+ import("@usegraft/compiler"),
38
+ import("@usegraft/db")
39
+ ]);
40
+ const { compile, compileStatic } = compiler;
41
+ const control = url !== void 0 ? createDb(url) : void 0;
42
+ const branch = control !== void 0 && url !== void 0 ? await resolveBranchHandle(control.db, options.branchId ?? "main", { databaseUrl: url }) : void 0;
43
+ const writeBranch = branch !== void 0 ? scopeWriteBranch(branch.scope) : "main";
44
+ let timer;
45
+ let rewatchContent;
46
+ let compiling = false;
47
+ let dirty = false;
48
+ let configDirty = false;
49
+ const runCompile = async () => {
50
+ if (compiling) {
51
+ dirty = true;
52
+ return;
53
+ }
54
+ compiling = true;
55
+ try {
56
+ if (configDirty) {
57
+ configDirty = false;
58
+ const previousContentDir = config.contentDir;
59
+ config = await loadConfig(configPath);
60
+ console.log(`reloaded ${basename(configPath)}`);
61
+ if (config.contentDir !== previousContentDir) rewatchContent?.(config.contentDir);
62
+ }
63
+ const result = branch !== void 0 ? await compile({
64
+ db: branch.db,
65
+ contentDir: config.contentDir,
66
+ collections: config.collections,
67
+ mdxTrust: config.mdxTrust,
68
+ branchId: writeBranch
69
+ }) : await compileStatic({
70
+ contentDir: config.contentDir,
71
+ collections: config.collections,
72
+ mdxTrust: config.mdxTrust,
73
+ indexPath: config.index.path
74
+ });
75
+ console.log(formatCompileResult(result));
76
+ } catch (error) {
77
+ if (error instanceof GraftError) printGraftError(error);
78
+ else console.error(error);
79
+ } finally {
80
+ compiling = false;
81
+ if (dirty) {
82
+ dirty = false;
83
+ schedule();
84
+ }
85
+ }
86
+ };
87
+ const schedule = (configChanged = false) => {
88
+ if (configChanged) configDirty = true;
89
+ clearTimeout(timer);
90
+ timer = setTimeout(() => void runCompile(), DEBOUNCE_MS);
91
+ };
92
+ await runCompile();
93
+ let contentWatcher = watch(config.contentDir, { recursive: true }, () => schedule());
94
+ const watchers = [
95
+ watch(config.projectDir, (_event, filename) => {
96
+ if (filename && CONFIG_FILENAMES.includes(filename)) schedule(true);
97
+ })
98
+ ];
99
+ rewatchContent = (dir) => {
100
+ contentWatcher.close();
101
+ contentWatcher = watch(dir, { recursive: true }, () => schedule());
102
+ console.log(`now watching ${relative(options.cwd, dir) || "."}`);
103
+ };
104
+ const watchedContent = relative(options.cwd, config.contentDir) || ".";
105
+ const branchLabel = branch === void 0 ? "static index" : branch.scope.kind === "physical" ? `${branch.name} (neon)` : options.branchId ?? "main";
106
+ console.log(
107
+ `Watching ${watchedContent} + ${basename(configPath)} (branch: ${branchLabel}). Ctrl+C to stop.`
108
+ );
109
+ await new Promise((resolveStopped) => {
110
+ const stop = () => {
111
+ clearTimeout(timer);
112
+ contentWatcher.close();
113
+ for (const watcher of watchers) watcher.close();
114
+ resolveStopped();
115
+ };
116
+ process.once("SIGINT", stop);
117
+ process.once("SIGTERM", stop);
118
+ });
119
+ await branch?.close();
120
+ await control?.close();
121
+ }
122
+ export {
123
+ devCommand
124
+ };
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ loadProjectEnv,
4
+ requireDatabaseUrl
5
+ } from "./chunk-2NH6KYTL.js";
6
+
7
+ // src/commands/harden.ts
8
+ async function hardenCommand(options) {
9
+ loadProjectEnv(options.cwd);
10
+ const url = requireDatabaseUrl();
11
+ const [{ createDb, hardenRuntimeRole }, { GraftError }] = await Promise.all([
12
+ import("@usegraft/db"),
13
+ import("@usegraft/contracts")
14
+ ]);
15
+ const handle = createDb(url);
16
+ try {
17
+ const statements = await hardenRuntimeRole(handle.db, options.role);
18
+ return { role: options.role, statements };
19
+ } catch (error) {
20
+ if (error instanceof GraftError) throw error;
21
+ let code;
22
+ for (let e = error; e; e = e.cause) {
23
+ if (typeof e.code === "string") {
24
+ code = e.code;
25
+ break;
26
+ }
27
+ }
28
+ if (code === "42704") {
29
+ throw new GraftError({
30
+ code: "INPUT_VALIDATION_FAILED",
31
+ message: `Role "${options.role}" does not exist in this database.`,
32
+ fix: `Create it first (as the operator): CREATE ROLE ${options.role} LOGIN PASSWORD '\u2026' \u2014 or via your provider's console \u2014 then rerun \`graft harden ${options.role}\`.`,
33
+ details: { role: options.role }
34
+ });
35
+ }
36
+ throw error;
37
+ } finally {
38
+ await handle.close();
39
+ }
40
+ }
41
+ export {
42
+ hardenCommand
43
+ };
package/dist/index.js ADDED
@@ -0,0 +1,437 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ printGraftError
4
+ } from "./chunk-GPA66AP5.js";
5
+
6
+ // src/cli.ts
7
+ import { createRequire } from "module";
8
+ var VERSION = createRequire(import.meta.url)("../package.json").version;
9
+ var PLANNED = [];
10
+ function printHelp() {
11
+ const lines = [
12
+ "graft \u2014 the agent-native CMS",
13
+ "",
14
+ "Usage: graft <command> [options]",
15
+ "",
16
+ "Commands:",
17
+ " init [dir] Scaffold a Graft project (graft.config.ts, content/, llms.txt).",
18
+ " Static index by default \u2014 no database, no env. --postgres for the",
19
+ " Postgres tier (operational data, functions, DB branching)",
20
+ " compile Project the content tree into the content index once",
21
+ " dev Watch content/ + graft.config.ts and recompile on change",
22
+ " db migrate Apply the Postgres schema migrations that ship with @usegraft/db",
23
+ " (the first command a Postgres-tier project runs; --dry-run lists them)",
24
+ " asset put <file> [key] Upload a binary to the asset store (S3_* env)",
25
+ " approvals List pending approvals for human-gated (destructive) function calls",
26
+ " approve <id> Approve a pending approval (the caller retries with x-graft-approval)",
27
+ " deny <id> Deny a pending approval",
28
+ " migrate Show pending content/data migrations (dry-run); --apply runs them",
29
+ " branch List branches (name, parent, backend)",
30
+ " branch create <name> Register a preview branch (instant; --from <parent>, default main;",
31
+ " --backend neon forks a physical Neon branch)",
32
+ " branch drop <name> Drop a branch (overlay: purge rows; neon: delete the fork)",
33
+ " merge <name> Merge a branch into --into (default main): replay ledger,",
34
+ " move data rows, recompile. Dry-run; --apply executes",
35
+ " add <item> Copy an owned primitive from the registry into graft/",
36
+ " (+ its deps; regenerates the graft/ barrel \u2014 no config edit)",
37
+ " mcp Serve the project MCP over stdio (content + function tools;",
38
+ " for .mcp.json / local agents). Requires DATABASE_URL.",
39
+ " serve Run the headless Graft runtime over HTTP: POST /api/fn/<name>,",
40
+ " POST /api/mcp, GET /healthz (what a self-host container runs)",
41
+ " studio Opt-in Studio UI (edit content, approve/deny, OpenAPI)",
42
+ " content List the content tree from the compiled index (Studio parity)",
43
+ " compilations List recent content projection trail rows (Studio parity)",
44
+ " harden <role> Grant an existing Postgres role the runtime privilege set",
45
+ " (can request + consume approvals but never decide them)",
46
+ ...PLANNED.map((cmd) => ` ${cmd.name.padEnd(12)} ${cmd.summary} (${cmd.phase})`),
47
+ "",
48
+ "Options:",
49
+ " --branch <id> Content branch (compile/dev/migrate/mcp/serve/studio/content/compilations; default: main)",
50
+ " --port <n> Port for `graft serve` / `graft studio` (serve default 3903; studio 4983)",
51
+ " --host <h> Host for `graft serve` / `graft studio` (default: 127.0.0.1, or HOST)",
52
+ " --studio Mount opt-in Studio on `graft serve` at /studio (or GRAFT_STUDIO=1)",
53
+ " --elicit-approvals",
54
+ " `graft mcp` asks THIS terminal's operator to decide a destructive call,",
55
+ " instead of failing with an id for `graft approve`. Local only: the human",
56
+ " must be present and the client must support MCP elicitation. Decisions",
57
+ " are recorded as the OS user, exactly as `graft approve` records them.",
58
+ " --from <name> Parent to fork from (branch create; default: main)",
59
+ " --into <name> Merge target (merge; default: main)",
60
+ " --backend <kind> Branch backend: overlay (default) or neon (branch create)",
61
+ " --apply Execute pending migrations / the merge (default is a dry-run report)",
62
+ " --postgres Scaffold `graft init` for the Postgres index (default: static)",
63
+ " --static Scaffold `graft init` for the static index (the default)",
64
+ " --dry-run Preview `graft add` without writing; list pending `graft db migrate`",
65
+ " --overwrite Let `graft add` replace files that differ",
66
+ " --prune-unknown Let `graft compile` remove index rows in collections this schema",
67
+ " doesn't know (default: refuse \u2014 the shared-DATABASE_URL guard)",
68
+ " -h, --help Show this help",
69
+ " -v, --version Show version"
70
+ ];
71
+ console.log(lines.join("\n"));
72
+ }
73
+ var UsageError = class extends Error {
74
+ };
75
+ function parseArgs(rest) {
76
+ const positionals = [];
77
+ let branchId;
78
+ let from;
79
+ let into;
80
+ let backend;
81
+ let port;
82
+ let host;
83
+ let apply = false;
84
+ let dryRun = false;
85
+ let overwrite = false;
86
+ let pruneUnknown = false;
87
+ let studio = false;
88
+ let elicitApprovals = false;
89
+ let initDriver;
90
+ const value = (flag, raw) => {
91
+ if (!raw || raw.startsWith("-")) {
92
+ throw new UsageError(`${flag} requires a value, e.g. ${flag} main`);
93
+ }
94
+ return raw;
95
+ };
96
+ for (let i = 0; i < rest.length; i++) {
97
+ const arg = rest[i];
98
+ if (arg === "--branch" || arg === "-b") {
99
+ branchId = value("--branch", rest[++i]);
100
+ } else if (arg === "--from") {
101
+ from = value("--from", rest[++i]);
102
+ } else if (arg === "--into") {
103
+ into = value("--into", rest[++i]);
104
+ } else if (arg === "--backend") {
105
+ backend = value("--backend", rest[++i]);
106
+ } else if (arg === "--port") {
107
+ const raw = rest[++i];
108
+ if (raw === void 0 || !/^\d+$/.test(raw)) {
109
+ throw new UsageError("--port requires a number, e.g. --port 3903");
110
+ }
111
+ port = Number(raw);
112
+ } else if (arg === "--host") {
113
+ host = value("--host", rest[++i]);
114
+ } else if (arg === "--apply") {
115
+ apply = true;
116
+ } else if (arg === "--dry-run") {
117
+ dryRun = true;
118
+ } else if (arg === "--overwrite") {
119
+ overwrite = true;
120
+ } else if (arg === "--prune-unknown") {
121
+ pruneUnknown = true;
122
+ } else if (arg === "--studio") {
123
+ studio = true;
124
+ } else if (arg === "--elicit-approvals") {
125
+ elicitApprovals = true;
126
+ } else if (arg === "--postgres") {
127
+ initDriver = "postgres";
128
+ } else if (arg === "--static") {
129
+ initDriver = "static";
130
+ } else if (arg.startsWith("-")) {
131
+ throw new UsageError(`unknown option "${arg}"`);
132
+ } else {
133
+ positionals.push(arg);
134
+ }
135
+ }
136
+ return {
137
+ positionals,
138
+ branchId,
139
+ from,
140
+ into,
141
+ backend,
142
+ port,
143
+ host,
144
+ apply,
145
+ dryRun,
146
+ overwrite,
147
+ pruneUnknown,
148
+ studio,
149
+ elicitApprovals,
150
+ initDriver
151
+ };
152
+ }
153
+ async function run(argv, options = {}) {
154
+ const cwd = options.cwd ?? process.cwd();
155
+ const [command, ...rest] = argv;
156
+ if (command === "-v" || command === "--version") {
157
+ console.log(VERSION);
158
+ return 0;
159
+ }
160
+ if (!command || command === "-h" || command === "--help") {
161
+ printHelp();
162
+ return 0;
163
+ }
164
+ try {
165
+ const args = parseArgs(rest);
166
+ switch (command) {
167
+ case "init": {
168
+ const { initCommand } = await import("./init-TRPSGUYW.js");
169
+ const result = initCommand({
170
+ targetDir: args.positionals[0] ?? cwd,
171
+ driver: args.initDriver
172
+ });
173
+ console.log(
174
+ `Initialized a Graft project in ${result.projectDir} (${result.driver} index, ${result.created.length} file(s)):`
175
+ );
176
+ for (const file of result.created) console.log(` created ${file}`);
177
+ console.log(
178
+ result.driver === "static" ? [
179
+ "",
180
+ "Next steps \u2014 no database, no environment variables:",
181
+ " 1. Install the runtime: add @usegraft/core and zod (plus @usegraft/cli as a dev dep)",
182
+ " 2. Run `graft compile` once, or keep `graft dev` running while you edit",
183
+ "",
184
+ "The index compiles to .graft/index.db (git-ignored \u2014 it is derived from your",
185
+ "files). Run `graft compile` before your app builds so it ships with the deploy.",
186
+ "Need operational data, typed functions, or DB branching? Switch `index` to",
187
+ '"postgres" in graft.config.ts and set DATABASE_URL.'
188
+ ].join("\n") : [
189
+ "",
190
+ "Next steps:",
191
+ " 1. Install the runtime: add @usegraft/core and zod (plus @usegraft/cli as a dev dep)",
192
+ " 2. Put DATABASE_URL=postgres://\u2026 in .env",
193
+ " 3. Run `graft compile` once, or keep `graft dev` running while you edit"
194
+ ].join("\n")
195
+ );
196
+ return 0;
197
+ }
198
+ case "compile": {
199
+ const { compileCommand } = await import("./compile-TCBGZZAU.js");
200
+ await compileCommand({ cwd, branchId: args.branchId, pruneUnknown: args.pruneUnknown });
201
+ return 0;
202
+ }
203
+ case "dev": {
204
+ const { devCommand } = await import("./dev-4YSMWIAS.js");
205
+ await devCommand({ cwd, branchId: args.branchId });
206
+ return 0;
207
+ }
208
+ case "db": {
209
+ const [subcommand] = args.positionals;
210
+ if (subcommand !== "migrate") {
211
+ throw new UsageError(
212
+ subcommand ? `unknown db subcommand "${subcommand}" \u2014 the only one is \`graft db migrate\`` : "usage: graft db migrate [--dry-run]"
213
+ );
214
+ }
215
+ const { dbMigrateCommand, formatMigrateResult } = await import("./db-WACSH3HN.js");
216
+ const result = await dbMigrateCommand({ cwd, dryRun: args.dryRun });
217
+ console.log(formatMigrateResult(result));
218
+ return 0;
219
+ }
220
+ case "asset": {
221
+ const [subcommand, file, key] = args.positionals;
222
+ if (subcommand !== "put" || !file) {
223
+ throw new UsageError("usage: graft asset put <file> [key]");
224
+ }
225
+ const { assetPutCommand } = await import("./asset-DMNX5JBZ.js");
226
+ const result = await assetPutCommand({ cwd, file, key });
227
+ console.log(`Uploaded ${result.key} (${result.contentType}, ${result.bytes} bytes)`);
228
+ console.log(
229
+ [
230
+ "",
231
+ "Reference it from an `asset` field in frontmatter:",
232
+ ` image:`,
233
+ ` key: ${result.key}`,
234
+ " alt: describe the image for screen readers"
235
+ ].join("\n")
236
+ );
237
+ return 0;
238
+ }
239
+ case "approvals": {
240
+ const { approvalsListCommand, formatApproval } = await import("./approvals-OEEQQN75.js");
241
+ const pending = await approvalsListCommand({ cwd });
242
+ if (pending.length === 0) {
243
+ console.log("No pending approvals.");
244
+ return 0;
245
+ }
246
+ console.log(`${pending.length} pending approval(s):
247
+ `);
248
+ for (const row of pending) console.log(`${formatApproval(row)}
249
+ `);
250
+ console.log("Decide with `graft approve <id>` or `graft deny <id>`.");
251
+ return 0;
252
+ }
253
+ case "approve":
254
+ case "deny": {
255
+ const id = args.positionals[0];
256
+ if (!id) throw new UsageError(`usage: graft ${command} <approval-id>`);
257
+ const { decideCommand } = await import("./approvals-OEEQQN75.js");
258
+ const decision = command === "approve" ? "approved" : "denied";
259
+ const row = await decideCommand({ cwd, id, decision });
260
+ console.log(
261
+ `${decision}: ${row.functionName} ${JSON.stringify(row.input)} (by ${row.decidedBy})`
262
+ );
263
+ if (decision === "approved") {
264
+ console.log(
265
+ `The caller can now retry the exact same request with the header \`x-graft-approval: ${row.id}\` (one-shot).`
266
+ );
267
+ }
268
+ return 0;
269
+ }
270
+ case "migrate": {
271
+ const { migrateCommand } = await import("./migrate-QB44VL6R.js");
272
+ await migrateCommand({ cwd, branchId: args.branchId, apply: args.apply });
273
+ return 0;
274
+ }
275
+ case "branch": {
276
+ const [subcommand, name] = args.positionals;
277
+ if (subcommand && subcommand !== "create" && subcommand !== "drop") {
278
+ throw new UsageError(
279
+ `unknown branch subcommand "${subcommand}" \u2014 use \`graft branch\`, \`graft branch create <name>\`, or \`graft branch drop <name>\``
280
+ );
281
+ }
282
+ if (subcommand && !name) {
283
+ throw new UsageError(`usage: graft branch ${subcommand} <name>`);
284
+ }
285
+ const mod = await import("./branch-IDY42TAV.js");
286
+ if (!subcommand) {
287
+ const rows = await mod.branchListCommand({ cwd });
288
+ if (rows.length === 0) {
289
+ console.log("No branches registered (main is seeded by migration 0006).");
290
+ return 0;
291
+ }
292
+ for (const row of rows) console.log(mod.formatBranch(row));
293
+ return 0;
294
+ }
295
+ if (subcommand === "create" && name) {
296
+ const meta = await mod.branchCreateCommand({
297
+ cwd,
298
+ name,
299
+ from: args.from,
300
+ backend: args.backend
301
+ });
302
+ console.log(
303
+ meta.backend === "neon" ? [
304
+ `Created neon branch "${meta.name}" from "${meta.parent}" \u2014 a physical fork at ${meta.endpointHost}.`,
305
+ `Content is inherited; operational data and approvals start empty on the fork.`,
306
+ ` graft compile --branch ${meta.name} (routes to the fork automatically)`,
307
+ `Merge it back with \`graft merge ${meta.name}\` when ready.`
308
+ ].join("\n") : [
309
+ `Created branch "${meta.name}" from "${meta.parent}" (${meta.backend} \u2014 zero rows copied).`,
310
+ `Reads overlay the parent until the branch writes its own rows:`,
311
+ ` graft compile --branch ${meta.name}`,
312
+ `Merge it back with \`graft merge ${meta.name}\` when ready.`
313
+ ].join("\n")
314
+ );
315
+ return 0;
316
+ }
317
+ const result = await mod.branchDropCommand({ cwd, name });
318
+ const p = result.purged;
319
+ console.log(
320
+ result.backend === "neon" ? `Dropped neon branch "${name}" (the fork and its endpoint were deleted).` : `Dropped branch "${name}"` + (p ? ` (purged ${p.content} content, ${p.data} data, ${p.ledger} ledger row(s)).` : ".")
321
+ );
322
+ return 0;
323
+ }
324
+ case "merge": {
325
+ const branch = args.positionals[0];
326
+ if (!branch)
327
+ throw new UsageError("usage: graft merge <branch> [--into <target>] [--apply]");
328
+ const { mergeCommand } = await import("./merge-XMTFPLHO.js");
329
+ await mergeCommand({ cwd, branch, into: args.into, apply: args.apply });
330
+ return 0;
331
+ }
332
+ case "add": {
333
+ const { addCommand } = await import("./add-ASXTMYCX.js");
334
+ await addCommand({
335
+ cwd,
336
+ names: args.positionals,
337
+ dryRun: args.dryRun,
338
+ overwrite: args.overwrite
339
+ });
340
+ return 0;
341
+ }
342
+ case "mcp": {
343
+ const { mcpCommand } = await import("./mcp-DKFT2M4Q.js");
344
+ await mcpCommand({ cwd, branchId: args.branchId, elicitApprovals: args.elicitApprovals });
345
+ return 0;
346
+ }
347
+ case "serve": {
348
+ const { serveCommand } = await import("./serve-5ABBWZU2.js");
349
+ await serveCommand({
350
+ cwd,
351
+ branchId: args.branchId,
352
+ port: args.port,
353
+ host: args.host,
354
+ studio: args.studio
355
+ });
356
+ return 0;
357
+ }
358
+ case "studio": {
359
+ const { studioCommand } = await import("./studio-6ZO3FMDR.js");
360
+ await studioCommand({
361
+ cwd,
362
+ branchId: args.branchId,
363
+ port: args.port,
364
+ host: args.host
365
+ });
366
+ return 0;
367
+ }
368
+ case "content": {
369
+ const { contentListCommand, formatContentLine } = await import("./content-VLKNYIHL.js");
370
+ const { branch, lines } = await contentListCommand({ cwd, branchId: args.branchId });
371
+ if (lines.length === 0) {
372
+ console.log(`No content on branch "${branch}" (run graft compile).`);
373
+ return 0;
374
+ }
375
+ console.log(`${lines.length} document(s) on branch "${branch}":
376
+ `);
377
+ for (const line of lines) console.log(formatContentLine(line));
378
+ return 0;
379
+ }
380
+ case "compilations": {
381
+ const { compilationsListCommand, formatCompilation } = await import("./compilations-72DJ2VEP.js");
382
+ const rows = await compilationsListCommand({ cwd, branchId: args.branchId });
383
+ if (rows.length === 0) {
384
+ console.log("No compilations recorded yet (run graft compile).");
385
+ return 0;
386
+ }
387
+ for (const row of rows) console.log(formatCompilation(row));
388
+ return 0;
389
+ }
390
+ case "harden": {
391
+ const role = args.positionals[0];
392
+ if (!role) throw new UsageError("usage: graft harden <role>");
393
+ const { hardenCommand } = await import("./harden-QASY2KMI.js");
394
+ const result = await hardenCommand({ cwd, role });
395
+ console.log(`Hardened runtime role "${result.role}" (${result.statements.length} grants):`);
396
+ for (const statement of result.statements) console.log(` ${statement}`);
397
+ console.log(
398
+ [
399
+ "",
400
+ "This role can request + consume approvals but can NEVER decide them",
401
+ "(no UPDATE on approvals) or rewrite content projections. Hand its",
402
+ "connection URL to the deployment as DATABASE_URL; keep the operator",
403
+ "URL for graft compile/migrate/branch/merge/approve."
404
+ ].join("\n")
405
+ );
406
+ return 0;
407
+ }
408
+ default: {
409
+ const planned = PLANNED.find((cmd) => cmd.name === command);
410
+ if (planned) {
411
+ console.error(
412
+ `graft: "${planned.name}" is planned for ${planned.phase} and is not implemented yet.`
413
+ );
414
+ return 1;
415
+ }
416
+ console.error(`graft: unknown command "${command}"
417
+ `);
418
+ printHelp();
419
+ return 1;
420
+ }
421
+ }
422
+ } catch (error) {
423
+ if (error instanceof UsageError) {
424
+ console.error(`graft: ${error.message}`);
425
+ return 1;
426
+ }
427
+ const { GraftError } = await import("@usegraft/contracts");
428
+ if (error instanceof GraftError) {
429
+ printGraftError(error);
430
+ return 1;
431
+ }
432
+ throw error;
433
+ }
434
+ }
435
+
436
+ // src/index.ts
437
+ process.exit(await run(process.argv.slice(2)));