auriga-cli 1.7.0 → 1.9.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/cli.js CHANGED
@@ -1,14 +1,487 @@
1
1
  #!/usr/bin/env node
2
- import { createRequire } from "node:module";
3
- import { checkbox } from "@inquirer/prompts";
4
- import { fetchContentRoot, printBanner, withEsc } from "./utils.js";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { exec, fetchContentRoot, getPackageRoot, isNonInteractive, LANGUAGES, log, readPackageVersion, } from "./utils.js";
5
6
  import { installWorkflow } from "./workflow.js";
6
7
  import { installSkills, installRecommendedSkills } from "./skills.js";
7
8
  import { installPlugins } from "./plugins.js";
8
9
  import { installHooks } from "./hooks.js";
9
- const require = createRequire(import.meta.url);
10
- const { version } = require("../package.json");
11
- async function main() {
10
+ import { loadCatalog } from "./catalog.js";
11
+ import { renderHelp, renderTypeHelp } from "./help.js";
12
+ import { renderGuide } from "./guide.js";
13
+ import { CATEGORY_NAMES } from "./types.js";
14
+ const RELOAD_REMINDER = "\n⚠ Reload your Claude Code session to pick up the new harness (CLAUDE.md / skills / plugins are loaded at session startup).\n";
15
+ const CATEGORY_SET = new Set(CATEGORY_NAMES);
16
+ const TYPE_FOR_FILTER = {
17
+ "--skill": "skills",
18
+ "--recommended-skill": "recommended",
19
+ "--plugin": "plugins",
20
+ "--hook": "hooks",
21
+ };
22
+ function parseErr(msg) {
23
+ throw new Error(msg);
24
+ }
25
+ // Sentinel thrown by parseInstall when `--help` / `-h` appears in the
26
+ // install subcommand argv. Caught in parseArgs and converted to a
27
+ // ParsedArgs of `{ command: "help", helpType }`. A sentinel class (vs.
28
+ // an error string) keeps the parse error path untouched by the help
29
+ // shortcut, so `install foo --help` still reports the real error.
30
+ class PerTypeHelpRequest {
31
+ type;
32
+ constructor(type) {
33
+ this.type = type;
34
+ }
35
+ }
36
+ function requireValue(argv, i, flag) {
37
+ const v = argv[i + 1];
38
+ if (v === undefined || v.startsWith("-")) {
39
+ parseErr(`${flag} requires a value.`);
40
+ }
41
+ return v;
42
+ }
43
+ /**
44
+ * Handle the `--flag=value` form for single-value flags (--lang, --cwd,
45
+ * --scope). Returns [value, advance] where `advance` is how many
46
+ * tokens to consume (1 for equals-form, 2 for space-form).
47
+ * Rejects empty values (`--lang=`) consistently with requireValue.
48
+ */
49
+ function readSingleValue(argv, i, flag) {
50
+ const t = argv[i];
51
+ const eqIdx = t.indexOf("=");
52
+ if (eqIdx > 0) {
53
+ const v = t.slice(eqIdx + 1);
54
+ if (v.length === 0)
55
+ parseErr(`${flag} requires a value.`);
56
+ return [v, 1];
57
+ }
58
+ return [requireValue(argv, i, flag), 2];
59
+ }
60
+ // Consume values for a filter flag until the next flag-like token
61
+ // ("--..." / "-..."), the explicit "--" terminator, or end-of-argv.
62
+ // Returns [values, nextIndex].
63
+ function consumeFilter(argv, start) {
64
+ const values = [];
65
+ let i = start;
66
+ while (i < argv.length) {
67
+ const t = argv[i];
68
+ if (t === "--") {
69
+ i += 1;
70
+ break;
71
+ }
72
+ if (t.startsWith("-"))
73
+ break;
74
+ values.push(t);
75
+ i += 1;
76
+ }
77
+ return [values, i];
78
+ }
79
+ export function parseArgs(argv) {
80
+ // Top-level verb / flag dispatch.
81
+ //
82
+ // Bare `npx auriga-cli` (empty argv) dispatches to the install bare
83
+ // form, NOT to help — runInstall then picks TTY legacy-menu vs
84
+ // non-TTY error hint. Routing here to help would break the documented
85
+ // zero-arg entrypoint used by the interactive menu.
86
+ if (argv.length === 0)
87
+ return { command: "install", install: { all: false } };
88
+ const head = argv[0];
89
+ if (head === "--help" || head === "-h" || head === "help")
90
+ return { command: "help" };
91
+ if (head === "--version" || head === "-v")
92
+ return { command: "version" };
93
+ if (head === "guide") {
94
+ // `guide --help` / `guide -h` is a universal affordance — route to
95
+ // top-level help rather than reject. Anything else after `guide`
96
+ // (positional, other flags) still fail-fasts per spec §3.6.
97
+ if (argv.length === 2 && (argv[1] === "--help" || argv[1] === "-h")) {
98
+ return { command: "help" };
99
+ }
100
+ if (argv.length > 1) {
101
+ parseErr("guide takes no arguments. Run 'npx auriga-cli --help' for usage.");
102
+ }
103
+ return { command: "guide" };
104
+ }
105
+ if (head !== "install") {
106
+ parseErr(`unknown argument '${head}'. Run 'npx auriga-cli --help' for usage.`);
107
+ }
108
+ try {
109
+ return { command: "install", install: parseInstall(argv.slice(1)) };
110
+ }
111
+ catch (e) {
112
+ if (e instanceof PerTypeHelpRequest) {
113
+ return e.type ? { command: "help", helpType: e.type } : { command: "help" };
114
+ }
115
+ throw e;
116
+ }
117
+ }
118
+ function parseInstall(argv) {
119
+ const out = { all: false };
120
+ let filterFlag = null;
121
+ let i = 0;
122
+ while (i < argv.length) {
123
+ const t = argv[i];
124
+ if (t === "--help" || t === "-h") {
125
+ // Per-type help: `install <type> --help` routes to renderTypeHelp
126
+ // at the main() dispatch level. parseInstall signals this via a
127
+ // sentinel thrown up to parseArgs.
128
+ throw new PerTypeHelpRequest(out.type);
129
+ }
130
+ if (t === "--all") {
131
+ out.all = true;
132
+ i += 1;
133
+ continue;
134
+ }
135
+ // Accept both `--lang en` and `--lang=en` (and same for --cwd, --scope).
136
+ // The equals form is a common CLI affordance; rejecting it confuses
137
+ // users with any prior gnu-style / node util.parseArgs experience.
138
+ if (t === "--lang" || t.startsWith("--lang=")) {
139
+ const [v, advance] = readSingleValue(argv, i, "--lang");
140
+ out.lang = v;
141
+ i += advance;
142
+ continue;
143
+ }
144
+ if (t === "--cwd" || t.startsWith("--cwd=")) {
145
+ const [v, advance] = readSingleValue(argv, i, "--cwd");
146
+ out.cwd = v;
147
+ i += advance;
148
+ continue;
149
+ }
150
+ if (t === "--scope" || t.startsWith("--scope=")) {
151
+ const [v, advance] = readSingleValue(argv, i, "--scope");
152
+ out.scope = v;
153
+ i += advance;
154
+ continue;
155
+ }
156
+ // Object.hasOwn (not `in`) so Object.prototype keys like `toString` /
157
+ // `constructor` don't slip into the filter-flag branch and produce a
158
+ // misleading error.
159
+ if (Object.hasOwn(TYPE_FOR_FILTER, t)) {
160
+ if (filterFlag !== null) {
161
+ // A second filter flag on the same install line used to silently
162
+ // overwrite the first. Fail-fast so the user notices — one
163
+ // install invocation gets one filter list.
164
+ parseErr(`repeated ${t}: pass one ${t} list per install.`);
165
+ }
166
+ const [values, next] = consumeFilter(argv, i + 1);
167
+ if (values.length === 0) {
168
+ parseErr(`${t} requires at least one name (or '*' for all).`);
169
+ }
170
+ out.filter = values;
171
+ filterFlag = t;
172
+ i = next;
173
+ continue;
174
+ }
175
+ if (CATEGORY_SET.has(t)) {
176
+ if (out.type)
177
+ parseErr("install takes one <type> at a time.");
178
+ out.type = t;
179
+ i += 1;
180
+ continue;
181
+ }
182
+ // Any other positional (non-flag) while a type is already set is
183
+ // the user trying to pass a second type or stray filter value — spec
184
+ // §3.5 rule 1: one type at a time.
185
+ if (!t.startsWith("-") && out.type) {
186
+ parseErr("install takes one <type> at a time.");
187
+ }
188
+ parseErr(`unknown argument '${t}'. Run 'npx auriga-cli --help' for usage.`);
189
+ }
190
+ validateInstall(out, filterFlag);
191
+ return out;
192
+ }
193
+ function validateInstall(out, filterFlag) {
194
+ // Rule 2: --all is atomic.
195
+ if (out.all) {
196
+ if (out.type || out.filter || out.lang !== undefined || out.cwd !== undefined) {
197
+ parseErr("--all is atomic; no extra types or filters allowed.");
198
+ }
199
+ // --all may combine with --scope.
200
+ if (out.scope !== undefined)
201
+ validateScopeValue(out.scope);
202
+ return;
203
+ }
204
+ // Rule 3: filter flag requires matching type.
205
+ if (filterFlag) {
206
+ const requiredType = TYPE_FOR_FILTER[filterFlag];
207
+ if (out.type !== requiredType) {
208
+ parseErr(`${filterFlag} requires 'install ${requiredType}'.`);
209
+ }
210
+ }
211
+ // Rule 5: --lang / --cwd only for workflow.
212
+ if ((out.lang !== undefined || out.cwd !== undefined) && out.type !== "workflow") {
213
+ parseErr("--lang/--cwd only apply to workflow.");
214
+ }
215
+ // Rule 6: --scope only for skills / recommended / plugins / hooks.
216
+ // workflow (single file + symlink) has no scope concept.
217
+ if (out.scope !== undefined) {
218
+ if (out.type === "workflow") {
219
+ parseErr("--scope does not apply to workflow.");
220
+ }
221
+ validateScopeValue(out.scope);
222
+ }
223
+ // Value validation for workflow.
224
+ if (out.type === "workflow" && out.lang !== undefined) {
225
+ const valid = LANGUAGES.map((l) => l.value);
226
+ if (!valid.includes(out.lang)) {
227
+ parseErr(`unknown language '${out.lang}'; available: ${valid.join(", ")}`);
228
+ }
229
+ }
230
+ if (out.type === "workflow" && out.cwd !== undefined) {
231
+ if (!fs.existsSync(out.cwd)) {
232
+ parseErr(`--cwd directory does not exist: ${out.cwd}`);
233
+ }
234
+ }
235
+ // Catalog-backed filter name validation (spec §7).
236
+ if (out.filter && out.type) {
237
+ validateFilterAgainstCatalog(out.type, out.filter);
238
+ }
239
+ }
240
+ function validateFilterAgainstCatalog(type, filter) {
241
+ if (filter.length === 1 && filter[0] === "*")
242
+ return;
243
+ const catalogPath = path.join(getPackageRoot(), "dist", "catalog.json");
244
+ if (!fs.existsSync(catalogPath))
245
+ return;
246
+ const catalog = JSON.parse(fs.readFileSync(catalogPath, "utf-8"));
247
+ const bucket = type === "skills" ? catalog.workflowSkills
248
+ : type === "recommended" ? catalog.recommendedSkills
249
+ : type === "plugins" ? catalog.plugins
250
+ : type === "hooks" ? catalog.hooks
251
+ : null;
252
+ if (!bucket)
253
+ return;
254
+ const available = bucket.map((e) => e.name);
255
+ const singular = categorySingular(type);
256
+ for (const name of filter) {
257
+ if (!available.includes(name)) {
258
+ parseErr(`unknown ${singular} '${name}'; available: ${available.join(", ")}`);
259
+ }
260
+ }
261
+ }
262
+ function categorySingular(type) {
263
+ return type === "recommended" ? "recommended skill"
264
+ : type === "skills" ? "skill"
265
+ : type.replace(/s$/, "");
266
+ }
267
+ function validateScopeValue(scope) {
268
+ if (scope !== "project" && scope !== "user") {
269
+ parseErr(`unknown --scope value '${scope}'; expected 'project' or 'user'.`);
270
+ }
271
+ }
272
+ // ---------------------------------------------------------------------------
273
+ // main — returns exit code (spec §5.3.1 / §7)
274
+ // ---------------------------------------------------------------------------
275
+ // --all excludes `recommended` (per spec §3.2) — they're opt-in utilities.
276
+ const ALL_CATEGORIES = ["workflow", "skills", "plugins", "hooks"];
277
+ export async function main(argv) {
278
+ let parsed;
279
+ try {
280
+ parsed = parseArgs(argv);
281
+ }
282
+ catch (e) {
283
+ log.error(e.message);
284
+ return 1;
285
+ }
286
+ const version = readPackageVersion();
287
+ if (parsed.command === "help") {
288
+ try {
289
+ const catalog = loadCatalog(getPackageRoot());
290
+ const out = parsed.helpType
291
+ ? renderTypeHelp(catalog, parsed.helpType, version)
292
+ : renderHelp(catalog, version);
293
+ process.stdout.write(out);
294
+ return 0;
295
+ }
296
+ catch (e) {
297
+ log.error(e.message);
298
+ return 1;
299
+ }
300
+ }
301
+ if (parsed.command === "version") {
302
+ process.stdout.write(`${version}\n`);
303
+ return 0;
304
+ }
305
+ if (parsed.command === "guide") {
306
+ const color = Boolean(process.stdout.isTTY) && !process.env.NO_COLOR;
307
+ process.stdout.write(renderGuide({ color, version }));
308
+ return 0;
309
+ }
310
+ // install — catalog is required for filter validation and for the TTY
311
+ // menu's category descriptions; fail-fast at entry rather than produce
312
+ // a cryptic error mid-dispatch (spec §7 / §11 acceptance).
313
+ // guide / --version deliberately skip this check — both are usable
314
+ // before any build.
315
+ try {
316
+ loadCatalog(getPackageRoot());
317
+ }
318
+ catch (e) {
319
+ log.error(e.message);
320
+ return 1;
321
+ }
322
+ return runInstall(parsed.install);
323
+ }
324
+ async function runInstall(p) {
325
+ // Bare `install` (no type, no --all, no filter): TTY → menu, non-TTY → exit 1.
326
+ if (!p.all && !p.type) {
327
+ if (isNonInteractive()) {
328
+ log.error("Interactive mode requires a TTY. Run 'npx auriga-cli --help' for non-interactive options.");
329
+ return 1;
330
+ }
331
+ return runLegacyMenu();
332
+ }
333
+ // --all: precheck + fan-out.
334
+ if (p.all) {
335
+ return runAll(p);
336
+ }
337
+ // Single-category install.
338
+ return runSingle(p);
339
+ }
340
+ /**
341
+ * Precheck external prerequisites before touching any files.
342
+ * Returns null if OK, or an error message.
343
+ */
344
+ function precheckExternal(need) {
345
+ if (need.includes("plugins")) {
346
+ try {
347
+ exec("which claude");
348
+ }
349
+ catch {
350
+ return "'claude' CLI not in PATH. Install Claude Code first (https://docs.claude.com/claude-code), then re-run.";
351
+ }
352
+ }
353
+ return null;
354
+ }
355
+ async function safeFetchContentRoot() {
356
+ try {
357
+ return { root: await fetchContentRoot() };
358
+ }
359
+ catch (e) {
360
+ return {
361
+ err: `fetch failed: ${e.message}. Check network and retry; if persistent, the GitHub raw endpoint may be blocked in your region.`,
362
+ };
363
+ }
364
+ }
365
+ /**
366
+ * Shared precheck + fetch skeleton for every non-interactive install
367
+ * entry. Returns either a ready-to-use packageRoot or an exit code to
368
+ * bubble up. Keeps runAll / runSingle from drifting apart as new
369
+ * pre-install behavior accrues.
370
+ */
371
+ async function prepareInstall(needs) {
372
+ const pre = precheckExternal(needs);
373
+ if (pre) {
374
+ log.error(pre);
375
+ return { exit: 1 };
376
+ }
377
+ const fetched = await safeFetchContentRoot();
378
+ if (fetched.err) {
379
+ log.error(fetched.err);
380
+ return { exit: 1 };
381
+ }
382
+ return { packageRoot: fetched.root };
383
+ }
384
+ async function runAll(p) {
385
+ const prep = await prepareInstall(["plugins"]);
386
+ if ("exit" in prep)
387
+ return prep.exit;
388
+ const { packageRoot } = prep;
389
+ const status = [];
390
+ for (const category of ALL_CATEGORIES) {
391
+ // Forward `scope` only when the user actually passed one. Each
392
+ // installer picks its own default for undefined so category-specific
393
+ // defaults (skills/recommended/plugins/hooks all map undefined → project)
394
+ // aren't flattened by a one-size-fits-all fallback here.
395
+ const opts = {
396
+ interactive: false,
397
+ scope: p.scope,
398
+ };
399
+ try {
400
+ await dispatchInstaller(category, packageRoot, opts);
401
+ status.push({ category, ok: true });
402
+ }
403
+ catch (e) {
404
+ status.push({ category, ok: false, err: e.message });
405
+ }
406
+ }
407
+ for (const s of status) {
408
+ if (s.ok) {
409
+ process.stderr.write(`[OK] ${s.category}\n`);
410
+ }
411
+ else {
412
+ process.stderr.write(`[FAIL] ${s.category} — ${s.err}\n`);
413
+ }
414
+ }
415
+ const failed = status.filter((s) => !s.ok);
416
+ if (failed.length === 0) {
417
+ process.stderr.write(RELOAD_REMINDER);
418
+ return 0;
419
+ }
420
+ // Retry hint must carry `--scope` forward for any scope-aware
421
+ // category (see scopeCategory). Dropping it silently retries into
422
+ // the default project scope and leaves the intended user-scope
423
+ // install incomplete.
424
+ const scopeSuffix = p.scope ? ` --scope ${p.scope}` : "";
425
+ process.stderr.write("\nRetry:\n");
426
+ for (const s of failed) {
427
+ const suffix = scopeCategory(s.category) ? scopeSuffix : "";
428
+ process.stderr.write(` npx -y auriga-cli install ${s.category}${suffix}\n`);
429
+ }
430
+ // Partial success still installed assets that need a session reload
431
+ // (CLAUDE.md / skills / plugins load at startup). Without this hint
432
+ // the user may retry the failed category and act on stale state.
433
+ if (failed.length < status.length) {
434
+ process.stderr.write(RELOAD_REMINDER);
435
+ }
436
+ return 2;
437
+ }
438
+ function scopeCategory(c) {
439
+ // Categories where `--scope` is a real flag. Only workflow ignores
440
+ // it (single file + symlink, no scope concept).
441
+ return c !== "workflow";
442
+ }
443
+ async function runSingle(p) {
444
+ const category = p.type;
445
+ const prep = await prepareInstall(category === "plugins" ? ["plugins"] : []);
446
+ if ("exit" in prep)
447
+ return prep.exit;
448
+ const { packageRoot } = prep;
449
+ const opts = {
450
+ interactive: false,
451
+ lang: p.lang,
452
+ cwd: p.cwd,
453
+ scope: p.scope,
454
+ selected: p.filter,
455
+ };
456
+ try {
457
+ await dispatchInstaller(category, packageRoot, opts);
458
+ process.stderr.write(RELOAD_REMINDER);
459
+ return 0;
460
+ }
461
+ catch (e) {
462
+ log.error(e.message);
463
+ return 1;
464
+ }
465
+ }
466
+ async function dispatchInstaller(category, packageRoot, opts) {
467
+ switch (category) {
468
+ case "workflow": return installWorkflow(packageRoot, opts);
469
+ case "skills": return installSkills(packageRoot, opts);
470
+ case "recommended": return installRecommendedSkills(packageRoot, opts);
471
+ case "plugins": return installPlugins(packageRoot, opts);
472
+ case "hooks": return installHooks(packageRoot, opts);
473
+ }
474
+ }
475
+ // ---------------------------------------------------------------------------
476
+ // Legacy checkbox menu — preserved for `npx auriga-cli install` in TTY
477
+ // and `npx auriga-cli` with no args.
478
+ // ---------------------------------------------------------------------------
479
+ async function runLegacyMenu() {
480
+ // Lazy-load TTY-only deps so the non-interactive code path doesn't
481
+ // force inquirer / printBanner / withEsc into the module graph.
482
+ const { checkbox } = await import("@inquirer/prompts");
483
+ const { printBanner, withEsc } = await import("./utils.js");
484
+ const version = readPackageVersion();
12
485
  printBanner(version);
13
486
  console.log("");
14
487
  if (process.env.DEV === "1") {
@@ -23,65 +496,72 @@ async function main() {
23
496
  const moduleTypes = await withEsc(checkbox({
24
497
  message: "Select module types to install:",
25
498
  choices: [
26
- {
27
- name: "WorkflowCLAUDE.md + AGENTS.md",
28
- value: "workflow",
29
- checked: true,
30
- },
31
- {
32
- name: "Skills — Development process skills (brainstorming, TDD, debugging...)",
33
- value: "skills",
34
- checked: true,
35
- },
36
- {
37
- name: "Recommended Skills — Extra utility skills (claude-code-agent, codex-agent...)",
38
- value: "recommended",
39
- checked: true,
40
- },
41
- {
42
- name: "Plugins — Claude Code plugins (skill-creator, claude-md-management, codex...)",
43
- value: "plugins",
44
- checked: true,
45
- },
46
- {
47
- name: "Hooks — Claude Code hooks (notifications, etc.)",
48
- value: "hooks",
49
- checked: true,
50
- },
499
+ { name: "Workflow — CLAUDE.md + AGENTS.md", value: "workflow", checked: true },
500
+ { name: "SkillsDevelopment process skills (brainstorming, TDD, debugging...)", value: "skills", checked: true },
501
+ { name: "Recommended Skills — Extra utility skills (claude-code-agent, codex-agent...)", value: "recommended", checked: true },
502
+ { name: "Plugins — Claude Code plugins (skill-creator, claude-md-management, codex...)", value: "plugins", checked: true },
503
+ { name: "Hooks — Claude Code hooks (notifications, etc.)", value: "hooks", checked: true },
51
504
  ],
52
505
  }));
53
506
  if (moduleTypes.length === 0) {
54
507
  console.log("Nothing selected. Bye!");
55
- return;
508
+ return 0;
56
509
  }
510
+ const interactiveOpts = { interactive: true };
57
511
  if (moduleTypes.includes("workflow")) {
58
512
  console.log("\n--- Workflow ---\n");
59
- await installWorkflow(packageRoot);
513
+ await installWorkflow(packageRoot, interactiveOpts);
60
514
  }
61
515
  if (moduleTypes.includes("skills")) {
62
516
  console.log("\n--- Skills ---\n");
63
- await installSkills(packageRoot);
517
+ await installSkills(packageRoot, interactiveOpts);
64
518
  }
65
519
  if (moduleTypes.includes("recommended")) {
66
520
  console.log("\n--- Recommended Skills ---\n");
67
- await installRecommendedSkills(packageRoot);
521
+ await installRecommendedSkills(packageRoot, interactiveOpts);
68
522
  }
69
523
  if (moduleTypes.includes("plugins")) {
70
524
  console.log("\n--- Plugins ---\n");
71
- await installPlugins(packageRoot);
525
+ await installPlugins(packageRoot, interactiveOpts);
72
526
  }
73
527
  if (moduleTypes.includes("hooks")) {
74
528
  console.log("\n--- Hooks ---\n");
75
- await installHooks(packageRoot);
529
+ await installHooks(packageRoot, interactiveOpts);
76
530
  }
77
- console.log("\n\u2728 Installation complete!\n");
531
+ console.log("\n Installation complete!\n");
532
+ return 0;
78
533
  }
79
- main().catch((err) => {
80
- if (err instanceof Error &&
81
- ["ExitPromptError", "CancelPromptError"].includes(err.name)) {
82
- console.log("\nCancelled.");
83
- process.exit(0);
534
+ // ---------------------------------------------------------------------------
535
+ // Script entrypoint
536
+ // ---------------------------------------------------------------------------
537
+ // Guard keeps `main()` from auto-running when a test imports this
538
+ // module. `.endsWith("cli.js")` looks simple but breaks for the
539
+ // canonical install surface: `npm install -g` / `npx` create a symlink
540
+ // at `node_modules/.bin/auriga-cli → .../dist/cli.js`, and the kernel
541
+ // passes the symlink path (basename `auriga-cli`, no `cli.js` suffix)
542
+ // as argv[1]. Under that check the CLI silently becomes a no-op.
543
+ // Compare realpaths instead — argv[1]'s symlink resolves to the real
544
+ // dist/cli.js, which matches `import.meta.url`'s file path exactly.
545
+ const invokedAsScript = (() => {
546
+ if (!process.argv[1])
547
+ return false;
548
+ try {
549
+ return fs.realpathSync(process.argv[1]) === fileURLToPath(import.meta.url);
84
550
  }
85
- console.error(err);
86
- process.exit(1);
87
- });
551
+ catch {
552
+ return false;
553
+ }
554
+ })();
555
+ if (invokedAsScript) {
556
+ main(process.argv.slice(2))
557
+ .then((code) => { if (code !== 0)
558
+ process.exit(code); })
559
+ .catch((err) => {
560
+ if (err instanceof Error && ["ExitPromptError", "CancelPromptError"].includes(err.name)) {
561
+ console.log("\nCancelled.");
562
+ process.exit(0);
563
+ }
564
+ console.error(err);
565
+ process.exit(1);
566
+ });
567
+ }
@@ -0,0 +1,12 @@
1
+ export interface GuideOpts {
2
+ color: boolean;
3
+ version: string;
4
+ }
5
+ /**
6
+ * Renders the Agent-bootstrap SOP per spec §3.6. Plain-text when
7
+ * `color: false`; adds ANSI escapes for headings / command examples
8
+ * / warnings when `color: true`. Color detection happens at the call
9
+ * site (`process.stdout.isTTY && !process.env.NO_COLOR`); this
10
+ * function just renders what it's told.
11
+ */
12
+ export declare function renderGuide(opts: GuideOpts): string;