@bitmagic/cli 0.1.37 → 0.1.39

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 (51) hide show
  1. package/README.md +41 -3
  2. package/dist/auth/subscribe-flow.d.ts +59 -0
  3. package/dist/auth/subscribe-flow.js +102 -0
  4. package/dist/auth/subscribe-flow.js.map +1 -0
  5. package/dist/browser-gl.d.ts +23 -0
  6. package/dist/browser-gl.js +50 -0
  7. package/dist/browser-gl.js.map +1 -1
  8. package/dist/cli.d.ts +41 -1
  9. package/dist/cli.js +4 -0
  10. package/dist/cli.js.map +1 -1
  11. package/dist/commands/login.js +33 -5
  12. package/dist/commands/login.js.map +1 -1
  13. package/dist/commands/subscribe.d.ts +14 -0
  14. package/dist/commands/subscribe.js +60 -0
  15. package/dist/commands/subscribe.js.map +1 -0
  16. package/dist/commands/usage.d.ts +76 -0
  17. package/dist/commands/usage.js +117 -0
  18. package/dist/commands/usage.js.map +1 -0
  19. package/dist/commands/verify.d.ts +13 -1
  20. package/dist/commands/verify.js +139 -15
  21. package/dist/commands/verify.js.map +1 -1
  22. package/dist/commands/whoami.d.ts +10 -4
  23. package/dist/commands/whoami.js +22 -7
  24. package/dist/commands/whoami.js.map +1 -1
  25. package/dist/forge/upload-proxy.js +1 -1
  26. package/dist/http/client.d.ts +10 -0
  27. package/dist/http/client.js +38 -2
  28. package/dist/http/client.js.map +1 -1
  29. package/dist/scaffold/claude-settings.js +4 -0
  30. package/dist/scaffold/claude-settings.js.map +1 -1
  31. package/dist/scaffold/project-files.d.ts +9 -0
  32. package/dist/scaffold/project-files.js +70 -9
  33. package/dist/scaffold/project-files.js.map +1 -1
  34. package/dist/scaffold/project.js +2 -1
  35. package/dist/scaffold/project.js.map +1 -1
  36. package/dist/verify/browser.d.ts +37 -1
  37. package/dist/verify/browser.js +389 -97
  38. package/dist/verify/browser.js.map +1 -1
  39. package/dist/verify/classify.d.ts +75 -2
  40. package/dist/verify/classify.js +48 -2
  41. package/dist/verify/classify.js.map +1 -1
  42. package/dist/verify/iterate.d.ts +47 -0
  43. package/dist/verify/iterate.js +162 -0
  44. package/dist/verify/iterate.js.map +1 -0
  45. package/dist/verify/result.d.ts +16 -3
  46. package/dist/verify/result.js +13 -3
  47. package/dist/verify/result.js.map +1 -1
  48. package/dist/verify/settle.d.ts +60 -0
  49. package/dist/verify/settle.js +90 -0
  50. package/dist/verify/settle.js.map +1 -0
  51. package/package.json +1 -1
