@bitmagic/cli 0.1.46-dev.3 → 0.1.46-dev.4

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.
Files changed (78) hide show
  1. package/README.md +46 -1
  2. package/dist/auth/session.js +1 -1
  3. package/dist/auth/session.js.map +1 -1
  4. package/dist/auth/subscribe-flow.d.ts +3 -2
  5. package/dist/auth/subscribe-flow.js.map +1 -1
  6. package/dist/bin.js +6 -2
  7. package/dist/bin.js.map +1 -1
  8. package/dist/cli.d.ts +26 -3
  9. package/dist/cli.js +76 -7
  10. package/dist/cli.js.map +1 -1
  11. package/dist/commands/dev.js +9 -0
  12. package/dist/commands/dev.js.map +1 -1
  13. package/dist/commands/forge.js +1 -1
  14. package/dist/commands/forge.js.map +1 -1
  15. package/dist/commands/generate.js +9 -1
  16. package/dist/commands/generate.js.map +1 -1
  17. package/dist/commands/login.js +5 -7
  18. package/dist/commands/login.js.map +1 -1
  19. package/dist/commands/reset-account.d.ts +22 -9
  20. package/dist/commands/reset-account.js +58 -42
  21. package/dist/commands/reset-account.js.map +1 -1
  22. package/dist/commands/subscribe.d.ts +12 -0
  23. package/dist/commands/subscribe.js +12 -11
  24. package/dist/commands/subscribe.js.map +1 -1
  25. package/dist/commands/upgrade.d.ts +2 -1
  26. package/dist/commands/upgrade.js +12 -5
  27. package/dist/commands/upgrade.js.map +1 -1
  28. package/dist/config/environments.d.ts +14 -0
  29. package/dist/config/environments.js +22 -0
  30. package/dist/config/environments.js.map +1 -1
  31. package/dist/errors.d.ts +10 -1
  32. package/dist/errors.js +11 -1
  33. package/dist/errors.js.map +1 -1
  34. package/dist/forge/run-pipeline.js +1 -1
  35. package/dist/forge/run-pipeline.js.map +1 -1
  36. package/dist/forge/stream.js +10 -5
  37. package/dist/forge/stream.js.map +1 -1
  38. package/dist/forge/upload-proxy.js +2 -0
  39. package/dist/forge/upload-proxy.js.map +1 -1
  40. package/dist/generate/stream.js +10 -5
  41. package/dist/generate/stream.js.map +1 -1
  42. package/dist/http/client.d.ts +6 -2
  43. package/dist/http/client.js +22 -12
  44. package/dist/http/client.js.map +1 -1
  45. package/dist/http/spark-refusal.d.ts +11 -0
  46. package/dist/http/spark-refusal.js +13 -0
  47. package/dist/http/spark-refusal.js.map +1 -1
  48. package/dist/publish/client.js +3 -1
  49. package/dist/publish/client.js.map +1 -1
  50. package/dist/scaffold/agents-md.d.ts +2 -1
  51. package/dist/scaffold/agents-md.js +7 -2
  52. package/dist/scaffold/agents-md.js.map +1 -1
  53. package/dist/scaffold/engine-download.js +5 -18
  54. package/dist/scaffold/engine-download.js.map +1 -1
  55. package/dist/scaffold/project-files.d.ts +3 -2
  56. package/dist/scaffold/project-files.js +46 -2
  57. package/dist/scaffold/project-files.js.map +1 -1
  58. package/dist/scaffold/project.d.ts +30 -0
  59. package/dist/scaffold/project.js +55 -29
  60. package/dist/scaffold/project.js.map +1 -1
  61. package/dist/scaffold/upgrade-project.js +19 -33
  62. package/dist/scaffold/upgrade-project.js.map +1 -1
  63. package/dist/telemetry/command-context.d.ts +73 -0
  64. package/dist/telemetry/command-context.js +114 -0
  65. package/dist/telemetry/command-context.js.map +1 -0
  66. package/dist/telemetry/report.d.ts +43 -0
  67. package/dist/telemetry/report.js +123 -0
  68. package/dist/telemetry/report.js.map +1 -0
  69. package/dist/telemetry/request-headers.d.ts +14 -0
  70. package/dist/telemetry/request-headers.js +48 -0
  71. package/dist/telemetry/request-headers.js.map +1 -0
  72. package/dist/update/check.d.ts +14 -0
  73. package/dist/update/check.js +17 -1
  74. package/dist/update/check.js.map +1 -1
  75. package/dist/update/notice.d.ts +40 -0
  76. package/dist/update/notice.js +53 -0
  77. package/dist/update/notice.js.map +1 -1
  78. package/package.json +4 -4
