@rigkit/cli 0.2.8 → 0.2.10
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/package.json +4 -4
- package/src/cli.test.ts +106 -1
- package/src/cli.ts +18 -0
- package/src/completion.test.ts +263 -8
- package/src/completion.ts +828 -339
- package/src/update-check.ts +224 -0
- package/src/version.ts +1 -1
package/src/completion.ts
CHANGED
|
@@ -18,6 +18,103 @@ 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
|
+
| "projects"
|
|
32
|
+
| "doctor"
|
|
33
|
+
| "version"
|
|
34
|
+
| "completion";
|
|
35
|
+
|
|
36
|
+
type CompletionContext = {
|
|
37
|
+
cwd: string;
|
|
38
|
+
words: string[];
|
|
39
|
+
currentIndex: number;
|
|
40
|
+
current: string;
|
|
41
|
+
before: string[];
|
|
42
|
+
command?: CommandName;
|
|
43
|
+
commandIndex?: number;
|
|
44
|
+
argsBefore: string[];
|
|
45
|
+
unknownRootPositionals: string[];
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
type ValueCompletionKind =
|
|
49
|
+
| "directories"
|
|
50
|
+
| "config-files"
|
|
51
|
+
| "filesystem"
|
|
52
|
+
| "package-managers";
|
|
53
|
+
|
|
54
|
+
type OptionDefinition = {
|
|
55
|
+
flags: string[];
|
|
56
|
+
completions?: Array<{ value: string; noSpace?: boolean }>;
|
|
57
|
+
description: string;
|
|
58
|
+
group: string;
|
|
59
|
+
takesValue?: boolean;
|
|
60
|
+
valueKind?: ValueCompletionKind;
|
|
61
|
+
operation?: RuntimeOperationDefinition;
|
|
62
|
+
runtimeOption?: RuntimeOperationCliOption;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
type RuntimeOperationManifest = {
|
|
66
|
+
operations: RuntimeOperationDefinition[];
|
|
67
|
+
workspaceOperations?: RuntimeOperationDefinition[];
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
type RuntimeOperationDefinition = {
|
|
71
|
+
id: string;
|
|
72
|
+
aliases?: string[];
|
|
73
|
+
title?: string;
|
|
74
|
+
description?: string;
|
|
75
|
+
createsWorkspace?: boolean;
|
|
76
|
+
cli?: {
|
|
77
|
+
positionals?: Array<{ name: string; index: number }>;
|
|
78
|
+
options?: RuntimeOperationCliOption[];
|
|
79
|
+
};
|
|
80
|
+
inputSchema?: {
|
|
81
|
+
properties?: Record<string, JsonSchemaProperty>;
|
|
82
|
+
required?: string[];
|
|
83
|
+
};
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
type RuntimeOperationCliOption = {
|
|
87
|
+
name: string;
|
|
88
|
+
flag: string;
|
|
89
|
+
aliases?: string[];
|
|
90
|
+
required?: boolean;
|
|
91
|
+
runtime?: boolean;
|
|
92
|
+
type?: "string" | "boolean" | "number";
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
type JsonSchemaProperty = {
|
|
96
|
+
type?: string;
|
|
97
|
+
description?: string;
|
|
98
|
+
default?: unknown;
|
|
99
|
+
enum?: unknown[];
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
type RuntimeWorkspaceCompletion = {
|
|
103
|
+
name: string;
|
|
104
|
+
workflow: string;
|
|
105
|
+
createdAt: string;
|
|
106
|
+
updatedAt: string;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
type RuntimeCacheCompletionEntry = {
|
|
110
|
+
scope: "local" | "global";
|
|
111
|
+
workflow: string;
|
|
112
|
+
nodePath: string;
|
|
113
|
+
nodeName: string;
|
|
114
|
+
invalidated: boolean;
|
|
115
|
+
createdAt: string;
|
|
116
|
+
};
|
|
117
|
+
|
|
21
118
|
const GROUP_COMMANDS = "Commands";
|
|
22
119
|
const GROUP_SUBCOMMANDS = "Subcommands";
|
|
23
120
|
const GROUP_FLAGS = "Flags";
|
|
@@ -28,6 +125,7 @@ const GROUP_OPERATIONS = "Operations";
|
|
|
28
125
|
const GROUP_VALUES = "Values";
|
|
29
126
|
const GROUP_PATHS = "Paths";
|
|
30
127
|
const GROUP_SHELLS = "Shells";
|
|
128
|
+
const GROUP_CACHE = "Cache entries";
|
|
31
129
|
|
|
32
130
|
const COMMANDS: CompletionItem[] = withGroup(GROUP_COMMANDS, [
|
|
33
131
|
{ value: "help", description: "show CLI help" },
|
|
@@ -45,239 +143,587 @@ const COMMANDS: CompletionItem[] = withGroup(GROUP_COMMANDS, [
|
|
|
45
143
|
{ value: "completion", description: "generate shell completion" },
|
|
46
144
|
]);
|
|
47
145
|
|
|
48
|
-
const
|
|
146
|
+
const COMMAND_NAMES = new Set(COMMANDS.map((command) => command.value as CommandName));
|
|
147
|
+
|
|
148
|
+
const JSON_OPTION = option(["--json"], "print JSON");
|
|
149
|
+
const HELP_OPTION = option(["--help"], "show help");
|
|
150
|
+
|
|
151
|
+
const GLOBAL_OPTIONS: OptionDefinition[] = [
|
|
152
|
+
option(["-chdir", "--chdir"], "working directory", {
|
|
153
|
+
group: GROUP_GLOBAL,
|
|
154
|
+
takesValue: true,
|
|
155
|
+
valueKind: "directories",
|
|
156
|
+
completions: [
|
|
157
|
+
{ value: "-chdir=", noSpace: true },
|
|
158
|
+
{ value: "--chdir=", noSpace: true },
|
|
159
|
+
],
|
|
160
|
+
}),
|
|
161
|
+
option(["-config", "--config"], "config file", {
|
|
162
|
+
group: GROUP_GLOBAL,
|
|
163
|
+
takesValue: true,
|
|
164
|
+
valueKind: "config-files",
|
|
165
|
+
completions: [
|
|
166
|
+
{ value: "-config=", noSpace: true },
|
|
167
|
+
{ value: "--config=", noSpace: true },
|
|
168
|
+
],
|
|
169
|
+
}),
|
|
170
|
+
option(["-state", "--state"], "state database path", {
|
|
171
|
+
group: GROUP_GLOBAL,
|
|
172
|
+
takesValue: true,
|
|
173
|
+
valueKind: "filesystem",
|
|
174
|
+
completions: [
|
|
175
|
+
{ value: "-state=", noSpace: true },
|
|
176
|
+
{ value: "--state=", noSpace: true },
|
|
177
|
+
],
|
|
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 }),
|
|
182
|
+
];
|
|
183
|
+
|
|
184
|
+
const COMMAND_OPTIONS: Record<CommandName, OptionDefinition[]> = {
|
|
185
|
+
init: [
|
|
186
|
+
option(["--name"], "project and workflow name", { takesValue: true }),
|
|
187
|
+
option(["--api-key"], "Freestyle API key", { takesValue: true }),
|
|
188
|
+
option(["--package-manager"], "npm, bun, pnpm, or skip", {
|
|
189
|
+
takesValue: true,
|
|
190
|
+
valueKind: "package-managers",
|
|
191
|
+
}),
|
|
192
|
+
option(["--force"], "overwrite existing config"),
|
|
193
|
+
JSON_OPTION,
|
|
194
|
+
HELP_OPTION,
|
|
195
|
+
],
|
|
196
|
+
plan: [
|
|
197
|
+
option(["--all"], "run against every discovered project"),
|
|
198
|
+
option(["--discover"], "discover projects below the selected directory"),
|
|
199
|
+
JSON_OPTION,
|
|
200
|
+
HELP_OPTION,
|
|
201
|
+
],
|
|
202
|
+
apply: [
|
|
203
|
+
option(["--all"], "run against every discovered project"),
|
|
204
|
+
option(["--discover"], "discover projects below the selected directory"),
|
|
205
|
+
JSON_OPTION,
|
|
206
|
+
HELP_OPTION,
|
|
207
|
+
],
|
|
208
|
+
create: [
|
|
209
|
+
JSON_OPTION,
|
|
210
|
+
HELP_OPTION,
|
|
211
|
+
],
|
|
212
|
+
rm: [
|
|
213
|
+
option(["-y", "--yes"], "skip confirmation"),
|
|
214
|
+
option(["--all"], "remove every workspace"),
|
|
215
|
+
JSON_OPTION,
|
|
216
|
+
HELP_OPTION,
|
|
217
|
+
],
|
|
218
|
+
run: [
|
|
219
|
+
JSON_OPTION,
|
|
220
|
+
HELP_OPTION,
|
|
221
|
+
],
|
|
222
|
+
ls: [
|
|
223
|
+
JSON_OPTION,
|
|
224
|
+
HELP_OPTION,
|
|
225
|
+
],
|
|
226
|
+
cache: [
|
|
227
|
+
HELP_OPTION,
|
|
228
|
+
],
|
|
229
|
+
projects: [
|
|
230
|
+
JSON_OPTION,
|
|
231
|
+
HELP_OPTION,
|
|
232
|
+
],
|
|
233
|
+
doctor: [
|
|
234
|
+
option(["--cli"], "show CLI diagnostics only"),
|
|
235
|
+
JSON_OPTION,
|
|
236
|
+
HELP_OPTION,
|
|
237
|
+
],
|
|
238
|
+
version: [
|
|
239
|
+
JSON_OPTION,
|
|
240
|
+
HELP_OPTION,
|
|
241
|
+
],
|
|
242
|
+
help: [
|
|
243
|
+
JSON_OPTION,
|
|
244
|
+
HELP_OPTION,
|
|
245
|
+
],
|
|
246
|
+
completion: [
|
|
247
|
+
HELP_OPTION,
|
|
248
|
+
],
|
|
249
|
+
};
|
|
49
250
|
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
251
|
+
const CORE_OPERATION_OPTIONS: Partial<Record<CommandName, OptionDefinition[]>> = {
|
|
252
|
+
plan: [
|
|
253
|
+
option(["--workflow"], "workflow name", { takesValue: true }),
|
|
254
|
+
],
|
|
255
|
+
apply: [
|
|
256
|
+
option(["--workflow"], "workflow name", { takesValue: true }),
|
|
257
|
+
option(["--dry-run"], "plan without applying changes"),
|
|
258
|
+
],
|
|
259
|
+
create: [
|
|
260
|
+
option(["--workflow"], "workflow name", { takesValue: true }),
|
|
261
|
+
option(["--name"], "workspace name", { takesValue: true }),
|
|
262
|
+
],
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
const LIST_TARGETS: CompletionItem[] = withGroup(GROUP_TARGETS, [
|
|
266
|
+
{ value: "workspaces", description: "list workspaces" },
|
|
267
|
+
{ value: "snapshots", description: "list snapshots" },
|
|
268
|
+
{ value: "config", description: "show project config" },
|
|
269
|
+
]);
|
|
270
|
+
|
|
271
|
+
const CACHE_SUBCOMMANDS: CompletionItem[] = withGroup(GROUP_SUBCOMMANDS, [
|
|
272
|
+
{ value: "ls", description: "list cache entries" },
|
|
273
|
+
{ value: "clear", description: "clear cache entries" },
|
|
274
|
+
{ value: "invalidate", description: "mark cached task outputs stale" },
|
|
57
275
|
]);
|
|
58
276
|
|
|
59
|
-
const
|
|
60
|
-
init: withGroup(GROUP_FLAGS, [
|
|
61
|
-
{ value: "--name", description: "project and workflow name" },
|
|
62
|
-
{ value: "--api-key", description: "Freestyle API key" },
|
|
63
|
-
{ value: "--package-manager", description: "npm, bun, pnpm, or skip" },
|
|
64
|
-
{ value: "--force", description: "overwrite existing config" },
|
|
65
|
-
{ value: "--json", description: "print JSON" },
|
|
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
|
-
]),
|
|
277
|
+
const CACHE_SUBCOMMAND_OPTIONS: Record<string, OptionDefinition[]> = {
|
|
88
278
|
ls: [
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
]),
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
279
|
+
JSON_OPTION,
|
|
280
|
+
HELP_OPTION,
|
|
281
|
+
],
|
|
282
|
+
clear: [
|
|
283
|
+
option(["--local"], "clear local cache entries"),
|
|
284
|
+
option(["--global"], "clear global cache fragments"),
|
|
285
|
+
option(["--all"], "clear every global fragment with --global"),
|
|
286
|
+
JSON_OPTION,
|
|
287
|
+
HELP_OPTION,
|
|
288
|
+
],
|
|
289
|
+
invalidate: [
|
|
290
|
+
option(["--all"], "invalidate every cached task"),
|
|
291
|
+
option(["-y", "--yes"], "skip confirmation"),
|
|
292
|
+
JSON_OPTION,
|
|
293
|
+
HELP_OPTION,
|
|
97
294
|
],
|
|
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
295
|
};
|
|
111
296
|
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
{ value: "--all", description: "clear every global fragment" },
|
|
120
|
-
{ value: "--json", description: "print JSON" },
|
|
121
|
-
]),
|
|
122
|
-
};
|
|
297
|
+
const COMPLETION_SHELLS: CompletionItem[] = withGroup(GROUP_SHELLS, [
|
|
298
|
+
{ value: "bash", description: "Bash completion" },
|
|
299
|
+
{ value: "fish", description: "fish completion" },
|
|
300
|
+
{ value: "zsh", description: "zsh completion" },
|
|
301
|
+
]);
|
|
302
|
+
|
|
303
|
+
const PROJECT_OPERATION_COMMANDS = new Set<CommandName>(["plan", "apply", "create"]);
|
|
123
304
|
|
|
124
305
|
function withGroup(group: string, items: Omit<CompletionItem, "group">[]): CompletionItem[] {
|
|
125
306
|
return items.map((item) => ({ ...item, group }));
|
|
126
307
|
}
|
|
127
308
|
|
|
128
|
-
|
|
309
|
+
function option(
|
|
310
|
+
flags: string[],
|
|
311
|
+
description: string,
|
|
312
|
+
input: Partial<Omit<OptionDefinition, "flags" | "description">> = {},
|
|
313
|
+
): OptionDefinition {
|
|
314
|
+
return {
|
|
315
|
+
flags,
|
|
316
|
+
description,
|
|
317
|
+
group: input.group ?? GROUP_FLAGS,
|
|
318
|
+
takesValue: input.takesValue,
|
|
319
|
+
valueKind: input.valueKind,
|
|
320
|
+
completions: input.completions,
|
|
321
|
+
operation: input.operation,
|
|
322
|
+
runtimeOption: input.runtimeOption,
|
|
323
|
+
};
|
|
324
|
+
}
|
|
129
325
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
"--chdir",
|
|
133
|
-
"-config",
|
|
134
|
-
"--config",
|
|
135
|
-
"-state",
|
|
136
|
-
"--state",
|
|
137
|
-
"--name",
|
|
138
|
-
"--api-key",
|
|
139
|
-
"--package-manager",
|
|
140
|
-
]);
|
|
326
|
+
export async function completeRig(input: CompleteRigInput): Promise<CompletionItem[]> {
|
|
327
|
+
const context = completionContext(input);
|
|
141
328
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
}
|
|
329
|
+
const valueRequest = await optionValueRequest(context);
|
|
330
|
+
if (valueRequest) {
|
|
331
|
+
return await completeOptionValue(valueRequest);
|
|
332
|
+
}
|
|
146
333
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
}
|
|
334
|
+
if (!context.command) {
|
|
335
|
+
if (context.unknownRootPositionals.length > 0) return [];
|
|
336
|
+
return filterItems(
|
|
337
|
+
context.current.startsWith("-")
|
|
338
|
+
? optionItems(GLOBAL_OPTIONS)
|
|
339
|
+
: [...COMMANDS, ...optionItems(GLOBAL_OPTIONS)],
|
|
340
|
+
context.current,
|
|
341
|
+
);
|
|
342
|
+
}
|
|
156
343
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
workflow: string;
|
|
160
|
-
createdAt: string;
|
|
161
|
-
updatedAt: string;
|
|
162
|
-
};
|
|
344
|
+
return await completeCommand(context);
|
|
345
|
+
}
|
|
163
346
|
|
|
164
|
-
|
|
347
|
+
function completionContext(input: CompleteRigInput): CompletionContext {
|
|
165
348
|
const cwd = input.cwd ?? process.cwd();
|
|
166
349
|
const words = input.words.length > 0 ? input.words : ["rig"];
|
|
167
350
|
const currentIndex = input.currentIndex ?? Math.max(0, words.length - 1);
|
|
168
351
|
const current = words[currentIndex] ?? "";
|
|
169
352
|
const before = words.slice(1, currentIndex);
|
|
170
|
-
const
|
|
353
|
+
const unknownRootPositionals: string[] = [];
|
|
354
|
+
let command: CommandName | undefined;
|
|
355
|
+
let commandIndex: number | undefined;
|
|
356
|
+
|
|
357
|
+
for (let index = 0; index < before.length; index += 1) {
|
|
358
|
+
const word = before[index]!;
|
|
359
|
+
const globalOption = findOption(GLOBAL_OPTIONS, word);
|
|
360
|
+
if (globalOption?.takesValue && !hasInlineValue(word)) {
|
|
361
|
+
index += 1;
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
364
|
+
if (isOptionToken(word)) continue;
|
|
365
|
+
|
|
366
|
+
if (isCommandName(word)) {
|
|
367
|
+
command = word;
|
|
368
|
+
commandIndex = index;
|
|
369
|
+
break;
|
|
370
|
+
}
|
|
171
371
|
|
|
172
|
-
|
|
372
|
+
unknownRootPositionals.push(word);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
return {
|
|
376
|
+
cwd,
|
|
377
|
+
words,
|
|
378
|
+
currentIndex,
|
|
379
|
+
current,
|
|
380
|
+
before,
|
|
381
|
+
command,
|
|
382
|
+
commandIndex,
|
|
383
|
+
argsBefore: commandIndex === undefined ? [] : before.slice(commandIndex + 1),
|
|
384
|
+
unknownRootPositionals,
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
async function optionValueRequest(context: CompletionContext): Promise<{
|
|
389
|
+
option: OptionDefinition;
|
|
390
|
+
current: string;
|
|
391
|
+
cwd: string;
|
|
392
|
+
words: string[];
|
|
393
|
+
inlinePrefix?: string;
|
|
394
|
+
} | undefined> {
|
|
395
|
+
const inlineOption = parseInlineValueOption(context.current);
|
|
173
396
|
if (inlineOption) {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
397
|
+
const definition = await resolveOptionDefinition(context, inlineOption.option);
|
|
398
|
+
if (definition?.takesValue || definition?.runtimeOption?.type === "boolean") {
|
|
399
|
+
return {
|
|
400
|
+
option: definition,
|
|
401
|
+
current: inlineOption.value,
|
|
402
|
+
cwd: context.cwd,
|
|
403
|
+
words: context.words,
|
|
404
|
+
inlinePrefix: inlineOption.prefix,
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const previous = context.before.at(-1);
|
|
410
|
+
if (!previous) return undefined;
|
|
411
|
+
if (hasInlineValue(previous)) return undefined;
|
|
412
|
+
|
|
413
|
+
const definition = await resolveOptionDefinition(context, previous);
|
|
414
|
+
if (!definition?.takesValue) return undefined;
|
|
415
|
+
return {
|
|
416
|
+
option: definition,
|
|
417
|
+
current: context.current,
|
|
418
|
+
cwd: context.cwd,
|
|
419
|
+
words: context.words,
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
async function resolveOptionDefinition(
|
|
424
|
+
context: CompletionContext,
|
|
425
|
+
flag: string,
|
|
426
|
+
): Promise<OptionDefinition | undefined> {
|
|
427
|
+
const globalOption = findOption(GLOBAL_OPTIONS, flag);
|
|
428
|
+
if (globalOption) return globalOption;
|
|
429
|
+
if (!context.command) return undefined;
|
|
430
|
+
|
|
431
|
+
const commandOptions = await optionsForCommandContext(context);
|
|
432
|
+
return findOption(commandOptions, flag);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
async function optionsForCommandContext(context: CompletionContext): Promise<OptionDefinition[]> {
|
|
436
|
+
if (!context.command) return GLOBAL_OPTIONS;
|
|
437
|
+
|
|
438
|
+
if (PROJECT_OPERATION_COMMANDS.has(context.command)) {
|
|
439
|
+
const operation = await safeResolveRuntimeOperation(resolveProjectDir(context.words, context.cwd), context.command);
|
|
440
|
+
return mergeOptions([
|
|
441
|
+
...operationOptions(operation),
|
|
442
|
+
...(CORE_OPERATION_OPTIONS[context.command] ?? []),
|
|
443
|
+
...COMMAND_OPTIONS[context.command],
|
|
444
|
+
]);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
if (context.command === "run") {
|
|
448
|
+
const run = parseRunArgs(context);
|
|
449
|
+
if (run.workspace && run.operation) {
|
|
450
|
+
const operation = await safeResolveWorkspaceOperation(resolveProjectDir(context.words, context.cwd), run.operation);
|
|
451
|
+
return mergeOptions([
|
|
452
|
+
...operationOptions(operation),
|
|
453
|
+
...COMMAND_OPTIONS.run,
|
|
454
|
+
]);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
if (context.command === "rm") {
|
|
459
|
+
const remove = parseRmArgs(context);
|
|
460
|
+
if (remove.workspace) {
|
|
461
|
+
const operation = await safeResolveWorkspaceOperation(resolveProjectDir(context.words, context.cwd), "remove");
|
|
462
|
+
return mergeOptions([
|
|
463
|
+
...operationOptions(operation),
|
|
464
|
+
...COMMAND_OPTIONS.rm,
|
|
465
|
+
]);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
if (context.command === "cache") {
|
|
470
|
+
const cache = parseCacheArgs(context);
|
|
471
|
+
if (cache.subcommand) return CACHE_SUBCOMMAND_OPTIONS[cache.subcommand] ?? [HELP_OPTION];
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
return COMMAND_OPTIONS[context.command] ?? [];
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
async function completeOptionValue(input: {
|
|
478
|
+
option: OptionDefinition;
|
|
479
|
+
current: string;
|
|
480
|
+
cwd: string;
|
|
481
|
+
words: string[];
|
|
482
|
+
inlinePrefix?: string;
|
|
483
|
+
}): Promise<CompletionItem[]> {
|
|
484
|
+
let items: CompletionItem[];
|
|
485
|
+
switch (input.option.valueKind) {
|
|
486
|
+
case "directories":
|
|
487
|
+
items = completeDirectories(input.cwd, input.current);
|
|
488
|
+
break;
|
|
489
|
+
case "config-files":
|
|
490
|
+
items = completeConfigPaths(projectBaseDir(input.words, input.cwd), input.current);
|
|
491
|
+
break;
|
|
492
|
+
case "filesystem":
|
|
493
|
+
items = completeFilesystemPaths(input.cwd, input.current);
|
|
494
|
+
break;
|
|
495
|
+
case "package-managers":
|
|
496
|
+
items = filterItems([
|
|
497
|
+
{ value: "npm", group: GROUP_VALUES },
|
|
498
|
+
{ value: "bun", group: GROUP_VALUES },
|
|
499
|
+
{ value: "pnpm", group: GROUP_VALUES },
|
|
500
|
+
{ value: "skip", group: GROUP_VALUES },
|
|
501
|
+
], input.current);
|
|
502
|
+
break;
|
|
503
|
+
default:
|
|
504
|
+
items = await completeRuntimeOptionValue(input.option, input.current, input.words, input.cwd);
|
|
505
|
+
break;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
if (!input.inlinePrefix) return items;
|
|
509
|
+
return items.map((item) => ({
|
|
510
|
+
...item,
|
|
511
|
+
value: `${input.inlinePrefix}${item.value}`,
|
|
512
|
+
}));
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
async function completeRuntimeOptionValue(
|
|
516
|
+
option: OptionDefinition,
|
|
517
|
+
current: string,
|
|
518
|
+
words: string[],
|
|
519
|
+
cwd: string,
|
|
520
|
+
): Promise<CompletionItem[]> {
|
|
521
|
+
const runtimeOption = option.runtimeOption;
|
|
522
|
+
const operation = option.operation;
|
|
523
|
+
|
|
524
|
+
if (runtimeOption?.type === "boolean") {
|
|
525
|
+
return filterItems([
|
|
526
|
+
{ value: "true", group: GROUP_VALUES },
|
|
527
|
+
{ value: "false", group: GROUP_VALUES },
|
|
528
|
+
], current);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
const schema = operation && runtimeOption ? operation.inputSchema?.properties?.[runtimeOption.name] : undefined;
|
|
532
|
+
const enumItems = enumCompletionItems(schema);
|
|
533
|
+
if (enumItems.length > 0) return filterItems(enumItems, current);
|
|
534
|
+
|
|
535
|
+
if (runtimeOption?.name === "workflow" || option.flags.includes("--workflow")) {
|
|
536
|
+
return filterItems(await safeWorkflowTargets(resolveProjectDir(words, cwd)), current);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
return [];
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
async function completeCommand(context: CompletionContext): Promise<CompletionItem[]> {
|
|
543
|
+
switch (context.command) {
|
|
544
|
+
case "plan":
|
|
545
|
+
case "apply":
|
|
546
|
+
case "create":
|
|
547
|
+
return await completeProjectOperationCommand(context);
|
|
548
|
+
case "run":
|
|
549
|
+
return await completeRunCommand(context);
|
|
550
|
+
case "rm":
|
|
551
|
+
return await completeRmCommand(context);
|
|
552
|
+
case "ls":
|
|
553
|
+
return completeLsCommand(context);
|
|
554
|
+
case "cache":
|
|
555
|
+
return await completeCacheCommand(context);
|
|
556
|
+
case "completion":
|
|
557
|
+
return completeCompletionCommand(context);
|
|
558
|
+
case "init":
|
|
559
|
+
case "projects":
|
|
560
|
+
case "doctor":
|
|
561
|
+
case "version":
|
|
562
|
+
case "help":
|
|
563
|
+
return completeOptionsOnlyCommand(context, COMMAND_OPTIONS[context.command]);
|
|
564
|
+
}
|
|
565
|
+
return [];
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
async function completeProjectOperationCommand(context: CompletionContext): Promise<CompletionItem[]> {
|
|
569
|
+
const operation = await safeResolveRuntimeOperation(resolveProjectDir(context.words, context.cwd), context.command!);
|
|
570
|
+
const options = mergeOptions([
|
|
571
|
+
...operationOptions(operation),
|
|
572
|
+
...(CORE_OPERATION_OPTIONS[context.command!] ?? []),
|
|
573
|
+
...COMMAND_OPTIONS[context.command!],
|
|
574
|
+
]);
|
|
575
|
+
const positionals = positionalsFrom(context.argsBefore, options);
|
|
576
|
+
|
|
577
|
+
if (context.current.startsWith("-") || context.current === "") {
|
|
578
|
+
const positionalItems = operation ? operationPositionalValueItems(operation, positionals.length, context.current) : [];
|
|
579
|
+
return filterItems([...positionalItems, ...optionItems(options)], context.current);
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
if (!operation) return [];
|
|
583
|
+
return filterItems(operationPositionalValueItems(operation, positionals.length, context.current), context.current);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
async function completeRunCommand(context: CompletionContext): Promise<CompletionItem[]> {
|
|
587
|
+
const paths = resolveProjectDir(context.words, context.cwd);
|
|
588
|
+
const baseOptions = COMMAND_OPTIONS.run;
|
|
589
|
+
const run = parseRunArgs(context);
|
|
590
|
+
|
|
591
|
+
if (!run.workspace) {
|
|
592
|
+
return completeMixed({
|
|
593
|
+
primary: await safeWorkspaceTargets(paths),
|
|
594
|
+
options: baseOptions,
|
|
595
|
+
current: context.current,
|
|
180
596
|
});
|
|
181
597
|
}
|
|
182
598
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
current,
|
|
188
|
-
cwd,
|
|
189
|
-
words,
|
|
599
|
+
if (!run.operation) {
|
|
600
|
+
return completeMixed({
|
|
601
|
+
primary: await safeWorkspaceOperationTargets(paths),
|
|
602
|
+
options: baseOptions,
|
|
603
|
+
current: context.current,
|
|
190
604
|
});
|
|
191
605
|
}
|
|
192
606
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
607
|
+
const operation = await safeResolveWorkspaceOperation(paths, run.operation);
|
|
608
|
+
const options = mergeOptions([
|
|
609
|
+
...operationOptions(operation),
|
|
610
|
+
...baseOptions,
|
|
611
|
+
]);
|
|
612
|
+
const positionals = positionalsFrom(run.args, options);
|
|
613
|
+
|
|
614
|
+
if (context.current.startsWith("-") || context.current === "") {
|
|
615
|
+
const positionalItems = operation ? operationPositionalValueItems(operation, positionals.length, context.current) : [];
|
|
616
|
+
return filterItems([...positionalItems, ...optionItems(options)], context.current);
|
|
200
617
|
}
|
|
201
618
|
|
|
202
|
-
if (
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
]),
|
|
226
|
-
...COMMAND_OPTIONS.run,
|
|
227
|
-
...GLOBAL_OPTIONS,
|
|
228
|
-
], current);
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
if (PROJECT_OPERATION_COMMANDS.has(command)) {
|
|
232
|
-
const operation = await safeResolveRuntimeOperation(resolveProjectDir(words, cwd), command);
|
|
233
|
-
return filterItems([
|
|
234
|
-
...(operation?.cli?.options ?? []).flatMap((option) => [
|
|
235
|
-
{ value: option.flag, description: option.name },
|
|
236
|
-
...(option.aliases ?? []).map((alias) => ({ value: alias, description: option.name })),
|
|
237
|
-
]),
|
|
238
|
-
...(COMMAND_OPTIONS[command] ?? []),
|
|
239
|
-
...GLOBAL_OPTIONS,
|
|
240
|
-
], current);
|
|
241
|
-
}
|
|
242
|
-
if (command === "cache") {
|
|
243
|
-
return filterItems([
|
|
244
|
-
...cacheOptionTargets(before),
|
|
245
|
-
...GLOBAL_OPTIONS,
|
|
246
|
-
], current);
|
|
247
|
-
}
|
|
248
|
-
return filterItems([...(COMMAND_OPTIONS[command] ?? []), ...GLOBAL_OPTIONS], current);
|
|
619
|
+
if (!operation) return [];
|
|
620
|
+
return filterItems(operationPositionalValueItems(operation, positionals.length, context.current), context.current);
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
async function completeRmCommand(context: CompletionContext): Promise<CompletionItem[]> {
|
|
624
|
+
const paths = resolveProjectDir(context.words, context.cwd);
|
|
625
|
+
const remove = parseRmArgs(context);
|
|
626
|
+
const operation = remove.workspace ? await safeResolveWorkspaceOperation(paths, "remove") : undefined;
|
|
627
|
+
const options = mergeOptions([
|
|
628
|
+
...operationOptions(operation),
|
|
629
|
+
...COMMAND_OPTIONS.rm,
|
|
630
|
+
]);
|
|
631
|
+
|
|
632
|
+
if (!remove.workspace) {
|
|
633
|
+
return completeMixed({
|
|
634
|
+
primary: await safeWorkspaceTargets(paths),
|
|
635
|
+
options,
|
|
636
|
+
current: context.current,
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
if (context.current.startsWith("-") || context.current === "") {
|
|
641
|
+
return filterItems(optionItems(options), context.current);
|
|
249
642
|
}
|
|
250
643
|
|
|
251
|
-
|
|
644
|
+
return [];
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
function completeLsCommand(context: CompletionContext): CompletionItem[] {
|
|
648
|
+
const options = COMMAND_OPTIONS.ls;
|
|
649
|
+
const targets = positionalsFrom(context.argsBefore, options);
|
|
650
|
+
if (targets.length === 0) {
|
|
651
|
+
return completeMixed({
|
|
652
|
+
primary: LIST_TARGETS,
|
|
653
|
+
options,
|
|
654
|
+
current: context.current,
|
|
655
|
+
});
|
|
656
|
+
}
|
|
252
657
|
|
|
253
|
-
if (
|
|
254
|
-
|
|
255
|
-
if (!run.workspace) return filterItems(await workspaceTargets(resolveProjectDir(words, cwd)), current);
|
|
256
|
-
if (!run.operation) return filterItems(await safeWorkspaceOperationTargets(resolveProjectDir(words, cwd)), current);
|
|
658
|
+
if (context.current.startsWith("-") || context.current === "") {
|
|
659
|
+
return filterItems(optionItems(options), context.current);
|
|
257
660
|
}
|
|
258
661
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
662
|
+
return [];
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
async function completeCacheCommand(context: CompletionContext): Promise<CompletionItem[]> {
|
|
666
|
+
const cache = parseCacheArgs(context);
|
|
667
|
+
if (!cache.subcommand) {
|
|
668
|
+
if (context.current.startsWith("-")) return filterItems(optionItems(COMMAND_OPTIONS.cache), context.current);
|
|
669
|
+
return filterItems(CACHE_SUBCOMMANDS, context.current);
|
|
262
670
|
}
|
|
263
671
|
|
|
264
|
-
|
|
265
|
-
|
|
672
|
+
const options = CACHE_SUBCOMMAND_OPTIONS[cache.subcommand] ?? [HELP_OPTION];
|
|
673
|
+
if (cache.subcommand !== "invalidate") {
|
|
674
|
+
return completeOptionsOnlyCommand(context, options);
|
|
266
675
|
}
|
|
267
676
|
|
|
268
|
-
|
|
269
|
-
|
|
677
|
+
const stepArgs = positionalsFrom(cache.args, options);
|
|
678
|
+
if (stepArgs.length === 0) {
|
|
679
|
+
return completeMixed({
|
|
680
|
+
primary: await safeCacheInvalidateTargets(resolveProjectDir(context.words, context.cwd)),
|
|
681
|
+
options,
|
|
682
|
+
current: context.current,
|
|
683
|
+
});
|
|
270
684
|
}
|
|
271
685
|
|
|
272
|
-
if (
|
|
273
|
-
|
|
274
|
-
if (!cache.subcommand) return filterItems(COMMAND_OPTIONS.cache ?? [], current);
|
|
275
|
-
return filterItems(cacheOptionTargets(before), current);
|
|
686
|
+
if (context.current.startsWith("-") || context.current === "") {
|
|
687
|
+
return filterItems(optionItems(options), context.current);
|
|
276
688
|
}
|
|
277
689
|
|
|
278
690
|
return [];
|
|
279
691
|
}
|
|
280
692
|
|
|
693
|
+
function completeCompletionCommand(context: CompletionContext): CompletionItem[] {
|
|
694
|
+
const shells = positionalsFrom(context.argsBefore, COMMAND_OPTIONS.completion);
|
|
695
|
+
if (shells.length === 0) {
|
|
696
|
+
return completeMixed({
|
|
697
|
+
primary: COMPLETION_SHELLS,
|
|
698
|
+
options: COMMAND_OPTIONS.completion,
|
|
699
|
+
current: context.current,
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
if (context.current.startsWith("-") || context.current === "") {
|
|
704
|
+
return filterItems(optionItems(COMMAND_OPTIONS.completion), context.current);
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
return [];
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
function completeOptionsOnlyCommand(context: CompletionContext, options: OptionDefinition[]): CompletionItem[] {
|
|
711
|
+
if (context.current.startsWith("-") || context.current === "") {
|
|
712
|
+
return filterItems(optionItems(options), context.current);
|
|
713
|
+
}
|
|
714
|
+
return [];
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
function completeMixed(input: {
|
|
718
|
+
primary: CompletionItem[];
|
|
719
|
+
options: OptionDefinition[];
|
|
720
|
+
current: string;
|
|
721
|
+
}): CompletionItem[] {
|
|
722
|
+
if (input.current.startsWith("-")) return filterItems(optionItems(input.options), input.current);
|
|
723
|
+
if (input.current === "") return filterItems([...input.primary, ...optionItems(input.options)], input.current);
|
|
724
|
+
return filterItems(input.primary, input.current);
|
|
725
|
+
}
|
|
726
|
+
|
|
281
727
|
export function formatCompletionItems(items: CompletionItem[], shell: CompletionShell): string {
|
|
282
728
|
const lines = items.map((item) => {
|
|
283
729
|
if (shell === "bash") return item.value;
|
|
@@ -287,7 +733,6 @@ export function formatCompletionItems(items: CompletionItem[], shell: Completion
|
|
|
287
733
|
const group = item.group ?? "";
|
|
288
734
|
return `${item.value}\t${description}\t${marker}\t${group}`;
|
|
289
735
|
}
|
|
290
|
-
// fish: legacy two-column format works fine; descriptions render dim by default
|
|
291
736
|
return item.description ? `${item.value}\t${item.description}` : item.value;
|
|
292
737
|
});
|
|
293
738
|
return lines.join("\n");
|
|
@@ -310,6 +755,9 @@ _rig_completion() {
|
|
|
310
755
|
local completions
|
|
311
756
|
completions="$(command rig __complete --shell bash --index "$COMP_CWORD" -- "\${COMP_WORDS[@]}" 2>/dev/null)"
|
|
312
757
|
COMPREPLY=($(compgen -W "$completions" -- "\${COMP_WORDS[COMP_CWORD]}"))
|
|
758
|
+
if [[ "\${#COMPREPLY[@]}" -eq 1 && ( "\${COMPREPLY[0]}" == */ || "\${COMPREPLY[0]}" == *= ) ]]; then
|
|
759
|
+
compopt -o nospace 2>/dev/null || true
|
|
760
|
+
fi
|
|
313
761
|
}
|
|
314
762
|
complete -F _rig_completion rig
|
|
315
763
|
`;
|
|
@@ -327,11 +775,8 @@ complete -c rig -f -a "(__rig_complete)"
|
|
|
327
775
|
`;
|
|
328
776
|
}
|
|
329
777
|
|
|
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.
|
|
778
|
+
return `#compdef rig
|
|
779
|
+
# rig zsh completion generated by \`rig completion zsh\`.
|
|
335
780
|
|
|
336
781
|
() {
|
|
337
782
|
zstyle ':completion:*:rig:*:descriptions' format $'\\e[1;34m%d\\e[0m'
|
|
@@ -381,166 +826,170 @@ compdef _rig rig
|
|
|
381
826
|
`;
|
|
382
827
|
}
|
|
383
828
|
|
|
384
|
-
function
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
829
|
+
function parseInlineValueOption(current: string): { option: string; value: string; prefix: string } | undefined {
|
|
830
|
+
const index = current.indexOf("=");
|
|
831
|
+
if (index < 0) return undefined;
|
|
832
|
+
return {
|
|
833
|
+
option: current.slice(0, index),
|
|
834
|
+
value: current.slice(index + 1),
|
|
835
|
+
prefix: current.slice(0, index + 1),
|
|
836
|
+
};
|
|
837
|
+
}
|
|
393
838
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
839
|
+
function parseRunArgs(context: CompletionContext): { workspace?: string; operation?: string; args: string[] } {
|
|
840
|
+
const basePositionals = positionalTokensFrom(context.argsBefore, COMMAND_OPTIONS.run);
|
|
841
|
+
const workspace = basePositionals[0]?.value;
|
|
842
|
+
const operation = basePositionals[1]?.value;
|
|
843
|
+
const operationTokenIndex = basePositionals[1]?.index;
|
|
844
|
+
return {
|
|
845
|
+
workspace,
|
|
846
|
+
operation,
|
|
847
|
+
args: operationTokenIndex === undefined ? [] : context.argsBefore.slice(operationTokenIndex + 1),
|
|
848
|
+
};
|
|
398
849
|
}
|
|
399
850
|
|
|
400
|
-
function
|
|
401
|
-
|
|
402
|
-
|
|
851
|
+
function parseRmArgs(context: CompletionContext): { workspace?: string } {
|
|
852
|
+
const positionals = positionalsFrom(context.argsBefore, COMMAND_OPTIONS.rm);
|
|
853
|
+
return { workspace: positionals[0] };
|
|
854
|
+
}
|
|
403
855
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
856
|
+
function parseCacheArgs(context: CompletionContext): { subcommand?: string; args: string[] } {
|
|
857
|
+
const positionals = positionalsFrom(context.argsBefore, COMMAND_OPTIONS.cache);
|
|
858
|
+
return {
|
|
859
|
+
subcommand: positionals[0],
|
|
860
|
+
args: positionals.slice(1),
|
|
861
|
+
};
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
function positionalsFrom(tokens: string[], options: OptionDefinition[]): string[] {
|
|
865
|
+
return positionalTokensFrom(tokens, options).map((token) => token.value);
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
function positionalTokensFrom(tokens: string[], options: OptionDefinition[]): Array<{ value: string; index: number }> {
|
|
869
|
+
const positionalTokens: Array<{ value: string; index: number }> = [];
|
|
870
|
+
|
|
871
|
+
for (let index = 0; index < tokens.length; index += 1) {
|
|
872
|
+
const word = tokens[index]!;
|
|
873
|
+
if (word === "--") {
|
|
874
|
+
positionalTokens.push(...tokens.slice(index + 1).map((value, offset) => ({ value, index: index + 1 + offset })));
|
|
875
|
+
break;
|
|
409
876
|
}
|
|
410
|
-
if (word.startsWith("--") && word.includes("=")) continue;
|
|
411
|
-
if (word.startsWith("-")) continue;
|
|
412
877
|
|
|
413
|
-
const
|
|
414
|
-
if (
|
|
415
|
-
|
|
878
|
+
const option = findOption(options, word) ?? findOption(GLOBAL_OPTIONS, word);
|
|
879
|
+
if (option && isOptionToken(word)) {
|
|
880
|
+
if (option.takesValue && !hasInlineValue(word)) index += 1;
|
|
416
881
|
continue;
|
|
417
882
|
}
|
|
418
|
-
|
|
883
|
+
|
|
884
|
+
if (isOptionToken(word)) continue;
|
|
885
|
+
positionalTokens.push({ value: word, index });
|
|
419
886
|
}
|
|
420
887
|
|
|
421
|
-
return
|
|
888
|
+
return positionalTokens;
|
|
422
889
|
}
|
|
423
890
|
|
|
424
|
-
function
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
if (word.startsWith("--") && word.includes("=")) continue;
|
|
434
|
-
if (word.startsWith("-")) continue;
|
|
435
|
-
if (!foundRun) {
|
|
436
|
-
if (word === "run") foundRun = true;
|
|
437
|
-
continue;
|
|
438
|
-
}
|
|
439
|
-
args.push(word);
|
|
440
|
-
}
|
|
441
|
-
return { workspace: args[0], operation: args[1], args: args.slice(2) };
|
|
891
|
+
function operationOptions(operation: RuntimeOperationDefinition | undefined): OptionDefinition[] {
|
|
892
|
+
if (!operation) return [];
|
|
893
|
+
return inferOperationOptions(operation).map((runtimeOption) =>
|
|
894
|
+
option([runtimeOption.flag, ...(runtimeOption.aliases ?? [])], optionDescription(operation, runtimeOption), {
|
|
895
|
+
takesValue: runtimeOption.type !== "boolean",
|
|
896
|
+
operation,
|
|
897
|
+
runtimeOption,
|
|
898
|
+
})
|
|
899
|
+
);
|
|
442
900
|
}
|
|
443
901
|
|
|
444
|
-
function
|
|
445
|
-
|
|
446
|
-
const
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
continue;
|
|
458
|
-
}
|
|
459
|
-
args.push(word);
|
|
460
|
-
}
|
|
461
|
-
return { workspace: args[0], args: args.slice(1) };
|
|
902
|
+
function inferOperationOptions(operation: RuntimeOperationDefinition): RuntimeOperationCliOption[] {
|
|
903
|
+
const properties = operation.inputSchema?.properties ?? {};
|
|
904
|
+
const runtimeOptions = operation.cli?.options ?? Object.entries(properties).map(([name, schema]) => ({
|
|
905
|
+
name,
|
|
906
|
+
flag: `--${dashCase(name)}`,
|
|
907
|
+
required: operation.inputSchema?.required?.includes(name),
|
|
908
|
+
type: schema.type === "boolean" ? "boolean" : schema.type === "number" ? "number" : "string",
|
|
909
|
+
} satisfies RuntimeOperationCliOption));
|
|
910
|
+
|
|
911
|
+
return runtimeOptions.map((runtimeOption) => ({
|
|
912
|
+
...runtimeOption,
|
|
913
|
+
type: runtimeOption.type ?? schemaType(properties[runtimeOption.name]) ?? "string",
|
|
914
|
+
}));
|
|
462
915
|
}
|
|
463
916
|
|
|
464
|
-
function
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
if (word === "cache") foundCache = true;
|
|
477
|
-
continue;
|
|
478
|
-
}
|
|
479
|
-
args.push(word);
|
|
480
|
-
}
|
|
481
|
-
return { subcommand: args[0], args: args.slice(1) };
|
|
917
|
+
function operationPositionalValueItems(
|
|
918
|
+
operation: RuntimeOperationDefinition,
|
|
919
|
+
positionalIndex: number,
|
|
920
|
+
current: string,
|
|
921
|
+
): CompletionItem[] {
|
|
922
|
+
const positionals = operation.cli?.positionals ?? [];
|
|
923
|
+
const positional = positionals.find((item) => item.index === positionalIndex);
|
|
924
|
+
if (!positional) return [];
|
|
925
|
+
|
|
926
|
+
const schema = operation.inputSchema?.properties?.[positional.name];
|
|
927
|
+
const enumItems = enumCompletionItems(schema);
|
|
928
|
+
return filterItems(enumItems, current);
|
|
482
929
|
}
|
|
483
930
|
|
|
484
|
-
function
|
|
485
|
-
const
|
|
486
|
-
|
|
931
|
+
function optionDescription(operation: RuntimeOperationDefinition, option: RuntimeOperationCliOption): string {
|
|
932
|
+
const schemaDescription = operation.inputSchema?.properties?.[option.name]?.description;
|
|
933
|
+
if (schemaDescription) return schemaDescription;
|
|
934
|
+
return option.required ? `${option.name} (required)` : option.name;
|
|
487
935
|
}
|
|
488
936
|
|
|
489
|
-
function
|
|
490
|
-
|
|
491
|
-
return
|
|
937
|
+
function schemaType(schema: JsonSchemaProperty | undefined): RuntimeOperationCliOption["type"] | undefined {
|
|
938
|
+
if (schema?.type === "boolean" || schema?.type === "number" || schema?.type === "string") return schema.type;
|
|
939
|
+
return undefined;
|
|
492
940
|
}
|
|
493
941
|
|
|
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
|
-
items = completeFilesystemPaths(input.cwd, input.current);
|
|
522
|
-
break;
|
|
523
|
-
default:
|
|
524
|
-
items = [];
|
|
942
|
+
function enumCompletionItems(schema: JsonSchemaProperty | undefined): CompletionItem[] {
|
|
943
|
+
const values = schema?.enum ?? [];
|
|
944
|
+
return values
|
|
945
|
+
.filter((value): value is string => typeof value === "string")
|
|
946
|
+
.map((value) => ({ value, group: GROUP_VALUES }));
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
function optionItems(options: OptionDefinition[]): CompletionItem[] {
|
|
950
|
+
return dedupeItems(options.flatMap((option) =>
|
|
951
|
+
(option.completions ?? option.flags.map((value): { value: string; noSpace?: boolean } => ({ value }))).map((completion) => ({
|
|
952
|
+
value: completion.value,
|
|
953
|
+
description: option.description,
|
|
954
|
+
noSpace: completion.noSpace,
|
|
955
|
+
group: option.group,
|
|
956
|
+
}))
|
|
957
|
+
));
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
function mergeOptions(options: OptionDefinition[]): OptionDefinition[] {
|
|
961
|
+
const seen = new Set<string>();
|
|
962
|
+
const merged: OptionDefinition[] = [];
|
|
963
|
+
for (const option of options) {
|
|
964
|
+
const key = option.flags.join("\0");
|
|
965
|
+
if (option.flags.some((flag) => seen.has(flag))) continue;
|
|
966
|
+
for (const flag of option.flags) seen.add(flag);
|
|
967
|
+
seen.add(key);
|
|
968
|
+
merged.push(option);
|
|
525
969
|
}
|
|
970
|
+
return merged;
|
|
971
|
+
}
|
|
526
972
|
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
value: `${input.inlinePrefix}${item.value}`,
|
|
531
|
-
}));
|
|
973
|
+
function findOption(options: OptionDefinition[], word: string): OptionDefinition | undefined {
|
|
974
|
+
const flag = optionFlag(word);
|
|
975
|
+
return options.find((option) => option.flags.includes(flag));
|
|
532
976
|
}
|
|
533
977
|
|
|
534
|
-
function
|
|
535
|
-
const index =
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
978
|
+
function optionFlag(word: string): string {
|
|
979
|
+
const index = word.indexOf("=");
|
|
980
|
+
return index < 0 ? word : word.slice(0, index);
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
function hasInlineValue(word: string): boolean {
|
|
984
|
+
return word.includes("=");
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
function isOptionToken(word: string): boolean {
|
|
988
|
+
return word.startsWith("-") && word !== "-";
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
function isCommandName(value: string): value is CommandName {
|
|
992
|
+
return COMMAND_NAMES.has(value as CommandName);
|
|
544
993
|
}
|
|
545
994
|
|
|
546
995
|
function resolveProjectDir(words: string[], cwd: string): { projectDir: string; configPath: string } {
|
|
@@ -662,7 +1111,7 @@ function completePathEntries(
|
|
|
662
1111
|
if (options.fileFilter && !options.fileFilter(entry.name)) return [];
|
|
663
1112
|
return [{
|
|
664
1113
|
value: `${dirPart}${entry.name}`,
|
|
665
|
-
description: "config",
|
|
1114
|
+
description: options.fileFilter ? "config" : "file",
|
|
666
1115
|
group: GROUP_PATHS,
|
|
667
1116
|
}];
|
|
668
1117
|
})
|
|
@@ -704,17 +1153,19 @@ function splitCompletionPath(baseDir: string, current: string): {
|
|
|
704
1153
|
};
|
|
705
1154
|
}
|
|
706
1155
|
|
|
707
|
-
async function
|
|
1156
|
+
async function safeWorkspaceTargets(
|
|
708
1157
|
paths: { projectDir: string; configPath: string },
|
|
709
1158
|
): Promise<CompletionItem[]> {
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
1159
|
+
try {
|
|
1160
|
+
const workspaces = await readWorkspaces(paths);
|
|
1161
|
+
return dedupeItems(workspaces.map((workspace) => ({
|
|
1162
|
+
value: workspace.name,
|
|
1163
|
+
description: workspaceDescription(workspace),
|
|
1164
|
+
group: GROUP_WORKSPACES,
|
|
1165
|
+
})));
|
|
1166
|
+
} catch {
|
|
1167
|
+
return [];
|
|
1168
|
+
}
|
|
718
1169
|
}
|
|
719
1170
|
|
|
720
1171
|
async function readWorkspaces(paths: { projectDir: string; configPath: string }): Promise<RuntimeWorkspaceCompletion[]> {
|
|
@@ -728,6 +1179,22 @@ async function readWorkspaces(paths: { projectDir: string; configPath: string })
|
|
|
728
1179
|
}));
|
|
729
1180
|
}
|
|
730
1181
|
|
|
1182
|
+
async function safeWorkflowTargets(
|
|
1183
|
+
paths: { projectDir: string; configPath: string },
|
|
1184
|
+
): Promise<CompletionItem[]> {
|
|
1185
|
+
try {
|
|
1186
|
+
const runtime = await getOrStartRuntime(paths);
|
|
1187
|
+
const { workflows } = await runtime.control.workflows();
|
|
1188
|
+
return workflows.map((workflow) => ({
|
|
1189
|
+
value: workflow.name,
|
|
1190
|
+
description: "workflow",
|
|
1191
|
+
group: GROUP_VALUES,
|
|
1192
|
+
}));
|
|
1193
|
+
} catch {
|
|
1194
|
+
return [];
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
|
|
731
1198
|
async function safeWorkspaceOperationTargets(
|
|
732
1199
|
paths: { projectDir: string; configPath: string },
|
|
733
1200
|
): Promise<CompletionItem[]> {
|
|
@@ -740,18 +1207,36 @@ async function safeWorkspaceOperationTargets(
|
|
|
740
1207
|
}
|
|
741
1208
|
|
|
742
1209
|
function workspaceOperationTargets(manifest: RuntimeOperationManifest): CompletionItem[] {
|
|
743
|
-
return (manifest.workspaceOperations ?? []).flatMap((operation) => [
|
|
1210
|
+
return dedupeItems((manifest.workspaceOperations ?? []).flatMap((operation) => [
|
|
744
1211
|
{
|
|
745
1212
|
value: operation.id,
|
|
746
|
-
description: operation.description
|
|
1213
|
+
description: operation.description || "workspace operation",
|
|
747
1214
|
group: GROUP_OPERATIONS,
|
|
748
1215
|
},
|
|
749
1216
|
...(operation.aliases ?? []).map((alias) => ({
|
|
750
1217
|
value: alias,
|
|
751
|
-
description: operation.description
|
|
1218
|
+
description: operation.description || "workspace operation",
|
|
752
1219
|
group: GROUP_OPERATIONS,
|
|
753
1220
|
})),
|
|
754
|
-
]);
|
|
1221
|
+
]));
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
async function safeCacheInvalidateTargets(
|
|
1225
|
+
paths: { projectDir: string; configPath: string },
|
|
1226
|
+
): Promise<CompletionItem[]> {
|
|
1227
|
+
try {
|
|
1228
|
+
const runtime = await getOrStartRuntime(paths);
|
|
1229
|
+
const cache = await runtime.control.cache() as unknown as { entries: readonly RuntimeCacheCompletionEntry[] };
|
|
1230
|
+
return dedupeItems(cache.entries
|
|
1231
|
+
.filter((entry) => entry.scope === "local" && !entry.invalidated)
|
|
1232
|
+
.map((entry) => ({
|
|
1233
|
+
value: entry.nodePath || entry.nodeName,
|
|
1234
|
+
description: entry.workflow ? `workflow ${entry.workflow}` : "cached task",
|
|
1235
|
+
group: GROUP_CACHE,
|
|
1236
|
+
})));
|
|
1237
|
+
} catch {
|
|
1238
|
+
return [];
|
|
1239
|
+
}
|
|
755
1240
|
}
|
|
756
1241
|
|
|
757
1242
|
async function resolveRuntimeOperation(
|
|
@@ -842,3 +1327,7 @@ export function formatWorkspaceAge(createdAt: string, nowMs = Date.now()): strin
|
|
|
842
1327
|
|
|
843
1328
|
return `${Math.floor(elapsedMonths / 12)}y ago`;
|
|
844
1329
|
}
|
|
1330
|
+
|
|
1331
|
+
function dashCase(value: string): string {
|
|
1332
|
+
return value.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);
|
|
1333
|
+
}
|