@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 +21 -0
- package/README.md +8 -5
- package/package.json +5 -4
- package/src/cli.test.ts +156 -2
- package/src/cli.ts +110 -15
- package/src/completion.test.ts +337 -9
- package/src/completion.ts +900 -337
- package/src/project.test.ts +3 -3
- package/src/project.ts +3 -3
- package/src/update-check.ts +224 -0
- package/src/version.ts +1 -1
package/src/completion.ts
CHANGED
|
@@ -18,6 +18,104 @@ type CompleteRigInput = {
|
|
|
18
18
|
cwd?: string;
|
|
19
19
|
};
|
|
20
20
|
|
|
21
|
+
type CommandName =
|
|
22
|
+
| "help"
|
|
23
|
+
| "init"
|
|
24
|
+
| "plan"
|
|
25
|
+
| "apply"
|
|
26
|
+
| "create"
|
|
27
|
+
| "rm"
|
|
28
|
+
| "run"
|
|
29
|
+
| "ls"
|
|
30
|
+
| "cache"
|
|
31
|
+
| "providers"
|
|
32
|
+
| "projects"
|
|
33
|
+
| "doctor"
|
|
34
|
+
| "version"
|
|
35
|
+
| "completion";
|
|
36
|
+
|
|
37
|
+
type CompletionContext = {
|
|
38
|
+
cwd: string;
|
|
39
|
+
words: string[];
|
|
40
|
+
currentIndex: number;
|
|
41
|
+
current: string;
|
|
42
|
+
before: string[];
|
|
43
|
+
command?: CommandName;
|
|
44
|
+
commandIndex?: number;
|
|
45
|
+
argsBefore: string[];
|
|
46
|
+
unknownRootPositionals: string[];
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
type ValueCompletionKind =
|
|
50
|
+
| "directories"
|
|
51
|
+
| "config-files"
|
|
52
|
+
| "filesystem"
|
|
53
|
+
| "package-managers";
|
|
54
|
+
|
|
55
|
+
type OptionDefinition = {
|
|
56
|
+
flags: string[];
|
|
57
|
+
completions?: Array<{ value: string; noSpace?: boolean }>;
|
|
58
|
+
description: string;
|
|
59
|
+
group: string;
|
|
60
|
+
takesValue?: boolean;
|
|
61
|
+
valueKind?: ValueCompletionKind;
|
|
62
|
+
operation?: RuntimeOperationDefinition;
|
|
63
|
+
runtimeOption?: RuntimeOperationCliOption;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
type RuntimeOperationManifest = {
|
|
67
|
+
operations: RuntimeOperationDefinition[];
|
|
68
|
+
workspaceOperations?: RuntimeOperationDefinition[];
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
type RuntimeOperationDefinition = {
|
|
72
|
+
id: string;
|
|
73
|
+
aliases?: string[];
|
|
74
|
+
title?: string;
|
|
75
|
+
description?: string;
|
|
76
|
+
createsWorkspace?: boolean;
|
|
77
|
+
cli?: {
|
|
78
|
+
positionals?: Array<{ name: string; index: number }>;
|
|
79
|
+
options?: RuntimeOperationCliOption[];
|
|
80
|
+
};
|
|
81
|
+
inputSchema?: {
|
|
82
|
+
properties?: Record<string, JsonSchemaProperty>;
|
|
83
|
+
required?: string[];
|
|
84
|
+
};
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
type RuntimeOperationCliOption = {
|
|
88
|
+
name: string;
|
|
89
|
+
flag: string;
|
|
90
|
+
aliases?: string[];
|
|
91
|
+
required?: boolean;
|
|
92
|
+
runtime?: boolean;
|
|
93
|
+
type?: "string" | "boolean" | "number";
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
type JsonSchemaProperty = {
|
|
97
|
+
type?: string;
|
|
98
|
+
description?: string;
|
|
99
|
+
default?: unknown;
|
|
100
|
+
enum?: unknown[];
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
type RuntimeWorkspaceCompletion = {
|
|
104
|
+
name: string;
|
|
105
|
+
workflow: string;
|
|
106
|
+
createdAt: string;
|
|
107
|
+
updatedAt: string;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
type RuntimeCacheCompletionEntry = {
|
|
111
|
+
scope: "local" | "global";
|
|
112
|
+
workflow: string;
|
|
113
|
+
nodePath: string;
|
|
114
|
+
nodeName: string;
|
|
115
|
+
invalidated: boolean;
|
|
116
|
+
createdAt: string;
|
|
117
|
+
};
|
|
118
|
+
|
|
21
119
|
const GROUP_COMMANDS = "Commands";
|
|
22
120
|
const GROUP_SUBCOMMANDS = "Subcommands";
|
|
23
121
|
const GROUP_FLAGS = "Flags";
|
|
@@ -28,6 +126,8 @@ const GROUP_OPERATIONS = "Operations";
|
|
|
28
126
|
const GROUP_VALUES = "Values";
|
|
29
127
|
const GROUP_PATHS = "Paths";
|
|
30
128
|
const GROUP_SHELLS = "Shells";
|
|
129
|
+
const GROUP_CACHE = "Cache entries";
|
|
130
|
+
const GROUP_PROVIDERS = "Providers";
|
|
31
131
|
|
|
32
132
|
const COMMANDS: CompletionItem[] = withGroup(GROUP_COMMANDS, [
|
|
33
133
|
{ value: "help", description: "show CLI help" },
|
|
@@ -39,245 +139,656 @@ const COMMANDS: CompletionItem[] = withGroup(GROUP_COMMANDS, [
|
|
|
39
139
|
{ value: "run", description: "run a workspace operation" },
|
|
40
140
|
{ value: "ls", description: "list project workspaces" },
|
|
41
141
|
{ value: "cache", description: "inspect and clear Rigkit cache" },
|
|
142
|
+
{ value: "providers", description: "manage provider-owned local state" },
|
|
42
143
|
{ value: "projects", description: "discover Rigkit projects" },
|
|
43
144
|
{ value: "doctor", description: "show runtime diagnostics" },
|
|
44
145
|
{ value: "version", description: "show CLI version" },
|
|
45
146
|
{ value: "completion", description: "generate shell completion" },
|
|
46
147
|
]);
|
|
47
148
|
|
|
48
|
-
const
|
|
149
|
+
const COMMAND_NAMES = new Set(COMMANDS.map((command) => command.value as CommandName));
|
|
150
|
+
|
|
151
|
+
const JSON_OPTION = option(["--json"], "print JSON");
|
|
152
|
+
const HELP_OPTION = option(["--help"], "show help");
|
|
153
|
+
|
|
154
|
+
const GLOBAL_OPTIONS: OptionDefinition[] = [
|
|
155
|
+
option(["--chdir", "-chdir"], "working directory", {
|
|
156
|
+
group: GROUP_GLOBAL,
|
|
157
|
+
takesValue: true,
|
|
158
|
+
valueKind: "directories",
|
|
159
|
+
completions: [
|
|
160
|
+
{ value: "--chdir=", noSpace: true },
|
|
161
|
+
],
|
|
162
|
+
}),
|
|
163
|
+
option(["--config", "-config"], "config file", {
|
|
164
|
+
group: GROUP_GLOBAL,
|
|
165
|
+
takesValue: true,
|
|
166
|
+
valueKind: "config-files",
|
|
167
|
+
completions: [
|
|
168
|
+
{ value: "--config=", noSpace: true },
|
|
169
|
+
],
|
|
170
|
+
}),
|
|
171
|
+
option(["--state", "-state"], "state database path", {
|
|
172
|
+
group: GROUP_GLOBAL,
|
|
173
|
+
takesValue: true,
|
|
174
|
+
valueKind: "filesystem",
|
|
175
|
+
completions: [
|
|
176
|
+
{ value: "--state=", noSpace: true },
|
|
177
|
+
],
|
|
178
|
+
}),
|
|
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
|
+
}),
|
|
191
|
+
];
|
|
192
|
+
|
|
193
|
+
const COMMAND_OPTIONS: Record<CommandName, OptionDefinition[]> = {
|
|
194
|
+
init: [
|
|
195
|
+
option(["--name"], "project and workflow name", { takesValue: true }),
|
|
196
|
+
option(["--api-key"], "Freestyle API key", { takesValue: true }),
|
|
197
|
+
option(["--package-manager"], "npm, bun, pnpm, or skip", {
|
|
198
|
+
takesValue: true,
|
|
199
|
+
valueKind: "package-managers",
|
|
200
|
+
}),
|
|
201
|
+
option(["--force"], "overwrite existing config"),
|
|
202
|
+
JSON_OPTION,
|
|
203
|
+
HELP_OPTION,
|
|
204
|
+
],
|
|
205
|
+
plan: [
|
|
206
|
+
option(["--all"], "run against every discovered project"),
|
|
207
|
+
option(["--discover"], "discover projects below the selected directory"),
|
|
208
|
+
JSON_OPTION,
|
|
209
|
+
HELP_OPTION,
|
|
210
|
+
],
|
|
211
|
+
apply: [
|
|
212
|
+
option(["--all"], "run against every discovered project"),
|
|
213
|
+
option(["--discover"], "discover projects below the selected directory"),
|
|
214
|
+
JSON_OPTION,
|
|
215
|
+
HELP_OPTION,
|
|
216
|
+
],
|
|
217
|
+
create: [
|
|
218
|
+
JSON_OPTION,
|
|
219
|
+
HELP_OPTION,
|
|
220
|
+
],
|
|
221
|
+
rm: [
|
|
222
|
+
option(["-y", "--yes"], "skip confirmation"),
|
|
223
|
+
option(["--all"], "remove every workspace"),
|
|
224
|
+
JSON_OPTION,
|
|
225
|
+
HELP_OPTION,
|
|
226
|
+
],
|
|
227
|
+
run: [
|
|
228
|
+
JSON_OPTION,
|
|
229
|
+
HELP_OPTION,
|
|
230
|
+
],
|
|
231
|
+
ls: [
|
|
232
|
+
JSON_OPTION,
|
|
233
|
+
HELP_OPTION,
|
|
234
|
+
],
|
|
235
|
+
cache: [
|
|
236
|
+
HELP_OPTION,
|
|
237
|
+
],
|
|
238
|
+
providers: [
|
|
239
|
+
HELP_OPTION,
|
|
240
|
+
],
|
|
241
|
+
projects: [
|
|
242
|
+
JSON_OPTION,
|
|
243
|
+
HELP_OPTION,
|
|
244
|
+
],
|
|
245
|
+
doctor: [
|
|
246
|
+
option(["--cli"], "show CLI diagnostics only"),
|
|
247
|
+
JSON_OPTION,
|
|
248
|
+
HELP_OPTION,
|
|
249
|
+
],
|
|
250
|
+
version: [
|
|
251
|
+
JSON_OPTION,
|
|
252
|
+
HELP_OPTION,
|
|
253
|
+
],
|
|
254
|
+
help: [
|
|
255
|
+
JSON_OPTION,
|
|
256
|
+
HELP_OPTION,
|
|
257
|
+
],
|
|
258
|
+
completion: [
|
|
259
|
+
HELP_OPTION,
|
|
260
|
+
],
|
|
261
|
+
};
|
|
49
262
|
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
263
|
+
const CORE_OPERATION_OPTIONS: Partial<Record<CommandName, OptionDefinition[]>> = {
|
|
264
|
+
plan: [
|
|
265
|
+
option(["--workflow"], "workflow name", { takesValue: true }),
|
|
266
|
+
],
|
|
267
|
+
apply: [
|
|
268
|
+
option(["--workflow"], "workflow name", { takesValue: true }),
|
|
269
|
+
option(["--dry-run"], "plan without applying changes"),
|
|
270
|
+
],
|
|
271
|
+
create: [
|
|
272
|
+
option(["--workflow"], "workflow name", { takesValue: true }),
|
|
273
|
+
option(["--name"], "workspace name", { takesValue: true }),
|
|
274
|
+
],
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
const LIST_TARGETS: CompletionItem[] = withGroup(GROUP_TARGETS, [
|
|
278
|
+
{ value: "workspaces", description: "list workspaces" },
|
|
279
|
+
{ value: "snapshots", description: "list snapshots" },
|
|
280
|
+
{ value: "config", description: "show project config" },
|
|
57
281
|
]);
|
|
58
282
|
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
]),
|
|
67
|
-
plan: withGroup(GROUP_FLAGS, [
|
|
68
|
-
{ value: "--all", description: "run against every discovered project" },
|
|
69
|
-
{ value: "--discover", description: "discover projects below the selected directory" },
|
|
70
|
-
{ value: "--json", description: "print JSON" },
|
|
71
|
-
]),
|
|
72
|
-
apply: withGroup(GROUP_FLAGS, [
|
|
73
|
-
{ value: "--all", description: "run against every discovered project" },
|
|
74
|
-
{ value: "--discover", description: "discover projects below the selected directory" },
|
|
75
|
-
{ value: "--json", description: "print JSON" },
|
|
76
|
-
]),
|
|
77
|
-
create: withGroup(GROUP_FLAGS, [
|
|
78
|
-
{ value: "--json", description: "print JSON" },
|
|
79
|
-
]),
|
|
80
|
-
rm: withGroup(GROUP_FLAGS, [
|
|
81
|
-
{ value: "-y", description: "skip confirmation" },
|
|
82
|
-
{ value: "--yes", description: "skip confirmation" },
|
|
83
|
-
{ value: "--json", description: "print JSON" },
|
|
84
|
-
]),
|
|
85
|
-
run: withGroup(GROUP_FLAGS, [
|
|
86
|
-
{ value: "--json", description: "print JSON" },
|
|
87
|
-
]),
|
|
283
|
+
const CACHE_SUBCOMMANDS: CompletionItem[] = withGroup(GROUP_SUBCOMMANDS, [
|
|
284
|
+
{ value: "ls", description: "list cache entries" },
|
|
285
|
+
{ value: "clear", description: "clear cache entries" },
|
|
286
|
+
{ value: "invalidate", description: "mark cached task outputs stale" },
|
|
287
|
+
]);
|
|
288
|
+
|
|
289
|
+
const CACHE_SUBCOMMAND_OPTIONS: Record<string, OptionDefinition[]> = {
|
|
88
290
|
ls: [
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
]),
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
291
|
+
JSON_OPTION,
|
|
292
|
+
HELP_OPTION,
|
|
293
|
+
],
|
|
294
|
+
clear: [
|
|
295
|
+
option(["--local"], "clear local cache entries"),
|
|
296
|
+
option(["--global"], "clear global cache fragments"),
|
|
297
|
+
option(["--all"], "clear every global fragment with --global"),
|
|
298
|
+
JSON_OPTION,
|
|
299
|
+
HELP_OPTION,
|
|
300
|
+
],
|
|
301
|
+
invalidate: [
|
|
302
|
+
option(["--all"], "invalidate every cached task"),
|
|
303
|
+
option(["-y", "--yes"], "skip confirmation"),
|
|
304
|
+
JSON_OPTION,
|
|
305
|
+
HELP_OPTION,
|
|
97
306
|
],
|
|
98
|
-
projects: withGroup(GROUP_FLAGS, [
|
|
99
|
-
{ value: "--json", description: "print JSON" },
|
|
100
|
-
]),
|
|
101
|
-
cache: withGroup(GROUP_SUBCOMMANDS, [
|
|
102
|
-
{ value: "ls", description: "list cache entries" },
|
|
103
|
-
{ value: "clear", description: "clear cache entries" },
|
|
104
|
-
]),
|
|
105
|
-
completion: withGroup(GROUP_SHELLS, [
|
|
106
|
-
{ value: "bash", description: "Bash completion" },
|
|
107
|
-
{ value: "fish", description: "fish completion" },
|
|
108
|
-
{ value: "zsh", description: "zsh completion" },
|
|
109
|
-
]),
|
|
110
307
|
};
|
|
111
308
|
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
{ value: "
|
|
119
|
-
{ value: "--all", description: "clear every global fragment" },
|
|
120
|
-
{ value: "--json", description: "print JSON" },
|
|
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" },
|
|
121
316
|
]),
|
|
122
317
|
};
|
|
123
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
|
+
|
|
334
|
+
const COMPLETION_SHELLS: CompletionItem[] = withGroup(GROUP_SHELLS, [
|
|
335
|
+
{ value: "bash", description: "Bash completion" },
|
|
336
|
+
{ value: "fish", description: "fish completion" },
|
|
337
|
+
{ value: "zsh", description: "zsh completion" },
|
|
338
|
+
]);
|
|
339
|
+
|
|
340
|
+
const PROJECT_OPERATION_COMMANDS = new Set<CommandName>(["plan", "apply", "create"]);
|
|
341
|
+
|
|
124
342
|
function withGroup(group: string, items: Omit<CompletionItem, "group">[]): CompletionItem[] {
|
|
125
343
|
return items.map((item) => ({ ...item, group }));
|
|
126
344
|
}
|
|
127
345
|
|
|
128
|
-
|
|
346
|
+
function option(
|
|
347
|
+
flags: string[],
|
|
348
|
+
description: string,
|
|
349
|
+
input: Partial<Omit<OptionDefinition, "flags" | "description">> = {},
|
|
350
|
+
): OptionDefinition {
|
|
351
|
+
return {
|
|
352
|
+
flags,
|
|
353
|
+
description,
|
|
354
|
+
group: input.group ?? GROUP_FLAGS,
|
|
355
|
+
takesValue: input.takesValue,
|
|
356
|
+
valueKind: input.valueKind,
|
|
357
|
+
completions: input.completions,
|
|
358
|
+
operation: input.operation,
|
|
359
|
+
runtimeOption: input.runtimeOption,
|
|
360
|
+
};
|
|
361
|
+
}
|
|
129
362
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
"--chdir",
|
|
133
|
-
"-config",
|
|
134
|
-
"--config",
|
|
135
|
-
"-state",
|
|
136
|
-
"--state",
|
|
137
|
-
"--name",
|
|
138
|
-
"--api-key",
|
|
139
|
-
"--package-manager",
|
|
140
|
-
]);
|
|
363
|
+
export async function completeRig(input: CompleteRigInput): Promise<CompletionItem[]> {
|
|
364
|
+
const context = completionContext(input);
|
|
141
365
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
}
|
|
366
|
+
const valueRequest = await optionValueRequest(context);
|
|
367
|
+
if (valueRequest) {
|
|
368
|
+
return await completeOptionValue(valueRequest);
|
|
369
|
+
}
|
|
146
370
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
}
|
|
371
|
+
if (!context.command) {
|
|
372
|
+
if (context.unknownRootPositionals.length > 0) return [];
|
|
373
|
+
return filterItems(
|
|
374
|
+
context.current.startsWith("-")
|
|
375
|
+
? optionItems(GLOBAL_OPTIONS)
|
|
376
|
+
: [...COMMANDS, ...optionItems(GLOBAL_OPTIONS)],
|
|
377
|
+
context.current,
|
|
378
|
+
);
|
|
379
|
+
}
|
|
156
380
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
workflow: string;
|
|
160
|
-
createdAt: string;
|
|
161
|
-
updatedAt: string;
|
|
162
|
-
};
|
|
381
|
+
return await completeCommand(context);
|
|
382
|
+
}
|
|
163
383
|
|
|
164
|
-
|
|
384
|
+
function completionContext(input: CompleteRigInput): CompletionContext {
|
|
165
385
|
const cwd = input.cwd ?? process.cwd();
|
|
166
386
|
const words = input.words.length > 0 ? input.words : ["rig"];
|
|
167
387
|
const currentIndex = input.currentIndex ?? Math.max(0, words.length - 1);
|
|
168
388
|
const current = words[currentIndex] ?? "";
|
|
169
389
|
const before = words.slice(1, currentIndex);
|
|
170
|
-
const
|
|
390
|
+
const unknownRootPositionals: string[] = [];
|
|
391
|
+
let command: CommandName | undefined;
|
|
392
|
+
let commandIndex: number | undefined;
|
|
393
|
+
|
|
394
|
+
for (let index = 0; index < before.length; index += 1) {
|
|
395
|
+
const word = before[index]!;
|
|
396
|
+
const globalOption = findOption(GLOBAL_OPTIONS, word);
|
|
397
|
+
if (globalOption?.takesValue && !hasInlineValue(word)) {
|
|
398
|
+
index += 1;
|
|
399
|
+
continue;
|
|
400
|
+
}
|
|
401
|
+
if (isOptionToken(word)) continue;
|
|
171
402
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
inlinePrefix: inlineOption.prefix,
|
|
180
|
-
});
|
|
403
|
+
if (isCommandName(word)) {
|
|
404
|
+
command = word;
|
|
405
|
+
commandIndex = index;
|
|
406
|
+
break;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
unknownRootPositionals.push(word);
|
|
181
410
|
}
|
|
182
411
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
412
|
+
return {
|
|
413
|
+
cwd,
|
|
414
|
+
words,
|
|
415
|
+
currentIndex,
|
|
416
|
+
current,
|
|
417
|
+
before,
|
|
418
|
+
command,
|
|
419
|
+
commandIndex,
|
|
420
|
+
argsBefore: commandIndex === undefined ? [] : before.slice(commandIndex + 1),
|
|
421
|
+
unknownRootPositionals,
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
async function optionValueRequest(context: CompletionContext): Promise<{
|
|
426
|
+
option: OptionDefinition;
|
|
427
|
+
current: string;
|
|
428
|
+
cwd: string;
|
|
429
|
+
words: string[];
|
|
430
|
+
inlinePrefix?: string;
|
|
431
|
+
} | undefined> {
|
|
432
|
+
const inlineOption = parseInlineValueOption(context.current);
|
|
433
|
+
if (inlineOption) {
|
|
434
|
+
const definition = await resolveOptionDefinition(context, inlineOption.option);
|
|
435
|
+
if (definition?.takesValue || definition?.runtimeOption?.type === "boolean") {
|
|
436
|
+
return {
|
|
437
|
+
option: definition,
|
|
438
|
+
current: inlineOption.value,
|
|
439
|
+
cwd: context.cwd,
|
|
440
|
+
words: context.words,
|
|
441
|
+
inlinePrefix: inlineOption.prefix,
|
|
442
|
+
};
|
|
443
|
+
}
|
|
191
444
|
}
|
|
192
445
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
446
|
+
const previous = context.before.at(-1);
|
|
447
|
+
if (!previous) return undefined;
|
|
448
|
+
if (hasInlineValue(previous)) return undefined;
|
|
449
|
+
|
|
450
|
+
const definition = await resolveOptionDefinition(context, previous);
|
|
451
|
+
if (!definition?.takesValue) return undefined;
|
|
452
|
+
return {
|
|
453
|
+
option: definition,
|
|
454
|
+
current: context.current,
|
|
455
|
+
cwd: context.cwd,
|
|
456
|
+
words: context.words,
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
async function resolveOptionDefinition(
|
|
461
|
+
context: CompletionContext,
|
|
462
|
+
flag: string,
|
|
463
|
+
): Promise<OptionDefinition | undefined> {
|
|
464
|
+
const globalOption = findOption(GLOBAL_OPTIONS, flag);
|
|
465
|
+
if (globalOption) return globalOption;
|
|
466
|
+
if (!context.command) return undefined;
|
|
467
|
+
|
|
468
|
+
const commandOptions = await optionsForCommandContext(context);
|
|
469
|
+
return findOption(commandOptions, flag);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
async function optionsForCommandContext(context: CompletionContext): Promise<OptionDefinition[]> {
|
|
473
|
+
if (!context.command) return GLOBAL_OPTIONS;
|
|
474
|
+
|
|
475
|
+
if (PROJECT_OPERATION_COMMANDS.has(context.command)) {
|
|
476
|
+
const operation = await safeResolveRuntimeOperation(resolveProjectDir(context.words, context.cwd), context.command);
|
|
477
|
+
return mergeOptions([
|
|
478
|
+
...operationOptions(operation),
|
|
479
|
+
...(CORE_OPERATION_OPTIONS[context.command] ?? []),
|
|
480
|
+
...COMMAND_OPTIONS[context.command],
|
|
481
|
+
]);
|
|
200
482
|
}
|
|
201
483
|
|
|
202
|
-
if (
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
...(option.aliases ?? []).map((alias) => ({ value: alias, description: option.name, group: GROUP_FLAGS })),
|
|
211
|
-
]),
|
|
212
|
-
...COMMAND_OPTIONS.rm,
|
|
213
|
-
...GLOBAL_OPTIONS,
|
|
214
|
-
], current);
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
if (command === "run") {
|
|
218
|
-
const run = parseWorkspaceRunCommand(before);
|
|
219
|
-
if (run.workspace && run.operation) {
|
|
220
|
-
const operation = await safeResolveWorkspaceOperation(resolveProjectDir(words, cwd), run.operation);
|
|
221
|
-
return filterItems([
|
|
222
|
-
...(operation?.cli?.options ?? []).flatMap((option) => [
|
|
223
|
-
{ value: option.flag, description: option.name, group: GROUP_FLAGS },
|
|
224
|
-
...(option.aliases ?? []).map((alias) => ({ value: alias, description: option.name, group: GROUP_FLAGS })),
|
|
225
|
-
]),
|
|
226
|
-
...COMMAND_OPTIONS.run,
|
|
227
|
-
...GLOBAL_OPTIONS,
|
|
228
|
-
], current);
|
|
229
|
-
}
|
|
484
|
+
if (context.command === "run") {
|
|
485
|
+
const run = parseRunArgs(context);
|
|
486
|
+
if (run.workspace && run.operation) {
|
|
487
|
+
const operation = await safeResolveWorkspaceOperation(resolveProjectDir(context.words, context.cwd), run.operation);
|
|
488
|
+
return mergeOptions([
|
|
489
|
+
...operationOptions(operation),
|
|
490
|
+
...COMMAND_OPTIONS.run,
|
|
491
|
+
]);
|
|
230
492
|
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
...(
|
|
239
|
-
...
|
|
240
|
-
]
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
if (context.command === "rm") {
|
|
496
|
+
const remove = parseRmArgs(context);
|
|
497
|
+
if (remove.workspace) {
|
|
498
|
+
const operation = await safeResolveWorkspaceOperation(resolveProjectDir(context.words, context.cwd), "remove");
|
|
499
|
+
return mergeOptions([
|
|
500
|
+
...operationOptions(operation),
|
|
501
|
+
...COMMAND_OPTIONS.rm,
|
|
502
|
+
]);
|
|
241
503
|
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
if (context.command === "cache") {
|
|
507
|
+
const cache = parseCacheArgs(context);
|
|
508
|
+
if (cache.subcommand) return CACHE_SUBCOMMAND_OPTIONS[cache.subcommand] ?? [HELP_OPTION];
|
|
509
|
+
}
|
|
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];
|
|
247
515
|
}
|
|
248
|
-
return
|
|
516
|
+
if (providers.provider) return PROVIDER_TARGET_OPTIONS[providers.provider] ?? [HELP_OPTION];
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
return COMMAND_OPTIONS[context.command] ?? [];
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
async function completeOptionValue(input: {
|
|
523
|
+
option: OptionDefinition;
|
|
524
|
+
current: string;
|
|
525
|
+
cwd: string;
|
|
526
|
+
words: string[];
|
|
527
|
+
inlinePrefix?: string;
|
|
528
|
+
}): Promise<CompletionItem[]> {
|
|
529
|
+
let items: CompletionItem[];
|
|
530
|
+
switch (input.option.valueKind) {
|
|
531
|
+
case "directories":
|
|
532
|
+
items = completeDirectories(input.cwd, input.current);
|
|
533
|
+
break;
|
|
534
|
+
case "config-files":
|
|
535
|
+
items = completeConfigPaths(projectBaseDir(input.words, input.cwd), input.current);
|
|
536
|
+
break;
|
|
537
|
+
case "filesystem":
|
|
538
|
+
items = completeFilesystemPaths(input.cwd, input.current);
|
|
539
|
+
break;
|
|
540
|
+
case "package-managers":
|
|
541
|
+
items = filterItems([
|
|
542
|
+
{ value: "npm", group: GROUP_VALUES },
|
|
543
|
+
{ value: "bun", group: GROUP_VALUES },
|
|
544
|
+
{ value: "pnpm", group: GROUP_VALUES },
|
|
545
|
+
{ value: "skip", group: GROUP_VALUES },
|
|
546
|
+
], input.current);
|
|
547
|
+
break;
|
|
548
|
+
default:
|
|
549
|
+
items = await completeRuntimeOptionValue(input.option, input.current, input.words, input.cwd);
|
|
550
|
+
break;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
if (!input.inlinePrefix) return items;
|
|
554
|
+
return items.map((item) => ({
|
|
555
|
+
...item,
|
|
556
|
+
value: `${input.inlinePrefix}${item.value}`,
|
|
557
|
+
}));
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
async function completeRuntimeOptionValue(
|
|
561
|
+
option: OptionDefinition,
|
|
562
|
+
current: string,
|
|
563
|
+
words: string[],
|
|
564
|
+
cwd: string,
|
|
565
|
+
): Promise<CompletionItem[]> {
|
|
566
|
+
const runtimeOption = option.runtimeOption;
|
|
567
|
+
const operation = option.operation;
|
|
568
|
+
|
|
569
|
+
if (runtimeOption?.type === "boolean") {
|
|
570
|
+
return filterItems([
|
|
571
|
+
{ value: "true", group: GROUP_VALUES },
|
|
572
|
+
{ value: "false", group: GROUP_VALUES },
|
|
573
|
+
], current);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
const schema = operation && runtimeOption ? operation.inputSchema?.properties?.[runtimeOption.name] : undefined;
|
|
577
|
+
const enumItems = enumCompletionItems(schema);
|
|
578
|
+
if (enumItems.length > 0) return filterItems(enumItems, current);
|
|
579
|
+
|
|
580
|
+
if (runtimeOption?.name === "workflow" || option.flags.includes("--workflow")) {
|
|
581
|
+
return filterItems(await safeWorkflowTargets(resolveProjectDir(words, cwd)), current);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
return [];
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
async function completeCommand(context: CompletionContext): Promise<CompletionItem[]> {
|
|
588
|
+
switch (context.command) {
|
|
589
|
+
case "plan":
|
|
590
|
+
case "apply":
|
|
591
|
+
case "create":
|
|
592
|
+
return await completeProjectOperationCommand(context);
|
|
593
|
+
case "run":
|
|
594
|
+
return await completeRunCommand(context);
|
|
595
|
+
case "rm":
|
|
596
|
+
return await completeRmCommand(context);
|
|
597
|
+
case "ls":
|
|
598
|
+
return completeLsCommand(context);
|
|
599
|
+
case "cache":
|
|
600
|
+
return await completeCacheCommand(context);
|
|
601
|
+
case "providers":
|
|
602
|
+
return completeProvidersCommand(context);
|
|
603
|
+
case "completion":
|
|
604
|
+
return completeCompletionCommand(context);
|
|
605
|
+
case "init":
|
|
606
|
+
case "projects":
|
|
607
|
+
case "doctor":
|
|
608
|
+
case "version":
|
|
609
|
+
case "help":
|
|
610
|
+
return completeOptionsOnlyCommand(context, COMMAND_OPTIONS[context.command]);
|
|
611
|
+
}
|
|
612
|
+
return [];
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
async function completeProjectOperationCommand(context: CompletionContext): Promise<CompletionItem[]> {
|
|
616
|
+
const operation = await safeResolveRuntimeOperation(resolveProjectDir(context.words, context.cwd), context.command!);
|
|
617
|
+
const options = mergeOptions([
|
|
618
|
+
...operationOptions(operation),
|
|
619
|
+
...(CORE_OPERATION_OPTIONS[context.command!] ?? []),
|
|
620
|
+
...COMMAND_OPTIONS[context.command!],
|
|
621
|
+
]);
|
|
622
|
+
const positionals = positionalsFrom(context.argsBefore, options);
|
|
623
|
+
|
|
624
|
+
if (context.current.startsWith("-") || context.current === "") {
|
|
625
|
+
const positionalItems = operation ? operationPositionalValueItems(operation, positionals.length, context.current) : [];
|
|
626
|
+
return filterItems([...positionalItems, ...optionItems(options)], context.current);
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
if (!operation) return [];
|
|
630
|
+
return filterItems(operationPositionalValueItems(operation, positionals.length, context.current), context.current);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
async function completeRunCommand(context: CompletionContext): Promise<CompletionItem[]> {
|
|
634
|
+
const paths = resolveProjectDir(context.words, context.cwd);
|
|
635
|
+
const baseOptions = COMMAND_OPTIONS.run;
|
|
636
|
+
const run = parseRunArgs(context);
|
|
637
|
+
|
|
638
|
+
if (!run.workspace) {
|
|
639
|
+
return completeMixed({
|
|
640
|
+
primary: await safeWorkspaceTargets(paths),
|
|
641
|
+
options: baseOptions,
|
|
642
|
+
current: context.current,
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
if (!run.operation) {
|
|
647
|
+
return completeMixed({
|
|
648
|
+
primary: await safeWorkspaceOperationTargets(paths),
|
|
649
|
+
options: baseOptions,
|
|
650
|
+
current: context.current,
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
const operation = await safeResolveWorkspaceOperation(paths, run.operation);
|
|
655
|
+
const options = mergeOptions([
|
|
656
|
+
...operationOptions(operation),
|
|
657
|
+
...baseOptions,
|
|
658
|
+
]);
|
|
659
|
+
const positionals = positionalsFrom(run.args, options);
|
|
660
|
+
|
|
661
|
+
if (context.current.startsWith("-") || context.current === "") {
|
|
662
|
+
const positionalItems = operation ? operationPositionalValueItems(operation, positionals.length, context.current) : [];
|
|
663
|
+
return filterItems([...positionalItems, ...optionItems(options)], context.current);
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
if (!operation) return [];
|
|
667
|
+
return filterItems(operationPositionalValueItems(operation, positionals.length, context.current), context.current);
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
async function completeRmCommand(context: CompletionContext): Promise<CompletionItem[]> {
|
|
671
|
+
const paths = resolveProjectDir(context.words, context.cwd);
|
|
672
|
+
const remove = parseRmArgs(context);
|
|
673
|
+
const operation = remove.workspace ? await safeResolveWorkspaceOperation(paths, "remove") : undefined;
|
|
674
|
+
const options = mergeOptions([
|
|
675
|
+
...operationOptions(operation),
|
|
676
|
+
...COMMAND_OPTIONS.rm,
|
|
677
|
+
]);
|
|
678
|
+
|
|
679
|
+
if (!remove.workspace) {
|
|
680
|
+
return completeMixed({
|
|
681
|
+
primary: await safeWorkspaceTargets(paths),
|
|
682
|
+
options,
|
|
683
|
+
current: context.current,
|
|
684
|
+
});
|
|
249
685
|
}
|
|
250
686
|
|
|
251
|
-
|
|
687
|
+
if (context.current.startsWith("-") || context.current === "") {
|
|
688
|
+
return filterItems(optionItems(options), context.current);
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
return [];
|
|
692
|
+
}
|
|
252
693
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
694
|
+
function completeLsCommand(context: CompletionContext): CompletionItem[] {
|
|
695
|
+
const options = COMMAND_OPTIONS.ls;
|
|
696
|
+
const targets = positionalsFrom(context.argsBefore, options);
|
|
697
|
+
if (targets.length === 0) {
|
|
698
|
+
return completeMixed({
|
|
699
|
+
primary: LIST_TARGETS,
|
|
700
|
+
options,
|
|
701
|
+
current: context.current,
|
|
702
|
+
});
|
|
257
703
|
}
|
|
258
704
|
|
|
259
|
-
if (
|
|
260
|
-
|
|
261
|
-
if (!remove.workspace) return filterItems(await workspaceTargets(resolveProjectDir(words, cwd)), current);
|
|
705
|
+
if (context.current.startsWith("-") || context.current === "") {
|
|
706
|
+
return filterItems(optionItems(options), context.current);
|
|
262
707
|
}
|
|
263
708
|
|
|
264
|
-
|
|
265
|
-
|
|
709
|
+
return [];
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
async function completeCacheCommand(context: CompletionContext): Promise<CompletionItem[]> {
|
|
713
|
+
const cache = parseCacheArgs(context);
|
|
714
|
+
if (!cache.subcommand) {
|
|
715
|
+
if (context.current.startsWith("-")) return filterItems(optionItems(COMMAND_OPTIONS.cache), context.current);
|
|
716
|
+
return filterItems(CACHE_SUBCOMMANDS, context.current);
|
|
266
717
|
}
|
|
267
718
|
|
|
268
|
-
|
|
269
|
-
|
|
719
|
+
const options = CACHE_SUBCOMMAND_OPTIONS[cache.subcommand] ?? [HELP_OPTION];
|
|
720
|
+
if (cache.subcommand !== "invalidate") {
|
|
721
|
+
return completeOptionsOnlyCommand(context, options);
|
|
270
722
|
}
|
|
271
723
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
724
|
+
const stepArgs = positionalsFrom(cache.args, options);
|
|
725
|
+
if (stepArgs.length === 0) {
|
|
726
|
+
return completeMixed({
|
|
727
|
+
primary: await safeCacheInvalidateTargets(resolveProjectDir(context.words, context.cwd)),
|
|
728
|
+
options,
|
|
729
|
+
current: context.current,
|
|
730
|
+
});
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
if (context.current.startsWith("-") || context.current === "") {
|
|
734
|
+
return filterItems(optionItems(options), context.current);
|
|
276
735
|
}
|
|
277
736
|
|
|
278
737
|
return [];
|
|
279
738
|
}
|
|
280
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
|
+
|
|
758
|
+
function completeCompletionCommand(context: CompletionContext): CompletionItem[] {
|
|
759
|
+
const shells = positionalsFrom(context.argsBefore, COMMAND_OPTIONS.completion);
|
|
760
|
+
if (shells.length === 0) {
|
|
761
|
+
return completeMixed({
|
|
762
|
+
primary: COMPLETION_SHELLS,
|
|
763
|
+
options: COMMAND_OPTIONS.completion,
|
|
764
|
+
current: context.current,
|
|
765
|
+
});
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
if (context.current.startsWith("-") || context.current === "") {
|
|
769
|
+
return filterItems(optionItems(COMMAND_OPTIONS.completion), context.current);
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
return [];
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
function completeOptionsOnlyCommand(context: CompletionContext, options: OptionDefinition[]): CompletionItem[] {
|
|
776
|
+
if (context.current.startsWith("-") || context.current === "") {
|
|
777
|
+
return filterItems(optionItems(options), context.current);
|
|
778
|
+
}
|
|
779
|
+
return [];
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
function completeMixed(input: {
|
|
783
|
+
primary: CompletionItem[];
|
|
784
|
+
options: OptionDefinition[];
|
|
785
|
+
current: string;
|
|
786
|
+
}): CompletionItem[] {
|
|
787
|
+
if (input.current.startsWith("-")) return filterItems(optionItems(input.options), input.current);
|
|
788
|
+
if (input.current === "") return filterItems([...input.primary, ...optionItems(input.options)], input.current);
|
|
789
|
+
return filterItems(input.primary, input.current);
|
|
790
|
+
}
|
|
791
|
+
|
|
281
792
|
export function formatCompletionItems(items: CompletionItem[], shell: CompletionShell): string {
|
|
282
793
|
const lines = items.map((item) => {
|
|
283
794
|
if (shell === "bash") return item.value;
|
|
@@ -287,7 +798,6 @@ export function formatCompletionItems(items: CompletionItem[], shell: Completion
|
|
|
287
798
|
const group = item.group ?? "";
|
|
288
799
|
return `${item.value}\t${description}\t${marker}\t${group}`;
|
|
289
800
|
}
|
|
290
|
-
// fish: legacy two-column format works fine; descriptions render dim by default
|
|
291
801
|
return item.description ? `${item.value}\t${item.description}` : item.value;
|
|
292
802
|
});
|
|
293
803
|
return lines.join("\n");
|
|
@@ -310,6 +820,9 @@ _rig_completion() {
|
|
|
310
820
|
local completions
|
|
311
821
|
completions="$(command rig __complete --shell bash --index "$COMP_CWORD" -- "\${COMP_WORDS[@]}" 2>/dev/null)"
|
|
312
822
|
COMPREPLY=($(compgen -W "$completions" -- "\${COMP_WORDS[COMP_CWORD]}"))
|
|
823
|
+
if [[ "\${#COMPREPLY[@]}" -eq 1 && ( "\${COMPREPLY[0]}" == */ || "\${COMPREPLY[0]}" == *= ) ]]; then
|
|
824
|
+
compopt -o nospace 2>/dev/null || true
|
|
825
|
+
fi
|
|
313
826
|
}
|
|
314
827
|
complete -F _rig_completion rig
|
|
315
828
|
`;
|
|
@@ -327,11 +840,8 @@ complete -c rig -f -a "(__rig_complete)"
|
|
|
327
840
|
`;
|
|
328
841
|
}
|
|
329
842
|
|
|
330
|
-
return `#compdef rig
|
|
331
|
-
# rig zsh completion
|
|
332
|
-
# Visual defaults are scoped to :completion:*:rig:* so they don't override your
|
|
333
|
-
# global completion theme. Group headers render bold blue; descriptions inherit
|
|
334
|
-
# your usual style.
|
|
843
|
+
return `#compdef rig
|
|
844
|
+
# rig zsh completion generated by \`rig completion zsh\`.
|
|
335
845
|
|
|
336
846
|
() {
|
|
337
847
|
zstyle ':completion:*:rig:*:descriptions' format $'\\e[1;34m%d\\e[0m'
|
|
@@ -381,166 +891,179 @@ compdef _rig rig
|
|
|
381
891
|
`;
|
|
382
892
|
}
|
|
383
893
|
|
|
384
|
-
function
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
894
|
+
function parseInlineValueOption(current: string): { option: string; value: string; prefix: string } | undefined {
|
|
895
|
+
const index = current.indexOf("=");
|
|
896
|
+
if (index < 0) return undefined;
|
|
897
|
+
return {
|
|
898
|
+
option: current.slice(0, index),
|
|
899
|
+
value: current.slice(index + 1),
|
|
900
|
+
prefix: current.slice(0, index + 1),
|
|
901
|
+
};
|
|
902
|
+
}
|
|
393
903
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
904
|
+
function parseRunArgs(context: CompletionContext): { workspace?: string; operation?: string; args: string[] } {
|
|
905
|
+
const basePositionals = positionalTokensFrom(context.argsBefore, COMMAND_OPTIONS.run);
|
|
906
|
+
const workspace = basePositionals[0]?.value;
|
|
907
|
+
const operation = basePositionals[1]?.value;
|
|
908
|
+
const operationTokenIndex = basePositionals[1]?.index;
|
|
909
|
+
return {
|
|
910
|
+
workspace,
|
|
911
|
+
operation,
|
|
912
|
+
args: operationTokenIndex === undefined ? [] : context.argsBefore.slice(operationTokenIndex + 1),
|
|
913
|
+
};
|
|
398
914
|
}
|
|
399
915
|
|
|
400
|
-
function
|
|
401
|
-
|
|
402
|
-
|
|
916
|
+
function parseRmArgs(context: CompletionContext): { workspace?: string } {
|
|
917
|
+
const positionals = positionalsFrom(context.argsBefore, COMMAND_OPTIONS.rm);
|
|
918
|
+
return { workspace: positionals[0] };
|
|
919
|
+
}
|
|
403
920
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
if (word.startsWith("-")) continue;
|
|
921
|
+
function parseCacheArgs(context: CompletionContext): { subcommand?: string; args: string[] } {
|
|
922
|
+
const positionals = positionalsFrom(context.argsBefore, COMMAND_OPTIONS.cache);
|
|
923
|
+
return {
|
|
924
|
+
subcommand: positionals[0],
|
|
925
|
+
args: positionals.slice(1),
|
|
926
|
+
};
|
|
927
|
+
}
|
|
412
928
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
}
|
|
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
|
+
}
|
|
420
937
|
|
|
421
|
-
|
|
938
|
+
function positionalsFrom(tokens: string[], options: OptionDefinition[]): string[] {
|
|
939
|
+
return positionalTokensFrom(tokens, options).map((token) => token.value);
|
|
422
940
|
}
|
|
423
941
|
|
|
424
|
-
function
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
for (let index = 0; index <
|
|
428
|
-
const word =
|
|
429
|
-
if (
|
|
430
|
-
index
|
|
431
|
-
|
|
942
|
+
function positionalTokensFrom(tokens: string[], options: OptionDefinition[]): Array<{ value: string; index: number }> {
|
|
943
|
+
const positionalTokens: Array<{ value: string; index: number }> = [];
|
|
944
|
+
|
|
945
|
+
for (let index = 0; index < tokens.length; index += 1) {
|
|
946
|
+
const word = tokens[index]!;
|
|
947
|
+
if (word === "--") {
|
|
948
|
+
positionalTokens.push(...tokens.slice(index + 1).map((value, offset) => ({ value, index: index + 1 + offset })));
|
|
949
|
+
break;
|
|
432
950
|
}
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
if (
|
|
436
|
-
if (
|
|
951
|
+
|
|
952
|
+
const option = findOption(options, word) ?? findOption(GLOBAL_OPTIONS, word);
|
|
953
|
+
if (option && isOptionToken(word)) {
|
|
954
|
+
if (option.takesValue && !hasInlineValue(word)) index += 1;
|
|
437
955
|
continue;
|
|
438
956
|
}
|
|
439
|
-
|
|
957
|
+
|
|
958
|
+
if (isOptionToken(word)) continue;
|
|
959
|
+
positionalTokens.push({ value: word, index });
|
|
440
960
|
}
|
|
441
|
-
|
|
961
|
+
|
|
962
|
+
return positionalTokens;
|
|
442
963
|
}
|
|
443
964
|
|
|
444
|
-
function
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
if (word.includes("=") && OPTIONS_WITH_VALUES.has(word.slice(0, word.indexOf("=")))) continue;
|
|
454
|
-
if (word.startsWith("-")) continue;
|
|
455
|
-
if (!foundRemove) {
|
|
456
|
-
if (word === "rm") foundRemove = true;
|
|
457
|
-
continue;
|
|
458
|
-
}
|
|
459
|
-
args.push(word);
|
|
460
|
-
}
|
|
461
|
-
return { workspace: args[0], args: args.slice(1) };
|
|
965
|
+
function operationOptions(operation: RuntimeOperationDefinition | undefined): OptionDefinition[] {
|
|
966
|
+
if (!operation) return [];
|
|
967
|
+
return inferOperationOptions(operation).map((runtimeOption) =>
|
|
968
|
+
option([runtimeOption.flag, ...(runtimeOption.aliases ?? [])], optionDescription(operation, runtimeOption), {
|
|
969
|
+
takesValue: runtimeOption.type !== "boolean",
|
|
970
|
+
operation,
|
|
971
|
+
runtimeOption,
|
|
972
|
+
})
|
|
973
|
+
);
|
|
462
974
|
}
|
|
463
975
|
|
|
464
|
-
function
|
|
465
|
-
|
|
466
|
-
const
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
continue;
|
|
478
|
-
}
|
|
479
|
-
args.push(word);
|
|
480
|
-
}
|
|
481
|
-
return { subcommand: args[0], args: args.slice(1) };
|
|
976
|
+
function inferOperationOptions(operation: RuntimeOperationDefinition): RuntimeOperationCliOption[] {
|
|
977
|
+
const properties = operation.inputSchema?.properties ?? {};
|
|
978
|
+
const runtimeOptions = operation.cli?.options ?? Object.entries(properties).map(([name, schema]) => ({
|
|
979
|
+
name,
|
|
980
|
+
flag: `--${dashCase(name)}`,
|
|
981
|
+
required: operation.inputSchema?.required?.includes(name),
|
|
982
|
+
type: schema.type === "boolean" ? "boolean" : schema.type === "number" ? "number" : "string",
|
|
983
|
+
} satisfies RuntimeOperationCliOption));
|
|
984
|
+
|
|
985
|
+
return runtimeOptions.map((runtimeOption) => ({
|
|
986
|
+
...runtimeOption,
|
|
987
|
+
type: runtimeOption.type ?? schemaType(properties[runtimeOption.name]) ?? "string",
|
|
988
|
+
}));
|
|
482
989
|
}
|
|
483
990
|
|
|
484
|
-
function
|
|
485
|
-
|
|
486
|
-
|
|
991
|
+
function operationPositionalValueItems(
|
|
992
|
+
operation: RuntimeOperationDefinition,
|
|
993
|
+
positionalIndex: number,
|
|
994
|
+
current: string,
|
|
995
|
+
): CompletionItem[] {
|
|
996
|
+
const positionals = operation.cli?.positionals ?? [];
|
|
997
|
+
const positional = positionals.find((item) => item.index === positionalIndex);
|
|
998
|
+
if (!positional) return [];
|
|
999
|
+
|
|
1000
|
+
const schema = operation.inputSchema?.properties?.[positional.name];
|
|
1001
|
+
const enumItems = enumCompletionItems(schema);
|
|
1002
|
+
return filterItems(enumItems, current);
|
|
487
1003
|
}
|
|
488
1004
|
|
|
489
|
-
function
|
|
490
|
-
const
|
|
491
|
-
|
|
1005
|
+
function optionDescription(operation: RuntimeOperationDefinition, option: RuntimeOperationCliOption): string {
|
|
1006
|
+
const schemaDescription = operation.inputSchema?.properties?.[option.name]?.description;
|
|
1007
|
+
if (schemaDescription) return schemaDescription;
|
|
1008
|
+
return option.required ? `${option.name} (required)` : option.name;
|
|
492
1009
|
}
|
|
493
1010
|
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
1011
|
+
function schemaType(schema: JsonSchemaProperty | undefined): RuntimeOperationCliOption["type"] | undefined {
|
|
1012
|
+
if (schema?.type === "boolean" || schema?.type === "number" || schema?.type === "string") return schema.type;
|
|
1013
|
+
return undefined;
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
function enumCompletionItems(schema: JsonSchemaProperty | undefined): CompletionItem[] {
|
|
1017
|
+
const values = schema?.enum ?? [];
|
|
1018
|
+
return values
|
|
1019
|
+
.filter((value): value is string => typeof value === "string")
|
|
1020
|
+
.map((value) => ({ value, group: GROUP_VALUES }));
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
function optionItems(options: OptionDefinition[]): CompletionItem[] {
|
|
1024
|
+
return dedupeItems(options.flatMap((option) =>
|
|
1025
|
+
(option.completions ?? option.flags.map((value): { value: string; noSpace?: boolean } => ({ value }))).map((completion) => ({
|
|
1026
|
+
value: completion.value,
|
|
1027
|
+
description: option.description,
|
|
1028
|
+
noSpace: completion.noSpace,
|
|
1029
|
+
group: option.group,
|
|
1030
|
+
}))
|
|
1031
|
+
));
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
function mergeOptions(options: OptionDefinition[]): OptionDefinition[] {
|
|
1035
|
+
const seen = new Set<string>();
|
|
1036
|
+
const merged: OptionDefinition[] = [];
|
|
1037
|
+
for (const option of options) {
|
|
1038
|
+
const key = option.flags.join("\0");
|
|
1039
|
+
if (option.flags.some((flag) => seen.has(flag))) continue;
|
|
1040
|
+
for (const flag of option.flags) seen.add(flag);
|
|
1041
|
+
seen.add(key);
|
|
1042
|
+
merged.push(option);
|
|
525
1043
|
}
|
|
1044
|
+
return merged;
|
|
1045
|
+
}
|
|
526
1046
|
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
value: `${input.inlinePrefix}${item.value}`,
|
|
531
|
-
}));
|
|
1047
|
+
function findOption(options: OptionDefinition[], word: string): OptionDefinition | undefined {
|
|
1048
|
+
const flag = optionFlag(word);
|
|
1049
|
+
return options.find((option) => option.flags.includes(flag));
|
|
532
1050
|
}
|
|
533
1051
|
|
|
534
|
-
function
|
|
535
|
-
const index =
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
1052
|
+
function optionFlag(word: string): string {
|
|
1053
|
+
const index = word.indexOf("=");
|
|
1054
|
+
return index < 0 ? word : word.slice(0, index);
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
function hasInlineValue(word: string): boolean {
|
|
1058
|
+
return word.includes("=");
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
function isOptionToken(word: string): boolean {
|
|
1062
|
+
return word.startsWith("-") && word !== "-";
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
function isCommandName(value: string): value is CommandName {
|
|
1066
|
+
return COMMAND_NAMES.has(value as CommandName);
|
|
544
1067
|
}
|
|
545
1068
|
|
|
546
1069
|
function resolveProjectDir(words: string[], cwd: string): { projectDir: string; configPath: string } {
|
|
@@ -662,7 +1185,7 @@ function completePathEntries(
|
|
|
662
1185
|
if (options.fileFilter && !options.fileFilter(entry.name)) return [];
|
|
663
1186
|
return [{
|
|
664
1187
|
value: `${dirPart}${entry.name}`,
|
|
665
|
-
description: "config",
|
|
1188
|
+
description: options.fileFilter ? "config" : "file",
|
|
666
1189
|
group: GROUP_PATHS,
|
|
667
1190
|
}];
|
|
668
1191
|
})
|
|
@@ -704,17 +1227,19 @@ function splitCompletionPath(baseDir: string, current: string): {
|
|
|
704
1227
|
};
|
|
705
1228
|
}
|
|
706
1229
|
|
|
707
|
-
async function
|
|
1230
|
+
async function safeWorkspaceTargets(
|
|
708
1231
|
paths: { projectDir: string; configPath: string },
|
|
709
1232
|
): Promise<CompletionItem[]> {
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
1233
|
+
try {
|
|
1234
|
+
const workspaces = await readWorkspaces(paths);
|
|
1235
|
+
return dedupeItems(workspaces.map((workspace) => ({
|
|
1236
|
+
value: workspace.name,
|
|
1237
|
+
description: workspaceDescription(workspace),
|
|
1238
|
+
group: GROUP_WORKSPACES,
|
|
1239
|
+
})));
|
|
1240
|
+
} catch {
|
|
1241
|
+
return [];
|
|
1242
|
+
}
|
|
718
1243
|
}
|
|
719
1244
|
|
|
720
1245
|
async function readWorkspaces(paths: { projectDir: string; configPath: string }): Promise<RuntimeWorkspaceCompletion[]> {
|
|
@@ -728,6 +1253,22 @@ async function readWorkspaces(paths: { projectDir: string; configPath: string })
|
|
|
728
1253
|
}));
|
|
729
1254
|
}
|
|
730
1255
|
|
|
1256
|
+
async function safeWorkflowTargets(
|
|
1257
|
+
paths: { projectDir: string; configPath: string },
|
|
1258
|
+
): Promise<CompletionItem[]> {
|
|
1259
|
+
try {
|
|
1260
|
+
const runtime = await getOrStartRuntime(paths);
|
|
1261
|
+
const { workflows } = await runtime.control.workflows();
|
|
1262
|
+
return workflows.map((workflow) => ({
|
|
1263
|
+
value: workflow.name,
|
|
1264
|
+
description: "workflow",
|
|
1265
|
+
group: GROUP_VALUES,
|
|
1266
|
+
}));
|
|
1267
|
+
} catch {
|
|
1268
|
+
return [];
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
|
|
731
1272
|
async function safeWorkspaceOperationTargets(
|
|
732
1273
|
paths: { projectDir: string; configPath: string },
|
|
733
1274
|
): Promise<CompletionItem[]> {
|
|
@@ -740,18 +1281,36 @@ async function safeWorkspaceOperationTargets(
|
|
|
740
1281
|
}
|
|
741
1282
|
|
|
742
1283
|
function workspaceOperationTargets(manifest: RuntimeOperationManifest): CompletionItem[] {
|
|
743
|
-
return (manifest.workspaceOperations ?? []).flatMap((operation) => [
|
|
1284
|
+
return dedupeItems((manifest.workspaceOperations ?? []).flatMap((operation) => [
|
|
744
1285
|
{
|
|
745
1286
|
value: operation.id,
|
|
746
|
-
description: operation.description
|
|
1287
|
+
description: operation.description || "workspace operation",
|
|
747
1288
|
group: GROUP_OPERATIONS,
|
|
748
1289
|
},
|
|
749
1290
|
...(operation.aliases ?? []).map((alias) => ({
|
|
750
1291
|
value: alias,
|
|
751
|
-
description: operation.description
|
|
1292
|
+
description: operation.description || "workspace operation",
|
|
752
1293
|
group: GROUP_OPERATIONS,
|
|
753
1294
|
})),
|
|
754
|
-
]);
|
|
1295
|
+
]));
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
async function safeCacheInvalidateTargets(
|
|
1299
|
+
paths: { projectDir: string; configPath: string },
|
|
1300
|
+
): Promise<CompletionItem[]> {
|
|
1301
|
+
try {
|
|
1302
|
+
const runtime = await getOrStartRuntime(paths);
|
|
1303
|
+
const cache = await runtime.control.cache() as unknown as { entries: readonly RuntimeCacheCompletionEntry[] };
|
|
1304
|
+
return dedupeItems(cache.entries
|
|
1305
|
+
.filter((entry) => entry.scope === "local" && !entry.invalidated)
|
|
1306
|
+
.map((entry) => ({
|
|
1307
|
+
value: entry.nodePath || entry.nodeName,
|
|
1308
|
+
description: entry.workflow ? `workflow ${entry.workflow}` : "cached task",
|
|
1309
|
+
group: GROUP_CACHE,
|
|
1310
|
+
})));
|
|
1311
|
+
} catch {
|
|
1312
|
+
return [];
|
|
1313
|
+
}
|
|
755
1314
|
}
|
|
756
1315
|
|
|
757
1316
|
async function resolveRuntimeOperation(
|
|
@@ -842,3 +1401,7 @@ export function formatWorkspaceAge(createdAt: string, nowMs = Date.now()): strin
|
|
|
842
1401
|
|
|
843
1402
|
return `${Math.floor(elapsedMonths / 12)}y ago`;
|
|
844
1403
|
}
|
|
1404
|
+
|
|
1405
|
+
function dashCase(value: string): string {
|
|
1406
|
+
return value.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);
|
|
1407
|
+
}
|