@@ -0,0 +1,73 @@
1
+ import { type Environment } from '../config/environments.js';
2
+ /**
3
+ * What is driving the CLI.
4
+ *
5
+ * - `claude-code` — a Claude Code session (its `CLAUDECODE` env var, verified).
6
+ * - `json` — some agent, unidentified: `--json` is the flag agents pass.
7
+ * - `tty` — a person at a terminal.
8
+ * - `pipe` — a script or CI: no `--json`, no terminal.
9
+ *
10
+ * Add an agent here only once its marker is verified against the real thing; a
11
+ * guessed env var never fires and just misleads whoever reads the table.
12
+ */
13
+ export type CliDriver = 'claude-code' | 'json' | 'tty' | 'pipe';
14
+ export declare function detectDriver(input: {
15
+ json: boolean;
16
+ env: Record<string, string | undefined>;
17
+ isTTY: boolean;
18
+ }): CliDriver;
19
+ /**
20
+ * `DO_NOT_TRACK` set to anything but `0`/`false`/empty. The convention says
21
+ * `1`; being generous about the spelling is the polite reading of a signal
22
+ * that only ever means "please don't".
23
+ *
24
+ * Lives here rather than in report.ts because it is consulted BEFORE anything
25
+ * is collected — `beginCommand` skips the whole snapshot when it is set — and
26
+ * report.ts already imports this module, so the other direction would close a
27
+ * cycle. Re-exported from report.ts, which is where callers expect it.
28
+ */
29
+ export declare function isTelemetryDisabled(env: Record<string, string | undefined>): boolean;
30
+ export interface TokenSnapshot {
31
+ accessToken: string;
32
+ /** Epoch milliseconds, as stored. */
33
+ expiresAt: number;
34
+ }
35
+ export interface CommandContext {
36
+ /** As typed, so a report reads the way the creator invoked it: `publish`, `generate skybox`. */
37
+ name: string;
38
+ json: boolean;
39
+ driver: CliDriver;
40
+ /**
41
+ * Where this command is talking to, resolved ONCE here through the same
42
+ * precedence every command uses (`--env` > BITMAGIC_ENV > the project's
43
+ * bitmagic.json > the stored default). `report.ts` reads it rather than
44
+ * resolving again: a second resolution is a second chance to disagree, and
45
+ * one that skipped the project tier would send a project's beacons to a
46
+ * different environment than the work they describe.
47
+ *
48
+ * Null only when resolution threw — an unknown `--env`, or a project whose
49
+ * `environment` field is not a valid name. The command itself fails with
50
+ * that message moments later; there is simply nowhere to report it to.
51
+ */
52
+ environment: Environment | null;
53
+ /** Epoch milliseconds when the command began. */
54
+ startedAt: number;
55
+ /** See the module header: the stored access token at start, for the one command that removes it. */
56
+ tokenSnapshot: TokenSnapshot | null;
57
+ }
58
+ export interface BeginCommandInput {
59
+ name: string;
60
+ json: boolean;
61
+ explicitEnv?: string;
62
+ env?: Record<string, string | undefined>;
63
+ isTTY?: boolean;
64
+ now?: () => number;
65
+ baseDir?: string;
66
+ /** Where to look for the project's own environment pin; defaults to process.cwd(). */
67
+ cwd?: string;
68
+ }
69
+ export declare function beginCommand(input: BeginCommandInput): CommandContext;
70
+ /** The running command, or null before `beginCommand` — e.g. under a test that calls a module directly. */
71
+ export declare function currentCommand(): CommandContext | null;
72
+ /** Test seam: forget the running command, so one test's context cannot leak into the next. */
73
+ export declare function resetCommandContextForTests(): void;
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Which command this process is running — the one piece of state the CLI's usage
3
+ * reporting is built on.
4
+ *
5
+ * One process runs one command, so this is process-scoped: cli.ts's wrapper calls
6
+ * {@link beginCommand} once before the command's `run`, and two readers use it —
7
+ * `request-headers.ts`, which names the command on every api-server call, and
8
+ * `report.ts`, which sends the outcome when the command ends. It is the CLI's
9
+ * equivalent of a request context, and it lives here rather than being threaded
10
+ * through every command signature because there are five separate fetch sites and
11
+ * eighteen commands, and a parameter every one of them must remember to pass is
12
+ * how one of them ends up unlabelled.
13
+ *
14
+ * Nothing here reaches the network. The token snapshot is a file read, and it
15
+ * exists for exactly one command: `logout` removes the stored credentials, so by
16
+ * the time its outcome is reported there is nothing to authenticate the report
17
+ * with — except what was there when it started.
18
+ */
19
+ import { readCredentials } from '../config/credentials.js';
20
+ import { resolveEnvironmentForCommand } from '../config/environments.js';
21
+ const AGENT_ENV_MARKERS = [['CLAUDECODE', 'claude-code']];
22
+ export function detectDriver(input) {
23
+ for (const [marker, driver] of AGENT_ENV_MARKERS) {
24
+ if (input.env[marker])
25
+ return driver;
26
+ }
27
+ if (input.json)
28
+ return 'json';
29
+ return input.isTTY ? 'tty' : 'pipe';
30
+ }
31
+ /**
32
+ * `DO_NOT_TRACK` set to anything but `0`/`false`/empty. The convention says
33
+ * `1`; being generous about the spelling is the polite reading of a signal
34
+ * that only ever means "please don't".
35
+ *
36
+ * Lives here rather than in report.ts because it is consulted BEFORE anything
37
+ * is collected — `beginCommand` skips the whole snapshot when it is set — and
38
+ * report.ts already imports this module, so the other direction would close a
39
+ * cycle. Re-exported from report.ts, which is where callers expect it.
40
+ */
41
+ export function isTelemetryDisabled(env) {
42
+ const value = env.DO_NOT_TRACK?.trim().toLowerCase();
43
+ return value !== undefined && value !== '' && value !== '0' && value !== 'false';
44
+ }
45
+ let current = null;
46
+ /**
47
+ * The environment this command targets, or null if it cannot be resolved.
48
+ *
49
+ * `resolveEnvironmentForCommand` is the same entry point the commands use, so
50
+ * a project pinned to an environment reports there too. It throws for an
51
+ * unknown name typed right now (a bad `--env`, a bad `bitmagic.json`), which
52
+ * the command surfaces properly — here it only means "no environment to report
53
+ * to", never a failed command.
54
+ */
55
+ function snapshotEnvironment(input, env) {
56
+ try {
57
+ return resolveEnvironmentForCommand({
58
+ explicit: input.explicitEnv,
59
+ cwd: input.cwd,
60
+ baseDir: input.baseDir,
61
+ env,
62
+ });
63
+ }
64
+ catch {
65
+ return null;
66
+ }
67
+ }
68
+ /**
69
+ * The stored access token for `environment`, or null.
70
+ *
71
+ * Every failure collapses to null on purpose: no environment, an unreadable
72
+ * credentials file, nothing stored — the command itself will report whichever
73
+ * of those matters, in its own words. This is a snapshot for a report, and a
74
+ * report is never worth failing a command over.
75
+ */
76
+ function snapshotToken(environment, baseDir) {
77
+ if (environment === null)
78
+ return null;
79
+ try {
80
+ const stored = readCredentials(environment.name, baseDir);
81
+ return stored ? { accessToken: stored.accessToken, expiresAt: stored.expiresAt } : null;
82
+ }
83
+ catch {
84
+ return null;
85
+ }
86
+ }
87
+ export function beginCommand(input) {
88
+ const env = input.env ?? process.env;
89
+ // Nothing is collected for a creator who opted out. Both snapshots below are
90
+ // filesystem work — a walk up the tree for the project's bitmagic.json, then
91
+ // credentials.json — done on every one of the eighteen commands, including
92
+ // the offline ones and the `reload` hook that runs after every agent turn.
93
+ // None of it has a purpose if no report will be sent.
94
+ const collect = !isTelemetryDisabled(env);
95
+ const environment = collect ? snapshotEnvironment(input, env) : null;
96
+ current = {
97
+ name: input.name,
98
+ json: input.json,
99
+ driver: detectDriver({ json: input.json, env, isTTY: input.isTTY ?? process.stdout.isTTY === true }),
100
+ environment,
101
+ startedAt: (input.now ?? Date.now)(),
102
+ tokenSnapshot: collect ? snapshotToken(environment, input.baseDir) : null,
103
+ };
104
+ return current;
105
+ }
106
+ /** The running command, or null before `beginCommand` — e.g. under a test that calls a module directly. */
107
+ export function currentCommand() {
108
+ return current;
109
+ }
110
+ /** Test seam: forget the running command, so one test's context cannot leak into the next. */
111
+ export function resetCommandContextForTests() {
112
+ current = null;
113
+ }
114
+ //# sourceMappingURL=command-context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command-context.js","sourceRoot":"","sources":["../../src/telemetry/command-context.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ import { isTelemetryDisabled } from './command-context.js';
2
+ export { isTelemetryDisabled };
3
+ /**
4
+ * Cap on each request the report makes (the token refresh, when one is due, and
5
+ * the POST itself). A pending fetch keeps Node alive, so an unbounded one would
6
+ * hold a finished command open for as long as the network sulks.
7
+ */
8
+ export declare const REPORT_TIMEOUT_MS = 1500;
9
+ export declare const CLI_EVENTS_PATH = "/api/cli/v1/events";
10
+ export interface CommandReport {
11
+ /** `finished` for every command's end; `started` for a long-running command once it is up. */
12
+ stage: 'started' | 'finished';
13
+ ok?: boolean;
14
+ durationMs?: number;
15
+ exitCode?: number;
16
+ /** A category — `CliError.code`, `cli_error`, `crash:TypeError` — never a message. */
17
+ failure?: string;
18
+ }
19
+ export interface ReportDeps {
20
+ fetch: typeof globalThis.fetch;
21
+ env: Record<string, string | undefined>;
22
+ now?: () => number;
23
+ /** Where credentials live; tests point this at a temp dir. */
24
+ baseDir?: string;
25
+ }
26
+ /**
27
+ * The failure category for an error that is not a CliError — a genuine bug, not
28
+ * a refusal we wrote a message for. `crash:TypeError`, `crash:RangeError`: the
29
+ * error's class, which says what kind of bug without saying anything about the
30
+ * creator's machine. Anything odd in the name is folded to `_` so the value
31
+ * always fits the shape api-server accepts.
32
+ */
33
+ export declare function crashCategory(error: unknown): string;
34
+ /**
35
+ * Report the running command's `report` to api-server. Resolves when the report
36
+ * has been sent, refused, or given up on — always, and never by throwing.
37
+ *
38
+ * Callers `await` it (bounded, see above) rather than fire and forget: on the
39
+ * failure path cli.ts calls `process.exit` next, which would otherwise cut the
40
+ * request off mid-flight, and on the success path the pending fetch would keep
41
+ * the process alive for the same time either way.
42
+ */
43
+ export declare function reportCommandEvent(report: CommandReport, deps?: ReportDeps): Promise<void>;
@@ -0,0 +1,123 @@
1
+ /**
2
+ * The command beacon: one `POST /api/cli/v1/events` per command, saying how it went.
3
+ *
4
+ * Why it exists: api-server can see the commands that call it — but `dev`,
5
+ * `build`, `check`, `verify` and `reload` are offline by design, and even a
6
+ * `publish` that fails on a stale verify never reaches the server. This is how
7
+ * every command becomes visible, and how the ones the server does see get an
8
+ * end-to-end outcome and duration to go with the server's own event. The
9
+ * server turns it into `cli_command` / `cli_command_started` (see
10
+ * `api-server/src/cli/cli-analytics.ts`); nothing goes to Google from here.
11
+ *
12
+ * Contract, in order of importance:
13
+ *
14
+ * 1. It never fails or delays a command. Every failure path — not logged in,
15
+ * no network, a slow server, a bad `--env` — returns silently. It is
16
+ * bounded to {@link REPORT_TIMEOUT_MS} per request so a finished command is
17
+ * never held open by a sulking network (the same reasoning as
18
+ * update/check.ts). It never prints.
19
+ * 2. It sends categories, never messages: the command name, ok/failed, the
20
+ * exit code, a `CliError.code`, and a duration. A message can name a path
21
+ * or a game; none of that belongs in an analytics event.
22
+ * 3. It is authenticated with the creator's own token, so the server can key
23
+ * the event to the account and look up their real Pro state. No token, no
24
+ * report — an anonymous beacon would be a spammable endpoint for nothing.
25
+ * 4. `DO_NOT_TRACK=1` (the cross-tool convention, consoledonottrack.com)
26
+ * turns it off. It does not touch the ordinary api-server calls a command
27
+ * makes — those are the command's work, and the server logs them anyway.
28
+ */
29
+ import { resolveAuth0ClientId } from '../config/environments.js';
30
+ import { getAccessToken } from '../auth/session.js';
31
+ import { apiServerHeaders } from './request-headers.js';
32
+ import { currentCommand, isTelemetryDisabled } from './command-context.js';
33
+ // Re-exported because this is where the opt-out is enforced for a report that is already
34
+ // under way; `beginCommand` consults the same predicate to skip collecting anything at all.
35
+ export { isTelemetryDisabled };
36
+ /**
37
+ * Cap on each request the report makes (the token refresh, when one is due, and
38
+ * the POST itself). A pending fetch keeps Node alive, so an unbounded one would
39
+ * hold a finished command open for as long as the network sulks.
40
+ */
41
+ export const REPORT_TIMEOUT_MS = 1500;
42
+ export const CLI_EVENTS_PATH = '/api/cli/v1/events';
43
+ /**
44
+ * The failure category for an error that is not a CliError — a genuine bug, not
45
+ * a refusal we wrote a message for. `crash:TypeError`, `crash:RangeError`: the
46
+ * error's class, which says what kind of bug without saying anything about the
47
+ * creator's machine. Anything odd in the name is folded to `_` so the value
48
+ * always fits the shape api-server accepts.
49
+ */
50
+ export function crashCategory(error) {
51
+ const name = error instanceof Error && error.name ? error.name : 'unknown';
52
+ return `crash:${name.replace(/[^A-Za-z0-9_.-]/g, '_').slice(0, 40)}`;
53
+ }
54
+ /** The given fetch, with every call bounded by REPORT_TIMEOUT_MS. */
55
+ function boundedFetch(fetchImpl) {
56
+ return (input, init) => fetchImpl(input, { ...init, signal: AbortSignal.timeout(REPORT_TIMEOUT_MS) });
57
+ }
58
+ /**
59
+ * The token to authenticate the report with, or null for "no report".
60
+ *
61
+ * The stored token first — refreshed if it has expired, exactly as any command
62
+ * would, so a report never wastes a request on a 401 — and failing that the
63
+ * snapshot taken when the command began, which is what `logout` leaves behind.
64
+ */
65
+ async function resolveReportToken(environment, command, deps) {
66
+ try {
67
+ const clientId = resolveAuth0ClientId(environment, deps.env);
68
+ return await getAccessToken(environment, clientId, {
69
+ fetch: boundedFetch(deps.fetch),
70
+ sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
71
+ now: deps.now,
72
+ }, deps.baseDir);
73
+ }
74
+ catch {
75
+ const snapshot = command.tokenSnapshot;
76
+ const now = (deps.now ?? Date.now)();
77
+ return snapshot !== null && snapshot.expiresAt > now ? snapshot.accessToken : null;
78
+ }
79
+ }
80
+ function defaultDeps() {
81
+ return { fetch: globalThis.fetch, env: process.env };
82
+ }
83
+ /**
84
+ * Report the running command's `report` to api-server. Resolves when the report
85
+ * has been sent, refused, or given up on — always, and never by throwing.
86
+ *
87
+ * Callers `await` it (bounded, see above) rather than fire and forget: on the
88
+ * failure path cli.ts calls `process.exit` next, which would otherwise cut the
89
+ * request off mid-flight, and on the success path the pending fetch would keep
90
+ * the process alive for the same time either way.
91
+ */
92
+ export async function reportCommandEvent(report, deps = defaultDeps()) {
93
+ try {
94
+ if (isTelemetryDisabled(deps.env))
95
+ return;
96
+ const command = currentCommand();
97
+ if (command === null)
98
+ return;
99
+ // Resolved once, at command start — see CommandContext.environment for why
100
+ // this must not resolve again. Null means it could not be resolved at all,
101
+ // so there is nowhere to send this.
102
+ const environment = command.environment;
103
+ if (environment === null)
104
+ return;
105
+ const token = await resolveReportToken(environment, command, deps);
106
+ if (token === null)
107
+ return;
108
+ await boundedFetch(deps.fetch)(`${environment.apiUrl}${CLI_EVENTS_PATH}`, {
109
+ method: 'POST',
110
+ headers: {
111
+ ...apiServerHeaders(),
112
+ Authorization: `Bearer ${token}`,
113
+ 'Content-Type': 'application/json',
114
+ },
115
+ body: JSON.stringify({ command: command.name, ...report }),
116
+ });
117
+ }
118
+ catch {
119
+ // Best-effort by design — see the module header. Whatever went wrong here,
120
+ // the command's own outcome has already been decided and printed.
121
+ }
122
+ }
123
+ //# sourceMappingURL=report.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"report.js","sourceRoot":"","sources":["../../src/telemetry/report.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ export declare const CLI_COMMAND_HEADER = "X-Bitmagic-Cli-Command";
2
+ export declare const CLI_DRIVER_HEADER = "X-Bitmagic-Cli-Driver";
3
+ /** `bitmagic-cli/0.1.46 (darwin-arm64; node/v22.12.0)` — the shape api-server's context reader parses. */
4
+ export declare function userAgent(): string;
5
+ /**
6
+ * Spread these into the `headers` of every fetch aimed at api-server. Callers
7
+ * add their own `Authorization`, `Accept` and `Content-Type` after — nothing
8
+ * here collides with those.
9
+ *
10
+ * The command headers are present only once cli.ts has begun a command; a
11
+ * module exercised directly (a test, a future library caller) still identifies
12
+ * the CLI, just not the command.
13
+ */
14
+ export declare function apiServerHeaders(): Record<string, string>;
@@ -0,0 +1,48 @@
1
+ /**
2
+ * The headers every api-server call carries, so the server's analytics know who
3
+ * asked and why.
4
+ *
5
+ * The CLI never sends analytics to Google itself — the GA4 Measurement Protocol
6
+ * needs an API secret, and a public npm package cannot keep one. api-server
7
+ * emits every event instead (`api-server/src/cli/cli-analytics.ts` reads these
8
+ * headers back as `cli_version`, `cli_platform`, `cli_node`, `cli_command` and
9
+ * `cli_driver`). That is what lets a `cli_asset_generated` event tell `generate
10
+ * image` from `cover`, which share a route, and a `publish` from a `forge`,
11
+ * which share the signed-URL route.
12
+ *
13
+ * Only for api-server. GCS signed URLs and the npm registry get nothing from
14
+ * here — a signed PUT can refuse headers it did not sign for, and neither has
15
+ * any use for them.
16
+ */
17
+ import { currentCliVersion } from '../update/cli-version.js';
18
+ import { currentCommand } from './command-context.js';
19
+ export const CLI_COMMAND_HEADER = 'X-Bitmagic-Cli-Command';
20
+ export const CLI_DRIVER_HEADER = 'X-Bitmagic-Cli-Driver';
21
+ let cachedUserAgent = null;
22
+ /** `bitmagic-cli/0.1.46 (darwin-arm64; node/v22.12.0)` — the shape api-server's context reader parses. */
23
+ export function userAgent() {
24
+ if (cachedUserAgent === null) {
25
+ const version = currentCliVersion() || 'unknown';
26
+ cachedUserAgent = `bitmagic-cli/${version} (${process.platform}-${process.arch}; node/${process.version})`;
27
+ }
28
+ return cachedUserAgent;
29
+ }
30
+ /**
31
+ * Spread these into the `headers` of every fetch aimed at api-server. Callers
32
+ * add their own `Authorization`, `Accept` and `Content-Type` after — nothing
33
+ * here collides with those.
34
+ *
35
+ * The command headers are present only once cli.ts has begun a command; a
36
+ * module exercised directly (a test, a future library caller) still identifies
37
+ * the CLI, just not the command.
38
+ */
39
+ export function apiServerHeaders() {
40
+ const headers = { 'User-Agent': userAgent() };
41
+ const command = currentCommand();
42
+ if (command) {
43
+ headers[CLI_COMMAND_HEADER] = command.name;
44
+ headers[CLI_DRIVER_HEADER] = command.driver;
45
+ }
46
+ return headers;
47
+ }
48
+ //# sourceMappingURL=request-headers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request-headers.js","sourceRoot":"","sources":["../../src/telemetry/request-headers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtD,MAAM,CAAC,MAAM,kBAAkB,GAAG,wBAAwB,CAAC;AAC3D,MAAM,CAAC,MAAM,iBAAiB,GAAG,uBAAuB,CAAC;AAEzD,IAAI,eAAe,GAAkB,IAAI,CAAC;AAE1C,0GAA0G;AAC1G,MAAM,UAAU,SAAS;IACvB,IAAI,eAAe,KAAK,IAAI,EAAE,CAAC;QAC7B,MAAM,OAAO,GAAG,iBAAiB,EAAE,IAAI,SAAS,CAAC;QACjD,eAAe,GAAG,gBAAgB,OAAO,KAAK,OAAO,CAAC,QAAQ,IAAI,OAAO,CAAC,IAAI,UAAU,OAAO,CAAC,OAAO,GAAG,CAAC;IAC7G,CAAC;IACD,OAAO,eAAe,CAAC;AACzB,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,gBAAgB;IAC9B,MAAM,OAAO,GAA2B,EAAE,YAAY,EAAE,SAAS,EAAE,EAAE,CAAC;IACtE,MAAM,OAAO,GAAG,cAAc,EAAE,CAAC;IACjC,IAAI,OAAO,EAAE,CAAC;QACZ,OAAO,CAAC,kBAAkB,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;QAC3C,OAAO,CAAC,iBAAiB,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC;IAC9C,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC"}
@@ -1,3 +1,4 @@
1
+ import type { EnvironmentName } from '../config/environments.js';
1
2
  import { type UpdateCheckCache, type UpdateTag } from './notice.js';
