@theholocron/cli 3.60.0 → 3.62.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.mjs CHANGED
@@ -9,9 +9,9 @@ import { AuthError, ProviderApiError, ProviderApiError as ProviderApiError$1, cr
9
9
  import { createEnvLookup } from "@theholocron/env-utils";
10
10
  import { Entry, findCredentials } from "@napi-rs/keyring";
11
11
  import { pathToFileURL } from "node:url";
12
+ import { createLogger, parseLogLevel, resolveAxiomFromEnv } from "@theholocron/logger";
12
13
  import ora from "ora";
13
14
  import chalk from "chalk";
14
- import { createLogger, parseLogLevel, resolveAxiomFromEnv } from "@theholocron/logger";
15
15
  import { execFile, execFileSync, spawnSync } from "node:child_process";
16
16
  import { homedir } from "node:os";
17
17
  import { access, copyFile, mkdir, readFile, readdir, rm, stat, symlink, unlink, writeFile } from "node:fs/promises";
@@ -288,6 +288,94 @@ function resolveConfig(raw) {
288
288
  };
289
289
  }
290
290
  //#endregion
291
+ //#region src/logger.ts
292
+ /**
293
+ * CLI-side wiring for `@theholocron/logger`.
294
+ *
295
+ * `logger` is the operational-output channel — internal state, debug
296
+ * traces, errors, structured context that routes to Axiom. It runs in
297
+ * parallel to `print` (user-facing UX output) and does not replace it.
298
+ */
299
+ /**
300
+ * Resolve the explicit level to hand to `createLogger`, in priority order:
301
+ *
302
+ * 1. `--verbose` → `"debug"` 2. `--quiet` → `"error"`
303
+ * 3. `HOLOCRON_LOG_LEVEL` env var
304
+ * 4. `holocron.config` `log.level` (`configLevel`)
305
+ *
306
+ * Returns `undefined` when nothing applies — `createLogger` then defaults
307
+ * to `"info"`. Resolving the full chain here (rather than passing
308
+ * `configLevel` straight through) keeps config below the env var.
309
+ */
310
+ function resolveLogLevel(argv, configLevel) {
311
+ if (argv.verbose) return "debug";
312
+ if (argv.quiet) return "error";
313
+ return parseLogLevel(env.get("HOLOCRON_LOG_LEVEL")) ?? configLevel;
314
+ }
315
+ let root;
316
+ let rootLevel;
317
+ let rootCommand;
318
+ let rootAxiomKey;
319
+ /**
320
+ * Resolve Axiom credentials for the CLI. Env vars win — same contract as
321
+ * `@theholocron/logger`'s `resolveAxiomFromEnv`. Failing that, the CLI-only
322
+ * bridge pairs the OS-keyring token (`axiom.<org>` then bare `axiom`) with a
323
+ * dataset from `HOLOCRON_AXIOM_DATASET` / `AXIOM_DATASET` or
324
+ * `holocron.config` `log.axiom.dataset`. Returns `undefined` unless both a
325
+ * token and a dataset are found.
326
+ */
327
+ function resolveCliAxiom(opts) {
328
+ const fromEnv = resolveAxiomFromEnv();
329
+ if (fromEnv) return fromEnv;
330
+ const dataset = env.get("HOLOCRON_AXIOM_DATASET") || env.get("AXIOM_DATASET") || opts.configAxiomDataset;
331
+ if (!dataset) return void 0;
332
+ const org = opts.org ?? env.get("HOLOCRON_ORG");
333
+ const token = (org ? getToken(`axiom.${org}`) : null) ?? getToken("axiom");
334
+ return token ? {
335
+ dataset,
336
+ token
337
+ } : void 0;
338
+ }
339
+ /**
340
+ * The process-wide root logger. Built once (from `cli.ts`'s middleware,
341
+ * with the command name + flags + env). Rebuilt at most once more when a
342
+ * command's handler supplies `holocron.config` context the flag/env-only
343
+ * first pass could not have known — `log.level` (unless `--verbose` /
344
+ * `--quiet` already fixed it) or `log.axiom.dataset` + the resolved org
345
+ * for a keyring-backed Axiom transport. That rebuild generates a fresh
346
+ * `runId`, which is harmless: nothing logs between the middleware and the
347
+ * handler.
348
+ */
349
+ function buildCliLogger(argv, opts = {}) {
350
+ const { command, configLevel } = opts;
351
+ if (command) rootCommand = command;
352
+ const level = resolveLogLevel(argv, configLevel);
353
+ const axiom = resolveCliAxiom(opts);
354
+ const axiomKey = axiom?.dataset;
355
+ const rebuildForConfig = configLevel !== void 0 && level !== rootLevel && !argv.verbose && !argv.quiet;
356
+ if (!root || rebuildForConfig || axiomKey !== void 0 && axiomKey !== rootAxiomKey) {
357
+ const built = createLogger({
358
+ ...level ? { level } : {},
359
+ ...axiom ? { axiom } : {}
360
+ });
361
+ root = {
362
+ logger: rootCommand ? built.logger.child({ command: rootCommand }) : built.logger,
363
+ runId: built.runId
364
+ };
365
+ rootLevel = level;
366
+ rootAxiomKey = axiomKey;
367
+ }
368
+ return root;
369
+ }
370
+ /** Lazily-memoized `Logger` for module-level call sites with no `argv` in scope. */
371
+ function getLogger() {
372
+ return (root ??= createLogger()).logger;
373
+ }
374
+ /** The current root logger's correlation id, if a root has been built. */
375
+ function getRunId() {
376
+ return root?.runId;
377
+ }
378
+ //#endregion
291
379
  //#region src/ui/progress.ts
