@theholocron/cli 4.14.0 → 4.15.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/README.md CHANGED
@@ -307,8 +307,8 @@ holocron auth list # show all stored providers
307
307
 
308
308
  ## Logging
309
309
 
310
- Operational output goes through [`@theholocron/logger`](../logger) — separate from
311
- the user-facing `print` surface. Global flags:
310
+ Operational output goes through `@theholocron/observability/logger` — separate
311
+ from the user-facing `print` surface. Global flags:
312
312
 
313
313
  ```sh
314
314
  holocron doctor --verbose # log level → debug (full structured output)
@@ -356,9 +356,11 @@ export default defineConfig({
356
356
  });
357
357
  ```
358
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
359
+ Each SDK sits behind an interface from `@theholocron/observability` —
360
+ `ErrorSink` (Sentry) / `AnalyticsSink` (PostHog); the adapters, and the only
361
+ `@sentry/node` / `posthog-node` imports, live in that package. `telemetry.ts`
362
+ holds the orchestration + the Holocron-specific credential resolution
363
+ (`telemetry/resolve.ts`). See the
362
364
  [telemetry guide](https://docs.theholocron.dev/holocron/telemetry/).
363
365
 
364
366
  ## What's in here
@@ -370,8 +372,8 @@ Each SDK sits behind a Holocron-owned interface — `ErrorSink` (Sentry) /
370
372
  - `src/config/load-config.ts` — `loadConfig` — reads `holocron.config.*`
371
373
  (file discovery via [`@theholocron/datapad`](../datapad))
372
374
  - `src/define-config.ts` — `defineConfig` typed pass-through
373
- - `src/logger.ts` — CLI-side `@theholocron/logger` wiring (`buildCliLogger`,
374
- `resolveLogLevel`)
375
+ - `src/logger.ts` — CLI-side `@theholocron/observability/logger` wiring
376
+ (`buildCliLogger`, `resolveLogLevel`)
375
377
  - `src/loader.ts` — `PluginLoader` — dynamic-imports plugins, resolves
376
378
  capability config packages, builds the capability registry
377
379
  - `src/cli.ts` — yargs entry, dispatches subcommands. `holocron run` / `ci` are
package/dist/cli.mjs CHANGED
@@ -11,7 +11,7 @@ import { AuthError, ProviderApiError, ProviderApiError as ProviderApiError$1, cr
11
11
  import { createEnvLookup } from "@theholocron/env-utils";
12
12
  import { Entry, findCredentials } from "@napi-rs/keyring";
13
13
  import { pathToFileURL } from "node:url";
14
- import { createLogger, parseLogLevel, resolveAxiomFromEnv } from "@theholocron/logger";
14
+ import { createLogger, parseLogLevel, resolveAxiomFromEnv } from "@theholocron/observability/logger";
15
15
  import ora from "ora";
16
16
  import chalk from "chalk";
17
17
  import { execFile, execFileSync, spawnSync } from "node:child_process";
@@ -21,8 +21,9 @@ 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 { PostHog } from "posthog-node";
25
- import * as Sentry from "@sentry/node";
24
+ import { PostHogSink } from "@theholocron/observability/analytics";
25
+ import { NoopAnalyticsSink, NoopErrorSink, redactObject } from "@theholocron/observability/core";
26
+ import { SentrySink } from "@theholocron/observability/errors";
26
27
  import { promisify } from "node:util";
27
28
  import { ConfigFileError, loadConfigFile } from "@theholocron/datapad";
28
29
  //#region src/env.ts
@@ -299,13 +300,6 @@ function resolveConfig(raw) {
299
300
  //#endregion
300
301
  //#region src/logger.ts
301
302
  /**
302
- * CLI-side wiring for `@theholocron/logger`.
303
- *
304
- * `logger` is the operational-output channel — internal state, debug
305
- * traces, errors, structured context that routes to Axiom. It runs in
306
- * parallel to `print` (user-facing UX output) and does not replace it.
307
- */
308
- /**
309
303
  * Resolve the explicit level to hand to `createLogger`, in priority order:
310
304
  *
311
305
  * 1. `--verbose` → `"debug"` 2. `--quiet` → `"error"`
@@ -327,7 +321,7 @@ let rootCommand;
327
321
  let rootAxiomKey;
328
322
  /**
329
323
  * Resolve Axiom credentials for the CLI. Env vars win — same contract as
330
- * `@theholocron/logger`'s `resolveAxiomFromEnv`. Failing that, the CLI-only
324
+ * `@theholocron/observability`'s `resolveAxiomFromEnv`. Failing that, the CLI-only
331
325
  * bridge pairs the OS-keyring token (`axiom.<org>` then bare `axiom`) with a
332
326
  * dataset from `HOLOCRON_AXIOM_DATASET` / `AXIOM_DATASET` or
333
327
  * `holocron.config` `log.axiom.dataset`. Returns `undefined` unless both a
@@ -5821,148 +5815,56 @@ async function writeWorkflowFile(repoRoot, filename, content) {
5821
5815
  await writeFile(join(dir, filename), content, "utf8");
5822
5816
  }
5823
5817
  //#endregion
5824
- //#region src/telemetry/posthog-sink.ts
5818
+ //#region src/telemetry/resolve.ts
5825
5819
  /**
5826
- * `PostHogSink` — the **only** `posthog-node` import in the CLI. Product
5827
- * analytics (usage, adoption, retention), activated self-contained per ADR-0008:
5820
+ * Holocron-specific credential resolution for the telemetry sinks — the env
5821
+ * chain plus Holocron's own shipped fallbacks. This is **CLI policy**, not part
5822
+ * of the portable adapter set: `sentry-sink.ts` / `posthog-sink.ts` take a
5823
+ * resolved DSN / key and never read the environment. When the adapters move to
5824
+ * `@theholocron/observability` (#635) this module stays here.
5828
5825
  *
5829
- * HOLOCRON_POSTHOG_PROJECT_TOKEN → POSTHOG_PROJECT_TOKEN → built-in fallback key
5830
- * HOLOCRON_POSTHOG_HOST → POSTHOG_HOST → https://us.i.posthog.com
5826
+ * errors: HOLOCRON_SENTRY_DSN → SENTRY_DSN → FALLBACK_DSN
5827
+ * analytics: HOLOCRON_POSTHOG_PROJECT_TOKEN → POSTHOG_PROJECT_TOKEN → FALLBACK_POSTHOG_PROJECT_TOKEN
5828
+ * host: HOLOCRON_POSTHOG_HOST → POSTHOG_HOST → DEFAULT_POSTHOG_HOST
5831
5829
  *
5832
- * The built-in fallback is an ingest-only project write key (`phc_…`) — it can
5833
- * capture events, never read data — so it ships in the published package, the
5834
- * same risk model as the hard-coded Sentry DSN. {@link resolvePostHogKey}
5835
- * returning `""` is how the caller decides to install a `NoopAnalyticsSink`.
5830
+ * The `SENTRY_DSN` / `POSTHOG_PROJECT_TOKEN` fallbacks are the vendor-native
5831
+ * names a consumer repo sets to route CLI telemetry to its own project. The
5832
+ * built-in fallbacks are ingest-only keys — publishable by design, same risk
5833
+ * model as ADR-0007 / ADR-0008.
5836
5834
  */
5835
+ /** Holocron's own Sentry project — the fallback when no env var points elsewhere. */
5836
+ const FALLBACK_DSN = "https://95cbb72ad5636c94e119a5405ee8f55f@o4508238154104832.ingest.us.sentry.io/4511810950791168";
5837
+ /** Holocron's own PostHog project write key — ingest-only. */
5837
5838
  const FALLBACK_POSTHOG_PROJECT_TOKEN = "phc_AC4vFCvYzwnzmG7Vg5nEc3PZKZztoPyfKp4Lb9BbXcLK";
5838
5839
  const DEFAULT_POSTHOG_HOST = "https://us.i.posthog.com";
5840
+ /** Resolve the Sentry DSN. `""` → error telemetry disabled. */
5841
+ function resolveDsn() {
5842
+ return env.get("HOLOCRON_SENTRY_DSN") ?? env.get("SENTRY_DSN") ?? FALLBACK_DSN;
5843
+ }
5839
5844
  /** Resolve the PostHog project key. `""` → usage analytics disabled. */
5840
5845
  function resolvePostHogKey() {
5841
5846
  return env.get("HOLOCRON_POSTHOG_PROJECT_TOKEN") ?? env.get("POSTHOG_PROJECT_TOKEN") ?? FALLBACK_POSTHOG_PROJECT_TOKEN;
5842
5847
  }
5848
+ /** Resolve the PostHog ingest host. */
5843
5849
  function resolvePostHogHost() {
5844
5850
  return env.get("HOLOCRON_POSTHOG_HOST") ?? env.get("POSTHOG_HOST") ?? DEFAULT_POSTHOG_HOST;
5845
5851
  }
5846
- var PostHogSink = class {
5847
- #client;
5848
- constructor() {
5849
- this.#client = new PostHog(resolvePostHogKey(), { host: resolvePostHogHost() });
5850
- }
5851
- identify(distinctId, props) {
5852
- this.#client.identify({
5853
- distinctId,
5854
- properties: props
5855
- });
5856
- }
5857
- capture(distinctId, event, props) {
5858
- this.#client.capture({
5859
- distinctId,
5860
- event,
5861
- properties: props
5862
- });
5863
- }
5864
- async shutdown() {
5865
- await this.#client.shutdown();
5866
- }
5867
- };
5868
- //#endregion
5869
- //#region src/telemetry/redact.ts
5870
- /**
5871
- * Token-shape scrubbing for telemetry payloads — a raw token never leaves the
5872
- * process, even when a value is controlled. Regex-on-the-serialised-string
5873
- * (matches a token shape *anywhere*), distinct from `@theholocron/logger`'s
5874
- * field-path redaction. Shared by {@link scrubError} (Sentry `beforeSend`) and
5875
- * the PostHog `event()` path.
5876
- */
5877
- const TOKEN_RE = /\b(ghp_|ghs_|glpat-|xoxb-|xoxp-|npm_|sk-|[A-Z][A-Z0-9_]{2,}_TOKEN[=\s])[^\s"]*/g;
5878
- /** Replace token-shaped substrings in a string with `[REDACTED]`. */
5879
- function redact(raw) {
5880
- return raw.replace(TOKEN_RE, "[REDACTED]");
5881
- }
5882
- /** Deep-scrub every string in an object by round-tripping through {@link redact}. */
5883
- function redactObject(value) {
5884
- return JSON.parse(redact(JSON.stringify(value)));
5885
- }
5886
- //#endregion
5887
- //#region src/telemetry/sentry-sink.ts
5888
- const FALLBACK_DSN = "https://95cbb72ad5636c94e119a5405ee8f55f@o4508238154104832.ingest.us.sentry.io/4511810950791168";
5889
- /** Resolve the Sentry DSN. `""` → error telemetry disabled. */
5890
- function resolveDsn() {
5891
- return env.get("HOLOCRON_SENTRY_DSN") ?? env.get("SENTRY_DSN") ?? FALLBACK_DSN;
5892
- }
5893
- /** `beforeSend` — token-shaped strings never leave the process. */
5894
- function scrubError(event, _hint) {
5895
- return redactObject(event);
5896
- }
5897
- var SentrySink = class {
5898
- init(ctx) {
5899
- Sentry.init({
5900
- dsn: resolveDsn(),
5901
- release: ctx.release,
5902
- environment: ctx.environment,
5903
- tracesSampleRate: 1,
5904
- beforeSend: scrubError
5905
- });
5906
- Sentry.startSession();
5907
- for (const [key, value] of Object.entries(ctx.tags)) Sentry.setTag(key, value);
5908
- }
5909
- startSpan(name) {
5910
- Sentry.setTag("command", name);
5911
- const span = Sentry.startInactiveSpan({
5912
- name,
5913
- op: "holocron.command",
5914
- forceTransaction: true
5915
- });
5916
- return {
5917
- setStatus: (ok) => span.setStatus({ code: ok ? 1 : 2 }),
5918
- end: () => span.end()
5919
- };
5920
- }
5921
- captureException(err) {
5922
- Sentry.captureException(err);
5923
- }
5924
- endSession() {
5925
- Sentry.endSession();
5926
- }
5927
- async flush() {
5928
- await Sentry.close(2e3);
5929
- }
5930
- };
5931
- //#endregion
5932
- //#region src/telemetry/sinks.ts
5933
- /**
5934
- * The disabled path made explicit — one shared no-op instead of a scatter of
5935
- * `if (!enabled) return` guards. Used when telemetry is opted out
5936
- * (`HOLOCRON_TELEMETRY=false`, `config.telemetry.enabled: false`) or no
5937
- * credentials resolve.
5938
- */
5939
- var NoopErrorSink = class {
5940
- init() {}
5941
- startSpan() {}
5942
- captureException() {}
5943
- endSession() {}
5944
- async flush() {}
5945
- };
5946
- /** @see NoopErrorSink */
5947
- var NoopAnalyticsSink = class {
5948
- identify() {}
5949
- capture() {}
5950
- async shutdown() {}
5951
- };
5952
5852
  //#endregion
5953
5853
  //#region src/telemetry.ts
5954
5854
  /**
5955
5855
  * CLI self-telemetry orchestration — errors (Sentry), usage analytics (PostHog).
5956
5856
  *
5957
- * This module holds *no* vendor SDK import. Each concern sits behind a
5958
- * Holocron-owned interface ({@link ErrorSink} / {@link AnalyticsSink}); the
5959
- * concrete `SentrySink` / `PostHogSink` are the only `@sentry/node` /
5960
- * `posthog-node` call sites, and a `Noop*Sink` is installed when telemetry is
5961
- * off. Same seam `@theholocron/logger` uses for Pino.
5857
+ * This module holds *no* vendor SDK import. Each concern sits behind an
5858
+ * interface from `@theholocron/observability` ({@link ErrorSink} /
5859
+ * {@link AnalyticsSink}); the `SentrySink` / `PostHogSink` adapters there are
5860
+ * the only `@sentry/node` / `posthog-node` call sites, and a `Noop*Sink` is
5861
+ * installed when telemetry is off. Same seam the package's `Logger` uses for
5862
+ * Pino.
5962
5863
  *
5963
- * Activation is env-var + shipped-fallback, resolved inside each sink. The
5964
- * single kill switch is `HOLOCRON_TELEMETRY=false` (legacy alias
5965
- * `NO_HOLOCRON_TELEMETRY`); see ADR-0007 / ADR-0008.
5864
+ * Activation is env-var + shipped-fallback, resolved here (`telemetry/resolve.ts`)
5865
+ * and passed to the sink — the adapters read no environment. The single kill
5866
+ * switch is `HOLOCRON_TELEMETRY=false` (legacy alias `NO_HOLOCRON_TELEMETRY`);
5867
+ * see ADR-0007 / ADR-0008.
5966
5868
  */
5967
5869
  let errors = new NoopErrorSink();
5968
5870
  let analytics = new NoopAnalyticsSink();
@@ -5998,9 +5900,11 @@ function baseProps() {
5998
5900
  };
5999
5901
  }
6000
5902
  function init(version) {
6001
- if (isEnabled() && resolveDsn() !== "") {
5903
+ const dsn = resolveDsn();
5904
+ if (isEnabled() && dsn !== "") {
6002
5905
  errors = new SentrySink();
6003
5906
  errors.init({
5907
+ dsn,
6004
5908
  release: `holocron@${version}`,
6005
5909
  environment: env.get("CI") ? "ci" : "local",
6006
5910
  tags: {
@@ -6010,8 +5914,12 @@ function init(version) {
6010
5914
  }
6011
5915
  });
6012
5916
  }
6013
- if (isEnabled() && resolvePostHogKey() !== "") {
6014
- analytics = new PostHogSink();
5917
+ const posthogKey = resolvePostHogKey();
5918
+ if (isEnabled() && posthogKey !== "") {
5919
+ analytics = new PostHogSink({
5920
+ key: posthogKey,
5921
+ host: resolvePostHogHost()
5922
+ });
6015
5923
  analytics.identify(machineId(), {
6016
5924
  ci: Boolean(env.get("CI")),
6017
5925
  os: process.platform,