2
3
  export declare function updateCachePath(baseDir?: string): string;
3
4
  /**
@@ -24,6 +25,19 @@ export declare function fetchTagVersion(tag: UpdateTag, fetchImpl?: typeof globa
24
25
  * every command wait on npm to be told something it could just as well be told tomorrow.
25
26
  */
26
27
  export declare function pendingUpdateNotice(currentVersion: string, baseDir?: string): string | null;
28
+ /**
29
+ * The single line bin.ts prints before a command runs — at most one install command, ever.
30
+ *
31
+ * The wrong-line notice wins over the newer-version one, and suppresses it. Both at once would
32
+ * print two different `npm i -g` commands in the same breath ("update to 0.1.45 on your line" next
33
+ * to "be on the other line"), which is the ambiguity this exists to end: a creator standing in a
34
+ * project should never have to choose which one they meant.
35
+ *
36
+ * `projectEnvironment` is the project's committed pin, or undefined outside a project and for one
37
+ * that has none. Undefined leaves the old behaviour exactly as it was: nothing here invents a line
38
+ * for a directory that never claimed one.
39
+ */
40
+ export declare function startupNotice(currentVersion: string, projectEnvironment: EnvironmentName | undefined, baseDir?: string): string | null;
27
41
  /**
28
42
  * Refresh the cache if it is stale. Fire-and-forget: callers must not await this.
29
43
  *
@@ -1,7 +1,7 @@
1
1
  import * as fs from 'fs';
2
2
  import * as path from 'path';
3
3
  import { defaultBaseDir } from '../config/credentials.js';
4
- import { cliUpdateNotice, isCacheStale, updateTagFor, } from './notice.js';
4
+ import { cliLineMismatchNotice, cliUpdateNotice, isCacheStale, updateTagFor, } from './notice.js';
5
5
  /** Public registry metadata, one document per dist-tag. No auth, no scope needed. */