292
380
  /**
293
381
  * Runs `fn`, showing an ora spinner for its duration in TTY environments.
@@ -356,6 +444,7 @@ function resolveAuthSetToken(input) {
356
444
  }
357
445
  async function runAuthSet(input) {
358
446
  const print = input.print ?? ((l) => console.log(l));
447
+ const logger = input.logger ?? getLogger();
359
448
  const importer = input.importer ?? defaultImporter$1;
360
449
  const { provider } = input;
361
450
  const keyringKey = input.org ? `${provider}.${input.org}` : provider;
@@ -374,6 +463,11 @@ async function runAuthSet(input) {
374
463
  const hint = await tryLoadHint(importer, packageName);
375
464
  if (hint) print(style.hint(` hint: ${hint}`));
376
465
  }
466
+ logger.warn({
467
+ provider,
468
+ keyringKey,
469
+ reason: "no token supplied"
470
+ }, `auth set: ${keyringKey}`);
377
471
  return {
378
472
  status: "fail",
379
473
  message: "no token supplied"
@@ -387,6 +481,12 @@ async function runAuthSet(input) {
387
481
  if (!verified.ok) {
388
482
  print(style.fail(`token rejected by ${provider}: ${verified.message}`));
389
483
  if (module.AUTH_HINT) print(style.hint(` hint: ${module.AUTH_HINT}`));
484
+ logger.warn({
485
+ provider,
486
+ keyringKey,
487
+ verified: false,
488
+ reason: verified.message
489
+ }, `auth set: ${keyringKey}`);
390
490
  return {
391
491
  status: "fail",
392
492
  message: verified.message
@@ -401,12 +501,24 @@ async function runAuthSet(input) {
401
501
  }
402
502
  if (!setToken(keyringKey, token)) {
403
503
  print(style.fail(`keyring unavailable — token not stored. Use env vars instead.`));
504
+ logger.warn({
505
+ provider,
506
+ keyringKey,
507
+ reason: "keyring unavailable"
508
+ }, `auth set: ${keyringKey}`);
404
509
  return {
405
510
  status: "fail",
406
511
  message: "keyring unavailable"
407
512
  };
408
513
  }
409
514
  print(style.success(`stored ${keyringKey} token${subject ? ` (${subject})` : ""}`));
515
+ logger.info({
516
+ provider,
517
+ keyringKey,
518
+ verified: subject !== void 0,
519
+ ...subject ? { subject } : {},
520
+ status: "ok"
521
+ }, `auth set: ${keyringKey}`);
410
522
  return {
411
523
  status: "ok",
412
524
  ...subject ? { message: subject } : {}
@@ -414,11 +526,20 @@ async function runAuthSet(input) {
414
526
  }
415
527
  function runAuthUnset(input) {
416
528
  const print = input.print ?? ((l) => console.log(l));
529
+ const logger = input.logger ?? getLogger();
417
530
  if (deleteToken(input.provider)) {
418
531
  print(style.success(`removed ${input.provider} token`));
532
+ logger.info({
533
+ provider: input.provider,
534
+ status: "ok"
535
+ }, `auth unset: ${input.provider}`);
419
536
  return { status: "ok" };
420
537
  }
421
538
  print(style.dim(`no stored token for ${input.provider}`));
539
+ logger.info({
540
+ provider: input.provider,
541
+ status: "skip"
542
+ }, `auth unset: ${input.provider}`);
422
543
  return {
423
544
  status: "skip",
424
545
  message: "nothing to remove"
@@ -426,12 +547,19 @@ function runAuthUnset(input) {
426
547
  }
427
548
  async function runAuthCheck(input) {
428
549
  const print = input.print ?? ((l) => console.log(l));
550
+ const logger = input.logger ?? getLogger();
429
551
  const importer = input.importer ?? defaultImporter$1;
430
552
  const { provider } = input;
431
553
  const keyringKey = input.org ? `${provider}.${input.org}` : provider;
432
554
  const token = getToken(keyringKey);
433
555
  if (!token) {
434
556
  print(style.dim(`no stored token for ${keyringKey}`));
557
+ logger.info({
558
+ provider,
559
+ keyringKey,
560
+ status: "skip",
561
+ reason: "no stored token"
562
+ }, `auth check: ${keyringKey}`);
435
563
  return {
436
564
  status: "skip",
437
565
  message: "no stored token"
@@ -458,6 +586,13 @@ async function runAuthCheck(input) {
458
586
  const verified = input.showSpinner !== false ? await withSpinner(`Verifying ${keyringKey} token…`, verify) : await verify();
459
587
  if (verified.ok) {
460
588
  print(style.success(`${keyringKey}: ok — ${verified.subject}`));
589
+ logger.info({
590
+ provider,
591
+ keyringKey,
592
+ verified: true,
593
+ subject: verified.subject,
594
+ status: "ok"
595
+ }, `auth check: ${keyringKey}`);
461
596
  return {
462
597
  status: "ok",
463
598
  message: verified.subject
@@ -465,6 +600,13 @@ async function runAuthCheck(input) {
465
600
  }
466
601
  print(style.fail(`${keyringKey}: rejected — ${verified.message}`));
467
602
  if (module.AUTH_HINT) print(style.hint(` hint: ${module.AUTH_HINT}`));
603
+ logger.warn({
604
+ provider,
605
+ keyringKey,
606
+ verified: false,
607
+ reason: verified.message,
608
+ status: "fail"
609
+ }, `auth check: ${keyringKey}`);
468
610
  return {
469
611
  status: "fail",
470
612
  message: verified.message
@@ -472,6 +614,12 @@ async function runAuthCheck(input) {
472
614
  } catch (err) {
473
615
  const msg = err instanceof Error ? err.message : String(err);
474
616
  print(style.fail(`${keyringKey}: cannot verify — ${msg}`));
617
+ logger.warn({
618
+ provider,
619
+ keyringKey,
620
+ reason: msg,
621
+ status: "fail"
622
+ }, `auth check: ${keyringKey}`);
475
623
  return {
476
624
  status: "fail",
477
625
  message: msg
@@ -480,28 +628,42 @@ async function runAuthCheck(input) {
480
628
  }
481
629
  async function runAuthList(input = {}) {
482
630
  const print = input.print ?? ((l) => console.log(l));
631
+ const logger = input.logger ?? getLogger();
483
632
  const importer = input.importer ?? defaultImporter$1;
484
633
  const providers = listStoredProviders();
485
634
  if (providers.length === 0) {
486
635
  print(style.dim("no stored tokens."));
487
636
  print(style.hint("run: holocron auth set <provider> <token>"));
637
+ logger.info({ providers: 0 }, "auth list: done");
488
638
  return {
489
639
  status: "ok",
490
640
  message: "none"
491
641
  };
492
642
  }
643
+ let ok = 0;
644
+ let fail = 0;
493
645
  for (const provider of providers.sort()) {
494
646
  const check = await runAuthCheck({
495
647
  provider,
496
648
  importer,
497
649
  print: () => {},
650
+ logger,
498
651
  showSpinner: false
499
652
  });
500
653
  const label = `${provider}${check.message ? ` — ${check.message}` : ""}`;
501
- if (check.status === "ok") print(` ${style.success(label)}`);
502
- else if (check.status === "fail") print(` ${style.fail(label)}`);
503
- else print(` ${style.dim(`· ${label}`)}`);
654
+ if (check.status === "ok") {
655
+ ok += 1;
656
+ print(` ${style.success(label)}`);
657
+ } else if (check.status === "fail") {
658
+ fail += 1;
659
+ print(` ${style.fail(label)}`);
660
+ } else print(` ${style.dim(`· ${label}`)}`);
504
661
  }
662
+ logger.info({
663
+ providers: providers.length,
664
+ ok,
665
+ fail
666
+ }, "auth list: done");
505
667
  return { status: "ok" };
506
668
  }
507
669
  async function tryLoadHint(importer, packageName) {
@@ -512,94 +674,6 @@ async function tryLoadHint(importer, packageName) {
512
674
  }
513
675
  }
514
676
  //#endregion
515
- //#region src/logger.ts
516
- /**
517
- * CLI-side wiring for `@theholocron/logger`.
518
- *
519
- * `logger` is the operational-output channel — internal state, debug
520
- * traces, errors, structured context that routes to Axiom. It runs in
521
- * parallel to `print` (user-facing UX output) and does not replace it.
522
- */
523
- /**
524
- * Resolve the explicit level to hand to `createLogger`, in priority order:
525
- *
526
- * 1. `--verbose` → `"debug"` 2. `--quiet` → `"error"`
527
- * 3. `HOLOCRON_LOG_LEVEL` env var
528
- * 4. `holocron.config` `log.level` (`configLevel`)
529
- *
530
- * Returns `undefined` when nothing applies — `createLogger` then defaults
531
- * to `"info"`. Resolving the full chain here (rather than passing
532
- * `configLevel` straight through) keeps config below the env var.
533
- */
534
- function resolveLogLevel(argv, configLevel) {
535
- if (argv.verbose) return "debug";
536
- if (argv.quiet) return "error";
537
- return parseLogLevel(env.get("HOLOCRON_LOG_LEVEL")) ?? configLevel;
538
- }
539
- let root;
540
- let rootLevel;
541
- let rootCommand;
542
- let rootAxiomKey;
543
- /**
544
- * Resolve Axiom credentials for the CLI. Env vars win — same contract as
545
- * `@theholocron/logger`'s `resolveAxiomFromEnv`. Failing that, the CLI-only
546
- * bridge pairs the OS-keyring token (`axiom.<org>` then bare `axiom`) with a
547
- * dataset from `HOLOCRON_AXIOM_DATASET` / `AXIOM_DATASET` or
548
- * `holocron.config` `log.axiom.dataset`. Returns `undefined` unless both a
549
- * token and a dataset are found.
550
- */
551
- function resolveCliAxiom(opts) {
552
- const fromEnv = resolveAxiomFromEnv();
553
- if (fromEnv) return fromEnv;
554
- const dataset = env.get("HOLOCRON_AXIOM_DATASET") || env.get("AXIOM_DATASET") || opts.configAxiomDataset;
555
- if (!dataset) return void 0;
556
- const org = opts.org ?? env.get("HOLOCRON_ORG");
557
- const token = (org ? getToken(`axiom.${org}`) : null) ?? getToken("axiom");
558
- return token ? {
559
- dataset,
560
- token
561
- } : void 0;
562
- }
563
- /**
564
- * The process-wide root logger. Built once (from `cli.ts`'s middleware,
565
- * with the command name + flags + env). Rebuilt at most once more when a
566
- * command's handler supplies `holocron.config` context the flag/env-only
567
- * first pass could not have known — `log.level` (unless `--verbose` /
568
- * `--quiet` already fixed it) or `log.axiom.dataset` + the resolved org
569
- * for a keyring-backed Axiom transport. That rebuild generates a fresh
570
- * `runId`, which is harmless: nothing logs between the middleware and the
571
- * handler.
572
- */
573
- function buildCliLogger(argv, opts = {}) {
574
- const { command, configLevel } = opts;
575
- if (command) rootCommand = command;
576
- const level = resolveLogLevel(argv, configLevel);
577
- const axiom = resolveCliAxiom(opts);
578
- const axiomKey = axiom?.dataset;
579
- const rebuildForConfig = configLevel !== void 0 && level !== rootLevel && !argv.verbose && !argv.quiet;
580
- if (!root || rebuildForConfig || axiomKey !== void 0 && axiomKey !== rootAxiomKey) {
581
- const built = createLogger({
582
- ...level ? { level } : {},
583
- ...axiom ? { axiom } : {}
584
- });
585
- root = {
586
- logger: rootCommand ? built.logger.child({ command: rootCommand }) : built.logger,
587
- runId: built.runId
588
- };
589
- rootLevel = level;
590
- rootAxiomKey = axiomKey;
591
- }
592
- return root;
593
- }
594
- /** Lazily-memoized `Logger` for module-level call sites with no `argv` in scope. */
595
- function getLogger() {
596
- return (root ??= createLogger()).logger;
597
- }
598
- /** The current root logger's correlation id, if a root has been built. */
599
- function getRunId() {
600
- return root?.runId;
601
- }
602
- //#endregion
603
677
  //#region src/plugin/loader.ts
