@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 +9 -7
- package/dist/cli.mjs +45 -137
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +1 -1
- package/package.json +8 -5
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
|
|
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
|
|
360
|
-
`AnalyticsSink` (PostHog)
|
|
361
|
-
`posthog-node`
|
|
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
|
|
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 {
|
|
25
|
-
import
|
|
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/
|
|
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/
|
|
5818
|
+
//#region src/telemetry/resolve.ts
|
|
5825
5819
|
/**
|
|
5826
|
-
*
|
|
5827
|
-
*
|
|
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
|
-
*
|
|
5830
|
-
*
|
|
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
|
|
5833
|
-
*
|
|
5834
|
-
*
|
|
5835
|
-
*
|
|
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
|
|
5958
|
-
*
|
|
5959
|
-
*
|
|
5960
|
-
* `posthog-node` call sites, and a `Noop*Sink` is
|
|
5961
|
-
* off. Same seam
|
|
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
|
|
5964
|
-
*
|
|
5965
|
-
* `NO_HOLOCRON_TELEMETRY`);
|
|
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
|
-
|
|
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
|
-
|
|
6014
|
-
|
|
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,
|