6
6
  const NPM_PACKAGE_URL = 'https://registry.npmjs.org/@bitmagic/cli';
7
7
  /**
@@ -90,6 +90,22 @@ export function pendingUpdateNotice(currentVersion, baseDir) {
90
90
  return null;
91
91
  return cliUpdateNotice(currentVersion, cache.latest);
92
92
  }
93
+ /**
94
+ * The single line bin.ts prints before a command runs — at most one install command, ever.
95
+ *
96
+ * The wrong-line notice wins over the newer-version one, and suppresses it. Both at once would
97
+ * print two different `npm i -g` commands in the same breath ("update to 0.1.45 on your line" next
98
+ * to "be on the other line"), which is the ambiguity this exists to end: a creator standing in a
99
+ * project should never have to choose which one they meant.
100
+ *
101
+ * `projectEnvironment` is the project's committed pin, or undefined outside a project and for one
102
+ * that has none. Undefined leaves the old behaviour exactly as it was: nothing here invents a line
103
+ * for a directory that never claimed one.
104
+ */
105
+ export function startupNotice(currentVersion, projectEnvironment, baseDir) {
106
+ const mismatch = projectEnvironment === undefined ? null : cliLineMismatchNotice(currentVersion, projectEnvironment);
107
+ return mismatch ?? pendingUpdateNotice(currentVersion, baseDir);
108
+ }
93
109
  /**
94
110
  * Refresh the cache if it is stale. Fire-and-forget: callers must not await this.
95
111
  *
@@ -1 +1 @@
1
- {"version":3,"file":"check.js","sourceRoot":"","sources":["../../src/update/check.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"check.js","sourceRoot":"","sources":["../../src/update/check.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,4 @@
1
+ import type { EnvironmentName } from '../config/environments.js';
1
2
  /** Which published line a build came from: a prerelease was installed from `@bitmagic/cli@dev`. */
