forgeos 0.1.0-alpha.32 → 0.1.0-alpha.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +23 -0
  3. package/docs/changelog.md +33 -0
  4. package/package.json +1 -1
  5. package/src/forge/_generated/releaseManifest.json +1 -1
  6. package/src/forge/_generated/releaseManifest.ts +3 -3
  7. package/src/forge/cli/changed.ts +102 -8
  8. package/src/forge/cli/commands.ts +116 -4
  9. package/src/forge/cli/dev.ts +228 -0
  10. package/src/forge/cli/handoff.ts +17 -0
  11. package/src/forge/cli/main.ts +36 -0
  12. package/src/forge/cli/new.ts +4 -0
  13. package/src/forge/cli/parse.ts +24 -6
  14. package/src/forge/cli/workos.ts +250 -23
  15. package/src/forge/compiler/app-graph/build.ts +44 -1
  16. package/src/forge/compiler/client-sdk/render-client.ts +31 -0
  17. package/src/forge/compiler/data-graph/parse.ts +17 -1
  18. package/src/forge/compiler/data-graph/sql/ddl.ts +24 -13
  19. package/src/forge/compiler/diagnostics/codes.ts +6 -0
  20. package/src/forge/compiler/diagnostics/create.ts +14 -0
  21. package/src/forge/compiler/integration/plan.ts +6 -0
  22. package/src/forge/compiler/integration/render.ts +9 -0
  23. package/src/forge/compiler/integration/templates/render.ts +2 -0
  24. package/src/forge/compiler/integration/templates/types.ts +3 -0
  25. package/src/forge/compiler/integration/templates/workos.ts +165 -99
  26. package/src/forge/compiler/orchestrator/plan.ts +7 -0
  27. package/src/forge/delta/status.ts +10 -1
  28. package/src/forge/impact/index.ts +158 -1
  29. package/src/forge/impact/types.ts +27 -1
  30. package/src/forge/react/index.ts +19 -2
  31. package/src/forge/runtime/auth/resolve.ts +33 -3
  32. package/src/forge/runtime/auth/types.ts +1 -0
  33. package/src/forge/server.ts +5 -1
  34. package/src/forge/version.ts +1 -1
  35. package/src/forge/vue/index.ts +16 -1
  36. package/src/forge/workspace/git-summary.ts +1 -1
  37. package/templates/nuxt-web/web/package.json +2 -1
  38. package/templates/nuxt-web/web/tsconfig.json +4 -1
@@ -1,4 +1,5 @@
1
1
  import { spawn } from "node:child_process";
2
+ import { closeSync, openSync } from "node:fs";
2
3
  import { createServer as createNetServer } from "node:net";
3
4
  import { run } from "../compiler/orchestrator/run.ts";
4
5
  import { basename, join } from "node:path";
@@ -56,6 +57,8 @@ export interface DevCommandOptions {
56
57
  mode?: "dev" | "serve";
57
58
  allowDevAuth?: boolean;
58
59
  skipStartupConsole?: boolean;
60
+ detach?: boolean;
61
+ lifecycle?: "status" | "stop";
59
62
  }
60
63
 
61
64
  export interface DevCommandResult {
@@ -173,6 +176,221 @@ interface DevStartFailure {
173
176
  };
174
177
  }
175
178
 
