@rigkit/cli 0.2.9 → 0.2.11

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Rigkit contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -7,7 +7,7 @@ npm i -g @rigkit/cli
7
7
  rig init
8
8
  rig plan
9
9
  rig ls
10
- rig -chdir=examples/smoke plan
10
+ rig --chdir=examples/smoke plan
11
11
  ```
12
12
 
13
13
  `rig init` asks for a project name, Freestyle API key, and package manager. It creates a project folder containing a workflow-based `rig.config.ts`, `.env`, `.env.example`, `package.json`, and local ignore rules.
@@ -23,12 +23,15 @@ defined by the project run as `rig run <workspace> <operation>`, for example
23
23
 
24
24
  `rig ls` lists workspaces for the selected project. `rig ls snapshots` lists cached snapshot runs, and `rig ls config` shows the resolved project paths.
25
25
 
26
- Use Terraform-style global context options before the command to select another
27
- project or config:
26
+ Use global context options before the command to select another project or
27
+ config:
28
28
 
29
29
  ```bash
30
- rig -chdir=examples/smoke plan
31
- rig -chdir=examples/global-fragments -config=api.rig.config.ts apply
30
+ rig --chdir=examples/smoke plan
31
+ rig --chdir=examples/global-fragments --config=api.rig.config.ts apply
32
32
  ```
33
33
 
34
+ Legacy Terraform-style aliases such as `-chdir=...` and `-config=...` are still
35
+ accepted.
36
+
34
37
  Projects should install matching `@rigkit/sdk` versions locally.
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@rigkit/cli",
3
- "version": "0.2.9",
3
+ "version": "0.2.11",
4
+ "license": "MIT",
4
5
  "type": "module",
5
6
  "repository": {
6
7
  "type": "git",
@@ -22,9 +23,9 @@
22
23
  "chalk": "^5.6.2",
23
24
  "commander": "^14.0.3",
24
25
  "inquirer": "^13.4.3",
25
- "@rigkit/engine": "0.2.9",
26
- "@rigkit/provider-cmux": "0.2.9",
27
- "@rigkit/runtime-client": "0.2.9"
26
+ "@rigkit/runtime-client": "0.2.11",
27
+ "@rigkit/engine": "0.2.11",
28
+ "@rigkit/provider-cmux": "0.2.11"
28
29
  },
29
30
  "devDependencies": {
30
31
  "@types/bun": "latest",
package/src/cli.test.ts CHANGED
@@ -2,6 +2,7 @@ import { describe, expect, test } from "bun:test";
2
2
  import { existsSync, mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs";
3
3
  import { tmpdir } from "node:os";
4
4
  import { join } from "node:path";
5
+ import { createFileProviderHostStorage } from "@rigkit/engine";
5
6
  import { projectIdFor, runtimeFingerprintFor, runtimePaths, SUPPORTED_RUNTIME_API_VERSION } from "@rigkit/runtime-client";
6
7
  import { RIGKIT_CLI_VERSION } from "./version.ts";
7
8
 
@@ -27,6 +28,7 @@ describe("CLI entrypoint", () => {
27
28
  expect(rootHelp.stdout).toContain("rm Remove a workspace");
28
29
  expect(rootHelp.stdout).toContain("run Run a workspace operation");
29
30
  expect(rootHelp.stdout).toContain("cache Inspect and clear Rigkit cache");
31
+ expect(rootHelp.stdout).toContain("providers Manage provider-owned local state");
30
32
 
31
33
  const version = await runCli(["version"]);
32
34
  expect(version.exitCode).toBe(0);
@@ -41,6 +43,77 @@ describe("CLI entrypoint", () => {
41
43
  expect(help.stdout).toContain("rm Remove a workspace");
42
44
  expect(help.stdout).toContain("run Run a workspace operation");
43
45
  expect(help.stdout).toContain("cache Inspect and clear Rigkit cache");
46
+ expect(help.stdout).toContain("providers Manage provider-owned local state");
47
+ });
48
+
49
+ test("prints an update notice when latest metadata is newer", async () => {
50
+ const rigkitHome = mkdtempSync(join(tmpdir(), "rigkit-cli-update-"));
51
+ const latestVersion = nextPatchVersion(RIGKIT_CLI_VERSION);
52
+ const server = Bun.serve({
53
+ hostname: "127.0.0.1",
54
+ port: 0,
55
+ fetch() {
56
+ return Response.json({
57
+ version: latestVersion,
58
+ installerUrl: "https://www.rigkit.dev/install",
59
+ });
60
+ },
61
+ });
62
+
63
+ try {
64
+ const result = await runCli(["doctor", "--cli"], {
65
+ env: {
66
+ RIGKIT_HOME: rigkitHome,
67
+ RIGKIT_UPDATE_CHECK: "1",
68
+ RIGKIT_UPDATE_TIMEOUT_MS: "2000",
69
+ RIGKIT_UPDATE_URL: `http://127.0.0.1:${server.port}/latest.json`,
70
+ },
71
+ });
72
+
73
+ expect(result.exitCode).toBe(0);
74
+ expect(result.stdout).toContain(RIGKIT_CLI_VERSION);
75
+ expect(result.stderr).toContain(`rig ${latestVersion} is available`);
76
+ expect(result.stderr).toContain("update with: curl -fsSL https://www.rigkit.dev/install | sh");
77
+ } finally {
78
+ server.stop(true);
79
+ rmSync(rigkitHome, { recursive: true, force: true });
80
+ }
81
+ });
82
+
83
+ test("does not print update notices for JSON output", async () => {
84
+ const rigkitHome = mkdtempSync(join(tmpdir(), "rigkit-cli-update-json-"));
85
+ let requests = 0;
86
+ const server = Bun.serve({
87
+ hostname: "127.0.0.1",
88
+ port: 0,
89
+ fetch() {
90
+ requests += 1;
91
+ return Response.json({
92
+ version: nextPatchVersion(RIGKIT_CLI_VERSION),
93
+ installerUrl: "https://www.rigkit.dev/install",
94
+ });
95
+ },
96
+ });
97
+
98
+ try {
99
+ const result = await runCli(["doctor", "--cli", "--json"], {
100
+ env: {
101
+ RIGKIT_HOME: rigkitHome,
102
+ RIGKIT_UPDATE_CHECK: "1",
103
+ RIGKIT_UPDATE_URL: `http://127.0.0.1:${server.port}/latest.json`,
104
+ },
105
+ });
106
+
107
+ expect(result.exitCode).toBe(0);
108
+ expect(result.stderr).toBe("");
109
+ expect(JSON.parse(result.stdout)).toMatchObject({
110
+ cliVersion: RIGKIT_CLI_VERSION,
111
+ });
112
+ expect(requests).toBe(0);
113
+ } finally {
114
+ server.stop(true);
115
+ rmSync(rigkitHome, { recursive: true, force: true });
116
+ }
44
117
  });
