@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.
- package/README.md +41 -3
- package/dist/auth/subscribe-flow.d.ts +59 -0
- package/dist/auth/subscribe-flow.js +102 -0
- package/dist/auth/subscribe-flow.js.map +1 -0
- package/dist/browser-gl.d.ts +23 -0
- package/dist/browser-gl.js +50 -0
- package/dist/browser-gl.js.map +1 -1
- package/dist/cli.d.ts +41 -1
- package/dist/cli.js +4 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/login.js +33 -5
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/subscribe.d.ts +14 -0
- package/dist/commands/subscribe.js +60 -0
- package/dist/commands/subscribe.js.map +1 -0
- package/dist/commands/usage.d.ts +76 -0
- package/dist/commands/usage.js +117 -0
- package/dist/commands/usage.js.map +1 -0
- package/dist/commands/verify.d.ts +13 -1
- package/dist/commands/verify.js +139 -15
- package/dist/commands/verify.js.map +1 -1
- package/dist/commands/whoami.d.ts +10 -4
- package/dist/commands/whoami.js +22 -7
- package/dist/commands/whoami.js.map +1 -1
- package/dist/forge/upload-proxy.js +1 -1
- package/dist/http/client.d.ts +10 -0
- package/dist/http/client.js +38 -2
- package/dist/http/client.js.map +1 -1
- package/dist/scaffold/claude-settings.js +4 -0
- package/dist/scaffold/claude-settings.js.map +1 -1
- package/dist/scaffold/project-files.d.ts +9 -0
- package/dist/scaffold/project-files.js +70 -9
- package/dist/scaffold/project-files.js.map +1 -1
- package/dist/scaffold/project.js +2 -1
- package/dist/scaffold/project.js.map +1 -1
- package/dist/verify/browser.d.ts +37 -1
- package/dist/verify/browser.js +389 -97
- package/dist/verify/browser.js.map +1 -1
- package/dist/verify/classify.d.ts +75 -2
- package/dist/verify/classify.js +48 -2
- package/dist/verify/classify.js.map +1 -1
- package/dist/verify/iterate.d.ts +47 -0
- package/dist/verify/iterate.js +162 -0
- package/dist/verify/iterate.js.map +1 -0
- package/dist/verify/result.d.ts +16 -3
- package/dist/verify/result.js +13 -3
- package/dist/verify/result.js.map +1 -1
- package/dist/verify/settle.d.ts +60 -0
- package/dist/verify/settle.js +90 -0
- package/dist/verify/settle.js.map +1 -0
- 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:
|
|
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";
|
package/dist/commands/verify.js
CHANGED
|
@@ -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: {
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
-
|
|
112
|
-
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AACtC,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,EAAE,KAAK,EAAE,SAAS,EAAqB,MAAM,eAAe,CAAC;AACpE,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AACxC,OAAO,EAAE,kBAAkB,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACvE,OAAO,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AACxG,OAAO,EAAE,gBAAgB,EAAE,yBAAyB,EAAE,MAAM,sBAAsB,CAAC;AACnF,OAAO,EAAE,iBAAiB,
|
|
1
|
+
{"version":3,"file":"verify.js","sourceRoot":"","sources":["../../src/commands/verify.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
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: {
|
package/dist/commands/whoami.js
CHANGED
|
@@ -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 {
|
|
5
|
+
import { fetchWhoami, defaultSubscribeDeps, describeProStatus } from '../auth/subscribe-flow.js';
|
|
6
6
|
import { createOutput } from '../output.js';
|
|
7
|
-
|
|
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,
|
|
15
|
-
return
|
|
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.
|
|
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,
|
|
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 +
|
|
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.
|
package/dist/http/client.d.ts
CHANGED
|
@@ -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 {};
|
package/dist/http/client.js
CHANGED
|
@@ -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
|
-
|
|
25
|
-
|
|
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.`);
|