@krapht/ork 0.3.0 → 1.0.0-alpha.0

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.
Files changed (39) hide show
  1. package/README.md +16 -12
  2. package/dist/__build_info__-DdTVd2p5.mjs +615 -0
  3. package/dist/bin.mjs +1572 -909
  4. package/dist/clean-NnboOuz6.mjs +88 -0
  5. package/dist/completion-snnYGjgK.mjs +405 -0
  6. package/dist/{context-BFGC_C02.mjs → context-DO3sBy9v.mjs} +30 -191
  7. package/dist/decide-B81wCZQg.mjs +113 -0
  8. package/dist/{find-BcvhT6UN.mjs → find-BKW-uGey.mjs} +1 -1
  9. package/dist/{format-DqpObyMl.mjs → format-CmTlTOjT.mjs} +72 -198
  10. package/dist/help-groups-BwYoAWsh.mjs +354 -0
  11. package/dist/{in-a-run-BbLhZOFW.mjs → in-a-run-BPXX09Bs.mjs} +1 -1
  12. package/dist/{limits-BKYAmqCR.mjs → limits-JCve2QpI.mjs} +1 -1
  13. package/dist/list-C7UEgmtd.mjs +110 -0
  14. package/dist/{mcp-BWsdQyri.mjs → mcp-CKPyNaKD.mjs} +7 -7
  15. package/dist/{output-Q2EdeVUt.mjs → output-B1V6ZJSx.mjs} +130 -36
  16. package/dist/{pause-DKACDfV1.mjs → pause-B_SL_yDz.mjs} +4 -4
  17. package/dist/{relay-CbK-Wme9.mjs → relay-o-Luee1c.mjs} +2 -2
  18. package/dist/remove-DAZ7ZCzt.mjs +194 -0
  19. package/dist/{send-C6yr04Fe.mjs → send-3B64fBNB.mjs} +3 -3
  20. package/dist/{src-D5ruZFgD.mjs → src-BJTegEzY.mjs} +1212 -57
  21. package/dist/{src-DtN1k-B3.mjs → src-C38BPzit.mjs} +251 -18
  22. package/dist/{src-Cz1yOUTg.mjs → src-DHvWU4tX.mjs} +2742 -1659
  23. package/dist/{src-CZOVF2yU.mjs → src-Dyz_Gy1q.mjs} +35 -8
  24. package/dist/ui/THIRD_PARTY_LICENSES.txt +2 -2
  25. package/dist/ui/assets/app-C8nJ6NpT.js +67 -0
  26. package/dist/ui/assets/app-CH7YC3in.css +1 -0
  27. package/dist/ui/assets/{index-BiO54L90.css → index-BPIbqRRb.css} +1 -1
  28. package/dist/ui/assets/{index-BoG6O-Lp.js → index-D83F2OKZ.js} +2 -2
  29. package/dist/ui/index.html +2 -3
  30. package/dist/ui-CY4xxJUu.mjs +472 -0
  31. package/dist/{usage-BIjf9Xdt.mjs → usage-CJnytoe4.mjs} +6 -5
  32. package/dist/{watch-B8o4Szd_.mjs → watch-BJB82ZJ7.mjs} +7 -7
  33. package/dist/workflows-CiM7S3l0.mjs +88 -0
  34. package/dist/worktrees-86ySXxR6.mjs +212 -0
  35. package/package.json +6 -6
  36. package/dist/__build_info__-CXXMLvjt.mjs +0 -104
  37. package/dist/ui/assets/app-BlDdP396.js +0 -61
  38. package/dist/ui/assets/app-C722xNZ3.css +0 -1
  39. package/dist/ui-CZS1XKla.mjs +0 -160