45
118
 
46
119
  test("rejects operation shorthand at the root", async () => {
@@ -95,7 +168,28 @@ describe("CLI entrypoint", () => {
95
168
  expect(result.stderr).toContain("Found named Rigkit configs");
96
169
  expect(result.stderr).toContain("api.rig.config.ts");
97
170
  expect(result.stderr).toContain("web.rig.config.ts");
98
- expect(result.stderr).toContain("rig -chdir=. -config=api.rig.config.ts <command>");
171
+ expect(result.stderr).toContain("rig --chdir=. --config=api.rig.config.ts <command>");
172
+ } finally {
173
+ rmSync(cwd, { recursive: true, force: true });
174
+ }
175
+ });
176
+
177
+ test("accepts conventional double-dash global options", async () => {
178
+ const cwd = mkdtempSync(join(tmpdir(), "rigkit-cli-global-options-"));
179
+ mkdirSync(join(cwd, "api"));
180
+ writeFileSync(join(cwd, "api", "rig.config.ts"), "export default {}\n");
181
+
182
+ try {
183
+ const result = await runCli(["--chdir=api", "projects", "--json"], { cwd });
184
+
185
+ expect(result.exitCode).toBe(0);
186
+ expect(result.stderr).toBe("");
187
+ expect(JSON.parse(result.stdout)).toEqual({
188
+ projects: [{
189
+ projectDir: join(realpathSync(cwd), "api"),
190
+ configPath: join(realpathSync(cwd), "api", "rig.config.ts"),
191
+ }],
192
+ });
99
193
  } finally {
100
194
  rmSync(cwd, { recursive: true, force: true });
101
195
  }
@@ -124,6 +218,56 @@ describe("CLI entrypoint", () => {
124
218
  }
125
219
  });
126
220
 
221
+ test("clears Freestyle provider host storage without loading a project", async () => {
222
+ const storageRoot = mkdtempSync(join(tmpdir(), "rigkit-provider-storage-"));
223
+ const storage = createFileProviderHostStorage({ providerId: "freestyle", rootDir: storageRoot });
224
+ storage.set("stack-auth:test", { refreshToken: "refresh-token", updatedAt: 1 });
225
+ storage.set("identity:test", { token: "ssh-token" });
226
+
227
+ try {
228
+ const result = await runCli(["providers", "freestyle", "clear", "--json"], {
229
+ env: { RIGKIT_HOST_STORAGE_DIR: storageRoot },
230
+ });
231
+
232
+ expect(result.exitCode).toBe(0);
233
+ expect(result.stderr).toBe("");
234
+ expect(JSON.parse(result.stdout)).toMatchObject({
235
+ ok: true,
236
+ providerId: "freestyle",
237
+ deleted: 2,
238
+ storageRoot,
239
+ });
240
+ expect(storage.entries()).toEqual([]);
241
+ } finally {
242
+ rmSync(storageRoot, { recursive: true, force: true });
243
+ }
244
+ });
245
+
246
+ test("does not render a success marker when cache invalidation is a no-op", async () => {
247
+ const projectDir = mkdtempSync(join(tmpdir(), "rigkit-cli-cache-invalidate-"));
248
+
249
+ await withWorkspaceRuntime({ projectDir, cacheInvalidated: 0 }, async ({ env }) => {
250
+ const result = await runCli([`-chdir=${projectDir}`, "cache", "invalidate", "missing-task"], { env });
251
+
252
+ expect(result.exitCode).toBe(0);
253
+ expect(result.stderr).toBe("");
254
+ expect(result.stdout.trim()).toBe("no cache entries invalidated");
255
+ expect(result.stdout).not.toContain("✓");
256
+ });
257
+ });
258
+
259
+ test("preserves JSON output for zero cache invalidations", async () => {
260
+ const projectDir = mkdtempSync(join(tmpdir(), "rigkit-cli-cache-invalidate-json-"));
261
+
262
+ await withWorkspaceRuntime({ projectDir, cacheInvalidated: 0 }, async ({ env }) => {
263
+ const result = await runCli([`-chdir=${projectDir}`, "cache", "invalidate", "missing-task", "--json"], { env });
264
+
265
+ expect(result.exitCode).toBe(0);
266
+ expect(result.stderr).toBe("");
267
+ expect(JSON.parse(result.stdout)).toEqual({ ok: true, invalidated: 0 });
268
+ });
269
+ });
270
+
127
271
  test("lists workspaces from the project runtime", async () => {
128
272
  const projectDir = mkdtempSync(join(tmpdir(), "rigkit-cli-ls-"));
129
273
 
@@ -241,6 +385,7 @@ async function runCli(
241
385
  stderr: "pipe",
242
386
  env: {
243
387
  ...process.env,
388
+ RIGKIT_UPDATE_CHECK: "0",
244
389
  ...options.env,
245
390
  FORCE_COLOR: "0",
246
391
  NO_COLOR: "1",
@@ -255,8 +400,14 @@ async function runCli(
255
400
  return { exitCode, stdout, stderr };
256
401
  }
257
402
 
403
+ function nextPatchVersion(version: string): string {
404
+ const match = version.match(/^(\d+)\.(\d+)\.(\d+)/);
405
+ if (!match) return "999.0.0";
406
+ return `${match[1]}.${match[2]}.${Number(match[3]) + 1}`;
407
+ }
408
+
258
409
  async function withWorkspaceRuntime(
259
- input: { projectDir: string },
410
+ input: { projectDir: string; cacheInvalidated?: number },
260
411
  run: (context: { env: Record<string, string> }) => Promise<void>,
261
412
  ): Promise<void> {
262
413
  const rigkitHome = mkdtempSync(join(tmpdir(), "rigkit-home-"));
@@ -306,6 +457,9 @@ async function withWorkspaceRuntime(
306
457
  }],
307
458
  });
308
459
  }
460
+ if (pathname === "/cache/invalidate") {
461
+ return runtimeJson({ ok: true, invalidated: input.cacheInvalidated ?? 1 });
462
+ }
309
463
  if (pathname === "/operations") {
310
464
  return runtimeJson({
311
465
  operations: [{
package/src/cli.ts CHANGED
@@ -10,6 +10,8 @@ import {
10
10
  type RuntimeClient,
11
11
  } from "@rigkit/runtime-client";
12
12
  import {
13
+ createFileProviderHostStorage,
14
+ defaultProviderHostStorageDir,
13
15
  type DevMachineEvent,
14
16
  type WorkflowPlan,
15
17
  type SnapshotRecord,
@@ -25,6 +27,7 @@ import { initProject, normalizeMachineName, type InitProjectResult } from "./ini
25
27
  import { openExternalTarget } from "./interaction.ts";
26
28
  import { createRunPresenter, type RunPresenter } from "./run-presenter.ts";
27
29
  import { createRunLogger, type RunLogger } from "./run-logger.ts";
30
+ import { maybePrintUpdateNotice } from "./update-check.ts";
28
31
  import {
29
32
  completeRig,
30
33
  formatCompletionItems,
@@ -161,6 +164,7 @@ const STATIC_COMMANDS = new Set([
161
164
  "run",
162
165
  "ls",
163
166
  "cache",
167
+ "providers",
164
168
  "projects",
165
169
  "doctor",
166
170
  "version",
@@ -204,22 +208,32 @@ async function runCli(argv: string[]): Promise<void> {
204
208
  .exitOverride()
205
209
  .argument("[command]")
206
210
  .addOption(new Option("--chdir <dir>", `Switch to a directory containing ${DEFAULT_CONFIG_FILE} before running the command`).hideHelp())
207
- .addOption(new Option("--config <file>", "Config file to load, relative to -chdir when set").hideHelp())
211
+ .addOption(new Option("--config <file>", "Config file to load, relative to --chdir when set").hideHelp())
208
212
  .addOption(new Option("--state <file>", "Local runtime state database path").hideHelp())
209
213
  .addOption(new Option("--json", "Print machine-readable JSON where supported").hideHelp())
210
214
  .addHelpText("after", [
211
215
  "",
212
216
  "Global Options:",
213
- " -chdir=DIR Switch to a directory containing rig.config.ts before running the command",
214
- " -config=FILE Config file to load, relative to -chdir when set",
215
- " -state=FILE Local runtime state database path",
216
- " -json Print machine-readable JSON where supported",
217
+ " --chdir <dir> Switch to a directory containing rig.config.ts before running the command",
218
+ " --config <file> Config file to load, relative to --chdir when set",
219
+ " --state <file> Local runtime state database path",
220
+ " --json Print machine-readable JSON where supported",
221
+ "",
222
+ "Legacy single-dash global aliases such as -chdir=DIR are still accepted.",
217
223
  ].join("\n"))
218
224
  .action(async (command?: string) => {
219
225
  if (command) program.error(`unknown command '${command}'`);
220
226
  await runHelp(makeInvocation(rootOptions(program)));
221
227
  });
222
228
 
229
+ program.hook("postAction", async (_thisCommand, actionCommand) => {
230
+ await maybePrintUpdateNotice({
231
+ commandName: actionCommand.name(),
232
+ currentVersion: RIGKIT_CLI_VERSION,
233
+ json: commandWantsJson(program, actionCommand),
234
+ });
235
+ });
236
+
223
237
  program
224
238
  .command("init")
225
239
  .description("Initialize a Rigkit project")
@@ -351,6 +365,22 @@ async function runCli(argv: string[]): Promise<void> {
351
365
  });
352
366
  });
353
367
 
368
+ const providers = program
369
+ .command("providers")
370
+ .description("Manage provider-owned local state");
371
+
372
+ const freestyleProvider = providers
373
+ .command("freestyle")
374
+ .description("Manage Freestyle provider local state");
375
+
376
+ freestyleProvider
377
+ .command("clear")
378
+ .description("Clear Freestyle provider local auth and identity state")
379
+ .option("--json", "Print machine-readable JSON")
380
+ .action(async (options: { json?: boolean }) => {
381
+ await runProvidersFreestyleClear(makeInvocation(rootOptions(program), options.json));
382
+ });
383
+
354
384
  program
355
385
  .command("projects")
356
386
  .description("Discover Rigkit projects below the current directory")
@@ -408,6 +438,11 @@ function rootOptions(program: Command): GlobalOptions {
408
438
  };
409
439
  }
410
440
 
441
+ function commandWantsJson(program: Command, actionCommand: Command): boolean {
442
+ const options = actionCommand.opts<{ json?: boolean }>();
443
+ return Boolean(rootOptions(program).json || options.json);
444
+ }
445
+
411
446
  function parsePackageManagerOption(value: string | undefined): PackageManager | undefined {
412
447
  if (value === undefined) return undefined;
413
448
  if (isPackageManager(value)) return value;
@@ -560,7 +595,7 @@ function canPrompt(): boolean {
560
595
  function resolveInitProjectPaths(invocation: CliInvocation, name: string): { projectDir: string; configPath: string } {
561
596
  const options = invocation.global;
562
597
  if (options.config) {
563
- throw new Error(`rig init does not support -config. Use -chdir to choose the parent directory.`);
598
+ throw new Error(`rig init does not support --config. Use --chdir to choose the parent directory.`);
564
599
  }
565
600
 
566
601
  const parentDir = resolve(process.cwd(), options.chdir ?? ".");
@@ -950,7 +985,7 @@ async function runDiscoveredProjectOperation(
950
985
  if (!options.all && projects.length > 1) {
951
986
  throw new Error([
952
987
  "Multiple Rigkit projects found.",
953
- "Use `rig projects` to list candidates, pass -chdir or -config to select one, or pass --all to run every discovered project.",
988
+ "Use `rig projects` to list candidates, pass --chdir or --config to select one, or pass --all to run every discovered project.",
954
989
  ...projects.map((project) => `- ${project.configPath}`),
955
990
  ].join("\n"));
956
991
  }
@@ -1068,7 +1103,7 @@ async function runCacheClear(invocation: CliInvocation, options: CacheClearOptio
1068
1103
 
1069
1104
  if (options.global && options.all) {
1070
1105
  if (invocation.global.chdir || invocation.global.config || invocation.global.state) {
1071
- throw new Error(`rig cache clear --global --all cannot be combined with -chdir, -config, or -state`);
1106
+ throw new Error(`rig cache clear --global --all cannot be combined with --chdir, --config, or --state`);
1072
1107
  }
1073
1108
  const fragmentRoot = join(defaultRigkitHome(), "fragments");
1074
1109
  rmSync(fragmentRoot, { recursive: true, force: true });
@@ -1090,6 +1125,32 @@ async function runCacheClear(invocation: CliInvocation, options: CacheClearOptio
1090
1125
  console.log(`Cleared ${result.deleted} cache ${result.deleted === 1 ? "entry" : "entries"}.`);
1091
1126
  }
1092
1127
 
1128
+ async function runProvidersFreestyleClear(invocation: CliInvocation): Promise<void> {
1129
+ const providerId = "freestyle";
1130
+ const storageRoot = defaultProviderHostStorageDir();
1131
+ const storage = createFileProviderHostStorage({ providerId, rootDir: storageRoot });
1132
+ const keys = storage.entries().map((entry) => entry.key);
1133
+ for (const key of keys) storage.delete(key);
1134
+
1135
+ const result = {
1136
+ ok: true,
1137
+ providerId,
1138
+ deleted: keys.length,
1139
+ storageRoot,
1140
+ };
1141
+
1142
+ if (wantsJson(invocation)) {
1143
+ printJson(result);
1144
+ return;
1145
+ }
1146
+
1147
+ if (keys.length === 0) {
1148
+ console.log("No Freestyle provider local state to clear.");
1149
+ return;
1150
+ }
1151
+ console.log(`Cleared ${keys.length} Freestyle provider ${keys.length === 1 ? "entry" : "entries"}.`);
1152
+ }
1153
+
1093
1154
  type CacheInvalidateOptions = {
1094
1155
  step?: string;
1095
1156
  all: boolean;
@@ -1144,6 +1205,10 @@ async function runCacheInvalidate(invocation: CliInvocation, options: CacheInval
1144
1205
  printJson(result);
1145
1206
  return;
1146
1207
  }
1208
+ if (result.invalidated === 0) {
1209
+ console.log(ui.dim("no cache entries invalidated"));
1210
+ return;
1211
+ }
1147
1212
  console.log(
1148
1213
  `${ui.ok(ui.sym.ok)} invalidated ${result.invalidated} cache ${result.invalidated === 1 ? "entry" : "entries"}`,
1149
1214
  );
@@ -1506,6 +1571,7 @@ async function runHelp(invocation: CliInvocation): Promise<void> {
1506
1571
  { name: "run", description: "Run a workspace operation" },
1507
1572
  { name: "ls", description: "List project workspaces" },
1508
1573
  { name: "cache", description: "Inspect and clear Rigkit cache" },
1574
+ { name: "providers", description: "Manage provider-owned local state" },
1509
1575
  { name: "projects", description: "Discover Rigkit projects below the current directory" },
1510
1576
  { name: "doctor", description: "Show Rigkit runtime diagnostics" },
1511
1577
  { name: "version", description: "Show Rigkit CLI version" },
@@ -1517,7 +1583,7 @@ async function runHelp(invocation: CliInvocation): Promise<void> {
1517
1583
  const cmd = (name: string, description: string): string =>
1518
1584
  ` ${ui.bold(name.padEnd(10))} ${description}`;
1519
1585
  const opt = (flag: string, description: string): string =>
1520
- ` ${ui.bold(flag.padEnd(12))} ${description}`;
1586
+ ` ${ui.bold(flag.padEnd(17))} ${description}`;
1521
1587
 
1522
1588
  console.log([
1523
1589
  `${ui.bold("rig")} ${ui.dim(RIGKIT_CLI_VERSION)}`,
@@ -1535,16 +1601,19 @@ async function runHelp(invocation: CliInvocation): Promise<void> {
1535
1601
  cmd("run", "Run a workspace operation"),
1536
1602
  cmd("ls", "List project workspaces"),
1537
1603
  cmd("cache", "Inspect and clear Rigkit cache"),
1604
+ cmd("providers", "Manage provider-owned local state"),
1538
1605
  cmd("projects", "Discover Rigkit projects below the current directory"),
1539
1606
  cmd("doctor", "Show Rigkit runtime diagnostics"),
1540
1607
  cmd("version", "Show Rigkit CLI version"),
1541
1608
  cmd("completion", "Generate shell completion script"),
1542
1609
  "",
1543
1610
  ui.dim("Options:"),
1544
- opt("-chdir=DIR", "Switch to a directory containing rig.config.ts before running the command"),
1545
- opt("-config=FILE", "Config file to load, relative to -chdir when set"),
1546
- opt("-state=FILE", "Local runtime state database path"),
1547
- opt("-json", "Print machine-readable JSON where supported"),
1611
+ opt("--chdir <dir>", "Switch to a directory containing rig.config.ts before running the command"),
1612
+ opt("--config <file>", "Config file to load, relative to --chdir when set"),
1613
+ opt("--state <file>", "Local runtime state database path"),
1614
+ opt("--json", "Print machine-readable JSON where supported"),
1615
+ "",
1616
+ ui.dim("Legacy single-dash global aliases such as -chdir=DIR are still accepted."),
1548
1617
  ].join("\n"));
1549
1618
  }
1550
1619
 
@@ -2098,8 +2167,12 @@ async function promptHostSelect(params: unknown): Promise<string> {
2098
2167
  .filter((item) => item.value)
2099
2168
  : [];
2100
2169
  if (options.length === 0) throw new Error(`Host select prompt has no options`);
2101
- const defaultValue = stringField(params, "defaultValue") ?? options[0]!.value;
2102
- if (!canPrompt()) return defaultValue;
2170
+ const configuredDefaultValue = stringField(params, "defaultValue");
2171
+ const defaultValue = configuredDefaultValue ?? options[0]!.value;
2172
+ if (!canPrompt()) {
2173
+ if (configuredDefaultValue !== undefined) return configuredDefaultValue;
2174
+ throw new Error(`Host select prompt requires an interactive terminal: ${message}`);
2175
+ }
2103
2176
  const answers = await inquirer.prompt<{ value: string }>([{
2104
2177
  type: "select",
2105
2178
  name: "value",
@@ -2283,6 +2356,16 @@ function printPlan(plan: WorkflowPlan): void {
2283
2356
  console.log(`${ui.bold(plan.workflow)} ${ui.dim(`${plan.cachedNodeCount}/${plan.nodeCount} cached`)}`);
2284
2357
  console.log("");
2285
2358
 
2359
+ if (plan.providerChecks?.length) {
2360
+ const rows = plan.providerChecks.map((check) => [
2361
+ { text: check.label || check.providerName, style: ui.dim },
2362
+ { text: check.status, style: providerCheckStatusStyle(check.status) },
2363
+ { text: providerCheckValue(check), style: check.status === "ok" ? ((value: string) => value) : ui.warn },
2364
+ ]);
2365
+ console.log(ui.columns(["provider check", "status", "current"], rows));
2366
+ console.log("");
2367
+ }
2368
+
2286
2369
  const rows = plan.nodes.map((node) => [
2287
2370
  { text: String(node.index + 1), style: ui.dim },
2288
2371
  { text: node.status, style: planStatusStyle(node.status) },
@@ -2292,6 +2375,18 @@ function printPlan(plan: WorkflowPlan): void {
2292
2375
  console.log(ui.columns(["#", "status", "node", "reason"], rows));
2293
2376
  }
2294
2377
 
2378
+ function providerCheckValue(check: NonNullable<WorkflowPlan["providerChecks"]>[number]): string {
2379
+ if (check.status === "required" && check.message) return check.message;
2380
+ if (check.detail && check.detail !== check.value && !check.value.includes(check.detail)) {
2381
+ return `${check.value} ${ui.dim(check.detail)}`;
2382
+ }
2383
+ return check.value;
2384
+ }
2385
+
2386
+ function providerCheckStatusStyle(status: string): (text: string) => string {
2387
+ return status === "ok" ? ui.ok : ui.warn;
2388
+ }
2389
+
2295
2390
  function planStatusStyle(status: string): (text: string) => string {
2296
2391
  switch (status) {
2297
2392
  case "cached":