zet-execute 1.0.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.
package/dist/index.js ADDED
@@ -0,0 +1,820 @@
1
+ import { spawn as nodeSpawn } from "node:child_process";
2
+ import { ExecutionContext, buildRunParts, expandRestSymbol, } from "./context.js";
3
+ import { regenerateTypes } from "./types-generator.js";
4
+ import { outAnsi, errAnsi, isTaggedTemplate } from "./util.js";
5
+ import { TemplateResult, flattenParts } from "./shared.js";
6
+ export { CommandOutput, TemplateResult, flattenParts } from "./shared.js";
7
+ export { ExecutionContext, buildRunParts, expandRestSymbol, REST_SYMBOL, spawnCommand } from "./context.js";
8
+ const PKG_ROOT_URL = new URL("../", import.meta.url).href;
9
+ export function formatUncaughtError(err) {
10
+ const red = errAnsi("31");
11
+ const dim = errAnsi("2");
12
+ const reset = errAnsi("0");
13
+ let output = `\n${red}x: ${err.message}${reset}\n`;
14
+ if (err.stack) {
15
+ const stackLines = err.stack.split("\n").slice(1);
16
+ const userLines = stackLines.filter((line) => {
17
+ const trimmed = line.trim();
18
+ if (!trimmed.startsWith("at "))
19
+ return false;
20
+ if (trimmed.includes(PKG_ROOT_URL))
21
+ return false;
22
+ if (trimmed.includes("node:"))
23
+ return false;
24
+ return true;
25
+ });
26
+ if (userLines.length > 0) {
27
+ output += "\n";
28
+ for (const line of userLines) {
29
+ output += `${dim}${line}${reset}\n`;
30
+ }
31
+ }
32
+ }
33
+ return output;
34
+ }
35
+ const TOKEN_RE = /\{[^}]+\}|\.\.\.|[^\s{}]+/g;
36
+ function validateOptionName(raw) {
37
+ const name = raw.replace(/^--/, "");
38
+ const segments = name.split("-");
39
+ for (const seg of segments) {
40
+ if (seg.length === 0)
41
+ continue;
42
+ for (let i = 1; i < seg.length; i++) {
43
+ if (seg[i] >= "A" && seg[i] <= "Z") {
44
+ throw new Error(`Invalid option name '--${name}': uppercase letters are only allowed at the start of hyphen-separated segments`);
45
+ }
46
+ }
47
+ }
48
+ }
49
+ function extractShortFlag(name) {
50
+ const segments = name.split("-");
51
+ let short = "";
52
+ for (const seg of segments) {
53
+ if (seg.length > 0 && seg[0] >= "A" && seg[0] <= "Z") {
54
+ short += seg[0];
55
+ }
56
+ }
57
+ return short.length > 0 ? `-${short}` : null;
58
+ }
59
+ export function parseSignature(signature) {
60
+ const tokens = signature.match(TOKEN_RE);
61
+ if (!tokens || tokens.length === 0) {
62
+ throw new Error("Empty signature");
63
+ }
64
+ const name = tokens[0];
65
+ const args = [];
66
+ const options = [];
67
+ let acceptsRest = false;
68
+ let seenOptional = false;
69
+ for (let i = 1; i < tokens.length; i++) {
70
+ const tok = tokens[i];
71
+ if (tok === "...") {
72
+ acceptsRest = true;
73
+ continue;
74
+ }
75
+ if (tok.startsWith("{") && tok.endsWith("}")) {
76
+ const inner = tok.slice(1, -1).trim();
77
+ if (inner.startsWith("--")) {
78
+ const m = inner.match(/^--([a-zA-Z][\w-]*)([=])?\s*([\s\S]*)?$/);
79
+ if (!m)
80
+ throw new Error(`Invalid option syntax: ${tok}`);
81
+ const rawName = m[1];
82
+ const hasEquals = !!m[2];
83
+ const desc = (m[3] || "").trim();
84
+ validateOptionName(rawName);
85
+ const longName = rawName.toLowerCase();
86
+ const shortFlag = extractShortFlag(rawName);
87
+ options.push({
88
+ long: `--${longName}`,
89
+ short: shortFlag,
90
+ acceptsValue: hasEquals,
91
+ description: desc,
92
+ });
93
+ }
94
+ else {
95
+ const m = inner.match(/^([a-zA-Z]\w*)(\?)?\s*([\s\S]*)?$/);
96
+ if (!m)
97
+ throw new Error(`Invalid argument syntax: ${tok}`);
98
+ const argName = m[1];
99
+ const optional = !!m[2];
100
+ const desc = (m[3] || "").trim();
101
+ if (!optional && seenOptional) {
102
+ throw new Error(`Required argument '${argName}' cannot follow an optional argument`);
103
+ }
104
+ if (optional)
105
+ seenOptional = true;
106
+ args.push({
107
+ name: argName,
108
+ required: !optional,
109
+ description: desc,
110
+ });
111
+ }
112
+ }
113
+ else {
114
+ throw new Error(`Unexpected token '${tok}' in signature -- command names must be a single word`);
115
+ }
116
+ }
117
+ const allNames = new Set();
118
+ for (const a of args) {
119
+ if (allNames.has(a.name)) {
120
+ throw new Error(`Duplicate argument name '${a.name}'`);
121
+ }
122
+ allNames.add(a.name);
123
+ }
124
+ for (const o of options) {
125
+ if (allNames.has(o.long)) {
126
+ throw new Error(`Duplicate option name '${o.long}'`);
127
+ }
128
+ allNames.add(o.long);
129
+ }
130
+ const shortFlags = new Set();
131
+ for (const o of options) {
132
+ if (o.short) {
133
+ if (shortFlags.has(o.short))
134
+ throw new Error(`Duplicate short flag '${o.short}'`);
135
+ shortFlags.add(o.short);
136
+ }
137
+ }
138
+ return { name, args, options, acceptsRest };
139
+ }
140
+ export class Command {
141
+ name;
142
+ group;
143
+ signature;
144
+ description;
145
+ type;
146
+ parts;
147
+ userCwd;
148
+ callback;
149
+ constructor(name, group, signature) {
150
+ this.name = name;
151
+ this.group = group;
152
+ this.signature = signature;
153
+ this.description = "";
154
+ this.type = null;
155
+ this.parts = null;
156
+ this.userCwd = false;
157
+ this.callback = null;
158
+ }
159
+ }
160
+ export class CommandBuilder {
161
+ #cmd;
162
+ constructor(cmd) {
163
+ this.#cmd = cmd;
164
+ }
165
+ description(text) {
166
+ this.#cmd.description = text;
167
+ return this;
168
+ }
169
+ run(stringsOrParts, ...values) {
170
+ if (this.#cmd.type !== null) {
171
+ throw new Error(`Command '${this.#cmd.name}' already has a ${this.#cmd.type} action defined`);
172
+ }
173
+ this.#cmd.type = "command";
174
+ if (isTaggedTemplate(stringsOrParts)) {
175
+ this.#cmd.parts = buildRunParts(stringsOrParts, values);
176
+ }
177
+ else {
178
+ this.#cmd.parts = flattenParts([stringsOrParts, ...values]);
179
+ }
180
+ return this;
181
+ }
182
+ userCwd() {
183
+ this.#cmd.userCwd = true;
184
+ return this;
185
+ }
186
+ callback(fn) {
187
+ if (this.#cmd.type !== null) {
188
+ throw new Error(`Command '${this.#cmd.name}' already has a ${this.#cmd.type} action defined`);
189
+ }
190
+ this.#cmd.type = "callback";
191
+ this.#cmd.callback = fn;
192
+ return this;
193
+ }
194
+ }
195
+ export class Group {
196
+ prefix;
197
+ description;
198
+ commands;
199
+ constructor(prefix, description) {
200
+ this.prefix = prefix;
201
+ this.description = description;
202
+ this.commands = new Map();
203
+ }
204
+ register(signature, description) {
205
+ const parsed = parseSignature(signature);
206
+ if (this.commands.has(parsed.name)) {
207
+ throw new Error(`Duplicate command '${parsed.name}' in group '${this.prefix || "root"}'`);
208
+ }
209
+ const cmd = new Command(parsed.name, this, parsed);
210
+ if (description)
211
+ cmd.description = description;
212
+ this.commands.set(parsed.name, cmd);
213
+ return new CommandBuilder(cmd);
214
+ }
215
+ }
216
+ const cliConfig = {
217
+ aiPublishPath: null,
218
+ idePublishPath: null,
219
+ autoPublishIde: false,
220
+ };
221
+ export function _getCliConfigForTesting() {
222
+ return { ...cliConfig };
223
+ }
224
+ const RESERVED_NAMES = new Set(["init", "cli"]);
225
+ export const rootGroup = new Group("", "Commands");
226
+ export const groups = new Map();
227
+ export function _resetForTesting() {
228
+ rootGroup.commands.clear();
229
+ groups.clear();
230
+ cliConfig.aiPublishPath = null;
231
+ cliConfig.idePublishPath = null;
232
+ cliConfig.autoPublishIde = false;
233
+ }
234
+ export function register(signature, description) {
235
+ const name = signature.match(/^\S+/)?.[0];
236
+ if (name && RESERVED_NAMES.has(name)) {
237
+ throw new Error(`Cannot register command '${name}': '${name}' is a reserved built-in command`);
238
+ }
239
+ if (name && groups.has(name)) {
240
+ throw new Error(`Cannot register command '${name}': a group with the same name already exists`);
241
+ }
242
+ const builder = rootGroup.register(signature);
243
+ if (description)
244
+ builder.description(description);
245
+ return builder;
246
+ }
247
+ export function group(prefix, description) {
248
+ if (RESERVED_NAMES.has(prefix)) {
249
+ throw new Error(`The group prefix '${prefix}' is reserved`);
250
+ }
251
+ if (groups.has(prefix)) {
252
+ throw new Error(`Group '${prefix}' already exists`);
253
+ }
254
+ if (rootGroup.commands.has(prefix)) {
255
+ throw new Error(`Cannot create group '${prefix}': a command with the same name already exists`);
256
+ }
257
+ const g = new Group(prefix, description);
258
+ groups.set(prefix, g);
259
+ return g;
260
+ }
261
+ export function template(fn) {
262
+ const wrapper = function (...args) {
263
+ if (fn.length > 0 && args.length < fn.length) {
264
+ throw new Error(`Template expects ${fn.length} arguments, got ${args.length}`);
265
+ }
266
+ const result = fn(...args);
267
+ if (typeof result === "string") {
268
+ return new TemplateResult(result.split(/\s+/).filter(Boolean));
269
+ }
270
+ if (!Array.isArray(result)) {
271
+ throw new Error("Template function must return a string or an array");
272
+ }
273
+ const flattened = [];
274
+ for (const item of result) {
275
+ if (item instanceof TemplateResult) {
276
+ flattened.push(...item.parts);
277
+ }
278
+ else if (typeof item === "function" && item._isZetTemplate) {
279
+ if (item._expectedArgs === 0) {
280
+ flattened.push(...item().parts);
281
+ }
282
+ else {
283
+ throw new Error("Template with required arguments must be called");
284
+ }
285
+ }
286
+ else {
287
+ flattened.push(String(item));
288
+ }
289
+ }
290
+ return new TemplateResult(flattened);
291
+ };
292
+ wrapper._isZetTemplate = true;
293
+ wrapper._expectedArgs = fn.length;
294
+ return wrapper;
295
+ }
296
+ export function setAiPublishPath(path) {
297
+ cliConfig.aiPublishPath = path;
298
+ }
299
+ export function setIdePublishPath(path) {
300
+ cliConfig.idePublishPath = path;
301
+ }
302
+ export function autoPublishIde() {
303
+ cliConfig.autoPublishIde = true;
304
+ }
305
+ export function formatGlobalHelp() {
306
+ const yellow = outAnsi("33");
307
+ const green = outAnsi("32");
308
+ const dim = outAnsi("2");
309
+ const reset = outAnsi("0");
310
+ const lines = [];
311
+ lines.push(`${yellow}Usage:${reset} x ${dim}<command> [options]${reset}`);
312
+ lines.push("");
313
+ const sections = [];
314
+ for (const [, g] of groups) {
315
+ if (g.commands.size > 0) {
316
+ const entries = [];
317
+ for (const [, cmd] of g.commands) {
318
+ const label = `${g.prefix} ${cmd.name}`;
319
+ entries.push({ label, description: cmd.description });
320
+ }
321
+ sections.push({ title: g.description || g.prefix, entries });
322
+ }
323
+ }
324
+ if (rootGroup.commands.size > 0) {
325
+ const entries = [];
326
+ for (const [, cmd] of rootGroup.commands) {
327
+ let label = cmd.name;
328
+ if (cmd.signature.acceptsRest)
329
+ label += " ...";
330
+ entries.push({ label, description: cmd.description });
331
+ }
332
+ sections.push({ title: "Commands", entries });
333
+ }
334
+ let maxWidth = 0;
335
+ for (const section of sections) {
336
+ for (const e of section.entries) {
337
+ if (e.label.length > maxWidth)
338
+ maxWidth = e.label.length;
339
+ }
340
+ }
341
+ for (const section of sections) {
342
+ lines.push(`${yellow}${section.title}:${reset}`);
343
+ for (const e of section.entries) {
344
+ const padding = " ".repeat(maxWidth - e.label.length + 4);
345
+ lines.push(` ${green}${e.label}${reset}${padding}${e.description}`);
346
+ }
347
+ lines.push("");
348
+ }
349
+ lines.push(`${dim}Run "x <command> --help" for more information.${reset}`);
350
+ lines.push(`${dim}Run "x cli" for built-in utility commands.${reset}`);
351
+ return lines.join("\n");
352
+ }
353
+ export function formatCommandHelp(cmd) {
354
+ const yellow = outAnsi("33");
355
+ const green = outAnsi("32");
356
+ const dim = outAnsi("2");
357
+ const reset = outAnsi("0");
358
+ const sig = cmd.signature;
359
+ const lines = [];
360
+ let usage = `${yellow}Usage:${reset} x `;
361
+ if (cmd.group && cmd.group.prefix) {
362
+ usage += `${cmd.group.prefix} `;
363
+ }
364
+ usage += cmd.name;
365
+ if (sig.args.length > 0) {
366
+ const argParts = sig.args.map((a) => a.required ? `${dim}<${a.name}>${reset}` : `${dim}[${a.name}]${reset}`);
367
+ usage += ` ${argParts.join(" ")}`;
368
+ }
369
+ if (sig.acceptsRest)
370
+ usage += ` ${dim}[args...]${reset}`;
371
+ if (sig.options.length > 0)
372
+ usage += ` ${dim}[options]${reset}`;
373
+ lines.push(usage);
374
+ if (cmd.description) {
375
+ lines.push("");
376
+ lines.push(` ${cmd.description}`);
377
+ }
378
+ if (sig.args.length > 0) {
379
+ lines.push("");
380
+ lines.push(`${yellow}Arguments:${reset}`);
381
+ let argMaxWidth = 0;
382
+ for (const a of sig.args) {
383
+ if (a.name.length > argMaxWidth)
384
+ argMaxWidth = a.name.length;
385
+ }
386
+ for (const a of sig.args) {
387
+ const suffix = a.required ? ` ${dim}(required)${reset}` : "";
388
+ const desc = a.description || "";
389
+ const padding = " ".repeat(argMaxWidth - a.name.length + 4);
390
+ lines.push(` ${green}${a.name}${reset}${padding}${desc}${suffix}`);
391
+ }
392
+ }
393
+ lines.push("");
394
+ lines.push(`${yellow}Options:${reset}`);
395
+ const optEntries = [];
396
+ for (const o of sig.options) {
397
+ let label = o.long;
398
+ if (o.acceptsValue)
399
+ label += " <value>";
400
+ if (o.short)
401
+ label += `, ${o.short}`;
402
+ optEntries.push({ label, description: o.description || "" });
403
+ }
404
+ optEntries.push({ label: "--help, -h", description: "Show help" });
405
+ let maxOptWidth = 0;
406
+ for (const e of optEntries) {
407
+ if (e.label.length > maxOptWidth)
408
+ maxOptWidth = e.label.length;
409
+ }
410
+ for (const e of optEntries) {
411
+ const padding = " ".repeat(maxOptWidth - e.label.length + 4);
412
+ lines.push(` ${green}${e.label}${reset}${padding}${e.description}`);
413
+ }
414
+ return lines.join("\n");
415
+ }
416
+ export function formatGroupHelp(grp) {
417
+ const yellow = outAnsi("33");
418
+ const green = outAnsi("32");
419
+ const dim = outAnsi("2");
420
+ const reset = outAnsi("0");
421
+ const lines = [];
422
+ lines.push(`${yellow}Usage:${reset} x ${grp.prefix} ${dim}<command> [options]${reset}`);
423
+ lines.push("");
424
+ if (grp.description) {
425
+ lines.push(` ${grp.description}`);
426
+ lines.push("");
427
+ }
428
+ if (grp.commands.size > 0) {
429
+ lines.push(`${yellow}Commands:${reset}`);
430
+ let maxWidth = 0;
431
+ for (const [, cmd] of grp.commands) {
432
+ if (cmd.name.length > maxWidth)
433
+ maxWidth = cmd.name.length;
434
+ }
435
+ for (const [, cmd] of grp.commands) {
436
+ const padding = " ".repeat(maxWidth - cmd.name.length + 4);
437
+ lines.push(` ${green}${cmd.name}${reset}${padding}${cmd.description}`);
438
+ }
439
+ lines.push("");
440
+ }
441
+ lines.push(`${dim}Run "x ${grp.prefix} <command> --help" for more information.${reset}`);
442
+ return lines.join("\n");
443
+ }
444
+ export function parseArgv(argv, cmd) {
445
+ const sig = cmd.signature;
446
+ const args = {};
447
+ const opts = {};
448
+ const rest = [];
449
+ const longMap = new Map();
450
+ const shortMap = new Map();
451
+ for (const o of sig.options) {
452
+ longMap.set(o.long, o);
453
+ if (o.short)
454
+ shortMap.set(o.short, o);
455
+ }
456
+ let positionalIndex = 0;
457
+ let stopParsing = false;
458
+ for (let i = 0; i < argv.length; i++) {
459
+ const token = argv[i];
460
+ if (!stopParsing && token === "--") {
461
+ stopParsing = true;
462
+ continue;
463
+ }
464
+ if (!stopParsing && (token === "--help" || token === "-h")) {
465
+ return { help: true };
466
+ }
467
+ if (!stopParsing && token.startsWith("--")) {
468
+ let name, value;
469
+ const eqIdx = token.indexOf("=");
470
+ if (eqIdx !== -1) {
471
+ name = token.slice(0, eqIdx);
472
+ value = token.slice(eqIdx + 1);
473
+ }
474
+ else {
475
+ name = token;
476
+ value = undefined;
477
+ }
478
+ const normalizedName = `--${name.slice(2).toLowerCase()}`;
479
+ const optDef = longMap.get(normalizedName);
480
+ if (!optDef) {
481
+ if (sig.acceptsRest) {
482
+ rest.push(token);
483
+ continue;
484
+ }
485
+ return {
486
+ error: `unknown option '${name}'`,
487
+ cmd,
488
+ };
489
+ }
490
+ if (optDef.acceptsValue) {
491
+ if (value !== undefined) {
492
+ opts[optDef.long] = value;
493
+ }
494
+ else if (i + 1 < argv.length) {
495
+ const next = argv[i + 1];
496
+ const isNextOption = next.startsWith("--")
497
+ ? longMap.has(`--${next.slice(2).toLowerCase()}`)
498
+ : shortMap.has(next);
499
+ if (!isNextOption) {
500
+ opts[optDef.long] = argv[++i];
501
+ }
502
+ else {
503
+ return {
504
+ error: `option '${optDef.long}' requires a value`,
505
+ cmd,
506
+ };
507
+ }
508
+ }
509
+ else {
510
+ return {
511
+ error: `option '${optDef.long}' requires a value`,
512
+ cmd,
513
+ };
514
+ }
515
+ }
516
+ else {
517
+ if (value !== undefined) {
518
+ return {
519
+ error: `option '${optDef.long}' does not accept a value`,
520
+ cmd,
521
+ };
522
+ }
523
+ opts[optDef.long] = true;
524
+ }
525
+ continue;
526
+ }
527
+ if (!stopParsing && token.startsWith("-") && token.length > 1 && !token.startsWith("--")) {
528
+ const optDef = shortMap.get(token);
529
+ if (!optDef) {
530
+ if (sig.acceptsRest) {
531
+ rest.push(token);
532
+ continue;
533
+ }
534
+ return {
535
+ error: `unknown option '${token}'`,
536
+ cmd,
537
+ };
538
+ }
539
+ if (optDef.acceptsValue) {
540
+ if (i + 1 < argv.length) {
541
+ const next = argv[i + 1];
542
+ const isNextOption = next.startsWith("--")
543
+ ? longMap.has(`--${next.slice(2).toLowerCase()}`)
544
+ : shortMap.has(next);
545
+ if (!isNextOption) {
546
+ opts[optDef.long] = argv[++i];
547
+ }
548
+ else {
549
+ return {
550
+ error: `option '${optDef.long}' requires a value`,
551
+ cmd,
552
+ };
553
+ }
554
+ }
555
+ else {
556
+ return {
557
+ error: `option '${optDef.long}' requires a value`,
558
+ cmd,
559
+ };
560
+ }
561
+ }
562
+ else {
563
+ opts[optDef.long] = true;
564
+ }
565
+ continue;
566
+ }
567
+ if (positionalIndex < sig.args.length) {
568
+ args[sig.args[positionalIndex].name] = token;
569
+ positionalIndex++;
570
+ }
571
+ else if (sig.acceptsRest) {
572
+ rest.push(token);
573
+ }
574
+ else {
575
+ return {
576
+ error: `unexpected argument '${token}' for command '${cmd.name}'`,
577
+ cmd,
578
+ };
579
+ }
580
+ }
581
+ for (let j = 0; j < sig.args.length; j++) {
582
+ if (sig.args[j].required && !(sig.args[j].name in args)) {
583
+ return {
584
+ error: `missing required argument '${sig.args[j].name}'`,
585
+ cmd,
586
+ };
587
+ }
588
+ }
589
+ return { args, options: opts, rest };
590
+ }
591
+ function formatUsageLine(cmd) {
592
+ const sig = cmd.signature;
593
+ let usage = "Usage: x ";
594
+ if (cmd.group && cmd.group.prefix) {
595
+ usage += `${cmd.group.prefix} `;
596
+ }
597
+ usage += cmd.name;
598
+ for (const a of sig.args) {
599
+ usage += a.required ? ` <${a.name}>` : ` [${a.name}]`;
600
+ }
601
+ if (sig.acceptsRest)
602
+ usage += " [args...]";
603
+ if (sig.options.length > 0)
604
+ usage += " [options]";
605
+ return usage;
606
+ }
607
+ function levenshtein(a, b) {
608
+ const m = a.length;
609
+ const n = b.length;
610
+ const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
611
+ for (let i = 0; i <= m; i++)
612
+ dp[i][0] = i;
613
+ for (let j = 0; j <= n; j++)
614
+ dp[0][j] = j;
615
+ for (let i = 1; i <= m; i++) {
616
+ for (let j = 1; j <= n; j++) {
617
+ dp[i][j] = a[i - 1] === b[j - 1]
618
+ ? dp[i - 1][j - 1]
619
+ : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
620
+ }
621
+ }
622
+ return dp[m][n];
623
+ }
624
+ function suggestCommand(input) {
625
+ const allNames = [];
626
+ for (const [, cmd] of rootGroup.commands) {
627
+ allNames.push(cmd.name);
628
+ }
629
+ for (const [prefix, grp] of groups) {
630
+ for (const [, cmd] of grp.commands) {
631
+ allNames.push(`${prefix} ${cmd.name}`);
632
+ }
633
+ }
634
+ let bestName = null;
635
+ let bestDist = Infinity;
636
+ for (const name of allNames) {
637
+ const dist = levenshtein(input, name);
638
+ if (dist < bestDist) {
639
+ bestDist = dist;
640
+ bestName = name;
641
+ }
642
+ }
643
+ if (bestName && bestDist <= Math.max(1, Math.floor(bestName.length / 2))) {
644
+ return bestName;
645
+ }
646
+ return null;
647
+ }
648
+ function showCliHelp() {
649
+ const yellow = outAnsi("33");
650
+ const green = outAnsi("32");
651
+ const dim = outAnsi("2");
652
+ const reset = outAnsi("0");
653
+ const lines = [];
654
+ lines.push(`${yellow}Usage:${reset} x cli ${dim}<command>${reset}`);
655
+ lines.push("");
656
+ lines.push(`${yellow}Commands:${reset}`);
657
+ lines.push(` ${green}publish ai${reset} Generate zet-execute.md (AI agent documentation)`);
658
+ lines.push(` ${green}publish ide${reset} Generate TypeScript declarations for IDE support`);
659
+ lines.push(` ${green}init-completion <shell>${reset} Output shell completion script (bash or zsh)`);
660
+ process.stdout.write(lines.join("\n") + "\n");
661
+ }
662
+ export async function init() {
663
+ const argv = process.argv.slice(2);
664
+ const rootDir = process.env.X_ROOT_DIR || process.cwd();
665
+ const userDir = process.cwd();
666
+ // Auto-regenerate types (opt-in via autoPublishIde())
667
+ if (cliConfig.autoPublishIde) {
668
+ await regenerateTypes(rootDir, cliConfig.idePublishPath);
669
+ }
670
+ // No args or --help → global help
671
+ if (argv.length === 0 || argv[0] === "--help" || argv[0] === "-h") {
672
+ process.stdout.write(formatGlobalHelp() + "\n");
673
+ process.exit(0);
674
+ }
675
+ const firstArg = argv[0];
676
+ // --completions: output command names one per line (for shell completion)
677
+ if (firstArg === "--completions") {
678
+ const completionArg = argv[1];
679
+ const completionArg2 = argv[2];
680
+ if (completionArg && completionArg2) {
681
+ // Third-position: e.g. x cli publish <tab>
682
+ if (completionArg === "cli" && completionArg2 === "publish") {
683
+ process.stdout.write("ai\n");
684
+ process.stdout.write("ide\n");
685
+ }
686
+ }
687
+ else if (completionArg) {
688
+ // Second-position: show subcommands for groups or cli
689
+ if (completionArg === "cli") {
690
+ process.stdout.write("publish\n");
691
+ process.stdout.write("init-completion\n");
692
+ }
693
+ else if (groups.has(completionArg)) {
694
+ const grp = groups.get(completionArg);
695
+ for (const [, cmd] of grp.commands) {
696
+ process.stdout.write(cmd.name + "\n");
697
+ }
698
+ }
699
+ // Non-group command — no further completions
700
+ }
701
+ else {
702
+ // First-position completion: show root commands + group prefixes + cli
703
+ for (const [, cmd] of rootGroup.commands) {
704
+ process.stdout.write(cmd.name + "\n");
705
+ }
706
+ for (const [prefix] of groups) {
707
+ process.stdout.write(prefix + "\n");
708
+ }
709
+ process.stdout.write("cli\n");
710
+ }
711
+ process.exit(0);
712
+ }
713
+ // Built-in `cli` subcommands
714
+ if (firstArg === "cli") {
715
+ const sub = argv[1];
716
+ if (!sub || sub === "--help" || sub === "-h") {
717
+ showCliHelp();
718
+ process.exit(0);
719
+ }
720
+ if (sub === "publish") {
721
+ const { handleCli } = await import("./publish.js");
722
+ await handleCli(argv.slice(2), rootDir, cliConfig);
723
+ process.exit(0);
724
+ }
725
+ // init-completion is handled in bin/x.mjs before config search
726
+ process.stderr.write(`x: unknown cli command '${sub}'\n`);
727
+ process.exit(1);
728
+ }
729
+ // Command resolution
730
+ let targetCmd = null;
731
+ let remainingArgv = [];
732
+ if (groups.has(firstArg)) {
733
+ const grp = groups.get(firstArg);
734
+ if (argv.length < 2) {
735
+ process.stderr.write(`${errAnsi("31")}x: missing command for group '${firstArg}'${errAnsi("0")}\n`);
736
+ process.exit(1);
737
+ }
738
+ const cmdName = argv[1];
739
+ if (cmdName === "--help" || cmdName === "-h") {
740
+ process.stdout.write(formatGroupHelp(grp) + "\n");
741
+ process.exit(0);
742
+ }
743
+ targetCmd = grp.commands.get(cmdName) || null;
744
+ if (!targetCmd) {
745
+ let msg = `${errAnsi("31")}x: unknown command '${firstArg} ${cmdName}'${errAnsi("0")}\n`;
746
+ const suggestion = suggestCommand(`${firstArg} ${cmdName}`);
747
+ if (suggestion)
748
+ msg += `\n Did you mean: x ${suggestion}?\n`;
749
+ process.stderr.write(msg);
750
+ process.exit(1);
751
+ }
752
+ remainingArgv = argv.slice(2);
753
+ }
754
+ else {
755
+ targetCmd = rootGroup.commands.get(firstArg) || null;
756
+ if (!targetCmd) {
757
+ let msg = `${errAnsi("31")}x: unknown command '${firstArg}'${errAnsi("0")}\n`;
758
+ const suggestion = suggestCommand(firstArg);
759
+ if (suggestion)
760
+ msg += `\n Did you mean: x ${suggestion}?\n`;
761
+ process.stderr.write(msg);
762
+ process.exit(1);
763
+ }
764
+ remainingArgv = argv.slice(1);
765
+ }
766
+ // Parse arguments
767
+ const result = parseArgv(remainingArgv, targetCmd);
768
+ if ("help" in result) {
769
+ process.stdout.write(formatCommandHelp(targetCmd) + "\n");
770
+ process.exit(0);
771
+ }
772
+ if ("error" in result) {
773
+ process.stderr.write(`${errAnsi("31")}x: ${result.error}${errAnsi("0")}\n`);
774
+ process.stderr.write(`\n${formatUsageLine(result.cmd)}\n`);
775
+ process.exit(1);
776
+ }
777
+ const parsed = result;
778
+ // Execute
779
+ if (targetCmd.type === "command") {
780
+ const expanded = expandRestSymbol(targetCmd.parts, parsed.rest);
781
+ if (expanded.length === 0) {
782
+ process.stderr.write(`${errAnsi("31")}x: command has no arguments to execute${errAnsi("0")}\n`);
783
+ process.exit(1);
784
+ }
785
+ const cwdPath = targetCmd.userCwd ? userDir : rootDir;
786
+ const [cmd, ...args] = expanded;
787
+ const child = nodeSpawn(cmd, args, { stdio: "inherit", cwd: cwdPath });
788
+ const exitCode = await new Promise((resolve) => {
789
+ child.on("error", (err) => {
790
+ if (err.code === "ENOENT") {
791
+ process.stderr.write(`${errAnsi("31")}x: command '${cmd}' not found${errAnsi("0")}\n`);
792
+ }
793
+ else {
794
+ process.stderr.write(`${errAnsi("31")}x: ${err.message}${errAnsi("0")}\n`);
795
+ }
796
+ resolve(1);
797
+ });
798
+ child.on("close", (code) => {
799
+ resolve(code ?? 1);
800
+ });
801
+ });
802
+ process.exit(exitCode);
803
+ }
804
+ else if (targetCmd.type === "callback") {
805
+ const initialCwd = targetCmd.userCwd ? userDir : rootDir;
806
+ const ctx = new ExecutionContext(parsed.args, parsed.options, parsed.rest, rootDir, userDir, initialCwd);
807
+ try {
808
+ await targetCmd.callback(ctx);
809
+ }
810
+ catch (err) {
811
+ process.stderr.write(formatUncaughtError(err));
812
+ process.exit(1);
813
+ }
814
+ }
815
+ else {
816
+ process.stderr.write(`${errAnsi("31")}x: command '${targetCmd.name}' has no action defined${errAnsi("0")}\n`);
817
+ process.exit(1);
818
+ }
819
+ }
820
+ //# sourceMappingURL=index.js.map