@@ -0,0 +1,76 @@
1
+ /**
2
+ * `bitmagic usage` — where this account's sparks went.
3
+ *
4
+ * The server does the aggregating (GET /api/cli/v1/sparks/usage), and the plan
5
+ * page on the web renders the SAME payload, so the two surfaces cannot disagree
6
+ * about what a category means or which window is which.
7
+ *
8
+ * Rendering is a pure function over the response, following the bake-progress
9
+ * convention: formatting is testable without a network, and printing is one
10
+ * call. Plain aligned text and no colour — an agent tool is usually what reads
11
+ * this, and render/inline-image.ts is documented as the only place in the
12
+ * package that writes escape sequences.
13
+ */
14
+ interface UsageWindow {
15
+ limit: number;
16
+ used: number;
17
+ remaining: number;
18
+ /** Epoch ms, or null when the window is closed and reopens on next use. */
19
+ resetsAt: number | null;
20
+ }
21
+ interface UsageAllowance {
22
+ total: number;
23
+ long: UsageWindow;
24
+ short: UsageWindow;
25
+ }
26
+ export interface UsageResponse {
27
+ success: boolean;
28
+ userId: string;
29
+ period: {
30
+ days: number;
31
+ fromMs: number;
32
+ toMs: number;
33
+ };
34
+ totalSparks: number;
35
+ fromAllowance: number;
36
+ fromPurchased: number;
37
+ byCategory: Array<{
38
+ category: string;
39
+ label: string;
40
+ sparks: number;
41
+ }>;
42
+ byDay: Array<{
43
+ day: string;
44
+ sparks: number;
45
+ }>;
46
+ allowance: UsageAllowance | null;
47
+ }
48
+ /** "2h 10m" / "40m" / "4d" — coarse on purpose; this is a report, not a countdown. */
49
+ export declare function formatResetIn(resetsAt: number | null, now: number): string;
50
+ /**
51
+ * PURE. The whole report as text.
52
+ *
53
+ * `now` is a parameter rather than a `Date.now()` call so reset times are
54
+ * testable, and so every line of one report agrees on a single instant.
55
+ */
56
+ export declare function formatUsageReport(report: UsageResponse, now?: number): string;
57
+ export interface UsageOptions {
58
+ days?: number;
59
+ env?: string;
60
+ }
61
+ export declare function runUsage(options?: UsageOptions): Promise<UsageResponse>;
62
+ export declare const usageCommand: import("citty").CommandDef<{
63
+ readonly days: {
64
+ readonly type: "string";
65
+ readonly description: "How many days to report on (default 30, max 90).";
66
+ };
67
+ readonly env: {
68
+ readonly type: "string";
69
+ readonly description: "Environment to query (dev, prod, local).";
70
+ };
71
+ readonly json: {
72
+ readonly type: "boolean";
73
+ readonly description: "Print the result as JSON on stdout; all progress goes to stderr.";
74
+ };
75
+ }>;
76
+ export {};
@@ -0,0 +1,117 @@
1
+ import { defineCommand } from 'citty';
2
+ import { resolveEnvironment, resolveAuth0ClientId } from '../config/environments.js';
3
+ import { readDefaultEnvironment } from '../config/credentials.js';
4
+ import { getAccessToken } from '../auth/session.js';
5
+ import { apiGet } from '../http/client.js';
6
+ import { createOutput } from '../output.js';
7
+ const BAR_WIDTH = 18;
8
+ function bar(value, max) {
9
+ if (max <= 0 || value <= 0)
10
+ return '';
11
+ // At least one block for any non-zero value, so a small category is visible
12
+ // rather than reading as nothing at all.
13
+ return '▇'.repeat(Math.max(1, Math.round((value / max) * BAR_WIDTH)));
14
+ }
15
+ function sparks(n) {
16
+ return n.toLocaleString('en-US');
17
+ }
18
+ /** "2h 10m" / "40m" / "4d" — coarse on purpose; this is a report, not a countdown. */
19
+ export function formatResetIn(resetsAt, now) {
20
+ if (resetsAt === null)
21
+ return 'resets when you next use it';
22
+ const minutes = Math.max(0, Math.ceil((resetsAt - now) / 60_000));
23
+ if (minutes < 60)
24
+ return `resets in ${minutes}m`;
25
+ const hours = Math.floor(minutes / 60);
26
+ if (hours < 48) {
27
+ const rest = minutes % 60;
28
+ return rest === 0 ? `resets in ${hours}h` : `resets in ${hours}h ${rest}m`;
29
+ }
30
+ return `resets in ${Math.floor(hours / 24)}d`;
31
+ }
32
+ /** '2026-08-13' -> 'Aug 13'. */
33
+ function shortDay(day) {
34
+ const d = new Date(`${day}T00:00:00Z`);
35
+ if (Number.isNaN(d.getTime()))
36
+ return day;
37
+ return `${d.toLocaleString('en-US', { month: 'short', timeZone: 'UTC' })} ${d.getUTCDate()}`;
38
+ }
39
+ /**
40
+ * PURE. The whole report as text.
41
+ *
42
+ * `now` is a parameter rather than a `Date.now()` call so reset times are
43
+ * testable, and so every line of one report agrees on a single instant.
44
+ */
45
+ export function formatUsageReport(report, now = Date.now()) {
46
+ const lines = [];
47
+ lines.push(`Bitmagic usage — last ${report.period.days} days`);
48
+ lines.push('');
49
+ if (report.allowance) {
50
+ const windows = [
51
+ ['This 5-hour window', report.allowance.short],
52
+ ['This week', report.allowance.long],
53
+ ];
54
+ const labelWidth = Math.max(...windows.map(([label]) => label.length));
55
+ for (const [label, w] of windows) {
56
+ const amount = `${sparks(w.used)} / ${sparks(w.limit)}`;
57
+ lines.push(` ${label.padEnd(labelWidth)} ${amount.padStart(13)} ${formatResetIn(w.resetsAt, now)}`);
58
+ }
59
+ lines.push('');
60
+ }
61
+ if (report.totalSparks === 0) {
62
+ lines.push(' No sparks spent in this period.');
63
+ return lines.join('\n');
64
+ }
65
+ const labelWidth = Math.max(...report.byCategory.map((c) => c.label.length), 'Total'.length);
66
+ const topCategory = report.byCategory[0]?.sparks ?? 0;
67
+ for (const c of report.byCategory) {
68
+ lines.push(` ${c.label.padEnd(labelWidth)} ${sparks(c.sparks).padStart(8)} ${bar(c.sparks, topCategory)}`);
69
+ }
70
+ const split = report.allowance || report.fromAllowance > 0
71
+ ? ` (${sparks(report.fromAllowance)} from your Pro allowance, ${sparks(report.fromPurchased)} from your balance)`
72
+ : '';
73
+ lines.push(` ${'Total'.padEnd(labelWidth)} ${sparks(report.totalSparks).padStart(8)}${split}`);
74
+ // Only the tail of the series, and only once something was actually spent —
75
+ // 30 mostly-empty rows is noise, not a trend.
76
+ const recent = report.byDay.slice(-14);
77
+ const busiest = Math.max(...recent.map((d) => d.sparks), 0);
78
+ if (busiest > 0) {
79
+ lines.push('');
80
+ for (const d of recent) {
81
+ lines.push(` ${shortDay(d.day).padEnd(7)} ${bar(d.sparks, busiest).padEnd(BAR_WIDTH)} ${sparks(d.sparks).padStart(7)}`);
82
+ }
83
+ }
84
+ return lines.join('\n');
85
+ }
86
+ const deviceFlowDeps = { fetch: globalThis.fetch, sleep: (ms) => new Promise((r) => setTimeout(r, ms)) };
87
+ export async function runUsage(options = {}) {
88
+ const environment = resolveEnvironment({
89
+ explicit: options.env,
90
+ storedDefault: readDefaultEnvironment(),
91
+ });
92
+ const clientId = resolveAuth0ClientId(environment);
93
+ const token = await getAccessToken(environment, clientId, deviceFlowDeps);
94
+ const query = options.days === undefined ? '' : `?days=${encodeURIComponent(String(options.days))}`;
95
+ return apiGet(environment, `/api/cli/v1/sparks/usage${query}`, token, {
96
+ fetch: globalThis.fetch,
97
+ });
98
+ }
99
+ export const usageCommand = defineCommand({
100
+ meta: { name: 'usage', description: 'Show where this account\'s sparks went, and what Pro allowance is left.' },
101
+ args: {
102
+ days: { type: 'string', description: 'How many days to report on (default 30, max 90).' },
103
+ env: { type: 'string', description: 'Environment to query (dev, prod, local).' },
104
+ json: {
105
+ type: 'boolean',
106
+ description: 'Print the result as JSON on stdout; all progress goes to stderr.',
107
+ },
108
+ },
109
+ async run({ args }) {
110
+ const output = createOutput(args.json === true);
111
+ const days = args.days === undefined ? undefined : Number(args.days);
112
+ const report = await runUsage({ days, env: args.env });
113
+ output.info(formatUsageReport(report));
114
+ output.result({ ok: true, ...report });
115
+ },
116
+ });
117
+ //# sourceMappingURL=usage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usage.js","sourceRoot":"","sources":["../../src/commands/usage.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,19 @@
1
1
  export declare const verifyCommand: import("citty").CommandDef<{
2
2
  readonly timeout: {
3
3
  readonly type: "string";
4
- readonly description: "Settle time in ms (default 15000).";
4
+ readonly description: string;
5
+ };
6
+ readonly renderer: {
7
+ readonly type: "string";
8
+ readonly description: "Renderer to boot the game with: webgpu (default, what players run) or webgl.";
9
+ };
10
+ readonly fast: {
11
+ readonly type: "boolean";
12
+ readonly description: string;
13
+ };
14
+ readonly watch: {
15
+ readonly type: "boolean";
16
+ readonly description: string;
5
17
  };
6
18
  readonly json: {
7
19
  readonly type: "boolean";
@@ -5,11 +5,13 @@ import { spawn, spawnSync } from 'child_process';
5
5
  import { CliError } from '../errors.js';
6
6
  import { loadProjectContext, projectBin } from '../project/context.js';
7
7
  import { reserveCorsAllowedPort, CORS_ALLOWED_PORT_MIN, CORS_ALLOWED_PORT_MAX } from '../local-port.js';
8
+ import { browserRendererArgs, shouldRetrySoftwareGl } from '../browser-gl.js';
8
9
  import { runHeadlessCheck, DEFAULT_START_INTERACTION } from '../verify/browser.js';
9
10
  import { classifyVerifyRun } from '../verify/classify.js';
10
11
  import { waitForServer } from '../verify/wait-for-server.js';
11
12
  import { computeFingerprint } from '../publish/fingerprint.js';
12
13
  import { writeVerifyRecord, decideVerifyOutcome, recordAndGateVerify } from '../verify/result.js';
14
+ import { runVerifyWatch } from '../verify/iterate.js';
13
15
  import { createOutput } from '../output.js';
14
16
  const DEFAULT_SETTLE_MS = 15_000;
15
17
  /**
@@ -51,6 +53,19 @@ function watchForEarlyExit(child, getStderr) {
51
53
  });
52
54
  });
53
55
  }
56
+ /**
57
+ * `--renderer` picks the pipeline the game boots with (`?renderer=` — the engine honours it over
58
+ * every other preference source). The default is webgpu because that is what players get
59
+ * (`DEFAULT_RENDERER_TYPE`), so verify exercises the shipped path; `webgl` is the deliberate
60
+ * escape hatch for environments whose headless WebGPU cannot come up.
61
+ */
62
+ function parseRendererArg(raw) {
63
+ if (raw === undefined)
64
+ return 'webgpu';
65
+ if (raw === 'webgpu' || raw === 'webgl')
66
+ return raw;
67
+ throw new CliError(`--renderer must be "webgpu" or "webgl", got ${JSON.stringify(raw)}.`);
68
+ }
54
69
  /** `--timeout` is settle time in ms and must be a usable duration, not just "parses as a number":
55
70
  * NaN (e.g. `30s`) would settle for ~0ms and fail a healthy game; 0 or negative are meaningless. */
56
71
  function parseTimeoutArg(raw) {
@@ -62,10 +77,56 @@ function parseTimeoutArg(raw) {
62
77
  }
63
78
  return value;
64
79
  }
80
+ /**
81
+ * The one-line live-state summary, e.g.
82
+ * `Settled in 5.2s · 58 fps · playing · player at (12, 3, -8) · 41 bodies`.
83
+ * Only observed parts appear — an older engine yields just the settle duration.
84
+ */
85
+ function describeRunState(observations) {
86
+ const parts = [];
87
+ if (observations.settle) {
88
+ const seconds = (observations.settle.durationMs / 1000).toFixed(1);
89
+ parts.push(observations.settle.reason === 'stable'
90
+ ? `Settled in ${seconds}s`
91
+ : `Settle hit the ${seconds}s cap`);
92
+ }
93
+ const snapshot = observations.snapshot;
94
+ if (snapshot) {
95
+ if (snapshot.fps !== null)
96
+ parts.push(`${snapshot.fps} fps`);
97
+ if (snapshot.gameState !== null)
98
+ parts.push(snapshot.gameState);
99
+ if (snapshot.player !== null) {
100
+ const { x, y, z } = snapshot.player;
101
+ parts.push(`player at (${Math.round(x)}, ${Math.round(y)}, ${Math.round(z)})`);
102
+ }
103
+ if (snapshot.physics !== null)
104
+ parts.push(`${snapshot.physics.rigidBodyCount} bodies`);
105
+ }
106
+ return parts.length === 0 ? 'Run state: unobserved' : parts.join(' · ');
107
+ }
65
108
  export const verifyCommand = defineCommand({
66
109
  meta: { name: 'verify', description: 'Boot the game in a real browser and report what broke.' },
67
110
  args: {
68
- timeout: { type: 'string', description: `Settle time in ms (default ${DEFAULT_SETTLE_MS}).` },
111
+ timeout: {
112
+ type: 'string',
113
+ description: `Max settle time in ms (default ${DEFAULT_SETTLE_MS}); the run ends sooner `
114
+ + 'when the game provably reaches stable gameplay.',
115
+ },
116
+ renderer: {
117
+ type: 'string',
118
+ description: 'Renderer to boot the game with: webgpu (default, what players run) or webgl.',
119
+ },
120
+ fast: {
121
+ type: 'boolean',
122
+ description: 'Skip the screenshot pipeline for a faster verdict (default in --watch; '
123
+ + 'publish then has no thumbnail candidate from this run).',
124
+ },
125
+ watch: {
126
+ type: 'boolean',
127
+ description: 'Keep everything warm and re-verify on file change — incremental tsc, one '
128
+ + 'vite, one browser. Runs until Ctrl-C.',
129
+ },
69
130
  json: {
70
131
  type: 'boolean',
71
132
  description: 'Print the result as JSON on stdout; all progress goes to stderr.',
@@ -73,23 +134,35 @@ export const verifyCommand = defineCommand({
73
134
  },
74
135
  async run({ args }) {
75
136
  const settleMs = parseTimeoutArg(args.timeout);
137
+ const renderer = parseRendererArg(args.renderer);
138
+ const watch = args.watch === true;
139
+ // Watch mode defaults to fast: its iterations are for the agent's inner loop, where the
140
+ // screenshot is dead weight; `--no-fast` (or a final one-shot run) restores it.
141
+ const fast = watch ? args.fast !== false : args.fast === true;
76
142
  const context = loadProjectContext();
77
143
  const tsc = projectBin(context.root, 'tsc');
78
144
  const vite = projectBin(context.root, 'vite');
79
145
  const output = createOutput(args.json === true);
80
- output.info('Building…');
81
- const build = spawnSync(tsc, [], { cwd: context.root, stdio: 'inherit' });
82
- if (build.error) {
83
- throw new CliError(`Could not run the TypeScript compiler: ${build.error.message}`);
84
- }
85
- if (build.status !== 0) {
86
- // Nothing to verify: the emitted output the browser would load does not exist.
87
- throw new CliError('Build failed, so there is nothing to verify.', build.status ?? 1);
146
+ if (!watch) {
147
+ output.info('Building…');
148
+ const build = spawnSync(tsc, [], { cwd: context.root, stdio: 'inherit' });
149
+ if (build.error) {
150
+ throw new CliError(`Could not run the TypeScript compiler: ${build.error.message}`);
151
+ }
152
+ if (build.status !== 0) {
153
+ // Nothing to verify: the emitted output the browser would load does not exist.
154
+ throw new CliError('Build failed, so there is nothing to verify.', build.status ?? 1);
155
+ }
88
156
  }
89
157
  const artifactDir = path.join(context.root, '.bitmagic', 'verify');
90
158
  fs.mkdirSync(artifactDir, { recursive: true });
91
159
  const screenshotPath = path.join(artifactDir, 'screenshot.png');
92
160
  const consolePath = path.join(artifactDir, 'console.log');
161
+ if (fast) {
162
+ // A fast run captures no screenshot; a stale one lying next to a fresh result.json would
163
+ // be worse than none — publish would ship an image of some earlier state of the game.
164
+ fs.rmSync(screenshotPath, { force: true });
165
+ }
93
166
  const port = await freePort();
94
167
  let server;
95
168
  try {
@@ -105,19 +178,66 @@ export const verifyCommand = defineCommand({
105
178
  waitForServer(port, 30_000),
106
179
  watchForEarlyExit(server, () => viteStderr),
107
180
  ]);
181
+ if (watch) {
182
+ await runVerifyWatch({
183
+ root: context.root,
184
+ tsc,
185
+ artifactDir,
186
+ consolePath,
187
+ screenshotPath,
188
+ port,
189
+ renderer,
190
+ settleMs,
191
+ fast,
192
+ output,
193
+ describeRunState,
194
+ });
195
+ return;
196
+ }
108
197
  // No ?autostart=1: verify clicks the engine Play button like a real player would
109
198
  // (startInteraction), which both proves the menu came up AND that gameplay starts from it.
110
199
  // Autostart bypassed the menu — a game whose Play flow was broken could still pass.
111
- const observations = await runHeadlessCheck({
112
- url: `http://localhost:${port}/`,
200
+ // ?renderer= pins the pipeline so the verdict does not depend on a stale localStorage
201
+ // preference left by whoever opened this game in a browser last.
202
+ const checkOptions = {
203
+ url: `http://localhost:${port}/?renderer=${renderer}`,
113
204
  screenshotPath,
114
205
  consolePath,
115
206
  settleMs,
116
207
  startInteraction: DEFAULT_START_INTERACTION,
208
+ requestedRenderer: renderer,
209
+ captureScreenshot: !fast,
210
+ };
211
+ let observations = await runHeadlessCheck({
212
+ ...checkOptions,
213
+ browserArgs: browserRendererArgs(process.env, renderer),
117
214
  });
118
- const result = classifyVerifyRun(observations);
119
- const outcome = decideVerifyOutcome(result, computeFingerprint(context.root), new Date().toISOString());
120
- recordAndGateVerify(artifactDir, outcome, { consolePath, screenshotPath }, {
215
+ let result = classifyVerifyRun(observations);
216
+ let retriedWithSoftwareGl = false;
217
+ if (!result.ok && shouldRetrySoftwareGl(observations, process.env)) {
218
+ // A lost context is a machine/driver problem, not a game problem (browser-gl.ts) — one
219
+ // automatic software-GL re-run answers "is the game broken or is this GPU stack broken"
220
+ // without the creator having to know the env var exists. The retry's console goes to its
221
+ // own file so the first run's evidence survives.
222
+ output.info('The WebGL context was lost — retrying once with software rendering…');
223
+ observations = await runHeadlessCheck({
224
+ ...checkOptions,
225
+ consolePath: path.join(artifactDir, 'console.retry.log'),
226
+ browserArgs: browserRendererArgs({ ...process.env, BITMAGIC_BROWSER_SOFTWARE_GL: 'true' }, renderer),
227
+ });
228
+ result = classifyVerifyRun(observations);
229
+ retriedWithSoftwareGl = true;
230
+ }
231
+ const observedBackend = observations.renderer?.backend ?? 'unobserved';
232
+ output.info(`Renderer: ${renderer} requested → ${observedBackend} backend`);
233
+ output.info(describeRunState(observations));
234
+ const outcome = decideVerifyOutcome(result, computeFingerprint(context.root), new Date().toISOString(), {
235
+ renderer: observations.renderer,
236
+ settle: observations.settle,
237
+ snapshot: observations.snapshot,
238
+ ...(retriedWithSoftwareGl ? { retriedWithSoftwareGl } : {}),
239
+ });
240
+ recordAndGateVerify(artifactDir, outcome, { consolePath, ...(fast ? {} : { screenshotPath }) }, {
121
241
  write: writeVerifyRecord,
122
242
  // Under --json both streams are prose and belong on stderr; the result document below
123
243
  // is the only thing stdout carries.
@@ -131,9 +251,13 @@ export const verifyCommand = defineCommand({
131
251
  failures: outcome.record.failures,
132
252
  warnings: outcome.record.warnings,
133
253
  fingerprint: outcome.record.fingerprint,
254
+ renderer: outcome.record.renderer,
255
+ retriedWithSoftwareGl: outcome.record.retriedWithSoftwareGl,
256
+ settle: outcome.record.settle,
257
+ snapshot: outcome.record.snapshot,
134
258
  artifactDir,
135
259
  consolePath,
136
- screenshotPath,
260
+ screenshotPath: fast ? undefined : screenshotPath,
137
261
  });
138
262
  }
139
263
  finally {
@@ -1 +1 @@
1
- {"version":3,"file":"verify.js","sourceRoot":"","sources":["../../src/commands/verify.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"verify.js","sourceRoot":"","sources":["../../src/commands/verify.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,13 @@
1
- export interface WhoamiResponse {
2
- userId: string;
3
- roles: string[];
4
- }
1
+ import { type WhoamiResponse } from '../auth/subscribe-flow.js';
2
+ export type { WhoamiResponse };
3
+ /**
4
+ * `bitmagic whoami` — who the CLI is authenticated as, and whether that account
5
+ * may use it.
6
+ *
7
+ * The endpoint behind this sits outside the CLI's entitlement gate, so it
8
+ * answers for an unsubscribed creator too — that is the whole point: "why is
9
+ * this refusing me" needs an answer that is not itself refused.
10
+ */
5
11
  export declare function runWhoami(explicitEnv?: string): Promise<WhoamiResponse>;
6
12
  export declare const whoamiCommand: import("citty").CommandDef<{
7
13
  readonly env: {
@@ -2,19 +2,24 @@ import { defineCommand } from 'citty';
2
2
  import { resolveEnvironment, resolveAuth0ClientId } from '../config/environments.js';
3
3
  import { readDefaultEnvironment } from '../config/credentials.js';
4
4
  import { getAccessToken } from '../auth/session.js';
5
- import { apiGet } from '../http/client.js';
5
+ import { fetchWhoami, defaultSubscribeDeps, describeProStatus } from '../auth/subscribe-flow.js';
6
6
  import { createOutput } from '../output.js';
7
- const deviceFlowDeps = { fetch: globalThis.fetch, sleep: (ms) => new Promise((r) => setTimeout(r, ms)) };
7
+ /**
8
+ * `bitmagic whoami` — who the CLI is authenticated as, and whether that account
9
+ * may use it.
10
+ *
11
+ * The endpoint behind this sits outside the CLI's entitlement gate, so it
12
+ * answers for an unsubscribed creator too — that is the whole point: "why is
13
+ * this refusing me" needs an answer that is not itself refused.
14
+ */
8
15
  export async function runWhoami(explicitEnv) {
9
16
  const environment = resolveEnvironment({
10
17
  explicit: explicitEnv,
11
18
  storedDefault: readDefaultEnvironment(),
12
19
  });
13
20
  const clientId = resolveAuth0ClientId(environment);
14
- const token = await getAccessToken(environment, clientId, deviceFlowDeps);
15
- return apiGet(environment, '/api/cli/v1/whoami', token, {
16
- fetch: globalThis.fetch,
17
- });
21
+ const token = await getAccessToken(environment, clientId, defaultSubscribeDeps);
22
+ return fetchWhoami(environment, token, defaultSubscribeDeps);
18
23
  }
19
24
  export const whoamiCommand = defineCommand({
20
25
  meta: { name: 'whoami', description: 'Show the identity the CLI is authenticated as.' },
@@ -30,7 +35,17 @@ export const whoamiCommand = defineCommand({
30
35
  const me = await runWhoami(args.env);
31
36
  output.info(`user: ${me.userId}`);
32
37
  output.info(`roles: ${me.roles.length > 0 ? me.roles.join(', ') : '(none)'}`);
33
- output.result({ ok: true, userId: me.userId, roles: me.roles });
38
+ output.info(`pro: ${describeProStatus(me)}`);
39
+ if (!me.pro)
40
+ output.info(' Run `bitmagic subscribe` to use the CLI.');
41
+ output.result({
42
+ ok: true,
43
+ userId: me.userId,
44
+ roles: me.roles,
45
+ pro: me.pro,
46
+ proUntil: me.proUntil,
47
+ cancelAtPeriodEnd: me.cancelAtPeriodEnd ?? false,
48
+ });
34
49
  },
35
50
  });
36
51
  //# sourceMappingURL=whoami.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"whoami.js","sourceRoot":"","sources":["../../src/commands/whoami.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AACtC,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACrF,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAO5C,MAAM,cAAc,GAAG,EAAE,KAAK,EAAE,UAAU,CAAC,KAAK,EAAE,KAAK,EAAE,CAAC,EAAU,EAAE,EAAE,CAAC,IAAI,OAAO,CAAO,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC;AAEvH,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,WAAoB;IAClD,MAAM,WAAW,GAAG,kBAAkB,CAAC;QACrC,QAAQ,EAAE,WAAW;QACrB,aAAa,EAAE,sBAAsB,EAAE;KACxC,CAAC,CAAC;IACH,MAAM,QAAQ,GAAG,oBAAoB,CAAC,WAAW,CAAC,CAAC;IACnD,MAAM,KAAK,GAAG,MAAM,cAAc,CAAC,WAAW,EAAE,QAAQ,EAAE,cAAc,CAAC,CAAC;IAC1E,OAAO,MAAM,CAAiB,WAAW,EAAE,oBAAoB,EAAE,KAAK,EAAE;QACtE,KAAK,EAAE,UAAU,CAAC,KAAK;KACxB,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,MAAM,aAAa,GAAG,aAAa,CAAC;IACzC,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,gDAAgD,EAAE;IACvF,IAAI,EAAE;QACJ,GAAG,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,0CAA0C,EAAE;QAChF,IAAI,EAAE;YACJ,IAAI,EAAE,SAAS;YACf,WAAW,EAAE,kEAAkE;SAChF;KACF;IACD,KAAK,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE;QAChB,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;QAChD,MAAM,EAAE,GAAG,MAAM,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACrC,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC;QACnC,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC;QAC9E,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,EAAE,CAAC,CAAC;IAClE,CAAC;CACF,CAAC,CAAC"}
1
+ {"version":3,"file":"whoami.js","sourceRoot":"","sources":["../../src/commands/whoami.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AACtC,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACrF,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,oBAAoB,EAAE,iBAAiB,EAAuB,MAAM,2BAA2B,CAAC;AACtH,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAI5C;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,WAAoB;IAClD,MAAM,WAAW,GAAG,kBAAkB,CAAC;QACrC,QAAQ,EAAE,WAAW;QACrB,aAAa,EAAE,sBAAsB,EAAE;KACxC,CAAC,CAAC;IACH,MAAM,QAAQ,GAAG,oBAAoB,CAAC,WAAW,CAAC,CAAC;IACnD,MAAM,KAAK,GAAG,MAAM,cAAc,CAAC,WAAW,EAAE,QAAQ,EAAE,oBAAoB,CAAC,CAAC;IAChF,OAAO,WAAW,CAAC,WAAW,EAAE,KAAK,EAAE,oBAAoB,CAAC,CAAC;AAC/D,CAAC;AAED,MAAM,CAAC,MAAM,aAAa,GAAG,aAAa,CAAC;IACzC,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,gDAAgD,EAAE;IACvF,IAAI,EAAE;QACJ,GAAG,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,0CAA0C,EAAE;QAChF,IAAI,EAAE;YACJ,IAAI,EAAE,SAAS;YACf,WAAW,EAAE,kEAAkE;SAChF;KACF;IACD,KAAK,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE;QAChB,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;QAChD,MAAM,EAAE,GAAG,MAAM,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACrC,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC;QACnC,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC;QAE9E,MAAM,CAAC,IAAI,CAAC,UAAU,iBAAiB,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;QAC/C,IAAI,CAAC,EAAE,CAAC,GAAG;YAAE,MAAM,CAAC,IAAI,CAAC,iDAAiD,CAAC,CAAC;QAE5E,MAAM,CAAC,MAAM,CAAC;YACZ,EAAE,EAAE,IAAI;YACR,MAAM,EAAE,EAAE,CAAC,MAAM;YACjB,KAAK,EAAE,EAAE,CAAC,KAAK;YACf,GAAG,EAAE,EAAE,CAAC,GAAG;YACX,QAAQ,EAAE,EAAE,CAAC,QAAQ;YACrB,iBAAiB,EAAE,EAAE,CAAC,iBAAiB,IAAI,KAAK;SACjD,CAAC,CAAC;IACL,CAAC;CACF,CAAC,CAAC"}
@@ -10,7 +10,7 @@
10
10
  *
11
11
  * Neither half of that reaches api-server. The path it calls is game-play-agent's
12
12
  * (`mastra/routes.ts`); api-server serves `POST /api/cli/v1/uploads/signed-url`. And every route
13
- * under `/api/cli/v1/*` is Auth0 + Admin gated, while a browser page has no token — so pointing
13
+ * under `/api/cli/v1/*` is Auth0 + entitlement gated, while a browser page has no token — so pointing
14
14
  * `agentUrl` straight at api-server answers 401 to every upload, which is exactly what the first
15
15
  * end-to-end forge did: 14 archetypes dropped and the level bake failed after voxelizing all 392
16
16
  * chunks.
@@ -2,5 +2,15 @@ import type { Environment } from '../config/environments.js';
2
2
  export interface ApiDeps {
3
3
  fetch: typeof globalThis.fetch;
4
4
  }
5
+ type FetchResponse = Awaited<ReturnType<typeof globalThis.fetch>>;
6
+ /**
7
+ * PURE-ish: turn a 403 into the sentence that actually helps.
8
+ *
9
+ * Falls back to the generic wording whenever the body is missing, unparseable
10
+ * or from an older api-server — a CLI that throws while explaining a refusal
11
+ * would be worse than one that explains it vaguely.
12
+ */
13
+ export declare function proRefusalMessage(response: FetchResponse): Promise<string>;
5
14
  export declare function apiGet<T>(environment: Environment, path: string, accessToken: string, deps: ApiDeps): Promise<T>;
6
15
  export declare function apiPost<T>(environment: Environment, path: string, accessToken: string, body: unknown, deps: ApiDeps): Promise<T>;
16
+ export {};
@@ -1,4 +1,33 @@
1
1
  import { CliError } from '../errors.js';
2
+ /** '2026-08-03T00:00:00.000Z' -> '2026-08-03'; anything unparseable is dropped. */
3
+ function refusalDate(iso) {
4
+ if (typeof iso !== 'string' || iso.length < 10)
5
+ return null;
6
+ return Number.isNaN(new Date(iso).getTime()) ? null : iso.slice(0, 10);
7
+ }
8
+ /**
9
+ * PURE-ish: turn a 403 into the sentence that actually helps.
10
+ *
11
+ * Falls back to the generic wording whenever the body is missing, unparseable
12
+ * or from an older api-server — a CLI that throws while explaining a refusal
13
+ * would be worse than one that explains it vaguely.
14
+ */
15
+ export async function proRefusalMessage(response) {
16
+ let body = {};
17
+ try {
18
+ body = (await response.json());
19
+ }
20
+ catch {
21
+ // Keep the generic message.
22
+ }
23
+ if (body.code === 'pro_expired') {
24
+ const ended = refusalDate(body.proUntil);
25
+ return (`Your Bitmagic Pro subscription ${ended ? `ended on ${ended}` : 'has ended'}. ` +
26
+ 'Run `bitmagic subscribe` to start it again — your games and projects are untouched.');
27
+ }
28
+ return ('The Bitmagic CLI requires an active Bitmagic Pro subscription. ' +
29
+ 'Run `bitmagic subscribe` to start one.');
30
+ }
2
31
  /**
3
32
  * Shared status handling for every api-server call: the transport-failure/error-status split,
4
33
  * and the 401/403/503/other mapping. `apiGet` and `apiPost` differ only in the fetch `init` they
@@ -21,8 +50,15 @@ async function request(environment, path, accessToken, deps, init) {
21
50
  throw new CliError('Your session has expired. Run `bitmagic login`.');
22
51
  }
23
52
  if (response.status === 403) {
24
- throw new CliError('Your account is authenticated but does not have the Admin role, which the ' +
25
- 'Bitmagic CLI requires. Ask a Bitmagic admin to grant it.');
53
+ // The gate is Bitmagic Pro and nothing else, and `subscribe` is a command
54
+ // that finishes the job so name it rather than describing a web page the
55
+ // creator would have to go find.
56
+ //
57
+ // Read the body: api-server distinguishes a lapsed subscription from one
58
+ // that never existed, and telling a returning customer to "start one"
59
+ // reads as though their year of paying never happened — it sends them
60
+ // hunting for a problem with their account instead of renewing.
61
+ throw new CliError(await proRefusalMessage(response));
26
62
  }
27
63
  if (response.status === 503) {
28
64
  throw new CliError(`api-server reported this feature is unavailable in the "${environment.name}" environment.`);