@rigkit/cli 0.2.7 → 0.2.8

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/src/completion.ts CHANGED
@@ -1,5 +1,7 @@
1
+ import { readdirSync } from "node:fs";
1
2
  import { dirname, join, resolve } from "node:path";
2
3
  import { getOrStartRuntime } from "@rigkit/runtime-client";
4
+ import { isRigConfigFileName } from "./project.ts";
3
5
 
4
6
  export type CompletionShell = "bash" | "fish" | "zsh";
5
7
 
@@ -7,6 +9,7 @@ export type CompletionItem = {
7
9
  value: string;
8
10
  description?: string;
9
11
  noSpace?: boolean;
12
+ group?: string;
10
13
  };
11
14
 
12
15
  type CompleteRigInput = {
@@ -15,78 +18,121 @@ type CompleteRigInput = {
15
18
  cwd?: string;
16
19
  };
17
20
 
18
- const COMMANDS: CompletionItem[] = [
21
+ const GROUP_COMMANDS = "Commands";
22
+ const GROUP_SUBCOMMANDS = "Subcommands";
23
+ const GROUP_FLAGS = "Flags";
24
+ const GROUP_GLOBAL = "Global flags";
25
+ const GROUP_TARGETS = "Targets";
26
+ const GROUP_WORKSPACES = "Workspaces";
27
+ const GROUP_OPERATIONS = "Operations";
28
+ const GROUP_VALUES = "Values";
29
+ const GROUP_PATHS = "Paths";
30
+ const GROUP_SHELLS = "Shells";
31
+
32
+ const COMMANDS: CompletionItem[] = withGroup(GROUP_COMMANDS, [
19
33
  { value: "help", description: "show CLI help" },
20
34
  { value: "init", description: "initialize a Rigkit project" },
21
35
  { value: "plan", description: "plan project workflow changes" },
22
36
  { value: "apply", description: "apply project workflow changes" },
23
37
  { value: "create", description: "create a workspace" },
38
+ { value: "rm", description: "remove a workspace" },
24
39
  { value: "run", description: "run a workspace operation" },
25
40
  { value: "ls", description: "list project workspaces" },
41
+ { value: "cache", description: "inspect and clear Rigkit cache" },
26
42
  { value: "projects", description: "discover Rigkit projects" },
27
43
  { value: "doctor", description: "show runtime diagnostics" },
28
44
  { value: "version", description: "show CLI version" },
29
45
  { value: "completion", description: "generate shell completion" },
30
- ];
46
+ ]);
31
47
 
32
48
  const COMMAND_ALIASES = new Map<string, string>();
33
49
 
34
- const GLOBAL_OPTIONS: CompletionItem[] = [
35
- { value: "-C", description: "project directory" },
36
- { value: "--project", description: "project directory" },
37
- { value: "--config", description: "exact config file" },
38
- { value: "--state", description: "local state database path" },
39
- { value: "--json", description: "print JSON" },
40
- { value: "--help", description: "show help" },
41
- { value: "--version", description: "show version" },
42
- ];
50
+ const GLOBAL_OPTIONS: CompletionItem[] = withGroup(GROUP_GLOBAL, [
51
+ { value: "-chdir=", description: "working directory", noSpace: true },
52
+ { value: "-config=", description: "config file", noSpace: true },
53
+ { value: "-state=", description: "state database path", noSpace: true },
54
+ { value: "-json", description: "print JSON" },
55
+ { value: "-help", description: "show help" },
56
+ { value: "-version", description: "show version" },
57
+ ]);
43
58
 
44
59
  const COMMAND_OPTIONS: Record<string, CompletionItem[]> = {
45
- init: [
60
+ init: withGroup(GROUP_FLAGS, [
46
61
  { value: "--name", description: "project and workflow name" },
47
62
  { value: "--api-key", description: "Freestyle API key" },
48
63
  { value: "--package-manager", description: "npm, bun, pnpm, or skip" },
49
64
  { value: "--force", description: "overwrite existing config" },
50
65
  { value: "--json", description: "print JSON" },
51
- ],
52
- plan: [
66
+ ]),
67
+ plan: withGroup(GROUP_FLAGS, [
53
68
  { value: "--all", description: "run against every discovered project" },
54
69
  { value: "--discover", description: "discover projects below the selected directory" },
55
70
  { value: "--json", description: "print JSON" },
56
- ],
57
- apply: [
71
+ ]),
72
+ apply: withGroup(GROUP_FLAGS, [
58
73
  { value: "--all", description: "run against every discovered project" },
59
74
  { value: "--discover", description: "discover projects below the selected directory" },
60
75
  { value: "--json", description: "print JSON" },
61
- ],
62
- create: [
76
+ ]),
77
+ create: withGroup(GROUP_FLAGS, [
63
78
  { value: "--json", description: "print JSON" },
64
- ],
65
- run: [
79
+ ]),
80
+ rm: withGroup(GROUP_FLAGS, [
81
+ { value: "-y", description: "skip confirmation" },
82
+ { value: "--yes", description: "skip confirmation" },
66
83
  { value: "--json", description: "print JSON" },
67
- ],
68
- ls: [
69
- { value: "workspaces", description: "list workspaces" },
70
- { value: "snapshots", description: "list snapshots" },
71
- { value: "config", description: "show project config" },
84
+ ]),
85
+ run: withGroup(GROUP_FLAGS, [
72
86
  { value: "--json", description: "print JSON" },
87
+ ]),
88
+ ls: [
89
+ ...withGroup(GROUP_TARGETS, [
90
+ { value: "workspaces", description: "list workspaces" },
91
+ { value: "snapshots", description: "list snapshots" },
92
+ { value: "config", description: "show project config" },
93
+ ]),
94
+ ...withGroup(GROUP_FLAGS, [
95
+ { value: "--json", description: "print JSON" },
96
+ ]),
73
97
  ],
74
- projects: [
98
+ projects: withGroup(GROUP_FLAGS, [
75
99
  { value: "--json", description: "print JSON" },
76
- ],
77
- completion: [
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, [
78
106
  { value: "bash", description: "Bash completion" },
79
107
  { value: "fish", description: "fish completion" },
80
108
  { value: "zsh", description: "zsh completion" },
81
- ],
109
+ ]),
110
+ };
111
+
112
+ const CACHE_SUBCOMMAND_OPTIONS: Record<string, CompletionItem[]> = {
113
+ ls: withGroup(GROUP_FLAGS, [
114
+ { value: "--json", description: "print JSON" },
115
+ ]),
116
+ clear: withGroup(GROUP_FLAGS, [
117
+ { value: "--local", description: "clear local cache entries" },
118
+ { value: "--global", description: "clear global cache fragments" },
119
+ { value: "--all", description: "clear every global fragment" },
120
+ { value: "--json", description: "print JSON" },
121
+ ]),
82
122
  };
83
123
 
124
+ function withGroup(group: string, items: Omit<CompletionItem, "group">[]): CompletionItem[] {
125
+ return items.map((item) => ({ ...item, group }));
126
+ }
127
+
84
128
  const PROJECT_OPERATION_COMMANDS = new Set(["plan", "apply", "create"]);
85
129
 
86
130
  const OPTIONS_WITH_VALUES = new Set([
87
- "-C",
88
- "--project",
131
+ "-chdir",
132
+ "--chdir",
133
+ "-config",
89
134
  "--config",
135
+ "-state",
90
136
  "--state",
91
137
  "--name",
92
138
  "--api-key",
@@ -123,7 +169,26 @@ export async function completeRig(input: CompleteRigInput): Promise<CompletionIt
123
169
  const before = words.slice(1, currentIndex);
124
170
  const command = findCommand(before);
125
171
 
126
- if (expectsOptionValue(before)) return [];
172
+ const inlineOption = parseInlineValueOption(current);
173
+ if (inlineOption) {
174
+ return await completeOptionValue({
175
+ option: inlineOption.option,
176
+ current: inlineOption.value,
177
+ cwd,
178
+ words,
179
+ inlinePrefix: inlineOption.prefix,
180
+ });
181
+ }
182
+
183
+ const valueOption = optionExpectingValue(before);
184
+ if (valueOption) {
185
+ return await completeOptionValue({
186
+ option: valueOption,
187
+ current,
188
+ cwd,
189
+ words,
190
+ });
191
+ }
127
192
 
128
193
  if (!command) {
129
194
  return filterItems(
@@ -135,14 +200,28 @@ export async function completeRig(input: CompleteRigInput): Promise<CompletionIt
135
200
  }
136
201
 
137
202
  if (current.startsWith("-")) {
203
+ if (command === "rm") {
204
+ const remove = parseRemoveCommand(before);
205
+ if (remove.workspace) {
206
+ const operation = await safeResolveWorkspaceOperation(resolveProjectDir(words, cwd), "remove");
207
+ return filterItems([
208
+ ...(operation?.cli?.options ?? []).flatMap((option) => [
209
+ { value: option.flag, description: option.name, group: GROUP_FLAGS },
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
+ }
138
217
  if (command === "run") {
139
218
  const run = parseWorkspaceRunCommand(before);
140
219
  if (run.workspace && run.operation) {
141
220
  const operation = await safeResolveWorkspaceOperation(resolveProjectDir(words, cwd), run.operation);
142
221
  return filterItems([
143
222
  ...(operation?.cli?.options ?? []).flatMap((option) => [
144
- { value: option.flag, description: option.name },
145
- ...(option.aliases ?? []).map((alias) => ({ value: alias, description: option.name })),
223
+ { value: option.flag, description: option.name, group: GROUP_FLAGS },
224
+ ...(option.aliases ?? []).map((alias) => ({ value: alias, description: option.name, group: GROUP_FLAGS })),
146
225
  ]),
147
226
  ...COMMAND_OPTIONS.run,
148
227
  ...GLOBAL_OPTIONS,
@@ -160,6 +239,12 @@ export async function completeRig(input: CompleteRigInput): Promise<CompletionIt
160
239
  ...GLOBAL_OPTIONS,
161
240
  ], current);
162
241
  }
242
+ if (command === "cache") {
243
+ return filterItems([
244
+ ...cacheOptionTargets(before),
245
+ ...GLOBAL_OPTIONS,
246
+ ], current);
247
+ }
163
248
  return filterItems([...(COMMAND_OPTIONS[command] ?? []), ...GLOBAL_OPTIONS], current);
164
249
  }
165
250
 
@@ -171,6 +256,11 @@ export async function completeRig(input: CompleteRigInput): Promise<CompletionIt
171
256
  if (!run.operation) return filterItems(await safeWorkspaceOperationTargets(resolveProjectDir(words, cwd)), current);
172
257
  }
173
258
 
259
+ if (command === "rm") {
260
+ const remove = parseRemoveCommand(before);
261
+ if (!remove.workspace) return filterItems(await workspaceTargets(resolveProjectDir(words, cwd)), current);
262
+ }
263
+
174
264
  if (command === "completion" && positionalCount === 0) {
175
265
  return filterItems(COMMAND_OPTIONS.completion, current);
176
266
  }
@@ -179,15 +269,25 @@ export async function completeRig(input: CompleteRigInput): Promise<CompletionIt
179
269
  return filterItems(COMMAND_OPTIONS.ls, current);
180
270
  }
181
271
 
272
+ if (command === "cache") {
273
+ const cache = parseCacheCommand(before);
274
+ if (!cache.subcommand) return filterItems(COMMAND_OPTIONS.cache ?? [], current);
275
+ return filterItems(cacheOptionTargets(before), current);
276
+ }
277
+
182
278
  return [];
183
279
  }
184
280
 
185
281
  export function formatCompletionItems(items: CompletionItem[], shell: CompletionShell): string {
186
282
  const lines = items.map((item) => {
187
283
  if (shell === "bash") return item.value;
188
- if (shell === "zsh" && item.noSpace) {
189
- return `${item.value}\t${item.description ?? ""}\tnospace`;
284
+ if (shell === "zsh") {
285
+ const description = item.description ?? "";
286
+ const marker = item.noSpace ? "nospace" : "";
287
+ const group = item.group ?? "";
288
+ return `${item.value}\t${description}\t${marker}\t${group}`;
190
289
  }
290
+ // fish: legacy two-column format works fine; descriptions render dim by default
191
291
  return item.description ? `${item.value}\t${item.description}` : item.value;
192
292
  });
193
293
  return lines.join("\n");
@@ -228,37 +328,55 @@ complete -c rig -f -a "(__rig_complete)"
228
328
  }
229
329
 
230
330
  return `#compdef rig
231
- # rig zsh completion
331
+ # rig zsh completion — auto-generated by \`rig completion zsh\`.
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.
335
+
336
+ () {
337
+ zstyle ':completion:*:rig:*:descriptions' format $'\\e[1;34m%d\\e[0m'
338
+ zstyle ':completion:*:rig:*' group-name ''
339
+ zstyle ':completion:*:rig:*' verbose true
340
+ }
341
+
232
342
  _rig() {
233
- local -a raw values displays nospace_values nospace_displays
234
- local line value description rest marker display
343
+ local raw line value description marker group key tag
344
+ local -A bucket_specs bucket_groups bucket_data
235
345
  raw=("\${(@f)$(command rig __complete --shell zsh --index $((CURRENT - 1)) -- "\${words[@]}" 2>/dev/null)}")
346
+
236
347
  for line in "\${raw[@]}"; do
237
- value="\${line%%$'\\t'*}"
238
- description=""
239
- marker=""
240
- if [[ "$line" == *$'\\t'* ]]; then
241
- rest="\${line#*$'\\t'}"
242
- description="\${rest%%$'\\t'*}"
243
- if [[ "$rest" == *$'\\t'* ]]; then
244
- marker="\${rest#*$'\\t'}"
245
- fi
246
- fi
247
- display="\${value}"
348
+ [[ -z "$line" ]] && continue
349
+ local -a parts
350
+ parts=("\${(@s: :)line}")
351
+ value="\${parts[1]}"
352
+ description="\${parts[2]:-}"
353
+ marker="\${parts[3]:-}"
354
+ group="\${parts[4]:-}"
355
+ [[ -z "$group" ]] && group="rig"
356
+ key="\${group}|\${marker}"
357
+ bucket_groups[$key]="$group"
358
+ bucket_specs[$key]="$marker"
248
359
  if [[ -n "$description" ]]; then
249
- display="\${value} -- \${description}"
360
+ bucket_data[$key]+="\${value}:\${description}"$'\\n'
361
+ else
362
+ bucket_data[$key]+="\${value}"$'\\n'
250
363
  fi
251
- if [[ "$marker" == "nospace" ]]; then
252
- nospace_values+=("\${value}")
253
- nospace_displays+=("\${display}")
364
+ done
365
+
366
+ for key in "\${(@k)bucket_data}"; do
367
+ local -a matches
368
+ matches=("\${(@f)bucket_data[$key]}")
369
+ matches=("\${(@)matches:#}")
370
+ tag="\${bucket_groups[$key]//[^A-Za-z0-9]/_}"
371
+ [[ -z "$tag" ]] && tag="rig"
372
+ if [[ "\${bucket_specs[$key]}" == "nospace" ]]; then
373
+ _describe -t "$tag" "\${bucket_groups[$key]}" matches -S ''
254
374
  else
255
- values+=("\${value}")
256
- displays+=("\${display}")
375
+ _describe -t "$tag" "\${bucket_groups[$key]}" matches
257
376
  fi
258
377
  done
259
- (( \${#nospace_values} )) && compadd -S '' -ld nospace_displays -a nospace_values
260
- (( \${#values} )) && compadd -ld displays -a values
261
378
  }
379
+
262
380
  compdef _rig rig
263
381
  `;
264
382
  }
@@ -323,38 +441,269 @@ function parseWorkspaceRunCommand(words: string[]): { workspace?: string; operat
323
441
  return { workspace: args[0], operation: args[1], args: args.slice(2) };
324
442
  }
325
443
 
326
- function expectsOptionValue(words: string[]): boolean {
444
+ function parseRemoveCommand(words: string[]): { workspace?: string; args: string[] } {
445
+ let foundRemove = false;
446
+ const args: string[] = [];
447
+ for (let index = 0; index < words.length; index += 1) {
448
+ const word = words[index]!;
449
+ if (OPTIONS_WITH_VALUES.has(word)) {
450
+ index += 1;
451
+ continue;
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) };
462
+ }
463
+
464
+ function parseCacheCommand(words: string[]): { subcommand?: string; args: string[] } {
465
+ let foundCache = false;
466
+ const args: string[] = [];
467
+ for (let index = 0; index < words.length; index += 1) {
468
+ const word = words[index]!;
469
+ if (OPTIONS_WITH_VALUES.has(word)) {
470
+ index += 1;
471
+ continue;
472
+ }
473
+ if (word.startsWith("--") && word.includes("=")) continue;
474
+ if (word.startsWith("-")) continue;
475
+ if (!foundCache) {
476
+ if (word === "cache") foundCache = true;
477
+ continue;
478
+ }
479
+ args.push(word);
480
+ }
481
+ return { subcommand: args[0], args: args.slice(1) };
482
+ }
483
+
484
+ function cacheOptionTargets(words: string[]): CompletionItem[] {
485
+ const subcommand = parseCacheCommand(words).subcommand;
486
+ return subcommand ? CACHE_SUBCOMMAND_OPTIONS[subcommand] ?? [] : [];
487
+ }
488
+
489
+ function optionExpectingValue(words: string[]): string | undefined {
327
490
  const previous = words.at(-1);
328
- return Boolean(previous && OPTIONS_WITH_VALUES.has(previous));
491
+ return previous && OPTIONS_WITH_VALUES.has(previous) ? previous : undefined;
492
+ }
493
+
494
+ async function completeOptionValue(input: {
495
+ option: string;
496
+ current: string;
497
+ cwd: string;
498
+ words: string[];
499
+ inlinePrefix?: string;
500
+ }): Promise<CompletionItem[]> {
501
+ let items: CompletionItem[];
502
+ switch (input.option) {
503
+ case "-chdir":
504
+ case "--chdir":
505
+ items = completeDirectories(input.cwd, input.current);
506
+ break;
507
+ case "-config":
508
+ case "--config":
509
+ items = completeConfigPaths(projectBaseDir(input.words, input.cwd), input.current);
510
+ break;
511
+ case "--package-manager":
512
+ items = filterItems([
513
+ { value: "npm", group: GROUP_VALUES },
514
+ { value: "bun", group: GROUP_VALUES },
515
+ { value: "pnpm", group: GROUP_VALUES },
516
+ { value: "skip", group: GROUP_VALUES },
517
+ ], input.current);
518
+ break;
519
+ case "-state":
520
+ case "--state":
521
+ items = completeFilesystemPaths(input.cwd, input.current);
522
+ break;
523
+ default:
524
+ items = [];
525
+ }
526
+
527
+ if (!input.inlinePrefix) return items;
528
+ return items.map((item) => ({
529
+ ...item,
530
+ value: `${input.inlinePrefix}${item.value}`,
531
+ }));
532
+ }
533
+
534
+ function parseInlineValueOption(current: string): { option: string; value: string; prefix: string } | undefined {
535
+ const index = current.indexOf("=");
536
+ if (index < 0) return undefined;
537
+ const option = current.slice(0, index);
538
+ if (!OPTIONS_WITH_VALUES.has(option)) return undefined;
539
+ return {
540
+ option,
541
+ value: current.slice(index + 1),
542
+ prefix: current.slice(0, index + 1),
543
+ };
329
544
  }
330
545
 
331
546
  function resolveProjectDir(words: string[], cwd: string): { projectDir: string; configPath: string } {
547
+ let chdir: string | undefined;
548
+ let config: string | undefined;
332
549
  for (let index = 0; index < words.length; index += 1) {
333
550
  const word = words[index]!;
334
- if (word === "-C" || word === "--project") {
335
- const value = words[index + 1];
336
- if (value) return projectPaths(resolve(cwd, value));
551
+ if (word === "-chdir" || word === "--chdir") {
552
+ chdir = words[index + 1];
553
+ index += 1;
554
+ continue;
555
+ }
556
+ if (word.startsWith("-chdir=")) {
557
+ chdir = word.slice("-chdir=".length);
558
+ continue;
337
559
  }
338
- if (word.startsWith("--project=")) {
339
- return projectPaths(resolve(cwd, word.slice("--project=".length)));
560
+ if (word.startsWith("--chdir=")) {
561
+ chdir = word.slice("--chdir=".length);
562
+ continue;
340
563
  }
341
- if (word === "--config") {
342
- const value = words[index + 1];
343
- if (value) return { projectDir: dirname(resolve(cwd, value)), configPath: resolve(cwd, value) };
564
+ if (word === "-config" || word === "--config") {
565
+ config = words[index + 1];
566
+ index += 1;
567
+ continue;
568
+ }
569
+ if (word.startsWith("-config=")) {
570
+ config = word.slice("-config=".length);
571
+ continue;
344
572
  }
345
573
  if (word.startsWith("--config=")) {
346
- const configPath = resolve(cwd, word.slice("--config=".length));
347
- return { projectDir: dirname(configPath), configPath };
574
+ config = word.slice("--config=".length);
575
+ continue;
348
576
  }
349
577
  }
350
578
 
351
- return projectPaths(cwd);
579
+ const baseDir = resolve(cwd, chdir ?? ".");
580
+ if (config) {
581
+ const configPath = resolve(baseDir, config);
582
+ return { projectDir: dirname(configPath), configPath };
583
+ }
584
+ return projectPaths(baseDir);
585
+ }
586
+
587
+ function projectBaseDir(words: string[], cwd: string): string {
588
+ for (let index = 0; index < words.length; index += 1) {
589
+ const word = words[index]!;
590
+ if (word === "-chdir" || word === "--chdir") {
591
+ const value = words[index + 1];
592
+ if (value) return resolve(cwd, value);
593
+ }
594
+ if (word.startsWith("-chdir=")) {
595
+ return resolve(cwd, word.slice("-chdir=".length));
596
+ }
597
+ if (word.startsWith("--chdir=")) {
598
+ return resolve(cwd, word.slice("--chdir=".length));
599
+ }
600
+ }
601
+ return cwd;
352
602
  }
353
603
 
354
604
  function projectPaths(projectDir: string): { projectDir: string; configPath: string } {
355
605
  return { projectDir, configPath: join(projectDir, "rig.config.ts") };
356
606
  }
357
607
 
608
+ function completeDirectories(baseDir: string, current: string): CompletionItem[] {
609
+ return completePathEntries(baseDir, current, {
610
+ includeFiles: false,
611
+ includeDirectories: true,
612
+ });
613
+ }
614
+
615
+ function completeConfigPaths(baseDir: string, current: string): CompletionItem[] {
616
+ return completePathEntries(baseDir, current, {
617
+ includeFiles: true,
618
+ includeDirectories: true,
619
+ fileFilter: isRigConfigFileName,
620
+ });
621
+ }
622
+
623
+ function completeFilesystemPaths(baseDir: string, current: string): CompletionItem[] {
624
+ return completePathEntries(baseDir, current, {
625
+ includeFiles: true,
626
+ includeDirectories: true,
627
+ });
628
+ }
629
+
630
+ function completePathEntries(
631
+ baseDir: string,
632
+ current: string,
633
+ options: {
634
+ includeFiles: boolean;
635
+ includeDirectories: boolean;
636
+ fileFilter?: (name: string) => boolean;
637
+ },
638
+ ): CompletionItem[] {
639
+ const { dirPart, namePrefix, dir } = splitCompletionPath(baseDir, current);
640
+ let entries;
641
+ try {
642
+ entries = readdirSync(dir, { withFileTypes: true });
643
+ } catch {
644
+ return [];
645
+ }
646
+
647
+ const items = entries
648
+ .filter((entry) => entry.name.startsWith(namePrefix))
649
+ .flatMap((entry): CompletionItem[] => {
650
+ if (entry.isDirectory()) {
651
+ if (!options.includeDirectories) return [];
652
+ if (shouldSkipCompletionDirectory(entry.name, namePrefix)) return [];
653
+ return [{
654
+ value: `${dirPart}${entry.name}/`,
655
+ description: "directory",
656
+ noSpace: true,
657
+ group: GROUP_PATHS,
658
+ }];
659
+ }
660
+
661
+ if (!entry.isFile() || !options.includeFiles) return [];
662
+ if (options.fileFilter && !options.fileFilter(entry.name)) return [];
663
+ return [{
664
+ value: `${dirPart}${entry.name}`,
665
+ description: "config",
666
+ group: GROUP_PATHS,
667
+ }];
668
+ })
669
+ .sort((left, right) => {
670
+ if (left.noSpace && !right.noSpace) return -1;
671
+ if (!left.noSpace && right.noSpace) return 1;
672
+ return left.value.localeCompare(right.value);
673
+ });
674
+
675
+ return dedupeItems(items);
676
+ }
677
+
678
+ function shouldSkipCompletionDirectory(name: string, namePrefix: string): boolean {
679
+ if (
680
+ name === ".git" ||
681
+ name === ".rigkit" ||
682
+ name === ".turbo" ||
683
+ name === "node_modules" ||
684
+ name === "dist" ||
685
+ name === "build"
686
+ ) {
687
+ return true;
688
+ }
689
+ return name.startsWith(".") && !namePrefix.startsWith(".");
690
+ }
691
+
692
+ function splitCompletionPath(baseDir: string, current: string): {
693
+ dirPart: string;
694
+ namePrefix: string;
695
+ dir: string;
696
+ } {
697
+ const slashIndex = current.lastIndexOf("/");
698
+ const dirPart = slashIndex >= 0 ? current.slice(0, slashIndex + 1) : "";
699
+ const namePrefix = slashIndex >= 0 ? current.slice(slashIndex + 1) : current;
700
+ return {
701
+ dirPart,
702
+ namePrefix,
703
+ dir: resolve(baseDir, dirPart || "."),
704
+ };
705
+ }
706
+
358
707
  async function workspaceTargets(
359
708
  paths: { projectDir: string; configPath: string },
360
709
  ): Promise<CompletionItem[]> {
@@ -362,6 +711,7 @@ async function workspaceTargets(
362
711
  const items = workspaces.map((workspace) => ({
363
712
  value: workspace.name,
364
713
  description: workspaceDescription(workspace),
714
+ group: GROUP_WORKSPACES,
365
715
  }));
366
716
 
367
717
  return dedupeItems(items);
@@ -391,10 +741,15 @@ async function safeWorkspaceOperationTargets(
391
741
 
392
742
  function workspaceOperationTargets(manifest: RuntimeOperationManifest): CompletionItem[] {
393
743
  return (manifest.workspaceOperations ?? []).flatMap((operation) => [
394
- { value: operation.id, description: operation.description ?? "workspace operation" },
744
+ {
745
+ value: operation.id,
746
+ description: operation.description ?? "workspace operation",
747
+ group: GROUP_OPERATIONS,
748
+ },
395
749
  ...(operation.aliases ?? []).map((alias) => ({
396
750
  value: alias,
397
751
  description: operation.description ?? "workspace operation",
752
+ group: GROUP_OPERATIONS,
398
753
  })),
399
754
  ]);
400
755
  }
package/src/init.ts CHANGED
@@ -113,10 +113,11 @@ const vmSpec = new VmSpec()
113
113
  const freestyleProvider = freestyle.provider();
114
114
 
115
115
  const dev = sequence(${workflowName})
116
- .step("verify-node-22", async ({ providers, step }) => {
116
+ .step("verify-node-22", async ({ providers }) => {
117
+ console.log("creating verification vm");
117
118
  const { vm, vmId } = await providers.freestyle.client.vms.create({
118
119
  spec: vmSpec,
119
- logger: step.log,
120
+ logger: console.log,
120
121
  });
121
122
  try {
122
123
  const result = await vm.exec("node --version");
@@ -130,11 +131,12 @@ const dev = sequence(${workflowName})
130
131
  }
131
132
  })
132
133
  .workspace({
133
- create: async ({ workflow, providers, step }) => {
134
+ create: async ({ workflow, providers }) => {
135
+ console.log("booting workspace vm");
134
136
  const { vmId } = await providers.freestyle.client.vms.create({
135
137
  snapshotId: workflow.ctx.snapshotId,
136
138
  idleTimeoutSeconds: vmIdleTimeoutSeconds,
137
- logger: step.log,
139
+ logger: console.log,
138
140
  });
139
141
  return {
140
142
  vmId,