604
678
  /**
605
679
  * `PluginLoader` — loads provider plugins per the resolved config and
@@ -928,9 +1002,15 @@ async function listOrgRepos(org, token, fetchFn) {
928
1002
  }
929
1003
  async function runClone(input) {
930
1004
  const print = input.print ?? ((line) => console.log(line));
1005
+ const logger = input.logger ?? getLogger();
931
1006
  const fetchFn = input.fetch ?? globalThis.fetch;
932
1007
  const dryRun = input.dryRun ?? false;
933
1008
  const targetDir = resolve(input.dir ?? join(homedir(), "Code", input.org));
1009
+ logger.info({
1010
+ org: input.org,
1011
+ targetDir,
1012
+ dryRun: dryRun || void 0
1013
+ }, "clone: start");
934
1014
  const exec = input.exec ?? ((cmd, args, opts) => {
935
1015
  return { status: spawnSync(cmd, args, {
936
1016
  cwd: opts.cwd,
@@ -944,12 +1024,17 @@ async function runClone(input) {
944
1024
  try {
945
1025
  repos = await listOrgRepos(input.org, input.token, fetchFn);
946
1026
  } catch (err) {
1027
+ const message = err instanceof Error ? err.message : String(err);
1028
+ logger.warn({
1029
+ org: input.org,
1030
+ reason: message
1031
+ }, "clone: failed to list org repos");
947
1032
  return {
948
1033
  status: "fail",
949
1034
  cloned: 0,
950
1035
  skipped: 0,
951
1036
  failed: 0,
952
- message: err instanceof Error ? err.message : String(err)
1037
+ message
953
1038
  };
954
1039
  }
955
1040
  let cloned = 0;
@@ -997,8 +1082,16 @@ async function runClone(input) {
997
1082
  }
998
1083
  const summary = `${cloned} cloned, ${skipped} skipped, ${failed} failed`;
999
1084
  print(dryRun ? style.dim(`\n dry-run: ${summary}`) : failed > 0 ? style.fail(`\n ${summary}`) : style.success(`\n ${summary}`));
1085
+ const status = dryRun ? "dry-run" : failed > 0 ? "fail" : "ok";
1086
+ logger[failed > 0 ? "warn" : "info"]({
1087
+ org: input.org,
1088
+ status,
1089
+ cloned,
1090
+ skipped,
1091
+ failed
1092
+ }, "clone: done");
1000
1093
  return {
1001
- status: dryRun ? "dry-run" : failed > 0 ? "fail" : "ok",
1094
+ status,
1002
1095
  cloned,
1003
1096
  skipped,
1004
1097
  failed
@@ -1384,6 +1477,7 @@ async function runNew(input) {
1384
1477
  const cwd = input.cwd ?? process.cwd();
1385
1478
  const org = input.org ?? "theholocron";
1386
1479
  const print = input.print ?? ((line) => console.log(line));
1480
+ const logger = input.logger ?? getLogger();
1387
1481
  const execFn = input.exec ?? defaultExec$3;
1388
1482
  const readFn = input.readFile ?? defaultReadFile;
1389
1483
  const writeFn = input.writeFile ?? defaultWriteFile;
@@ -1392,6 +1486,11 @@ async function runNew(input) {
1392
1486
  const templateRepo = `${org}/${input.type}-template`;
1393
1487
  const newRepo = `${org}/${input.name}`;
1394
1488
  const repoDir = path.join(cwd, input.name);
1489
+ logger.info({
1490
+ repo: newRepo,
1491
+ template: templateRepo,
1492
+ dryRun: input.dryRun || void 0
1493
+ }, "new: start");
1395
1494
  if (input.dryRun) {
1396
1495
  print(` Would create ${newRepo} from template ${templateRepo}`);
1397
1496
  print(` Would clone to ${repoDir}`);
@@ -1400,6 +1499,10 @@ async function runNew(input) {
1400
1499
  if (input.homepage) print(` Would replace <homepage> → "${input.homepage}"`);
1401
1500
  if (input.runtimeEnvironment) print(` Would replace <runtime_environment> → "${input.runtimeEnvironment}"`);
1402
1501
  print(` Would generate holocron.config.ts`);
1502
+ logger.info({
1503
+ repo: newRepo,
1504
+ status: "dry-run"
1505
+ }, "new: done");
1403
1506
  return { status: "dry-run" };
1404
1507
  }
1405
1508
  if (existsSync(repoDir)) throw new NewError(`\`${repoDir}\` already exists — delete it or pick a different name.`);
@@ -1555,6 +1658,12 @@ async function runNew(input) {
1555
1658
  print(` 3. holocron setup # wire up secrets, teams, labels, etc.`);
1556
1659
  print(` 4. git push -u origin HEAD`);
1557
1660
  } else print(` 2. git push -u origin HEAD`);
1661
+ logger.info({
1662
+ repo: newRepo,
1663
+ repoDir,
1664
+ filesPatched,
1665
+ status: "ok"
1666
+ }, "new: done");
1558
1667
  return {
1559
1668
  status: "ok",
1560
1669
  repoDir,
@@ -1565,6 +1674,7 @@ async function runNew(input) {
1565
1674
  //#region src/commands/npm-bump-versions.ts
1566
1675
  async function runNpmBumpVersions(input) {
1567
1676
  const print = input.print ?? ((line) => console.log(line));
1677
+ const logger = input.logger ?? getLogger();
1568
1678
  const cwd = input.cwd ?? process.cwd();
1569
1679
  const { version, dryRun = false } = input;
1570
1680
  const readFile = input.readFile ?? ((p) => readFileSync(p, "utf8"));
@@ -1574,6 +1684,10 @@ async function runNpmBumpVersions(input) {
1574
1684
  const bumped = [];
1575
1685
  const skipped = [];
1576
1686
  print(`Bumping monorepo to ${version}${dryRun ? " (dry-run)" : ""}…`);
1687
+ logger.info({
1688
+ version,
1689
+ dryRun: dryRun || void 0
1690
+ }, "npm bump-versions: start");
1577
1691
  function bumpFile(absPath, label) {
1578
1692
  let pkg;
1579
1693
  try {
@@ -1596,8 +1710,15 @@ async function runNpmBumpVersions(input) {
1596
1710
  try {
1597
1711
  entries = listDir(packagesDir);
1598
1712
  } catch {
1713
+ const status = dryRun ? "dry-run" : "ok";
1714
+ logger.info({
1715
+ version,
1716
+ status,
1717
+ bumped: bumped.length,
1718
+ skipped: skipped.length
1719
+ }, "npm bump-versions: done");
1599
1720
  return {
1600
- status: dryRun ? "dry-run" : "ok",
1721
+ status,
1601
1722
  bumped,
1602
1723
  skipped
1603
1724
  };
@@ -1624,8 +1745,15 @@ async function runNpmBumpVersions(input) {
1624
1745
  }
1625
1746
  bumpFile(pkgFile, `packages/${entry}`);
1626
1747
  }
1748
+ const status = dryRun ? "dry-run" : "ok";
1749
+ logger.info({
1750
+ version,
1751
+ status,
1752
+ bumped: bumped.length,
1753
+ skipped: skipped.length
1754
+ }, "npm bump-versions: done");
1627
1755
  return {
1628
- status: dryRun ? "dry-run" : "ok",
1756
+ status,
1629
1757
  bumped,
1630
1758
  skipped
1631
1759
  };
@@ -1659,6 +1787,7 @@ async function runNpmBumpVersions(input) {
1659
1787
  */
