@rigkit/cli 0.2.10 → 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.10",
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/provider-cmux": "0.2.10",
26
- "@rigkit/engine": "0.2.10",
27
- "@rigkit/runtime-client": "0.2.10"
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,7 @@ 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");
44
47
  });
45
48
 
46
49
  test("prints an update notice when latest metadata is newer", async () => {
@@ -165,7 +168,28 @@ describe("CLI entrypoint", () => {
165
168
  expect(result.stderr).toContain("Found named Rigkit configs");
166
169
  expect(result.stderr).toContain("api.rig.config.ts");
167
170
  expect(result.stderr).toContain("web.rig.config.ts");
168
- 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
+ });
169
193
  } finally {
170
194
  rmSync(cwd, { recursive: true, force: true });
171
195
  }
@@ -194,6 +218,31 @@ describe("CLI entrypoint", () => {
194
218
  }
195
219
  });
196
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
+
197
246
  test("does not render a success marker when cache invalidation is a no-op", async () => {
198
247
  const projectDir = mkdtempSync(join(tmpdir(), "rigkit-cli-cache-invalidate-"));
199
248
 
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,
@@ -162,6 +164,7 @@ const STATIC_COMMANDS = new Set([
162
164
  "run",
163
165
  "ls",
164
166
  "cache",
167
+ "providers",
165
168
  "projects",
166
169
  "doctor",
167
170
  "version",
@@ -205,16 +208,18 @@ async function runCli(argv: string[]): Promise<void> {
205
208
  .exitOverride()
206
209
  .argument("[command]")
207
210
  .addOption(new Option("--chdir <dir>", `Switch to a directory containing ${DEFAULT_CONFIG_FILE} before running the command`).hideHelp())
208
- .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())
209
212
  .addOption(new Option("--state <file>", "Local runtime state database path").hideHelp())
210
213
  .addOption(new Option("--json", "Print machine-readable JSON where supported").hideHelp())
211
214
  .addHelpText("after", [
212
215
  "",
213
216
  "Global Options:",
214
- " -chdir=DIR Switch to a directory containing rig.config.ts before running the command",
215
- " -config=FILE Config file to load, relative to -chdir when set",
216
- " -state=FILE Local runtime state database path",
217
- " -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.",
218
223
  ].join("\n"))
219
224
  .action(async (command?: string) => {
220
225
  if (command) program.error(`unknown command '${command}'`);
@@ -360,6 +365,22 @@ async function runCli(argv: string[]): Promise<void> {
360
365
  });
361
366
  });
362
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
+
363
384
  program
364
385
  .command("projects")
365
386
  .description("Discover Rigkit projects below the current directory")
@@ -574,7 +595,7 @@ function canPrompt(): boolean {
574
595
  function resolveInitProjectPaths(invocation: CliInvocation, name: string): { projectDir: string; configPath: string } {
575
596
  const options = invocation.global;
576
597
  if (options.config) {
577
- 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.`);
578
599
  }
579
600
 
580
601
  const parentDir = resolve(process.cwd(), options.chdir ?? ".");
@@ -964,7 +985,7 @@ async function runDiscoveredProjectOperation(
964
985
  if (!options.all && projects.length > 1) {
965
986
  throw new Error([
966
987
  "Multiple Rigkit projects found.",
967
- "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.",
968
989
  ...projects.map((project) => `- ${project.configPath}`),
969
990
  ].join("\n"));
970
991
  }
@@ -1082,7 +1103,7 @@ async function runCacheClear(invocation: CliInvocation, options: CacheClearOptio
1082
1103
 
1083
1104
  if (options.global && options.all) {
1084
1105
  if (invocation.global.chdir || invocation.global.config || invocation.global.state) {
1085
- 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`);
1086
1107
  }
1087
1108
  const fragmentRoot = join(defaultRigkitHome(), "fragments");
1088
1109
  rmSync(fragmentRoot, { recursive: true, force: true });
@@ -1104,6 +1125,32 @@ async function runCacheClear(invocation: CliInvocation, options: CacheClearOptio
1104
1125
  console.log(`Cleared ${result.deleted} cache ${result.deleted === 1 ? "entry" : "entries"}.`);
1105
1126
  }
1106
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
+
1107
1154
  type CacheInvalidateOptions = {
1108
1155
  step?: string;
1109
1156
  all: boolean;
@@ -1524,6 +1571,7 @@ async function runHelp(invocation: CliInvocation): Promise<void> {
1524
1571
  { name: "run", description: "Run a workspace operation" },
1525
1572
  { name: "ls", description: "List project workspaces" },
1526
1573
  { name: "cache", description: "Inspect and clear Rigkit cache" },
1574
+ { name: "providers", description: "Manage provider-owned local state" },
1527
1575
  { name: "projects", description: "Discover Rigkit projects below the current directory" },
1528
1576
  { name: "doctor", description: "Show Rigkit runtime diagnostics" },
1529
1577
  { name: "version", description: "Show Rigkit CLI version" },
@@ -1535,7 +1583,7 @@ async function runHelp(invocation: CliInvocation): Promise<void> {
1535
1583
  const cmd = (name: string, description: string): string =>
1536
1584
  ` ${ui.bold(name.padEnd(10))} ${description}`;
1537
1585
  const opt = (flag: string, description: string): string =>
1538
- ` ${ui.bold(flag.padEnd(12))} ${description}`;
1586
+ ` ${ui.bold(flag.padEnd(17))} ${description}`;
1539
1587
 
1540
1588
  console.log([
1541
1589
  `${ui.bold("rig")} ${ui.dim(RIGKIT_CLI_VERSION)}`,
@@ -1553,16 +1601,19 @@ async function runHelp(invocation: CliInvocation): Promise<void> {
1553
1601
  cmd("run", "Run a workspace operation"),
1554
1602
  cmd("ls", "List project workspaces"),
1555
1603
  cmd("cache", "Inspect and clear Rigkit cache"),
1604
+ cmd("providers", "Manage provider-owned local state"),
1556
1605
  cmd("projects", "Discover Rigkit projects below the current directory"),
1557
1606
  cmd("doctor", "Show Rigkit runtime diagnostics"),
1558
1607
  cmd("version", "Show Rigkit CLI version"),
1559
1608
  cmd("completion", "Generate shell completion script"),
1560
1609
  "",
1561
1610
  ui.dim("Options:"),
1562
- opt("-chdir=DIR", "Switch to a directory containing rig.config.ts before running the command"),
1563
- opt("-config=FILE", "Config file to load, relative to -chdir when set"),
1564
- opt("-state=FILE", "Local runtime state database path"),
1565
- 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."),
1566
1617
  ].join("\n"));
1567
1618
  }
1568
1619
 
@@ -2116,8 +2167,12 @@ async function promptHostSelect(params: unknown): Promise<string> {
2116
2167
  .filter((item) => item.value)
2117
2168
  : [];
2118
2169
  if (options.length === 0) throw new Error(`Host select prompt has no options`);
2119
- const defaultValue = stringField(params, "defaultValue") ?? options[0]!.value;
2120
- 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
+ }
2121
2176
  const answers = await inquirer.prompt<{ value: string }>([{
2122
2177
  type: "select",
2123
2178
  name: "value",
@@ -2301,6 +2356,16 @@ function printPlan(plan: WorkflowPlan): void {
2301
2356
  console.log(`${ui.bold(plan.workflow)} ${ui.dim(`${plan.cachedNodeCount}/${plan.nodeCount} cached`)}`);
2302
2357
  console.log("");
2303
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
+
2304
2369
  const rows = plan.nodes.map((node) => [
2305
2370
  { text: String(node.index + 1), style: ui.dim },
2306
2371
  { text: node.status, style: planStatusStyle(node.status) },
@@ -2310,6 +2375,18 @@ function printPlan(plan: WorkflowPlan): void {
2310
2375
  console.log(ui.columns(["#", "status", "node", "reason"], rows));
2311
2376
  }
2312
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
+
2313
2390
  function planStatusStyle(status: string): (text: string) => string {
2314
2391
  switch (status) {
2315
2392
  case "cached":
@@ -82,6 +82,45 @@ describe("CLI completion", () => {
82
82
  }
83
83
  });
84
84
 
85
+ test("suggests conventional double-dash global flags by default", async () => {
86
+ const items = await completeRig({
87
+ cwd: process.cwd(),
88
+ words: ["rig", "--"],
89
+ currentIndex: 1,
90
+ });
91
+
92
+ expect(items.map((item) => item.value)).toEqual([
93
+ "--chdir=",
94
+ "--config=",
95
+ "--state=",
96
+ "--json",
97
+ "--help",
98
+ "--version",
99
+ ]);
100
+ });
101
+
102
+ test("completes project directories for --chdir", async () => {
103
+ const cwd = mkdtempSync(join(tmpdir(), "rigkit-completion-dirs-"));
104
+ mkdirSync(join(cwd, "examples", "global-fragments"), { recursive: true });
105
+
106
+ try {
107
+ const roots = await completeRig({
108
+ cwd,
109
+ words: ["rig", "--chdir="],
110
+ currentIndex: 1,
111
+ });
112
+
113
+ expect(roots).toContainEqual({
114
+ value: "--chdir=examples/",
115
+ description: "directory",
116
+ noSpace: true,
117
+ group: "Paths",
118
+ });
119
+ } finally {
120
+ rmSync(cwd, { recursive: true, force: true });
121
+ }
122
+ });
123
+
85
124
  test("completes named config files", async () => {
86
125
  const cwd = mkdtempSync(join(tmpdir(), "rigkit-completion-configs-"));
87
126
  writeFileSync(join(cwd, "api.rig.config.ts"), "export default {}\n");
@@ -183,7 +222,7 @@ describe("CLI completion", () => {
183
222
  currentIndex: 1,
184
223
  });
185
224
 
186
- expect(items.map((item) => item.value)).toEqual(["plan", "projects"]);
225
+ expect(items.map((item) => item.value)).toEqual(["plan", "providers", "projects"]);
187
226
  });
188
227
  });
189
228
 
@@ -221,6 +260,40 @@ describe("CLI completion", () => {
221
260
  ]);
222
261
  });
223
262
 
263
+ test("completes provider targets, subcommands, and flags", async () => {
264
+ const targets = await completeRig({
265
+ cwd: process.cwd(),
266
+ words: ["rig", "providers", ""],
267
+ currentIndex: 2,
268
+ });
269
+
270
+ expect(targets.map((item) => item.value)).toEqual(["freestyle"]);
271
+
272
+ const targetFlags = await completeRig({
273
+ cwd: process.cwd(),
274
+ words: ["rig", "providers", "--"],
275
+ currentIndex: 2,
276
+ });
277
+
278
+ expect(targetFlags.map((item) => item.value)).toEqual(["--help"]);
279
+
280
+ const subcommands = await completeRig({
281
+ cwd: process.cwd(),
282
+ words: ["rig", "providers", "freestyle", ""],
283
+ currentIndex: 3,
284
+ });
285
+
286
+ expect(subcommands.map((item) => item.value)).toEqual(["clear"]);
287
+
288
+ const clearFlags = await completeRig({
289
+ cwd: process.cwd(),
290
+ words: ["rig", "providers", "freestyle", "clear", "--"],
291
+ currentIndex: 4,
292
+ });
293
+
294
+ expect(clearFlags.map((item) => item.value)).toEqual(["--json", "--help"]);
295
+ });
296
+
224
297
  test("completes project operation flags and workflow values", async () => {
225
298
  const projectDir = mkdtempSync(join(tmpdir(), "rigkit-completion-"));
226
299
  await withWorkspaceRuntime({ projectDir }, async () => {
package/src/completion.ts CHANGED
@@ -28,6 +28,7 @@ type CommandName =
28
28
  | "run"
29
29
  | "ls"
30
30
  | "cache"
31
+ | "providers"
31
32
  | "projects"
32
33
  | "doctor"
33
34
  | "version"
@@ -126,6 +127,7 @@ const GROUP_VALUES = "Values";
126
127
  const GROUP_PATHS = "Paths";
127
128
  const GROUP_SHELLS = "Shells";
128
129
  const GROUP_CACHE = "Cache entries";
130
+ const GROUP_PROVIDERS = "Providers";
129
131
 
130
132
  const COMMANDS: CompletionItem[] = withGroup(GROUP_COMMANDS, [
131
133
  { value: "help", description: "show CLI help" },
@@ -137,6 +139,7 @@ const COMMANDS: CompletionItem[] = withGroup(GROUP_COMMANDS, [
137
139
  { value: "run", description: "run a workspace operation" },
138
140
  { value: "ls", description: "list project workspaces" },
139
141
  { value: "cache", description: "inspect and clear Rigkit cache" },
142
+ { value: "providers", description: "manage provider-owned local state" },
140
143
  { value: "projects", description: "discover Rigkit projects" },
141
144
  { value: "doctor", description: "show runtime diagnostics" },
142
145
  { value: "version", description: "show CLI version" },
@@ -149,36 +152,42 @@ const JSON_OPTION = option(["--json"], "print JSON");
149
152
  const HELP_OPTION = option(["--help"], "show help");
150
153
 
151
154
  const GLOBAL_OPTIONS: OptionDefinition[] = [
152
- option(["-chdir", "--chdir"], "working directory", {
155
+ option(["--chdir", "-chdir"], "working directory", {
153
156
  group: GROUP_GLOBAL,
154
157
  takesValue: true,
155
158
  valueKind: "directories",
156
159
  completions: [
157
- { value: "-chdir=", noSpace: true },
158
160
  { value: "--chdir=", noSpace: true },
159
161
  ],
160
162
  }),
161
- option(["-config", "--config"], "config file", {
163
+ option(["--config", "-config"], "config file", {
162
164
  group: GROUP_GLOBAL,
163
165
  takesValue: true,
164
166
  valueKind: "config-files",
165
167
  completions: [
166
- { value: "-config=", noSpace: true },
167
168
  { value: "--config=", noSpace: true },
168
169
  ],
169
170
  }),
170
- option(["-state", "--state"], "state database path", {
171
+ option(["--state", "-state"], "state database path", {
171
172
  group: GROUP_GLOBAL,
172
173
  takesValue: true,
173
174
  valueKind: "filesystem",
174
175
  completions: [
175
- { value: "-state=", noSpace: true },
176
176
  { value: "--state=", noSpace: true },
177
177
  ],
178
178
  }),
179
- option(["-json", "--json"], "print JSON", { group: GROUP_GLOBAL }),
180
- option(["-help", "--help"], "show help", { group: GROUP_GLOBAL }),
181
- option(["-version", "--version", "-v"], "show version", { group: GROUP_GLOBAL }),
179
+ option(["--json", "-json"], "print JSON", {
180
+ group: GROUP_GLOBAL,
181
+ completions: [{ value: "--json" }],
182
+ }),
183
+ option(["--help", "-help"], "show help", {
184
+ group: GROUP_GLOBAL,
185
+ completions: [{ value: "--help" }],
186
+ }),
187
+ option(["--version", "-version", "-v"], "show version", {
188
+ group: GROUP_GLOBAL,
189
+ completions: [{ value: "--version" }, { value: "-v" }],
190
+ }),
182
191
  ];
183
192
 
184
193
  const COMMAND_OPTIONS: Record<CommandName, OptionDefinition[]> = {
@@ -226,6 +235,9 @@ const COMMAND_OPTIONS: Record<CommandName, OptionDefinition[]> = {
226
235
  cache: [
227
236
  HELP_OPTION,
228
237
  ],
238
+ providers: [
239
+ HELP_OPTION,
240
+ ],
229
241
  projects: [
230
242
  JSON_OPTION,
231
243
  HELP_OPTION,
@@ -294,6 +306,31 @@ const CACHE_SUBCOMMAND_OPTIONS: Record<string, OptionDefinition[]> = {
294
306
  ],
295
307
  };
296
308
 
309
+ const PROVIDER_TARGETS: CompletionItem[] = withGroup(GROUP_PROVIDERS, [
310
+ { value: "freestyle", description: "Freestyle provider state" },
311
+ ]);
312
+
313
+ const PROVIDER_SUBCOMMANDS: Record<string, CompletionItem[]> = {
314
+ freestyle: withGroup(GROUP_SUBCOMMANDS, [
315
+ { value: "clear", description: "clear Freestyle provider local auth and identity state" },
316
+ ]),
317
+ };
318
+
319
+ const PROVIDER_TARGET_OPTIONS: Record<string, OptionDefinition[]> = {
320
+ freestyle: [
321
+ HELP_OPTION,
322
+ ],
323
+ };
324
+
325
+ const PROVIDER_SUBCOMMAND_OPTIONS: Record<string, Record<string, OptionDefinition[]>> = {
326
+ freestyle: {
327
+ clear: [
328
+ JSON_OPTION,
329
+ HELP_OPTION,
330
+ ],
331
+ },
332
+ };
333
+
297
334
  const COMPLETION_SHELLS: CompletionItem[] = withGroup(GROUP_SHELLS, [
298
335
  { value: "bash", description: "Bash completion" },
299
336
  { value: "fish", description: "fish completion" },
@@ -471,6 +508,14 @@ async function optionsForCommandContext(context: CompletionContext): Promise<Opt
471
508
  if (cache.subcommand) return CACHE_SUBCOMMAND_OPTIONS[cache.subcommand] ?? [HELP_OPTION];
472
509
  }
473
510
 
511
+ if (context.command === "providers") {
512
+ const providers = parseProvidersArgs(context);
513
+ if (providers.provider && providers.subcommand) {
514
+ return PROVIDER_SUBCOMMAND_OPTIONS[providers.provider]?.[providers.subcommand] ?? [HELP_OPTION];
515
+ }
516
+ if (providers.provider) return PROVIDER_TARGET_OPTIONS[providers.provider] ?? [HELP_OPTION];
517
+ }
518
+
474
519
  return COMMAND_OPTIONS[context.command] ?? [];
475
520
  }
476
521
 
@@ -553,6 +598,8 @@ async function completeCommand(context: CompletionContext): Promise<CompletionIt
553
598
  return completeLsCommand(context);
554
599
  case "cache":
555
600
  return await completeCacheCommand(context);
601
+ case "providers":
602
+ return completeProvidersCommand(context);
556
603
  case "completion":
557
604
  return completeCompletionCommand(context);
558
605
  case "init":
@@ -690,6 +737,24 @@ async function completeCacheCommand(context: CompletionContext): Promise<Complet
690
737
  return [];
691
738
  }
692
739
 
740
+ function completeProvidersCommand(context: CompletionContext): CompletionItem[] {
741
+ const providers = parseProvidersArgs(context);
742
+ if (!providers.provider) {
743
+ if (context.current.startsWith("-")) return filterItems(optionItems(COMMAND_OPTIONS.providers), context.current);
744
+ return filterItems(PROVIDER_TARGETS, context.current);
745
+ }
746
+
747
+ if (!providers.subcommand) {
748
+ const options = PROVIDER_TARGET_OPTIONS[providers.provider] ?? [HELP_OPTION];
749
+ const subcommands = PROVIDER_SUBCOMMANDS[providers.provider] ?? [];
750
+ if (context.current.startsWith("-")) return filterItems(optionItems(options), context.current);
751
+ return filterItems(subcommands, context.current);
752
+ }
753
+
754
+ const options = PROVIDER_SUBCOMMAND_OPTIONS[providers.provider]?.[providers.subcommand] ?? [HELP_OPTION];
755
+ return completeOptionsOnlyCommand(context, options);
756
+ }
757
+
693
758
  function completeCompletionCommand(context: CompletionContext): CompletionItem[] {
694
759
  const shells = positionalsFrom(context.argsBefore, COMMAND_OPTIONS.completion);
695
760
  if (shells.length === 0) {
@@ -861,6 +926,15 @@ function parseCacheArgs(context: CompletionContext): { subcommand?: string; args
861
926
  };
862
927
  }
863
928
 
929
+ function parseProvidersArgs(context: CompletionContext): { provider?: string; subcommand?: string; args: string[] } {
930
+ const positionals = positionalsFrom(context.argsBefore, COMMAND_OPTIONS.providers);
931
+ return {
932
+ provider: positionals[0],
933
+ subcommand: positionals[1],
934
+ args: positionals.slice(2),
935
+ };
936
+ }
937
+
864
938
  function positionalsFrom(tokens: string[], options: OptionDefinition[]): string[] {
865
939
  return positionalTokensFrom(tokens, options).map((token) => token.value);
866
940
  }
@@ -5,7 +5,7 @@ import { join } from "node:path";
5
5
  import { discoverProjectConfigs, resolveConfigPaths } from "./project.ts";
6
6
 
7
7
  describe("CLI project resolution", () => {
8
- test("resolves -chdir to that directory's rig.config.ts", () => {
8
+ test("resolves --chdir to that directory's rig.config.ts", () => {
9
9
  const cwd = mkdtempSync(join(tmpdir(), "rigkit-cli-"));
10
10
  mkdirSync(join(cwd, "example"));
11
11
  writeFileSync(join(cwd, "example", "rig.config.ts"), "export default {}\n");
@@ -15,7 +15,7 @@ describe("CLI project resolution", () => {
15
15
  expect(paths.configPath).toBe(join(cwd, "example", "rig.config.ts"));
16
16
  });
17
17
 
18
- test("resolves -config project root from the config dirname", () => {
18
+ test("resolves --config project root from the config dirname", () => {
19
19
  const cwd = mkdtempSync(join(tmpdir(), "rigkit-cli-"));
20
20
  const paths = resolveConfigPaths({ cwd, config: "machines/platform.ts" });
21
21
 
@@ -40,7 +40,7 @@ describe("CLI project resolution", () => {
40
40
  writeFileSync(join(cwd, "web.rig.config.ts"), "export default {}\n");
41
41
 
42
42
  expect(() => resolveConfigPaths({ cwd })).toThrow(
43
- /Found named Rigkit configs[\s\S]*api\.rig\.config\.ts[\s\S]*web\.rig\.config\.ts[\s\S]*rig -chdir=\. -config=api\.rig\.config\.ts <command>/,
43
+ /Found named Rigkit configs[\s\S]*api\.rig\.config\.ts[\s\S]*web\.rig\.config\.ts[\s\S]*rig --chdir=\. --config=api\.rig\.config\.ts <command>/,
44
44
  );
45
45
  });
46
46
 
package/src/project.ts CHANGED
@@ -159,7 +159,7 @@ function appendConfigFilesHint(
159
159
  options: { commandCwd: string; hint?: ConfigFilesHint },
160
160
  ): string {
161
161
  const hint = options.hint;
162
- if (!hint) return `${message} Run "rig init" or pass -config=<file>.`;
162
+ if (!hint) return `${message} Run "rig init" or pass --config=<file>.`;
163
163
 
164
164
  const configFile = hint.files[0]!;
165
165
  const configPath = displayPath(options.commandCwd, join(hint.dir, configFile));
@@ -171,8 +171,8 @@ function appendConfigFilesHint(
171
171
  ...hint.files.map((file) => `- ${file}`),
172
172
  "",
173
173
  "Choose one explicitly:",
174
- ` rig -config=${configPath} <command>`,
175
- ` rig -chdir=${projectDir} -config=${configFile} <command>`,
174
+ ` rig --config=${configPath} <command>`,
175
+ ` rig --chdir=${projectDir} --config=${configFile} <command>`,
176
176
  ].join("\n");
177
177
  }
178
178
 
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const RIGKIT_CLI_VERSION = "0.2.10";
1
+ export const RIGKIT_CLI_VERSION = "0.2.11";