@@ -0,0 +1,88 @@
1
+ import { A as gitOk, O as removeWorktree } from "./src-DHvWU4tX.mjs";
2
+ // ../core/src/worktree/clean.ts
3
+ /**
4
+ * @file What `ork worktrees clean` and `ork worktrees remove` do with what
5
+ * list.ts reads: the plan (what goes, what's kept and why), by one rule for
6
+ * every caller, and carrying it out. The CLI and the Worktrees panel both
7
+ * plan here, so neither removes what the other would keep.
8
+ */
9
+ /** What names an item for `remove`: its branch, or its worktree's path. */
10
+ const keyOf = (item) => item.branch ?? item.path ?? `${item.runId}/${item.nodeId}`;
11
+ /** The reasons no `force` overrides: removing would break something. */
12
+ const MAIN_CHECKOUT = "checked out in the main checkout";
13
+ const RUN_GOING = "its run is still going";
14
+ const kept = (item, skip) => ({
15
+ item,
16
+ removeWorktree: false,
17
+ deleteBranch: false,
18
+ skip
19
+ });
20
+ /** What clean does with one item. */
21
+ const cleanStep = (item, options) => {
22
+ if (item.inMainCheckout) return kept(item, MAIN_CHECKOUT);
23
+ if (item.heldBy !== void 0) return kept(item, `checked out in ${item.heldBy}`);
24
+ if (options.live?.has(item.runId) === true) return kept(item, RUN_GOING);
25
+ if (item.dirty && !options.force) return kept(item, "uncommitted changes");
26
+ if (item.branch === void 0 && item.checkedOut === void 0 && item.ahead > 0 && !options.force) return kept(item, "commits on no branch");
27
+ const removable = item.path !== null && !item.missing;
28
+ if (options.branches && item.branch !== void 0 && !item.merged && !options.force) return {
29
+ item,
30
+ removeWorktree: removable,
31
+ deleteBranch: false,
32
+ skip: "branch not merged"
33
+ };
34
+ return {
35
+ item,
36
+ removeWorktree: removable,
37
+ deleteBranch: options.branches && item.branch !== void 0
38
+ };
39
+ };
40
+ /** Whether a step drops a worktree whose folder is gone, by its own path. */
41
+ const stepPrunes = (step) => step.item.missing && step.item.path !== null && step.skip === void 0;
42
+ /** Whether a step does anything. */
43
+ const stepActs = (step) => step.removeWorktree || step.deleteBranch || stepPrunes(step);
44
+ /**
45
+ * What `ork worktrees clean` would do. A worktree with uncommitted changes, an
46
+ * unmerged branch, or a detached worktree holding commits no branch has, is
47
+ * kept unless `force`; one checked out in the main checkout or in a worktree
48
+ * Ork didn't create, or whose run is still going, is kept even then.
49
+ */
50
+ const planClean = (items, filter, options) => items.filter((item) => filter.runId === void 0 || item.runId === filter.runId).filter((item) => filter.key === void 0 || keyOf(item) === filter.key).filter((item) => !filter.merged || item.merged).map((item) => cleanStep(item, options));
51
+ /**
52
+ * Carries out one step; what it did goes into `done` as it goes. It touches
53
+ * only its own item: a missing worktree is dropped by its own path (`git
54
+ * worktree remove`), never by `git worktree prune`, which would drop every
55
+ * missing worktree in the repository, the user's own included.
56
+ */
57
+ const applyStep = async (root, step, force, done) => {
58
+ const { item } = step;
59
+ if (stepPrunes(step) && !step.removeWorktree && item.path !== null) {
60
+ await gitOk(root, [
61
+ "worktree",
62
+ "remove",
63
+ item.path
64
+ ]);
65
+ done.push(`pruned ${item.path}`);
66
+ }
67
+ if (step.removeWorktree && item.path !== null) {
68
+ await removeWorktree(root, item.path, force);
69
+ done.push(`removed ${item.path}`);
70
+ }
71
+ if (step.deleteBranch && item.branch !== void 0) {
72
+ await gitOk(root, [
73
+ "branch",
74
+ force ? "-D" : "-d",
75
+ item.branch
76
+ ]);
77
+ done.push(`deleted ${item.branch}`);
78
+ }
79
+ };
80
+ /**
81
+ * Carries out a clean plan, step by step. What it did goes into `done` as it
82
+ * goes, so a caller still has it when git fails halfway.
83
+ */
84
+ const applyClean = async (root, steps, force, done = []) => {
85
+ for (const step of steps) await applyStep(root, step, force, done);
86
+ return done;
87
+ };
88
+ export { planClean as a, keyOf as i, applyStep as n, stepActs as o, cleanStep as r, stepPrunes as s, applyClean as t };
@@ -0,0 +1,405 @@
1
+ import { Qn as NodeIdSchema, ar as CLAUDE_DRIVERS, bn as placeFiles, ht as liveStatus, xn as placeFor } from "./src-DHvWU4tX.mjs";
2
+ import { a as builtinWorkflowNames, n as pendingDecisions, o as builtinWorkflowText, s as isBuiltinArg } from "./decide-B81wCZQg.mjs";
3
+ import { t as findRun } from "./find-BKW-uGey.mjs";
4
+ import { c as openStore, l as storePath, o as writeErr } from "./output-B1V6ZJSx.mjs";
5
+ import { a as findEntry, c as subcommandsOf, d as SHELL_SCRIPTS, l as COMPLETE_COMMAND, n as groupedEntries, r as argInfoOf, t as groupOf, u as SHELLS } from "./help-groups-BwYoAWsh.mjs";
6
+ import { a as resolveWorkflowArg, n as WORKFLOW_SUFFIX } from "./workflows-CiM7S3l0.mjs";
7
+ import path from "node:path";
8
+ import { parse } from "yaml";
9
+ import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
10
+ import { defineCommand } from "citty";
11
+ /** The store at `~/.ork/state.db`, read-only; none when it can't be read. */
12
+ const readOnlyStore = (file = storePath()) => {
13
+ if (!existsSync(file)) return void 0;
14
+ try {
15
+ return openStore(file, { readOnly: true });
16
+ } catch {
17
+ return;
18
+ }
19
+ };
20
+ /** The project `cwd` is in, from the store's registered projects if any. */
21
+ const placeOf = (cwd, store) => placeFor(cwd, store?.projects() ?? []);
22
+ /** The project's workflows by name, then Ork's own by their `ork:` name. */
23
+ const workflowCandidates = (place) => {
24
+ const folder = placeFiles(place).workflows;
25
+ return [...(existsSync(folder) ? readdirSync(folder).filter((name) => name.endsWith(WORKFLOW_SUFFIX)).map((name) => name.slice(0, -WORKFLOW_SUFFIX.length)).toSorted() : []).map((value) => ({
26
+ value,
27
+ description: "this project's workflow"
28
+ })), ...builtinWorkflowNames().map((value) => ({
29
+ value,
30
+ description: "one of Ork's workflows"
31
+ }))];
32
+ };
33
+ /**
34
+ * A workflow argument's text: Ork's own (never written out here), or the file
35
+ * the argument names as every command resolves it; none for a folder.
36
+ */
37
+ const workflowText = (arg, cwd, place) => {
38
+ if (isBuiltinArg(arg)) return builtinWorkflowText(arg);
39
+ const file = path.resolve(cwd, resolveWorkflowArg(arg, cwd, placeFiles(place).workflows));
40
+ return existsSync(file) && statSync(file).isFile() ? readFileSync(file, "utf8") : void 0;
41
+ };
42
+ /** The node ids of the workflow `arg` names; none when it can't be read. */
43
+ const workflowNodes = (arg, cwd, place) => {
44
+ const text = workflowText(arg, cwd, place);
45
+ const parsed = text === void 0 ? void 0 : parse(text);
46
+ const nodes = parsed !== null && typeof parsed === "object" && "nodes" in parsed ? parsed.nodes : void 0;
47
+ return (nodes !== null && typeof nodes === "object" ? Object.keys(nodes) : []).filter((key) => NodeIdSchema.safeParse(key).success).map((value) => ({
48
+ value,
49
+ description: arg
50
+ }));
51
+ };
52
+ /**
53
+ * Runs whose id starts with `prefix`, newest first: the project's, else any
54
+ * project's, at most `RUN_LIMIT`.
55
+ */
56
+ const runCandidates = (store, place, prefix) => {
57
+ const starts = (id) => id.startsWith(prefix);
58
+ const own = store.runs({
59
+ project: place.root,
60
+ limit: 50
61
+ }).filter((run) => starts(run.id));
62
+ const fallback = prefix === "" ? store.runs({ limit: 50 }) : store.lookup(prefix).slice(0, 50);
63
+ return (own.length > 0 ? own : fallback).map((run) => ({
64
+ value: run.id,
65
+ description: `${run.workflow} · ${liveStatus(run)}`
66
+ }));
67
+ };
68
+ /** The run `arg` names, or the project's newest; its nodes in order. */
69
+ const runNodes = (store, arg, cwd) => {
70
+ const found = findRun(store, arg, cwd);
71
+ if (!found.ok) return [];
72
+ const { id } = found.run;
73
+ return store.nodes(id).map((value) => ({
74
+ value,
75
+ description: `run ${id}`
76
+ }));
77
+ };
78
+ /** Whose decisions: the run `arg` names, else the project's runs. */
79
+ const decisionFilter = (store, arg, cwd, place) => {
80
+ if (arg === void 0) return { project: place.root };
81
+ const found = findRun(store, arg, cwd);
82
+ return found.ok ? { runId: found.run.id } : void 0;
83
+ };
84
+ /** The open decisions of the run `arg` names, else of the project's runs. */
85
+ const decisionCandidates = (store, arg, cwd, place) => {
86
+ const filter = decisionFilter(store, arg, cwd, place);
87
+ return filter === void 0 ? [] : pendingDecisions(store, filter).map((decision) => ({
88
+ value: decision.id,
89
+ description: `${decision.kind} of ${decision.runId}/${decision.node}`
90
+ }));
91
+ };
92
+ // src/completion/values.ts
93
+ const withStore = (context, read) => {
94
+ const store = context.store();
95
+ return store === void 0 ? [] : read(store);
96
+ };
97
+ const runs = {
98
+ group: "runs",
99
+ values: (context) => withStore(context, (store) => runCandidates(store, placeOf(context.cwd, store), context.current))
100
+ };
101
+ const workflows = {
102
+ group: "workflows",
103
+ values: (context) => workflowCandidates(placeOf(context.cwd, context.store()))
104
+ };
105
+ /** A workflow's nodes when the command takes a workflow, else a run's. */
106
+ const nodes = {
107
+ group: "nodes",
108
+ values: (context) => {
109
+ const workflow = context.given["workflow"];
110
+ if (context.positionals.includes("workflow")) return workflow === void 0 ? [] : workflowNodes(workflow, context.cwd, placeOf(context.cwd, context.store()));
111
+ return withStore(context, (store) => runNodes(store, context.given["run"], context.cwd));
112
+ }
113
+ };
114
+ const decisions = {
115
+ group: "decisions",
116
+ values: (context) => withStore(context, (store) => decisionCandidates(store, context.given["run"], context.cwd, placeOf(context.cwd, store)))
117
+ };
118
+ const drivers = {
119
+ group: "drivers",
120
+ values: () => CLAUDE_DRIVERS.map((driver) => ({
121
+ value: driver.id,
122
+ description: driver.label
123
+ }))
124
+ };
125
+ const files = {
126
+ group: "files",
127
+ values: () => [],
128
+ files: true
129
+ };
130
+ const shells = {
131
+ group: "shells",
132
+ values: () => SHELLS.map((value) => ({
133
+ value,
134
+ description: ""
135
+ }))
136
+ };
137
+ /** Providers by argument name, or by command path and name. */
138
+ const VALUE_PROVIDERS = {
139
+ run: runs,
140
+ workflow: workflows,
141
+ files: {
142
+ ...workflows,
143
+ files: true
144
+ },
145
+ node: nodes,
146
+ "decide id": decisions,
147
+ "completion shell": shells,
148
+ driver: drivers,
149
+ path: files,
150
+ file: files,
151
+ output: files
152
+ };
153
+ /** The provider for argument `name` of the command at `path`, if any. */
154
+ const providerFor = (providers, path, name) => providers[[...path, name].join(" ")] ?? providers[name];
155
+ // src/completion/candidates.ts
156
+ /**
157
+ * @file What `ork __complete` offers at a position: subcommands (the root's in
158
+ * `ork --help`'s groups), a command's public flags, an argument's values from
159
+ * its provider, or an enum flag's values; filtered by what was typed, and
160
+ * printed one per line as `value<TAB>description`, with `:group <title>`
161
+ * before each group and `:files` when file names fit too.
162
+ */
163
+ const NONE = {
164
+ sections: [],
165
+ files: false
166
+ };
167
+ /** The first line of a description, as a menu shows it. */
168
+ const brief = (text) => text.split("\n")[0]?.trim() ?? "";
169
+ const subcommandSections = (position) => {
170
+ if (position.path.length > 0) return [{
171
+ title: "commands",
172
+ candidates: position.subcommands.filter((entry) => !entry.hidden).map((entry) => ({
173
+ value: entry.name,
174
+ description: brief(entry.description)
175
+ }))
176
+ }];
177
+ return groupedEntries(position.subcommands).map((group) => ({
178
+ title: group.title,
179
+ candidates: group.entries.map((entry) => ({
180
+ value: entry.name,
181
+ description: groupOf(entry.name)?.short ?? brief(entry.description)
182
+ }))
183
+ }));
184
+ };
185
+ const flagSection = (position) => ({
186
+ title: "flags",
187
+ candidates: [...position.flags.flatMap((flag) => [{
188
+ value: `--${flag.name}`,
189
+ description: brief(flag.description)
190
+ }, ...flag.negatable ? [{
191
+ value: `--no-${flag.name}`,
192
+ description: `Turn off --${flag.name}`
193
+ }] : []]), {
194
+ value: "--help",
195
+ description: "Show this command's help"
196
+ }]
197
+ });
198
+ const fromProvider = (provider, context) => provider === void 0 ? NONE : {
199
+ sections: [{
200
+ title: provider.group,
201
+ candidates: provider.values(context)
202
+ }],
203
+ files: provider.files === true
204
+ };
205
+ /** Everything that fits at `position`, before filtering. */
206
+ const offerAt = (position, providers, context) => {
207
+ const { slot, path } = position;
208
+ if (slot.kind === "flag") return {
209
+ sections: [flagSection(position)],
210
+ files: false
211
+ };
212
+ if (slot.kind === "value") {
213
+ const { flag } = slot;
214
+ if (flag.options.length > 0) {
215
+ const candidates = flag.options.map((value) => ({
216
+ value,
217
+ description: ""
218
+ }));
219
+ return {
220
+ sections: [{
221
+ title: flag.name,
222
+ candidates
223
+ }],
224
+ files: false
225
+ };
226
+ }
227
+ return fromProvider(providerFor(providers, path, flag.name), context);
228
+ }
229
+ const values = slot.positional === void 0 ? NONE : fromProvider(providerFor(providers, path, slot.positional.name), context);
230
+ return {
231
+ sections: [...position.index === 0 ? subcommandSections(position) : [], ...values.sections],
232
+ files: values.files
233
+ };
234
+ };
235
+ /** What fits at `position`, keeping what starts with the typed word. */
236
+ const offerFor = (position, providers, context) => {
237
+ const offer = offerAt(position, providers, context);
238
+ return {
239
+ sections: offer.sections.map((section) => ({
240
+ ...section,
241
+ candidates: section.candidates.filter((c) => c.value.startsWith(position.current))
242
+ })).filter((section) => section.candidates.length > 0),
243
+ files: offer.files
244
+ };
245
+ };
246
+ /**
247
+ * A value a shell may insert as it is: no space, control character, quote or
248
+ * shell syntax (`$(…)`, `;`, `*`), and no leading `:`, which would read as one
249
+ * of this output's own lines. Ids, names and flags all fit.
250
+ */
251
+ const SAFE_VALUE = /^[\w.@+,=/~-][\w.@+,=/~:-]*$/u;
252
+ /** One line per candidate, unsafe values dropped; descriptions on one line. */
253
+ const offerLines = (offer) => [...offer.sections.flatMap((section) => [`:group ${section.title}`, ...section.candidates.filter((c) => SAFE_VALUE.test(c.value)).map((c) => `${c.value}\t${c.description.replaceAll(/\p{Cc}+/gu, " ")}`)]), ...offer.files ? [":files"] : []];
254
+ // src/completion/position.ts
255
+ const flagNamed = (flags, word) => {
256
+ const name = word.replace(/^-{1,2}/u, "").split("=")[0] ?? "";
257
+ return flags.find((flag) => flag.name === name || flag.aliases.includes(name) || flag.negatable && name === `no-${flag.name}`);
258
+ };
259
+ /** One word before the cursor, folded into the walk. */
260
+ const step = async (walk, word) => {
261
+ const { pending, ...settled } = walk;
262
+ if (pending !== void 0) return {
263
+ ...settled,
264
+ given: {
265
+ ...walk.given,
266
+ [pending.name]: word
267
+ }
268
+ };
269
+ if (!walk.rest && word === "--") return {
270
+ ...walk,
271
+ rest: true
272
+ };
273
+ if (!walk.rest && word.startsWith("-")) {
274
+ const flag = flagNamed(walk.flags, word);
275
+ if (flag === void 0 || !flag.takesValue) return walk;
276
+ if (!word.includes("=")) return {
277
+ ...walk,
278
+ pending: flag
279
+ };
280
+ const value = word.slice(word.indexOf("=") + 1);
281
+ return {
282
+ ...walk,
283
+ given: {
284
+ ...walk.given,
285
+ [flag.name]: value
286
+ }
287
+ };
288
+ }
289
+ const sub = walk.index === 0 ? findEntry(walk.subcommands, word) : void 0;
290
+ if (sub !== void 0) return {
291
+ ...await argInfoOf(sub.def),
292
+ path: [...walk.path, sub.name],
293
+ subcommands: await subcommandsOf(sub.def),
294
+ given: walk.given,
295
+ index: 0,
296
+ rest: false
297
+ };
298
+ const positional = walk.positionals[walk.index];
299
+ const given = positional === void 0 ? walk.given : {
300
+ ...walk.given,
301
+ [positional.name]: word
302
+ };
303
+ return {
304
+ ...walk,
305
+ given,
306
+ index: walk.index + 1
307
+ };
308
+ };
309
+ const slotOf = (walk, current) => {
310
+ if (walk.pending !== void 0) return {
311
+ kind: "value",
312
+ flag: walk.pending
313
+ };
314
+ if (!walk.rest && current.startsWith("-")) return { kind: "flag" };
315
+ const positional = walk.positionals[walk.index];
316
+ return positional === void 0 ? { kind: "word" } : {
317
+ kind: "word",
318
+ positional
319
+ };
320
+ };
321
+ /**
322
+ * Where `words` (what follows `ork`, the word under the cursor last, maybe
323
+ * empty) leave the cursor, in the command `root`.
324
+ */
325
+ const positionOf = async (root, words) => {
326
+ const before = words.slice(0, -1);
327
+ const current = words.at(-1) ?? "";
328
+ const start = {
329
+ ...await argInfoOf(root),
330
+ path: [],
331
+ subcommands: await subcommandsOf(root),
332
+ given: {},
333
+ index: 0,
334
+ rest: false
335
+ };
336
+ const walk = await before.reduce(async (previous, word) => step(await previous, word), Promise.resolve(start));
337
+ const { pending: _pending, rest: _rest, ...reached } = walk;
338
+ return {
339
+ ...reached,
340
+ current,
341
+ slot: slotOf(walk, current)
342
+ };
343
+ };
344
+ // src/commands/completion.ts
345
+ /**
346
+ * The candidates for the last of `words` (what follows `ork`; the word under
347
+ * the cursor, maybe empty, last), as `__complete` prints them.
348
+ */
349
+ const completionLines = async (root, words, cwd, storeFile) => {
350
+ const position = await positionOf(root, words.length === 0 ? [""] : words);
351
+ const opened = {};
352
+ const store = () => {
353
+ if (!("store" in opened)) opened.store = readOnlyStore(storeFile);
354
+ return opened.store;
355
+ };
356
+ try {
357
+ return offerLines(offerFor(position, VALUE_PROVIDERS, {
358
+ cwd,
359
+ given: position.given,
360
+ current: position.current,
361
+ positionals: position.positionals.map((p) => p.name),
362
+ store
363
+ }));
364
+ } finally {
365
+ opened.store?.close();
366
+ }
367
+ };
368
+ /** `ork __complete`'s whole job: print the candidates, or nothing. */
369
+ const printCompletions = async (root, args, cwd = process.cwd()) => {
370
+ try {
371
+ const words = args[0] === "--" ? args.slice(1) : args;
372
+ const lines = await completionLines(root, words, cwd);
373
+ process.stdout.write(lines.map((line) => `${line}\n`).join(""));
374
+ } catch {}
375
+ };
376
+ /** The hidden `ork __complete` of the command `root`. */
377
+ const completeCommand = (root) => defineCommand({
378
+ meta: {
379
+ name: COMPLETE_COMMAND,
380
+ description: "Print tab-completion candidates (the scripts run it)",
381
+ hidden: true
382
+ },
383
+ run: ({ rawArgs }) => printCompletions(root, rawArgs)
384
+ });
385
+ const completion = defineCommand({
386
+ meta: {
387
+ name: "completion",
388
+ description: `Print a tab-completion script for your shell: ${SHELLS.join(", ")}`
389
+ },
390
+ args: { shell: {
391
+ type: "positional",
392
+ description: SHELLS.join(", "),
393
+ required: true
394
+ } },
395
+ run: ({ args }) => {
396
+ const script = SHELL_SCRIPTS[args.shell];
397
+ if (script === void 0) {
398
+ writeErr(`No completion for \`${args.shell}\`; there is one for ${SHELLS.join(", ")}.`);
399
+ process.exitCode = 1;
400
+ return;
401
+ }
402
+ process.stdout.write(script);
403
+ }
404
+ });
405
+ export { completeCommand, completion, printCompletions };