1660
1788
  async function runNpmPublishInitial(input = {}) {
1661
1789
  const print = input.print ?? ((line) => console.log(line));
1790
+ const logger = input.logger ?? getLogger();
1662
1791
  const cwd = input.cwd ?? process.cwd();
1663
1792
  const tag = input.tag ?? "alpha";
1664
1793
  const dryRun = input.dryRun ?? false;
@@ -1679,6 +1808,10 @@ async function runNpmPublishInitial(input = {}) {
1679
1808
  print(`Holocron npm publish-initial${dryRun ? " (dry-run)" : ""}`);
1680
1809
  print(` cwd: ${cwd}`);
1681
1810
  print(` tag: ${tag}`);
1811
+ logger.info({
1812
+ tag,
1813
+ dryRun: dryRun || void 0
1814
+ }, "npm publish-initial: start");
1682
1815
  if (otp) print(` otp: <${otp.length} chars>`);
1683
1816
  print("");
1684
1817
  print(" → verifying npm auth (`npm whoami`)…");
@@ -1686,10 +1819,12 @@ async function runNpmPublishInitial(input = {}) {
1686
1819
  if (whoami.exitCode !== 0) {
1687
1820
  const message = "npm is not authenticated. Run `npm login --auth-type=web` (browser flow, no token stored) or `npm login`, then re-run this command.";
1688
1821
  print(` ✗ ${message}`);
1822
+ const packageNames = input.packages ?? discoverPublicPackages(cwd);
1823
+ logger.warn({ reason: "npm not authenticated" }, "npm publish-initial: done");
1689
1824
  return {
1690
1825
  status: "fail",
1691
1826
  message,
1692
- packageNames: input.packages ?? discoverPublicPackages(cwd)
1827
+ packageNames
1693
1828
  };
1694
1829
  }
1695
1830
  print(` ✓ authed as ${whoami.stdout.trim() || "<unknown>"}`);
@@ -1700,6 +1835,11 @@ async function runNpmPublishInitial(input = {}) {
1700
1835
  print(" … (dry-run) skipping actual publish");
1701
1836
  print(` would run: pnpm ${publishArgs.join(" ")}`);
1702
1837
  printNextSteps$1(print, env, packageNames, repoName);
1838
+ logger.info({
1839
+ tag,
1840
+ status: "dry-run",
1841
+ packages: packageNames.length
1842
+ }, "npm publish-initial: done");
1703
1843
  return {
1704
1844
  status: "dry-run",
1705
1845
  message: "dry-run — no publish executed",
@@ -1717,6 +1857,10 @@ async function runNpmPublishInitial(input = {}) {
1717
1857
  print(" → hint: your npm account requires 2FA for writes. Re-run with `--otp <code>`:");
1718
1858
  print(` pnpm exec tsx packages/cli/src/cli.ts npm publish-initial --otp <6-digit-code>`);
1719
1859
  }
1860
+ logger.warn({
1861
+ tag,
1862
+ reason: message
1863
+ }, "npm publish-initial: done");
1720
1864
  return {
1721
1865
  status: "fail",
1722
1866
  message,
@@ -1725,6 +1869,11 @@ async function runNpmPublishInitial(input = {}) {
1725
1869
  }
1726
1870
  print(" ✓ publish complete");
1727
1871
  printNextSteps$1(print, env, packageNames, repoName);
1872
+ logger.info({
1873
+ tag,
1874
+ status: "ok",
1875
+ packages: packageNames.length
1876
+ }, "npm publish-initial: done");
1728
1877
  return {
1729
1878
  status: "ok",
1730
1879
  packageNames
@@ -2704,6 +2853,7 @@ function resolvePath(template, inputs) {
2704
2853
  function runPluginCreate(input) {
2705
2854
  const cwd = input.cwd ?? process.cwd();
2706
2855
  const print = input.print ?? ((line) => console.log(line));
2856
+ const logger = input.logger ?? getLogger();
2707
2857
  const write = input.writeFile ?? defaultWrite;
2708
2858
  preflight(cwd);
2709
2859
  validateSlug(input.slug);
@@ -2730,6 +2880,11 @@ function runPluginCreate(input) {
2730
2880
  const filesWritten = [];
2731
2881
  print(`Scaffolding @theholocron/holocron-plugin-${inputs.slug}${input.dryRun ? " (dry-run)" : ""}`);
2732
2882
  print(` → ${packageDir}`);
2883
+ logger.info({
2884
+ slug: inputs.slug,
2885
+ capability: inputs.capability,
2886
+ dryRun: input.dryRun || void 0
2887
+ }, "plugin create: start");
2733
2888
  for (const template of TEMPLATES) {
2734
2889
  const resolvedPath = resolvePath(template.path, inputs);
2735
2890
  const filepath = path.join(packageDir, resolvedPath);
@@ -2777,7 +2932,14 @@ function runPluginCreate(input) {
2777
2932
  });
2778
2933
  print(" ✓ scaffold verified");
2779
2934
  } catch (err) {
2780
- print(` ✗ verify failed — ${err instanceof Error ? err.message : String(err)}`);
2935
+ const message = err instanceof Error ? err.message : String(err);
2936
+ print(` ✗ verify failed — ${message}`);
2937
+ logger.warn({
2938
+ slug: inputs.slug,
2939
+ files: filesWritten.length,
2940
+ reason: message,
2941
+ status: "fail"
2942
+ }, "plugin create: done");
2781
2943
  return {
2782
2944
  status: "fail",
2783
2945
  packagePath: packageDir,
@@ -2787,6 +2949,12 @@ function runPluginCreate(input) {
2787
2949
  }
2788
2950
  }
2789
2951
  if (!input.dryRun) printNextSteps(print, inputs);
2952
+ logger.info({
2953
+ slug: inputs.slug,
2954
+ capability: inputs.capability,
2955
+ files: filesWritten.length,
2956
+ status: input.dryRun ? "dry-run" : "ok"
2957
+ }, "plugin create: done");
2790
2958
  return {
2791
2959
  status: "ok",
2792
2960
  packagePath: packageDir,
@@ -2852,6 +3020,7 @@ function printNextSteps(print, inputs) {
2852
3020
  //#region src/commands/secret-set.ts
2853
3021
  async function runSecretSet(input) {
2854
3022
  const print = input.print ?? ((line) => console.log(line));
3023
+ const logger = input.logger ?? getLogger();
2855
3024
  const loader = input.loader ?? new PluginLoader(input.loaded.resolved, input.context);
2856
3025
  await loader.load();
2857
3026
  const dryRun = input.context.dryRun ?? false;
@@ -2874,6 +3043,12 @@ async function runSecretSet(input) {
2874
3043
  const secrets = loader.get("secrets");
2875
3044
  await secrets.setSecret(scope, input.name, value);
2876
3045
  print(` ✓ set ${input.name} via ${secrets.providerName}`);
3046
+ logger.info({
3047
+ key: input.name,
3048
+ scope: describeScope(scope),
3049
+ provider: secrets.providerName,
3050
+ status: "ok"
3051
+ }, `secret set: ${input.name}`);
2877
3052
  return {
2878
3053
  status: "ok",
2879
3054
  name: input.name,
@@ -2882,6 +3057,11 @@ async function runSecretSet(input) {
2882
3057
  } catch (err) {
2883
3058
  const message = err instanceof Error ? err.message : String(err);
2884
3059
  print(` ✗ ${message}`);
3060
+ logger.warn({
3061
+ key: input.name,
3062
+ scope: describeScope(scope),
3063
+ reason: message
3064
+ }, `secret set: ${input.name}`);
2885
3065
  return {
2886
3066
  status: "fail",
2887
3067
  name: input.name,
@@ -4931,25 +5111,50 @@ const defaultExec = (cmd, args, opts) => {
4931
5111
  };
4932
5112
  async function runSkillsInstall(input) {
4933
5113
  const print = input.print ?? ((line) => console.log(line));
5114
+ const logger = input.logger ?? getLogger();
4934
5115
  const config = input.loaded.resolved;
4935
5116
  if (!config.agent || !config.skills?.length) {
4936
5117
  print("Nothing to install — set `agent` and `skills` in holocron.config.ts");
5118
+ logger.info({
5119
+ status: "skip",
5120
+ reason: "no agent/skills configured"
5121
+ }, "skills install: done");
4937
5122
  return;
4938
5123
  }
4939
5124
  if (input.context.dryRun) {
4940
5125
  print(`Would install ${config.skills.length} skill(s) for agent: ${config.agent}`);
4941
5126
  for (const name of config.skills) print(` → would install: ${name}`);
5127
+ logger.info({
5128
+ agent: config.agent,
5129
+ skills: config.skills,
5130
+ status: "dry-run"
5131
+ }, "skills install: done");
4942
5132
  return;
4943
5133
  }
4944
5134
  print(`Installing ${config.skills.length} skill(s) for agent: ${config.agent}`);
5135
+ logger.info({
5136
+ agent: config.agent,
5137
+ count: config.skills.length
5138
+ }, "skills install: start");
4945
5139
  try {
4946
5140
  print(` → ${await installSkills({
4947
5141
  agent: config.agent,
4948
5142
  skills: config.skills,
4949
5143
  repoRoot: input.context.repoRoot
4950
5144
  })}`);
5145
+ logger.info({
5146
+ agent: config.agent,
5147
+ skills: config.skills,
5148
+ status: "ok"
5149
+ }, "skills install: done");
4951
5150
  } catch (err) {
4952
- print(` ✗ ${err instanceof Error ? err.message : String(err)}`);
5151
+ const message = err instanceof Error ? err.message : String(err);
5152
+ print(` ✗ ${message}`);
5153
+ logger.warn({
5154
+ agent: config.agent,
5155
+ reason: message,
5156
+ status: "fail"
5157
+ }, "skills install: done");
4953
5158
  }
4954
5159
  }
4955
5160
  function runSkillsRemove(input) {
@@ -5073,6 +5278,7 @@ async function updateIndexMdxDescription(repoRoot, description, dryRun, readFile
5073
5278
  }
5074
5279
  async function runSyncReadme(input) {
5075
5280
  const { loaded, context, print = console.log, readFileFn = readFile, writeFileFn = writeFile } = input;
5281
+ const logger = input.logger ?? getLogger();
5076
5282
  const { repoRoot, dryRun = false } = context;
5077
5283
  const namespaces = loaded.resolved.env?.namespaces ?? [];
5078
5284
  print(style.header(`Syncing README installation block${dryRun ? " (dry-run)" : ""}…`));
@@ -5082,6 +5288,10 @@ async function runSyncReadme(input) {
5082
5288
  const raw = await readFileFn(join(repoRoot, "package.json"), "utf8");
5083
5289
  pkg = JSON.parse(raw);
5084
5290
  } catch {
5291
+ logger.warn({
5292
+ repoRoot,
5293
+ reason: "could not read package.json"
5294
+ }, "sync readme: done");
5085
5295
  return {
5086
5296
  status: "fail",
5087
5297
  updated: false,
@@ -5103,6 +5313,10 @@ async function runSyncReadme(input) {
5103
5313
  const updated = await updateReadme(repoRoot, generateInstallBlock(pkg), markerSections, dryRun, readFileFn, writeFileFn);
5104
5314
  if (!updated) {
5105
5315
  print(style.warn("README.md not found or has no writable location for installation block"));
5316
+ logger.warn({
5317
+ repoRoot,
5318
+ reason: "README.md not found / no marker block"
5319
+ }, "sync readme: done");
5106
5320
  return {
5107
5321
  status: "fail",
5108
5322
  updated: false,
@@ -5111,8 +5325,14 @@ async function runSyncReadme(input) {
5111
5325
  }
5112
5326
  if (description) await updateIndexMdxDescription(repoRoot, description, dryRun, readFileFn, writeFileFn);
5113
5327
  print(dryRun ? style.hint(" would update README.md") : style.success(" updated README.md"));
5328
+ const status = dryRun ? "dry-run" : "ok";
5329
+ logger.info({
5330
+ repoRoot,
5331
+ status,
5332
+ updated
5333
+ }, "sync readme: done");
5114
5334
  return {
5115
- status: dryRun ? "dry-run" : "ok",
5335
+ status,
5116
5336
  updated
5117
5337
  };
5118
5338
  }
@@ -6217,19 +6437,38 @@ function defaultWalkFiles(dir) {
6217
6437
  }
6218
6438
  async function runUpgradeNode(input) {
6219
6439
  const print = input.print ?? ((line) => console.log(line));
6440
+ const logger = input.logger ?? getLogger();
6220
6441
  const cwd = input.cwd ?? process.cwd();
6221
6442
  const { to, dryRun = false, extra = [] } = input;
6222
6443
  const _readFile = input.readFile ?? ((p) => readFileSync(p, "utf8"));
6223
6444
  const _writeFile = input.writeFile ?? ((p, c) => writeFileSync(p, c));
6224
6445
  const _walkFiles = input.walkFiles ?? defaultWalkFiles;
6225
6446
  const from = input.from ?? detectFrom(cwd, _readFile);
6226
- if (from === null) return {
6227
- status: "fail",
6228
- updated: [],
6229
- message: "could not detect current Node version — pass --from <major>"
6230
- };
6447
+ if (from === null) {
6448
+ const message = "could not detect current Node version — pass --from <major>";
6449
+ logger.warn({
6450
+ to,
6451
+ reason: message
6452
+ }, "upgrade node: done");
6453
+ return {
6454
+ status: "fail",
6455
+ updated: [],
6456
+ message
6457
+ };
6458
+ }
6459
+ logger.info({
6460
+ from,
6461
+ to,
6462
+ dryRun: dryRun || void 0
6463
+ }, "upgrade node: start");
6231
6464
  if (from === to) {
6232
6465
  print(`Already at Node.js ${to} — nothing to do.`);
6466
+ logger.info({
6467
+ from,
6468
+ to,
6469
+ updated: 0,
6470
+ status: "ok"
6471
+ }, "upgrade node: done");
6233
6472
  return {
6234
6473
  status: "ok",
6235
6474
  updated: []
@@ -6253,11 +6492,23 @@ async function runUpgradeNode(input) {
6253
6492
  const rel = abs.startsWith(cwd + "/") ? abs.slice(cwd.length + 1) : abs;
6254
6493
  if (!dryRun) _writeFile(abs, patched);
6255
6494
  print(` ${dryRun ? "~" : "✓"} ${rel}`);
6495
+ logger.debug({
6496
+ file: rel,
6497
+ from,
6498
+ to
6499
+ }, "upgrade node: patched");
6256
6500
  updated.push(rel);
6257
6501
  }
6258
6502
  if (updated.length === 0) print(` · no files contained Node.js ${from} pins`);
6503
+ const status = dryRun ? "dry-run" : "ok";
6504
+ logger.info({
6505
+ from,
6506
+ to,
6507
+ updated: updated.length,
6508
+ status
6509
+ }, "upgrade node: done");
6259
6510
  return {
6260
- status: dryRun ? "dry-run" : "ok",
6511
+ status,
6261
6512
  updated
6262
6513
  };
6263
6514
  }