@theholocron/cli 4.13.0 → 4.14.1

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/README.md CHANGED
@@ -338,7 +338,28 @@ The CLI reports on itself through three sinks: **Sentry** (errors), **Axiom**
338
338
  duration, failure rates, keyed to a one-way `sha256(hostname + username)`
339
339
  fingerprint). PostHog activates from `HOLOCRON_POSTHOG_PROJECT_TOKEN` → `POSTHOG_PROJECT_TOKEN`
340
340
  → a shipped ingest-only key (on by default). `HOLOCRON_TELEMETRY=false` disables
341
- all three. See the [telemetry guide](https://docs.theholocron.dev/holocron/telemetry/).
341
+ all three.
342
+
343
+ For a committed, repo-wide opt-out, add a `telemetry` block to `holocron.config`
344
+ — the version-controlled peer of `HOLOCRON_TELEMETRY=false` (the env var still
345
+ wins; this is an override layer applied after config loads):
346
+
347
+ ```ts
348
+ import { defineConfig } from "@theholocron/cli";
349
+
350
+ export default defineConfig({
351
+ // …
352
+ telemetry: {
353
+ enabled: false, // no-ops Sentry + PostHog for every command
354
+ // analytics: "none", // alternative: PostHog off, Sentry error reporting kept
355
+ },
356
+ });
357
+ ```
358
+
359
+ Each SDK sits behind a Holocron-owned interface — `ErrorSink` (Sentry) /
360
+ `AnalyticsSink` (PostHog) in `src/telemetry/sinks.ts`; `@sentry/node` and
361
+ `posthog-node` are imported only from their adapter modules. See the
362
+ [telemetry guide](https://docs.theholocron.dev/holocron/telemetry/).
342
363
 
343
364
  ## What's in here
344
365
 
package/dist/cli.mjs CHANGED
@@ -21,8 +21,8 @@ import { createHash } from "node:crypto";
21
21
  import { generateReadme } from "@theholocron/components-doc/markdown";
22
22
  import { getClients, getConfigs, getDocs, getPlugins, getSkills, getThemes, getUtils } from "@theholocron/registry-doc";
23
23
  import { createGitHubClient } from "@theholocron/github-client";
24
- import * as Sentry from "@sentry/node";
25
24
  import { PostHog } from "posthog-node";
25
+ import * as Sentry from "@sentry/node";
26
26
  import { promisify } from "node:util";
27
27
  import { ConfigFileError, loadConfigFile } from "@theholocron/datapad";
28
28
  //#region src/env.ts
@@ -292,7 +292,8 @@ function resolveConfig(raw) {
292
292
  skills: raw.skills,
293
293
  docs,
294
294
  env: raw.env,
295
- log: raw.log
295
+ log: raw.log,
296
+ telemetry: raw.telemetry
296
297
  };
297
298
  }
298
299
  //#endregion
@@ -5820,50 +5821,185 @@ async function writeWorkflowFile(repoRoot, filename, content) {
5820
5821
  await writeFile(join(dir, filename), content, "utf8");
5821
5822
  }
5822
5823
  //#endregion
5823
- //#region src/telemetry.ts
5824
- const FALLBACK_DSN = "https://95cbb72ad5636c94e119a5405ee8f55f@o4508238154104832.ingest.us.sentry.io/4511810950791168";
5824
+ //#region src/telemetry/posthog-sink.ts
5825
5825
  /**
5826
- * Resolve the Sentry DSN — the `errors` capability's self-contained
5827
- * activation, per ADR-0007:
5826
+ * `PostHogSink` — the **only** `posthog-node` call site. A portable
5827
+ * `AnalyticsSink` adapter: usage, adoption, retention. It reads no environment
5828
+ * and ships no credentials — the constructor takes a resolved `key` + `host`;
5829
+ * the caller (the CLI's `telemetry.ts`, via `telemetry/resolve.ts`) owns the
5830
+ * env chain and the fallback. An empty `key` is the caller's signal to install
5831
+ * a `NoopAnalyticsSink` instead — it is never passed here.
5828
5832
  *
5829
- * HOLOCRON_SENTRY_DSN → SENTRY_DSN → built-in fallback
5830
- *
5831
- * A consumer repo with `SENTRY_DSN` set gets CLI errors routed to its own
5832
- * project with no config. Opt out entirely with `HOLOCRON_TELEMETRY=false`.
5833
+ * Destined for `@theholocron/observability/analytics` (#635).
5833
5834
  */
5834
- function resolveDsn() {
5835
- return env.get("HOLOCRON_SENTRY_DSN") ?? env.get("SENTRY_DSN") ?? FALLBACK_DSN;
5835
+ var PostHogSink = class {
5836
+ #client;
5837
+ constructor(config) {
5838
+ this.#client = new PostHog(config.key, { host: config.host });
5839
+ }
5840
+ identify(distinctId, props) {
5841
+ this.#client.identify({
5842
+ distinctId,
5843
+ properties: props
5844
+ });
5845
+ }
5846
+ capture(distinctId, event, props) {
5847
+ this.#client.capture({
5848
+ distinctId,
5849
+ event,
5850
+ properties: props
5851
+ });
5852
+ }
5853
+ async shutdown() {
5854
+ await this.#client.shutdown();
5855
+ }
5856
+ };
5857
+ //#endregion
5858
+ //#region src/telemetry/redact.ts
5859
+ /**
5860
+ * Token-shape scrubbing for telemetry payloads — a raw token never leaves the
5861
+ * process, even when a value is controlled. Regex-on-the-serialised-string
5862
+ * (matches a token shape *anywhere*), distinct from `@theholocron/logger`'s
5863
+ * field-path redaction. Shared by {@link scrubError} (Sentry `beforeSend`) and
5864
+ * the PostHog `event()` path.
5865
+ */
5866
+ const TOKEN_RE = /\b(ghp_|ghs_|glpat-|xoxb-|xoxp-|npm_|sk-|[A-Z][A-Z0-9_]{2,}_TOKEN[=\s])[^\s"]*/g;
5867
+ /** Replace token-shaped substrings in a string with `[REDACTED]`. */
5868
+ function redact(raw) {
5869
+ return raw.replace(TOKEN_RE, "[REDACTED]");
5836
5870
  }
5837
- const FALLBACK_POSTHOG_PROJECT_TOKEN = "phc_AC4vFCvYzwnzmG7Vg5nEc3PZKZztoPyfKp4Lb9BbXcLK";
5838
- const DEFAULT_POSTHOG_HOST = "https://us.i.posthog.com";
5871
+ /** Deep-scrub every string in an object by round-tripping through {@link redact}. */
5872
+ function redactObject(value) {
5873
+ return JSON.parse(redact(JSON.stringify(value)));
5874
+ }
5875
+ //#endregion
5876
+ //#region src/telemetry/resolve.ts
5839
5877
  /**
5840
- * Resolve the PostHog project key — mirrors {@link resolveDsn} and the
5841
- * `HOLOCRON_AXIOM_TOKEN` / `AXIOM_TOKEN` pattern, per ADR-0008:
5878
+ * Holocron-specific credential resolution for the telemetry sinks — the env
5879
+ * chain plus Holocron's own shipped fallbacks. This is **CLI policy**, not part
5880
+ * of the portable adapter set: `sentry-sink.ts` / `posthog-sink.ts` take a
5881
+ * resolved DSN / key and never read the environment. When the adapters move to
5882
+ * `@theholocron/observability` (#635) this module stays here.
5842
5883
  *
5843
- * HOLOCRON_POSTHOG_PROJECT_TOKEN → POSTHOG_PROJECT_TOKEN → built-in fallback
5884
+ * errors: HOLOCRON_SENTRY_DSN → SENTRY_DSN → FALLBACK_DSN
5885
+ * analytics: HOLOCRON_POSTHOG_PROJECT_TOKEN → POSTHOG_PROJECT_TOKEN → FALLBACK_POSTHOG_PROJECT_TOKEN
5886
+ * host: HOLOCRON_POSTHOG_HOST → POSTHOG_HOST → DEFAULT_POSTHOG_HOST
5844
5887
  *
5845
- * `POSTHOG_PROJECT_TOKEN` is the vendor-native name a consumer repo sets (as a
5846
- * plain variable, not a secret — the `phc_…` key is publishable) to route CLI
5847
- * usage to its own project.
5888
+ * The `SENTRY_DSN` / `POSTHOG_PROJECT_TOKEN` fallbacks are the vendor-native
5889
+ * names a consumer repo sets to route CLI telemetry to its own project. The
5890
+ * built-in fallbacks are ingest-only keys — publishable by design, same risk
5891
+ * model as ADR-0007 / ADR-0008.
5848
5892
  */
5893
+ /** Holocron's own Sentry project — the fallback when no env var points elsewhere. */
5894
+ const FALLBACK_DSN = "https://95cbb72ad5636c94e119a5405ee8f55f@o4508238154104832.ingest.us.sentry.io/4511810950791168";
5895
+ /** Holocron's own PostHog project write key — ingest-only. */
5896
+ const FALLBACK_POSTHOG_PROJECT_TOKEN = "phc_AC4vFCvYzwnzmG7Vg5nEc3PZKZztoPyfKp4Lb9BbXcLK";
5897
+ const DEFAULT_POSTHOG_HOST = "https://us.i.posthog.com";
5898
+ /** Resolve the Sentry DSN. `""` → error telemetry disabled. */
5899
+ function resolveDsn() {
5900
+ return env.get("HOLOCRON_SENTRY_DSN") ?? env.get("SENTRY_DSN") ?? FALLBACK_DSN;
5901
+ }
5902
+ /** Resolve the PostHog project key. `""` → usage analytics disabled. */
5849
5903
  function resolvePostHogKey() {
5850
5904
  return env.get("HOLOCRON_POSTHOG_PROJECT_TOKEN") ?? env.get("POSTHOG_PROJECT_TOKEN") ?? FALLBACK_POSTHOG_PROJECT_TOKEN;
5851
5905
  }
5906
+ /** Resolve the PostHog ingest host. */
5852
5907
  function resolvePostHogHost() {
5853
5908
  return env.get("HOLOCRON_POSTHOG_HOST") ?? env.get("POSTHOG_HOST") ?? DEFAULT_POSTHOG_HOST;
5854
5909
  }
5910
+ //#endregion
5911
+ //#region src/telemetry/sentry-sink.ts
5912
+ /** `beforeSend` — token-shaped strings never leave the process. */
5913
+ function scrubError(event, _hint) {
5914
+ return redactObject(event);
5915
+ }
5916
+ var SentrySink = class {
5917
+ init(ctx) {
5918
+ Sentry.init({
5919
+ dsn: ctx.dsn,
5920
+ release: ctx.release,
5921
+ environment: ctx.environment,
5922
+ tracesSampleRate: 1,
5923
+ beforeSend: scrubError
5924
+ });
5925
+ Sentry.startSession();
5926
+ for (const [key, value] of Object.entries(ctx.tags)) Sentry.setTag(key, value);
5927
+ }
5928
+ startSpan(name) {
5929
+ Sentry.setTag("command", name);
5930
+ const span = Sentry.startInactiveSpan({
5931
+ name,
5932
+ op: "holocron.command",
5933
+ forceTransaction: true
5934
+ });
5935
+ return {
5936
+ setStatus: (ok) => span.setStatus({ code: ok ? 1 : 2 }),
5937
+ end: () => span.end()
5938
+ };
5939
+ }
5940
+ captureException(err) {
5941
+ Sentry.captureException(err);
5942
+ }
5943
+ endSession() {
5944
+ Sentry.endSession();
5945
+ }
5946
+ async flush() {
5947
+ await Sentry.close(2e3);
5948
+ }
5949
+ };
5950
+ //#endregion
5951
+ //#region src/telemetry/sinks.ts
5952
+ /**
5953
+ * The disabled path made explicit — one shared no-op instead of a scatter of
5954
+ * `if (!enabled) return` guards. Used when telemetry is opted out
5955
+ * (`HOLOCRON_TELEMETRY=false`, `config.telemetry.enabled: false`) or no
5956
+ * credentials resolve.
5957
+ */
5958
+ var NoopErrorSink = class {
5959
+ init() {}
5960
+ startSpan() {}
5961
+ captureException() {}
5962
+ endSession() {}
5963
+ async flush() {}
5964
+ };
5965
+ /** @see NoopErrorSink */
5966
+ var NoopAnalyticsSink = class {
5967
+ identify() {}
5968
+ capture() {}
5969
+ async shutdown() {}
5970
+ };
5971
+ //#endregion
5972
+ //#region src/telemetry.ts
5973
+ /**
5974
+ * CLI self-telemetry orchestration — errors (Sentry), usage analytics (PostHog).
5975
+ *
5976
+ * This module holds *no* vendor SDK import. Each concern sits behind a
5977
+ * Holocron-owned interface ({@link ErrorSink} / {@link AnalyticsSink}); the
5978
+ * concrete `SentrySink` / `PostHogSink` are the only `@sentry/node` /
5979
+ * `posthog-node` call sites, and a `Noop*Sink` is installed when telemetry is
5980
+ * off. Same seam `@theholocron/logger` uses for Pino.
5981
+ *
5982
+ * Activation is env-var + shipped-fallback, resolved here (`telemetry/resolve.ts`)
5983
+ * and passed to the sink — the adapters read no environment. The single kill
5984
+ * switch is `HOLOCRON_TELEMETRY=false` (legacy alias `NO_HOLOCRON_TELEMETRY`);
5985
+ * see ADR-0007 / ADR-0008.
5986
+ */
5987
+ let errors = new NoopErrorSink();
5988
+ let analytics = new NoopAnalyticsSink();
5989
+ /**
5990
+ * The shared opt-out. `HOLOCRON_TELEMETRY=false` (going-forward) or
5991
+ * `NO_HOLOCRON_TELEMETRY` (any truthy value, back-compat) disables all sinks.
5992
+ */
5855
5993
  function isEnabled() {
5856
5994
  return env.get("HOLOCRON_TELEMETRY") !== "false" && !env.get("NO_HOLOCRON_TELEMETRY");
5857
5995
  }
5858
- const sentryEnabled = () => isEnabled() && resolveDsn() !== "";
5859
- const posthogEnabled = () => isEnabled() && resolvePostHogKey() !== "";
5860
- let posthog;
5861
5996
  let commandStartedAt = 0;
5862
5997
  let currentCommand = "unknown";
5998
+ let lastErrorType;
5999
+ let currentSpan;
5863
6000
  /**
5864
6001
  * Anonymous, stable per-machine id — `sha256(hostname + username)` truncated
5865
- * (ADR-0008). A one-way fingerprint: no raw hostname or username is ever
5866
- * transmitted, and it is not personal data under this project's rule.
6002
+ * (ADR-0008). One-way: no raw hostname or username is ever transmitted.
5867
6003
  */
5868
6004
  function machineId() {
5869
6005
  try {
@@ -5872,7 +6008,7 @@ function machineId() {
5872
6008
  return "unknown";
5873
6009
  }
5874
6010
  }
5875
- /** Properties every event carries, so any event can be pivoted to its Axiom trace. */
6011
+ /** Properties every analytics event carries — pivot key to the Axiom trace. */
5876
6012
  function baseProps() {
5877
6013
  const runId = getRunId();
5878
6014
  return {
@@ -5881,76 +6017,80 @@ function baseProps() {
5881
6017
  ...runId ? { runId } : {}
5882
6018
  };
5883
6019
  }
5884
- /**
5885
- * Capture a PostHog event. Runs every `properties` object through the same
5886
- * token scrubber Sentry uses (defence-in-depth — values are controlled).
5887
- */
5888
- function event(name, properties = {}) {
5889
- if (!posthogEnabled() || !posthog) return;
5890
- const merged = {
5891
- ...baseProps(),
5892
- ...properties
5893
- };
5894
- posthog.capture({
5895
- distinctId: machineId(),
5896
- event: name,
5897
- properties: JSON.parse(redact(JSON.stringify(merged)))
5898
- });
5899
- }
5900
6020
  function init(version) {
5901
- initSentry(version);
5902
- initPostHog(version);
5903
- }
5904
- function initSentry(version) {
5905
- if (!sentryEnabled()) return;
5906
- Sentry.init({
5907
- dsn: resolveDsn(),
5908
- release: `holocron@${version}`,
5909
- environment: env.get("CI") ? "ci" : "local",
5910
- tracesSampleRate: 1,
5911
- beforeSend: scrubError
5912
- });
5913
- Sentry.startSession();
5914
- Sentry.setTag("os", process.platform);
5915
- Sentry.setTag("node", process.version);
5916
- Sentry.setTag("ci", String(Boolean(env.get("CI"))));
5917
- }
5918
- function initPostHog(version) {
5919
- if (!posthogEnabled()) return;
5920
- posthog = new PostHog(resolvePostHogKey(), { host: resolvePostHogHost() });
5921
- posthog.identify({
5922
- distinctId: machineId(),
5923
- properties: {
6021
+ const dsn = resolveDsn();
6022
+ if (isEnabled() && dsn !== "") {
6023
+ errors = new SentrySink();
6024
+ errors.init({
6025
+ dsn,
6026
+ release: `holocron@${version}`,
6027
+ environment: env.get("CI") ? "ci" : "local",
6028
+ tags: {
6029
+ os: process.platform,
6030
+ node: process.version,
6031
+ ci: String(Boolean(env.get("CI")))
6032
+ }
6033
+ });
6034
+ }
6035
+ const posthogKey = resolvePostHogKey();
6036
+ if (isEnabled() && posthogKey !== "") {
6037
+ analytics = new PostHogSink({
6038
+ key: posthogKey,
6039
+ host: resolvePostHogHost()
6040
+ });
6041
+ analytics.identify(machineId(), {
5924
6042
  ci: Boolean(env.get("CI")),
5925
6043
  os: process.platform,
5926
6044
  node: process.version,
5927
6045
  cli: version,
5928
6046
  ...env.get("HOLOCRON_ORG") ? { org: env.get("HOLOCRON_ORG") } : {}
5929
- }
5930
- });
6047
+ });
6048
+ }
5931
6049
  }
5932
- let lastErrorType;
5933
- /** Start a Sentry command span, or `undefined` when Sentry is off. */
5934
- function startSpan(name) {
5935
- if (!sentryEnabled()) return void 0;
5936
- Sentry.setTag("command", name);
5937
- return Sentry.startInactiveSpan({
5938
- name,
5939
- op: "holocron.command",
5940
- forceTransaction: true
5941
- });
6050
+ /**
6051
+ * Apply the `telemetry` config block — the committed override layer, run once
6052
+ * per command *after* config loads (so it lands after {@link init}). Env still
6053
+ * wins: this only ever *narrows* what `init` turned on, never re-enables.
6054
+ *
6055
+ * - `enabled: false` → swap both sinks to no-ops for the rest of the run.
6056
+ * - `analytics: "none"` → drop usage analytics only; error reporting stays.
6057
+ *
6058
+ * A live sink is drained best-effort before it is dropped so an in-flight
6059
+ * Sentry session / PostHog queue still leaves the process cleanly.
6060
+ */
6061
+ function applyConfig(cfg) {
6062
+ if (!cfg) return;
6063
+ const drain = (p) => {
6064
+ p.catch(() => {});
6065
+ };
6066
+ if (cfg.enabled === false) {
6067
+ drain(errors.flush());
6068
+ drain(analytics.shutdown());
6069
+ errors = new NoopErrorSink();
6070
+ analytics = new NoopAnalyticsSink();
6071
+ return;
6072
+ }
6073
+ if (cfg.analytics === "none") {
6074
+ drain(analytics.shutdown());
6075
+ analytics = new NoopAnalyticsSink();
6076
+ }
6077
+ }
6078
+ /** Capture a PostHog event. Every `properties` object runs through the scrubber. */
6079
+ function event(name, properties = {}) {
6080
+ analytics.capture(machineId(), name, redactObject({
6081
+ ...baseProps(),
6082
+ ...properties
6083
+ }));
5942
6084
  }
5943
6085
  function startCommand(name) {
5944
6086
  currentCommand = name;
5945
6087
  commandStartedAt = Date.now();
5946
6088
  lastErrorType = void 0;
5947
- const span = startSpan(name);
6089
+ currentSpan = errors.startSpan(name);
5948
6090
  event("command_started", { dry_run: process.argv.includes("--dry-run") });
5949
6091
  return (ok) => {
5950
- if (span) {
5951
- span.setStatus({ code: ok ? 1 : 2 });
5952
- span.end();
5953
- }
6092
+ currentSpan?.setStatus(ok);
6093
+ currentSpan?.end();
5954
6094
  event(ok ? "command_completed" : "command_failed", {
5955
6095
  status: ok ? "ok" : "fail",
5956
6096
  duration_ms: Date.now() - commandStartedAt,
@@ -5960,26 +6100,16 @@ function startCommand(name) {
5960
6100
  }
5961
6101
  function captureException(err) {
5962
6102
  lastErrorType = err instanceof Error ? err.constructor.name : "Error";
5963
- if (!sentryEnabled()) return;
5964
- Sentry.captureException(err);
6103
+ errors.captureException(err);
5965
6104
  }
5966
6105
  function endSession() {
5967
- if (!sentryEnabled()) return;
5968
- Sentry.endSession();
6106
+ errors.endSession();
5969
6107
  }
5970
6108
  async function flush() {
5971
- if (sentryEnabled()) await Sentry.close(2e3);
5972
- if (posthog) {
5973
- await posthog.shutdown();
5974
- posthog = void 0;
5975
- }
5976
- }
5977
- const TOKEN_RE = /\b(ghp_|ghs_|glpat-|xoxb-|xoxp-|npm_|sk-|[A-Z][A-Z0-9_]{2,}_TOKEN[=\s])[^\s"]*/g;
5978
- function redact(raw) {
5979
- return raw.replace(TOKEN_RE, "[REDACTED]");
5980
- }
5981
- function scrubError(event, _hint) {
5982
- return JSON.parse(redact(JSON.stringify(event)));
6109
+ await errors.flush();
6110
+ await analytics.shutdown();
6111
+ errors = new NoopErrorSink();
6112
+ analytics = new NoopAnalyticsSink();
5983
6113
  }
5984
6114
  //#endregion
5985
6115
  //#region src/commands/sync-github.ts
@@ -6630,6 +6760,17 @@ function cliLoggerOpts(argv, resolved) {
6630
6760
  org: resolveOrg(argv, resolved)
6631
6761
  };
6632
6762
  }
6763
+ /**
6764
+ * Fold a loaded `holocron.config` into the running process: reconfigure the
6765
+ * logger from `log:`, then apply the `telemetry:` override layer. Every handler
6766
+ * that has called `loadConfig` runs this, so a repo's `telemetry.enabled: false`
6767
+ * (or `analytics: "none"`) is honoured for that command. Env still wins —
6768
+ * `applyConfig` only narrows what `init` already turned on.
6769
+ */
6770
+ function applyResolvedConfig(argv, resolved) {
6771
+ buildCliLogger(argv, cliLoggerOpts(argv, resolved));
6772
+ applyConfig(resolved.telemetry);
6773
+ }
6633
6774
  /** Parses --token values and returns the context spread, or null on parse error (exits with code 1). */
6634
6775
  function tokenContext(rawTokens) {
6635
6776
  if (!rawTokens?.length) return {};
@@ -6716,7 +6857,7 @@ try {
6716
6857
  const tokens = tokenContext(argv.token);
6717
6858
  if (!tokens) return;
6718
6859
  const loaded = await loadConfig(argv.cwd);
6719
- buildCliLogger(argv, cliLoggerOpts(argv, loaded.resolved));
6860
+ applyResolvedConfig(argv, loaded.resolved);
6720
6861
  if ((await runDoctor({
6721
6862
  loaded,
6722
6863
  context: {
@@ -6737,7 +6878,7 @@ try {
6737
6878
  const tokens = tokenContext(argv.token);
6738
6879
  if (!tokens) return;
6739
6880
  const loaded = await loadConfig(argv.cwd);
6740
- buildCliLogger(argv, cliLoggerOpts(argv, loaded.resolved));
6881
+ applyResolvedConfig(argv, loaded.resolved);
6741
6882
  if ((await runSetup({
6742
6883
  loaded,
6743
6884
  context: {
@@ -6804,7 +6945,7 @@ try {
6804
6945
  const scopeArg = argv.scope;
6805
6946
  const scope = parseScope(scopeArg);
6806
6947
  const loaded = await loadConfig(argv.cwd);
6807
- buildCliLogger(argv, cliLoggerOpts(argv, loaded.resolved));
6948
+ applyResolvedConfig(argv, loaded.resolved);
6808
6949
  if ((await runSecretSet({
6809
6950
  loaded,
6810
6951
  context: {
@@ -6834,7 +6975,7 @@ try {
6834
6975
  const tokens = tokenContext(argv.token);
6835
6976
  if (!tokens) return;
6836
6977
  const loaded = await loadConfig(argv.cwd);
6837
- buildCliLogger(argv, cliLoggerOpts(argv, loaded.resolved));
6978
+ applyResolvedConfig(argv, loaded.resolved);
6838
6979
  if ((await runSecretsSync({
6839
6980
  loaded,
6840
6981
  context: {
@@ -6863,7 +7004,7 @@ try {
6863
7004
  const tokens = tokenContext(argv.token);
6864
7005
  if (!tokens) return;
6865
7006
  const loaded = await loadConfig(argv.cwd);
6866
- buildCliLogger(argv, cliLoggerOpts(argv, loaded.resolved));
7007
+ applyResolvedConfig(argv, loaded.resolved);
6867
7008
  if ((await runDeploy({
6868
7009
  loaded,
6869
7010
  context: {
@@ -6891,7 +7032,7 @@ try {
6891
7032
  const tokens = tokenContext(argv.token);
6892
7033
  if (!tokens) return;
6893
7034
  const loaded = await loadConfig(argv.cwd);
6894
- buildCliLogger(argv, cliLoggerOpts(argv, loaded.resolved));
7035
+ applyResolvedConfig(argv, loaded.resolved);
6895
7036
  if ((await runCleanupPreview({
6896
7037
  loaded,
6897
7038
  context: {
@@ -6939,7 +7080,7 @@ try {
6939
7080
  const tokens = tokenContext(argv.token);
6940
7081
  if (!tokens) return;
6941
7082
  const loaded = await loadConfig(argv.cwd);
6942
- buildCliLogger(argv, cliLoggerOpts(argv, loaded.resolved));
7083
+ applyResolvedConfig(argv, loaded.resolved);
6943
7084
  if ((await runSync({
6944
7085
  loaded,
6945
7086
  context: {
@@ -7048,7 +7189,7 @@ try {
7048
7189
  default: false
7049
7190
  }), async (argv) => {
7050
7191
  const loaded = await loadConfig(argv.cwd);
7051
- buildCliLogger(argv, cliLoggerOpts(argv, loaded.resolved));
7192
+ applyResolvedConfig(argv, loaded.resolved);
7052
7193
  if ((await runSyncReadme({
7053
7194
  loaded,
7054
7195
  context: {