2
3
  export type UpdateTag = 'latest' | 'dev';
3
4
  /**
@@ -11,6 +12,32 @@ export type UpdateTag = 'latest' | 'dev';
11
12
  export declare function updateTagFor(version: string): UpdateTag;
12
13
  /** How to install the line a build is already on, so a nudge never moves someone between lines. */
13
14
  export declare function installCommandFor(tag: UpdateTag): string;
15
+ /**
16
+ * What to call a line in a sentence.
17
+ *
18
+ * The dev line says "prerelease" out loud because the only place this is used asks someone to
19
+ * install it, and portal/src/cli-install.ts is right that putting an untested build on the machine
20
+ * of someone who never opted into testing is the worse failure. Being asked to cross lines is
21
+ * legitimate — the project's environment is what wants it — but nobody should cross without knowing
22
+ * which side they are landing on.
23
+ */
24
+ export declare function describeUpdateTag(tag: UpdateTag): string;
25
+ /**
26
+ * Which line a PROJECT's CLI should come from — the one answer to "dev or prod?" for anyone
27
+ * standing in a project directory, and the reason nobody should ever be asked.
28
+ *
29
+ * The project decides because the project is where the mismatch bites: new `/api/cli/v1` endpoints
30
+ * reach the dev api-server before production, so a prerelease run against `prod` calls endpoints
31
+ * that are not there yet, and a release run against `dev` is behind the engine that environment
32
+ * serves. `local` sits with `dev` — a localhost api-server is built from the same `main` a
33
+ * prerelease is cut from, so it is ahead of the release by construction.
34
+ *
35
+ * Note this is NOT `updateTagFor` applied to something else: that reads a version string and
36
+ * answers "which line is this build ON", which is a fact about the install. This answers "which
37
+ * line SHOULD it be on here", which is a fact about the project. The two disagreeing is exactly
38
+ * what `cliLineMismatchNotice` reports.
39
+ */
40
+ export declare function updateTagForEnvironment(environment: EnvironmentName): UpdateTag;
14
41
  /**
15
42
  * How stale a cached npm answer may be before it is refreshed.
16
43
  *
@@ -46,3 +73,16 @@ export declare function isCacheStale(cache: UpdateCheckCache | null, now: number
46
73
  * 0.1.46-dev.3 to "update" to the older 0.1.45 release.
47
74
  */