179
+ const DEV_STATE_DIR = ".forge/dev";
180
+ const DEV_PID_FILE = `${DEV_STATE_DIR}/dev.pid`;
181
+ const DEV_LOG_FILE = `${DEV_STATE_DIR}/dev.log`;
182
+
183
+ function devStatePaths(workspaceRoot: string): { dir: string; pidFile: string; logFile: string } {
184
+ return {
185
+ dir: join(workspaceRoot, DEV_STATE_DIR),
186
+ pidFile: join(workspaceRoot, DEV_PID_FILE),
187
+ logFile: join(workspaceRoot, DEV_LOG_FILE),
188
+ };
189
+ }
190
+
191
+ function readDetachedDevPid(workspaceRoot: string): number | null {
192
+ const text = nodeFileSystem.readText(devStatePaths(workspaceRoot).pidFile);
193
+ if (text === null) {
194
+ return null;
195
+ }
196
+ const pid = Number(text.trim());
197
+ return Number.isInteger(pid) && pid > 0 ? pid : null;
198
+ }
199
+
200
+ function isProcessRunning(pid: number): boolean {
201
+ try {
202
+ process.kill(pid, 0);
203
+ return true;
204
+ } catch (error) {
205
+ return error instanceof Error && "code" in error && error.code === "EPERM";
206
+ }
207
+ }
208
+
209
+ function removeDevPidFile(workspaceRoot: string): void {
210
+ const paths = devStatePaths(workspaceRoot);
211
+ if (nodeFileSystem.exists(paths.pidFile)) {
212
+ nodeFileSystem.remove(paths.pidFile);
213
+ }
214
+ }
215
+
216
+ function printDevLifecycleResult(input: {
217
+ options: DevCommandOptions;
218
+ ok: boolean;
219
+ action: "detach" | "status" | "stop";
220
+ running: boolean;
221
+ pid?: number;
222
+ logFile: string;
223
+ message: string;
224
+ exitCode: 0 | 1;
225
+ }): DevCommandResult {
226
+ const payload = {
227
+ ok: input.ok,
228
+ action: input.action,
229
+ running: input.running,
230
+ ...(input.pid !== undefined ? { pid: input.pid } : {}),
231
+ logFile: input.logFile,
232
+ message: input.message,
233
+ nextActions: input.running
234
+ ? ["forge dev status --json", "forge dev stop --json", `tail -f ${input.logFile}`]
235
+ : ["forge dev --detach --json"],
236
+ exitCode: input.exitCode,
237
+ };
238
+ if (input.options.json) {
239
+ process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
240
+ } else {
241
+ process.stdout.write(`${input.message}\n`);
242
+ if (input.pid !== undefined) process.stdout.write(`pid: ${input.pid}\n`);
243
+ process.stdout.write(`log: ${input.logFile}\n`);
244
+ }
245
+ return { exitCode: input.exitCode };
246
+ }
247
+
248
+ function buildDetachedDevArgs(options: DevCommandOptions): string[] {
249
+ const args = ["dev", "--skip-startup-console"];
250
+ if (options.host) args.push("--host", options.host);
251
+ if (options.port !== undefined) args.push("--port", String(options.port));
252
+ if (options.mock) args.push("--mock");
253
+ if (options.mockAi) args.push("--mock-ai");
254
+ if (!options.watch) args.push("--no-watch");
255
+ if (options.db) args.push("--db", options.db);
256
+ if (options.databaseUrl) args.push("--database-url", options.databaseUrl);
257
+ if (!options.worker) args.push("--no-worker");
258
+ if (options.withWeb === false) args.push("--no-web");
259
+ if (options.apiOnly) args.push("--api-only");
260
+ if (options.webOnly) args.push("--web-only");
261
+ if (options.open) args.push("--open");
262
+ if (options.webPort !== undefined) args.push("--web-port", String(options.webPort));
263
+ if (options.telemetry.length > 0) args.push("--telemetry", options.telemetry.join(","));
264
+ if (options.envFile) args.push("--env-file", options.envFile);
265
+ return args;
266
+ }
267
+
268
+ function runDevStatus(options: DevCommandOptions): DevCommandResult {
269
+ const paths = devStatePaths(options.workspaceRoot);
270
+ const pid = readDetachedDevPid(options.workspaceRoot);
271
+ const running = pid !== null && isProcessRunning(pid);
272
+ if (pid !== null && !running) {
273
+ removeDevPidFile(options.workspaceRoot);
274
+ }
275
+ return printDevLifecycleResult({
276
+ options,
277
+ ok: true,
278
+ action: "status",
279
+ running,
280
+ ...(pid !== null ? { pid } : {}),
281
+ logFile: paths.logFile,
282
+ message: running ? "forge dev detached server is running" : "forge dev detached server is not running",
283
+ exitCode: 0,
284
+ });
285
+ }
286
+
287
+ function runDevStop(options: DevCommandOptions): DevCommandResult {
288
+ const paths = devStatePaths(options.workspaceRoot);
289
+ const pid = readDetachedDevPid(options.workspaceRoot);
290
+ if (pid === null || !isProcessRunning(pid)) {
291
+ removeDevPidFile(options.workspaceRoot);
292
+ return printDevLifecycleResult({
293
+ options,
294
+ ok: true,
295
+ action: "stop",
296
+ running: false,
297
+ ...(pid !== null ? { pid } : {}),
298
+ logFile: paths.logFile,
299
+ message: "no detached forge dev server was running",
300
+ exitCode: 0,
301
+ });
302
+ }
303
+ process.kill(pid, "SIGTERM");
304
+ removeDevPidFile(options.workspaceRoot);
305
+ return printDevLifecycleResult({
306
+ options,
307
+ ok: true,
308
+ action: "stop",
309
+ running: false,
310
+ pid,
311
+ logFile: paths.logFile,
312
+ message: "stopped detached forge dev server",
313
+ exitCode: 0,
314
+ });
315
+ }
316
+
317
+ function runDevDetach(options: DevCommandOptions): DevCommandResult {
318
+ if (options.once) {
319
+ return printDevLifecycleResult({
320
+ options,
321
+ ok: false,
322
+ action: "detach",
323
+ running: false,
324
+ logFile: devStatePaths(options.workspaceRoot).logFile,
325
+ message: "forge dev --detach cannot be combined with --once",
326
+ exitCode: 1,
327
+ });
328
+ }
329
+ const existingPid = readDetachedDevPid(options.workspaceRoot);
330
+ const paths = devStatePaths(options.workspaceRoot);
331
+ nodeFileSystem.mkdirp(paths.dir);
332
+ if (existingPid !== null && isProcessRunning(existingPid)) {
333
+ return printDevLifecycleResult({
334
+ options,
335
+ ok: true,
336
+ action: "detach",
337
+ running: true,
338
+ pid: existingPid,
339
+ logFile: paths.logFile,
340
+ message: "forge dev detached server is already running",
341
+ exitCode: 0,
342
+ });
343
+ }
344
+
345
+ const cliPath = process.argv[1];
346
+ if (!cliPath) {
347
+ return printDevLifecycleResult({
348
+ options,
349
+ ok: false,
350
+ action: "detach",
351
+ running: false,
352
+ logFile: paths.logFile,
353
+ message: "could not resolve current forge CLI path for detached dev server",
354
+ exitCode: 1,
355
+ });
356
+ }
357
+
358
+ const fd = openSync(paths.logFile, "a");
359
+ try {
360
+ const child = spawn(process.execPath, [cliPath, ...buildDetachedDevArgs(options)], {
361
+ cwd: options.workspaceRoot,
362
+ detached: true,
363
+ stdio: ["ignore", fd, fd],
364
+ windowsHide: true,
365
+ });
366
+ if (child.pid === undefined) {
367
+ return printDevLifecycleResult({
368
+ options,
369
+ ok: false,
370
+ action: "detach",
371
+ running: false,
372
+ logFile: paths.logFile,
373
+ message: "detached forge dev process started without a pid",
374
+ exitCode: 1,
375
+ });
376
+ }
377
+ child.unref();
378
+ nodeFileSystem.writeText(paths.pidFile, `${child.pid}\n`);
379
+ return printDevLifecycleResult({
380
+ options,
381
+ ok: true,
382
+ action: "detach",
383
+ running: true,
384
+ pid: child.pid,
385
+ logFile: paths.logFile,
386
+ message: "started detached forge dev server",
387
+ exitCode: 0,
388
+ });
389
+ } finally {
390
+ closeSync(fd);
391
+ }
392
+ }
393
+
176
394
  function nextPreviewPort(webUrl?: string): number {
177
395
  if (!webUrl) {
178
396
  return 5174;
@@ -862,6 +1080,16 @@ export async function runDevCommand(
862
1080
  const workspaceRoot = options.workspaceRoot.replace(/\\/g, "/");
863
1081
  const startedAt = new Date();
864
1082
 
1083
+ if (options.lifecycle === "status") {
1084
+ return runDevStatus({ ...options, workspaceRoot });
1085
+ }
1086
+ if (options.lifecycle === "stop") {
1087
+ return runDevStop({ ...options, workspaceRoot });
1088
+ }
1089
+ if (options.detach) {
1090
+ return runDevDetach({ ...options, workspaceRoot });
1091
+ }
1092
+
865
1093
  if (options.once) {
866
1094
  const cycle = await runDevConsoleCycle({
867
1095
  workspaceRoot,
@@ -8,10 +8,12 @@ import type { UiRunReport } from "../ui/types.ts";
8
8
  import { summarizeChangeTypes, type CategorizedFileSummary } from "../workspace/change-summary.ts";
9
9
  import { forgeCliCommandsForWorkspace } from "../workspace/forge-cli.ts";
10
10
  import { buildWorkspaceGitSummary } from "../workspace/git-summary.ts";
11
+ import { runChangedCommand } from "./changed.ts";
11
12
 
12
13
  export interface HandoffCommandOptions {
13
14
  workspaceRoot: string;
14
15
  json: boolean;
16
+ commitReady?: boolean;
15
17
  }
16
18
 
17
19
  export interface HandoffCommandResult {
@@ -93,6 +95,10 @@ export interface HandoffCommandResult {
93
95
  recommendedCommands: string[];
94
96
  risks: string[];
95
97
  };
98
+ commitReady?: {
99
+ count: number;
100
+ files: string[];
101
+ };
96
102
  diagnosticSummary: {
97
103
  total: number;
98
104
  sample: Diagnostic[];
@@ -205,6 +211,9 @@ export async function runHandoffCommand(options: HandoffCommandOptions): Promise
205
211
  includeImpact: true,
206
212
  });
207
213
  const git = buildWorkspaceGitSummary(workspaceRoot);
214
+ const commitReady = options.commitReady
215
+ ? runChangedCommand(workspaceRoot, { commitReady: true }).data.commitReady as { count: number; files: string[] } | undefined
216
+ : undefined;
208
217
  const recentRuns = summarizeRecentRuns(workspaceRoot);
209
218
  const agent = dev.summary.agentContext;
210
219
  const risks = [
@@ -269,6 +278,7 @@ export async function runHandoffCommand(options: HandoffCommandOptions): Promise
269
278
  recommendedCommands: [...new Set(nextActions)].slice(0, 10),
270
279
  risks,
271
280
  },
281
+ ...(commitReady ? { commitReady } : {}),
272
282
  diagnosticSummary,
273
283
  diagnostics: diagnosticSummary.sample,
274
284
  nextActions: [...new Set(nextActions)].slice(0, 10),
@@ -303,6 +313,13 @@ export function formatHandoffHuman(result: HandoffCommandResult): string {
303
313
  (result.diagnosticSummary.hidden > 0 ? ` (${result.diagnosticSummary.hidden} hidden in compact handoff)` : ""),
304
314
  );
305
315
  }
316
+ if (result.commitReady) {
317
+ lines.push("", "Commit-ready files:", ` ${result.commitReady.count}`);
318
+ lines.push(...result.commitReady.files.slice(0, 8).map((file) => ` ${file}`));
319
+ if (result.commitReady.files.length > 8) {
320
+ lines.push(` ... ${result.commitReady.files.length - 8} more`);
321
+ }
322
+ }
306
323
  const changedTypes = summarizeChangeTypes(result.git.changeSummary.changed);
307
324
  if (changedTypes) {
308
325
  lines.push("", "Change types:", ` ${changedTypes}`);
@@ -15,6 +15,7 @@ function formatHelp(): string {
15
15
  " forge changed --json Group changed files into human, generated, and risk buckets",
16
16
  " forge changed --authored --json Show only authored changed files, excluding generated artifacts",
17
17
  " forge changed --review --json Show review-focused app/config/docs changes, excluding local agent/browser artifacts",
18
+ " forge changed --commit-ready --json Show files suitable for git add, excluding generated and operational artifacts",
18
19
  " forge new my-app --template minimal-web --field-test Create an installed WorkOS/auth.md field-test app",
19
20
  " forge diff authored Run the authored-only git diff pathspec",
20
21
  " forge handoff --json Compact work handoff for the next external code agent",
@@ -75,7 +76,42 @@ function formatHelp(): string {
75
76
  ].join("\n");
76
77
  }
77
78
 
79
+ function formatDevHelp(): string {
80
+ return [
81
+ "ForgeOS dev",
82
+ "",
83
+ "Usage:",
84
+ " forge dev [status|stop] [options]",
85
+ "",
86
+ "Options:",
87
+ " --db memory|pglite|postgres|none Choose the development database adapter",
88
+ " --port <port> API runtime port; use 0 for an ephemeral port",
89
+ " --web-port <port> Web dev server port",
90
+ " --host <host> Bind host, default 127.0.0.1",
91
+ " --no-web Start API/runtime only",
92
+ " --api-only Start API/runtime only",
93
+ " --web-only Start web server only, expecting an API runtime",
94
+ " --no-worker Disable local worker",
95
+ " --no-watch Disable file watching",
96
+ " --once --json Run one-shot diagnostics without starting servers",
97
+ " --detach --json Start dev in the background with .forge/dev/dev.pid and .forge/dev/dev.log",
98
+ "",
99
+ "Examples:",
100
+ " forge dev --db memory --port 3777 --web-port 5174",
101
+ " forge dev --db pglite --once --json",
102
+ " forge dev --detach --db memory --port 0 --json",
103
+ " forge dev status --json",
104
+ " forge dev stop --json",
105
+ "",
106
+ ].join("\n");
107
+ }
108
+
78
109
  export async function main(argv: string[] = process.argv.slice(2)): Promise<number> {
110
+ if (argv[0] === "dev" && (argv.includes("--help") || argv.includes("-h"))) {
111
+ process.stdout.write(formatDevHelp());
112
+ return 0;
113
+ }
114
+
79
115
  if (argv.length === 0 || argv.includes("--help") || argv.includes("-h")) {
80
116
  process.stdout.write(formatHelp());
81
117
  return 0;
@@ -49,12 +49,16 @@ export interface NewCommandResult {
49
49
  const REQUIRED_GITIGNORE_PATHS = [
50
50
  "src/forge/_generated/",
51
51
  "forge.lock",
52
+ ".codex/",
52
53
  ".forge/cache/",
53
54
  ".forge/pglite/",
54
55
  ".forge/delta/",
56
+ ".forge/agent/",
55
57
  ".forge/agent/*.ndjson",
56
58
  ".forge/agent/*.history",
59
+ ".forge/last-run.json",
57
60
  ".forge/local/",
61
+ ".forge/runtime-cache/",
58
62
  ".forge/test-cache/",
59
63
  ".forge/test-runs/",
60
64
  ".forge/ui-runs/",
@@ -222,9 +222,9 @@ export type ForgeCommand =
222
222
  redacted: boolean;
223
223
  }
224
224
  | { kind: "status"; json: boolean; workspaceRoot: string }
225
- | { kind: "changed"; json: boolean; authoredOnly: boolean; reviewOnly: boolean; workspaceRoot: string }
225
+ | { kind: "changed"; json: boolean; authoredOnly: boolean; reviewOnly: boolean; commitReady: boolean; workspaceRoot: string }
226
226
  | { kind: "diff"; target: "authored" | "generated" | "full"; json: boolean; workspaceRoot: string }
227
- | { kind: "handoff"; json: boolean; workspaceRoot: string }
227
+ | { kind: "handoff"; json: boolean; commitReady: boolean; workspaceRoot: string }
228
228
  | {
229
229
  kind: "studio";
230
230
  subcommand: "attach" | "snapshot" | "watch" | "open" | "doctor" | "bridge" | "codex-server";
@@ -264,7 +264,7 @@ export type ForgeCommand =
264
264
  }
265
265
  | { kind: "generate"; check: boolean; dryRun: boolean; json: boolean; concurrency: number; workspaceRoot: string }
266
266
  | { kind: "add"; alias: string; options: AddOptions & { workspaceRoot: string } }
267
- | { kind: "inspect"; target: InspectTarget; json: boolean; dryRun: boolean; full: boolean; brief: boolean }
267
+ | { kind: "inspect"; target: InspectTarget; json: boolean; dryRun: boolean; full: boolean; brief: boolean; ergonomics: boolean; workspaceRoot: string }
268
268
  | { kind: "check"; json: boolean; dryRun: boolean; strictSecrets: boolean }
269
269
  | { kind: "verify"; options: VerifyOptions }
270
270
  | { kind: "run"; name?: string; list: boolean; json: boolean; mock: boolean; userId?: string; tenantId?: string; role?: string; envFile?: string; workspaceRoot: string; queryMode?: boolean; args?: unknown }
@@ -288,6 +288,8 @@ export type ForgeCommand =
288
288
  telemetry: string[];
289
289
  envFile?: string;
290
290
  skipStartupConsole: boolean;
291
+ detach: boolean;
292
+ lifecycle?: "status" | "stop";
291
293
  workspaceRoot: string;
292
294
  }
293
295
  | {
@@ -618,7 +620,7 @@ const RENAME_TARGETS: RenameTarget[] = [
618
620
  "workflow",
619
621
  "event",
620
622
  ];
621
- const TEST_SUBCOMMANDS: TestSubcommand[] = ["plan", "run", "explain"];
623
+ const TEST_SUBCOMMANDS: TestSubcommand[] = ["plan", "run", "explain", "authz"];
622
624
  const TEST_COSTS: TestCost[] = ["instant", "fast", "standard", "slow", "docker", "browser"];
623
625
  const REPAIR_SUBCOMMANDS: RepairSubcommand[] = [
624
626
  "diagnose",
@@ -1696,7 +1698,7 @@ export function parseCli(argv: string[]): ParsedCli {
1696
1698
  case "test": {
1697
1699
  const subcommand = rest[0] as TestSubcommand | undefined;
1698
1700
  if (!subcommand || !TEST_SUBCOMMANDS.includes(subcommand)) {
1699
- errors.push("forge test requires subcommand: plan, run, or explain");
1701
+ errors.push("forge test requires subcommand: plan, run, explain, or authz");
1700
1702
  return { command: null, workspaceRoot, errors };
1701
1703
  }
1702
1704
  const timeoutRaw = parseOptionValue(argv, "--timeout-ms");
@@ -1715,7 +1717,7 @@ export function parseCli(argv: string[]): ParsedCli {
1715
1717
  workspaceRoot,
1716
1718
  json: parseFlag(argv, "--json"),
1717
1719
  write: parseFlag(argv, "--write"),
1718
- changed: parseFlag(argv, "--changed") || (!parseFlag(argv, "--staged") && !parseOptionValue(argv, "--since") && !parseOptionValue(argv, "--feature") && !parseOptionValue(argv, "--refactor") && !parseOptionValue(argv, "--upgrade") && !parseOptionValue(argv, "--plan") && subcommand !== "explain"),
1720
+ changed: parseFlag(argv, "--changed") || (!parseFlag(argv, "--staged") && !parseOptionValue(argv, "--since") && !parseOptionValue(argv, "--feature") && !parseOptionValue(argv, "--refactor") && !parseOptionValue(argv, "--upgrade") && !parseOptionValue(argv, "--plan") && subcommand !== "explain" && subcommand !== "authz"),
1719
1721
  staged: parseFlag(argv, "--staged"),
1720
1722
  since: parseOptionValue(argv, "--since"),
1721
1723
  featureId: parseOptionValue(argv, "--feature"),
@@ -1729,6 +1731,8 @@ export function parseCli(argv: string[]): ParsedCli {
1729
1731
  bail: parseFlag(argv, "--bail"),
1730
1732
  report: parseOptionValue(argv, "--report"),
1731
1733
  timeoutMs: timeoutMs ? Math.floor(timeoutMs) : undefined,
1734
+ tenant: parseOptionValue(argv, "--tenant") ?? "acme",
1735
+ otherTenant: parseOptionValue(argv, "--other-tenant") ?? "globex",
1732
1736
  },
1733
1737
  },
1734
1738
  workspaceRoot,
@@ -1908,6 +1912,7 @@ export function parseCli(argv: string[]): ParsedCli {
1908
1912
  json: parseFlag(argv, "--json"),
1909
1913
  authoredOnly: parseFlag(argv, "--authored"),
1910
1914
  reviewOnly: parseFlag(argv, "--review"),
1915
+ commitReady: parseFlag(argv, "--commit-ready"),
1911
1916
  workspaceRoot,
1912
1917
  },
1913
1918
  workspaceRoot,
@@ -1935,6 +1940,7 @@ export function parseCli(argv: string[]): ParsedCli {
1935
1940
  command: {
1936
1941
  kind: "handoff",
1937
1942
  json: parseFlag(argv, "--json"),
1943
+ commitReady: parseFlag(argv, "--commit-ready"),
1938
1944
  workspaceRoot,
1939
1945
  },
1940
1946
  workspaceRoot,
@@ -2220,6 +2226,8 @@ export function parseCli(argv: string[]): ParsedCli {
2220
2226
  dryRun: parseFlag(argv, "--dry-run"),
2221
2227
  full: parseFlag(argv, "--full"),
2222
2228
  brief: parseFlag(argv, "--brief"),
2229
+ ergonomics: parseFlag(argv, "--ergonomics"),
2230
+ workspaceRoot,
2223
2231
  },
2224
2232
  workspaceRoot,
2225
2233
  errors,
@@ -2472,6 +2480,7 @@ export function parseCli(argv: string[]): ParsedCli {
2472
2480
  };
2473
2481
  }
2474
2482
  case "dev": {
2483
+ const lifecycle = rest[0] === "status" || rest[0] === "stop" ? rest[0] : undefined;
2475
2484
  const portRaw = parseOptionValue(argv, "--port");
2476
2485
  const port = portRaw !== undefined ? Number(portRaw) : undefined;
2477
2486
  if (portRaw !== undefined && (!Number.isFinite(port) || port! < 0)) {
@@ -2509,6 +2518,8 @@ export function parseCli(argv: string[]): ParsedCli {
2509
2518
  .filter(Boolean),
2510
2519
  envFile: parseOptionValue(argv, "--env-file"),
2511
2520
  skipStartupConsole: parseFlag(argv, "--skip-startup-console"),
2521
+ detach: parseFlag(argv, "--detach"),
2522
+ lifecycle,
2512
2523
  workspaceRoot,
2513
2524
  },
2514
2525
  workspaceRoot,
@@ -2956,6 +2967,8 @@ export function hasUnknownOption(argv: string[]): string | null {
2956
2967
  "--telemetry",
2957
2968
  "--user-id",
2958
2969
  "--tenant-id",
2970
+ "--tenant",
2971
+ "--other-tenant",
2959
2972
  "--role",
2960
2973
  "--strict-policies",
2961
2974
  "--strict",
@@ -2981,6 +2994,9 @@ export function hasUnknownOption(argv: string[]): string | null {
2981
2994
  "--git",
2982
2995
  "--no-git",
2983
2996
  "--field-test",
2997
+ "--commit-ready",
2998
+ "--detach",
2999
+ "--ergonomics",
2984
3000
  "--with-web",
2985
3001
  "--no-web",
2986
3002
  "--api-only",
@@ -3093,6 +3109,8 @@ export function hasUnknownOption(argv: string[]): string | null {
3093
3109
  arg === "--file" ||
3094
3110
  arg === "--telemetry" ||
3095
3111
  arg === "--user-id" ||
3112
+ arg === "--tenant" ||
3113
+ arg === "--other-tenant" ||
3096
3114
  arg === "--tenant-id" ||
3097
3115
  arg === "--role" ||
3098
3116
  arg === "--strict-policies" ||