48
75
  export declare function cliUpdateNotice(currentVersion: string, latestVersion: string | null | undefined): string | null;
76
+ /**
77
+ * The line to say when the CLI running inside a project came from the other one.
78
+ *
79
+ * This is the one place the two lines are deliberately crossed, and it needs no version comparison
80
+ * to do it: whatever the numbers say, a project pinned to `dev` wants the dev line and a project
81
+ * pinned to `prod` wants the release. Version-driven nudges structurally cannot report this — a
82
+ * prerelease sitting in a `prod` project is *ahead* of the release tag, so `cliUpdateNotice` is
83
+ * correctly silent about a CLI that is on the wrong line entirely.
84
+ *
85
+ * `projectEnvironment` must be a project's committed pin, never a resolved environment: a one-off
86
+ * `--env` is a statement about this command, not about which CLI belongs in this directory.
87
+ */
88
+ export declare function cliLineMismatchNotice(currentVersion: string, projectEnvironment: EnvironmentName): string | null;
@@ -14,6 +14,36 @@ export function updateTagFor(version) {
14
14
  export function installCommandFor(tag) {
15
15
  return tag === 'dev' ? 'npm i -g @bitmagic/cli@dev' : 'npm i -g @bitmagic/cli';
16
16
  }
17
+ /**
18
+ * What to call a line in a sentence.
19
+ *
20
+ * The dev line says "prerelease" out loud because the only place this is used asks someone to
21
+ * install it, and portal/src/cli-install.ts is right that putting an untested build on the machine
22
+ * of someone who never opted into testing is the worse failure. Being asked to cross lines is
23
+ * legitimate — the project's environment is what wants it — but nobody should cross without knowing
24
+ * which side they are landing on.
25
+ */
26
+ export function describeUpdateTag(tag) {
27
+ return tag === 'dev' ? 'dev (prerelease)' : 'release';
28
+ }
29
+ /**
30
+ * Which line a PROJECT's CLI should come from — the one answer to "dev or prod?" for anyone
31
+ * standing in a project directory, and the reason nobody should ever be asked.
32
+ *
33
+ * The project decides because the project is where the mismatch bites: new `/api/cli/v1` endpoints
34
+ * reach the dev api-server before production, so a prerelease run against `prod` calls endpoints
35
+ * that are not there yet, and a release run against `dev` is behind the engine that environment
36
+ * serves. `local` sits with `dev` — a localhost api-server is built from the same `main` a
37
+ * prerelease is cut from, so it is ahead of the release by construction.
38
+ *
39
+ * Note this is NOT `updateTagFor` applied to something else: that reads a version string and
40
+ * answers "which line is this build ON", which is a fact about the install. This answers "which
41
+ * line SHOULD it be on here", which is a fact about the project. The two disagreeing is exactly
42
+ * what `cliLineMismatchNotice` reports.
43
+ */
44
+ export function updateTagForEnvironment(environment) {
45
+ return environment === 'prod' ? 'latest' : 'dev';
46
+ }
17
47
  /**
18
48
  * How stale a cached npm answer may be before it is refreshed.
19
49
  *
@@ -52,4 +82,27 @@ export function cliUpdateNotice(currentVersion, latestVersion) {
52
82
  const install = installCommandFor(updateTagFor(currentVersion));
53
83
  return `A newer bitmagic CLI is available: ${latestVersion} (you have ${currentVersion}). Run \`${install}\` to update.`;
54
84
  }
85
+ /**
86
+ * The line to say when the CLI running inside a project came from the other one.
87
+ *
88
+ * This is the one place the two lines are deliberately crossed, and it needs no version comparison
89
+ * to do it: whatever the numbers say, a project pinned to `dev` wants the dev line and a project
90
+ * pinned to `prod` wants the release. Version-driven nudges structurally cannot report this — a
91
+ * prerelease sitting in a `prod` project is *ahead* of the release tag, so `cliUpdateNotice` is
92
+ * correctly silent about a CLI that is on the wrong line entirely.
93
+ *
94
+ * `projectEnvironment` must be a project's committed pin, never a resolved environment: a one-off
95
+ * `--env` is a statement about this command, not about which CLI belongs in this directory.
96
+ */
97
+ export function cliLineMismatchNotice(currentVersion, projectEnvironment) {
98
+ if (typeof currentVersion !== 'string' || currentVersion === '')
99
+ return null;
100
+ const wanted = updateTagForEnvironment(projectEnvironment);
101
+ const installed = updateTagFor(currentVersion);
102
+ if (installed === wanted)
103
+ return null;
104
+ return (`This project is on the ${projectEnvironment} environment, so its CLI comes from the ` +
105
+ `${describeUpdateTag(wanted)} line — but you are running a ${describeUpdateTag(installed)} ` +
106
+ `build (${currentVersion}). Run \`${installCommandFor(wanted)}\`.`);
107
+ }
55
108
  //# sourceMappingURL=notice.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"notice.js","sourceRoot":"","sources":["../../src/update/notice.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AAKrE;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAAC,OAAe;IAC1C,OAAO,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC;AAClD,CAAC;AAED,mGAAmG;AACnG,MAAM,UAAU,iBAAiB,CAAC,GAAc;IAC9C,OAAO,GAAG,KAAK,KAAK,CAAC,CAAC,CAAC,4BAA4B,CAAC,CAAC,CAAC,wBAAwB,CAAC;AACjF,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAkB5D,MAAM,UAAU,YAAY,CAAC,KAA8B,EAAE,GAAW;IACtE,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAChC,+FAA+F;IAC/F,oBAAoB;IACpB,IAAI,KAAK,CAAC,SAAS,GAAG,GAAG;QAAE,OAAO,IAAI,CAAC;IACvC,OAAO,GAAG,GAAG,KAAK,CAAC,SAAS,IAAI,wBAAwB,CAAC;AAC3D,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,eAAe,CAAC,cAAsB,EAAE,aAAwC;IAC9F,IAAI,OAAO,aAAa,KAAK,QAAQ,IAAI,aAAa,KAAK,EAAE;QAAE,OAAO,IAAI,CAAC;IAC3E,IAAI,OAAO,cAAc,KAAK,QAAQ,IAAI,cAAc,KAAK,EAAE;QAAE,OAAO,IAAI,CAAC;IAC7E,IAAI,oBAAoB,CAAC,cAAc,EAAE,aAAa,CAAC,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAC9E,8FAA8F;IAC9F,MAAM,OAAO,GAAG,iBAAiB,CAAC,YAAY,CAAC,cAAc,CAAC,CAAC,CAAC;IAChE,OAAO,sCAAsC,aAAa,cAAc,cAAc,YAAY,OAAO,eAAe,CAAC;AAC3H,CAAC"}
1
+ {"version":3,"file":"notice.js","sourceRoot":"","sources":["../../src/update/notice.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AAKrE;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAAC,OAAe;IAC1C,OAAO,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC;AAClD,CAAC;AAED,mGAAmG;AACnG,MAAM,UAAU,iBAAiB,CAAC,GAAc;IAC9C,OAAO,GAAG,KAAK,KAAK,CAAC,CAAC,CAAC,4BAA4B,CAAC,CAAC,CAAC,wBAAwB,CAAC;AACjF,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,iBAAiB,CAAC,GAAc;IAC9C,OAAO,GAAG,KAAK,KAAK,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,SAAS,CAAC;AACxD,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,uBAAuB,CAAC,WAA4B;IAClE,OAAO,WAAW,KAAK,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC;AACnD,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAkB5D,MAAM,UAAU,YAAY,CAAC,KAA8B,EAAE,GAAW;IACtE,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAChC,+FAA+F;IAC/F,oBAAoB;IACpB,IAAI,KAAK,CAAC,SAAS,GAAG,GAAG;QAAE,OAAO,IAAI,CAAC;IACvC,OAAO,GAAG,GAAG,KAAK,CAAC,SAAS,IAAI,wBAAwB,CAAC;AAC3D,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,eAAe,CAAC,cAAsB,EAAE,aAAwC;IAC9F,IAAI,OAAO,aAAa,KAAK,QAAQ,IAAI,aAAa,KAAK,EAAE;QAAE,OAAO,IAAI,CAAC;IAC3E,IAAI,OAAO,cAAc,KAAK,QAAQ,IAAI,cAAc,KAAK,EAAE;QAAE,OAAO,IAAI,CAAC;IAC7E,IAAI,oBAAoB,CAAC,cAAc,EAAE,aAAa,CAAC,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAC9E,8FAA8F;IAC9F,MAAM,OAAO,GAAG,iBAAiB,CAAC,YAAY,CAAC,cAAc,CAAC,CAAC,CAAC;IAChE,OAAO,sCAAsC,aAAa,cAAc,cAAc,YAAY,OAAO,eAAe,CAAC;AAC3H,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,qBAAqB,CACnC,cAAsB,EACtB,kBAAmC;IAEnC,IAAI,OAAO,cAAc,KAAK,QAAQ,IAAI,cAAc,KAAK,EAAE;QAAE,OAAO,IAAI,CAAC;IAC7E,MAAM,MAAM,GAAG,uBAAuB,CAAC,kBAAkB,CAAC,CAAC;IAC3D,MAAM,SAAS,GAAG,YAAY,CAAC,cAAc,CAAC,CAAC;IAC/C,IAAI,SAAS,KAAK,MAAM;QAAE,OAAO,IAAI,CAAC;IACtC,OAAO,CACL,0BAA0B,kBAAkB,0CAA0C;QACtF,GAAG,iBAAiB,CAAC,MAAM,CAAC,iCAAiC,iBAAiB,CAAC,SAAS,CAAC,GAAG;QAC5F,UAAU,cAAc,YAAY,iBAAiB,CAAC,MAAM,CAAC,KAAK,CACnE,CAAC;AACJ,CAAC"}