@bitmagic/cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/dist/auth/device-flow.d.ts +18 -0
  2. package/dist/auth/device-flow.js +130 -0
  3. package/dist/auth/device-flow.js.map +1 -0
  4. package/dist/auth/session.d.ts +3 -0
  5. package/dist/auth/session.js +19 -0
  6. package/dist/auth/session.js.map +1 -0
  7. package/dist/bin.d.ts +2 -0
  8. package/dist/bin.js +18 -0
  9. package/dist/bin.js.map +1 -0
  10. package/dist/cli.d.ts +70 -0
  11. package/dist/cli.js +96 -0
  12. package/dist/cli.js.map +1 -0
  13. package/dist/commands/check.d.ts +1 -0
  14. package/dist/commands/check.js +22 -0
  15. package/dist/commands/check.js.map +1 -0
  16. package/dist/commands/dev.d.ts +6 -0
  17. package/dist/commands/dev.js +89 -0
  18. package/dist/commands/dev.js.map +1 -0
  19. package/dist/commands/generate.d.ts +62 -0
  20. package/dist/commands/generate.js +348 -0
  21. package/dist/commands/generate.js.map +1 -0
  22. package/dist/commands/init.d.ts +23 -0
  23. package/dist/commands/init.js +123 -0
  24. package/dist/commands/init.js.map +1 -0
  25. package/dist/commands/login.d.ts +6 -0
  26. package/dist/commands/login.js +36 -0
  27. package/dist/commands/login.js.map +1 -0
  28. package/dist/commands/logout.d.ts +6 -0
  29. package/dist/commands/logout.js +18 -0
  30. package/dist/commands/logout.js.map +1 -0
  31. package/dist/commands/verify.d.ts +6 -0
  32. package/dist/commands/verify.js +124 -0
  33. package/dist/commands/verify.js.map +1 -0
  34. package/dist/commands/whoami.d.ts +11 -0
  35. package/dist/commands/whoami.js +29 -0
  36. package/dist/commands/whoami.js.map +1 -0
  37. package/dist/config/credentials.d.ts +13 -0
  38. package/dist/config/credentials.js +114 -0
  39. package/dist/config/credentials.js.map +1 -0
  40. package/dist/config/environments.d.ts +21 -0
  41. package/dist/config/environments.js +67 -0
  42. package/dist/config/environments.js.map +1 -0
  43. package/dist/errors.d.ts +8 -0
  44. package/dist/errors.js +13 -0
  45. package/dist/errors.js.map +1 -0
  46. package/dist/generate/apply-patches.d.ts +7 -0
  47. package/dist/generate/apply-patches.js +97 -0
  48. package/dist/generate/apply-patches.js.map +1 -0
  49. package/dist/generate/stream.d.ts +33 -0
  50. package/dist/generate/stream.js +185 -0
  51. package/dist/generate/stream.js.map +1 -0
  52. package/dist/http/client.d.ts +6 -0
  53. package/dist/http/client.js +49 -0
  54. package/dist/http/client.js.map +1 -0
  55. package/dist/project/context.d.ts +18 -0
  56. package/dist/project/context.js +67 -0
  57. package/dist/project/context.js.map +1 -0
  58. package/dist/scaffold/aliases.d.ts +27 -0
  59. package/dist/scaffold/aliases.js +51 -0
  60. package/dist/scaffold/aliases.js.map +1 -0
  61. package/dist/scaffold/engine-download.d.ts +27 -0
  62. package/dist/scaffold/engine-download.js +78 -0
  63. package/dist/scaffold/engine-download.js.map +1 -0
  64. package/dist/scaffold/project-files.d.ts +15 -0
  65. package/dist/scaffold/project-files.js +225 -0
  66. package/dist/scaffold/project-files.js.map +1 -0
  67. package/dist/scaffold/project.d.ts +28 -0
  68. package/dist/scaffold/project.js +118 -0
  69. package/dist/scaffold/project.js.map +1 -0
  70. package/dist/verify/browser.d.ts +13 -0
  71. package/dist/verify/browser.js +75 -0
  72. package/dist/verify/browser.js.map +1 -0
  73. package/dist/verify/classify.d.ts +29 -0
  74. package/dist/verify/classify.js +59 -0
  75. package/dist/verify/classify.js.map +1 -0
  76. package/dist/verify/wait-for-server.d.ts +7 -0
  77. package/dist/verify/wait-for-server.js +25 -0
  78. package/dist/verify/wait-for-server.js.map +1 -0
  79. package/package.json +70 -0
@@ -0,0 +1,18 @@
1
+ import type { Environment } from '../config/environments.js';
2
+ import type { StoredCredentials } from '../config/credentials.js';
3
+ export interface DeviceCodeGrant {
4
+ deviceCode: string;
5
+ userCode: string;
6
+ verificationUri: string;
7
+ verificationUriComplete: string;
8
+ expiresInSeconds: number;
9
+ intervalSeconds: number;
10
+ }
11
+ export interface DeviceFlowDeps {
12
+ fetch: typeof globalThis.fetch;
13
+ sleep: (ms: number) => Promise<void>;
14
+ now?: () => number;
15
+ }
16
+ export declare function requestDeviceCode(environment: Environment, clientId: string, deps: DeviceFlowDeps): Promise<DeviceCodeGrant>;
17
+ export declare function pollForToken(environment: Environment, clientId: string, grant: DeviceCodeGrant, deps: DeviceFlowDeps): Promise<StoredCredentials>;
18
+ export declare function refreshAccessToken(environment: Environment, clientId: string, refreshToken: string, deps: DeviceFlowDeps): Promise<StoredCredentials>;
@@ -0,0 +1,130 @@
1
+ import { URLSearchParams } from 'url';
2
+ import { CliError } from '../errors.js';
3
+ const SCOPE = 'openid profile email offline_access';
4
+ const DEVICE_CODE_GRANT = 'urn:ietf:params:oauth:grant-type:device_code';
5
+ const SLOW_DOWN_INCREMENT_SECONDS = 5;
6
+ // RFC 8628 §3.2 makes `interval` OPTIONAL with a client-side default of 5 seconds. `expires_in`
7
+ // is REQUIRED by the RFC, but we default it too: a misbehaving Auth0 tenant omitting it must not
8
+ // make the wall-clock expiry guard in pollForToken compare against NaN and loop forever.
9
+ const DEFAULT_INTERVAL_SECONDS = 5;
10
+ const DEFAULT_EXPIRES_IN_SECONDS = 900;
11
+ /**
12
+ * Coerces `value` to a number, falling back to `fallback` unless the result is finite and
13
+ * positive. Deliberately not `Number(value) || fallback`: that reads as if it protects against
14
+ * `NaN` alone, but the field would also need to reject a legitimate `0`, which never applies to
15
+ * either interval or expires_in.
16
+ */
17
+ function positiveNumberOr(value, fallback) {
18
+ const n = Number(value);
19
+ return Number.isFinite(n) && n > 0 ? n : fallback;
20
+ }
21
+ async function readJson(response) {
22
+ try {
23
+ return await response.json();
24
+ }
25
+ catch {
26
+ return {};
27
+ }
28
+ }
29
+ function errorCode(body) {
30
+ if (typeof body !== 'object' || body === null)
31
+ return undefined;
32
+ const { error } = body;
33
+ return typeof error === 'string' ? error : undefined;
34
+ }
35
+ export async function requestDeviceCode(environment, clientId, deps) {
36
+ const response = await deps.fetch(`https://${environment.auth0Domain}/oauth/device/code`, {
37
+ method: 'POST',
38
+ headers: { 'content-type': 'application/x-www-form-urlencoded' },
39
+ body: new URLSearchParams({
40
+ client_id: clientId,
41
+ scope: SCOPE,
42
+ audience: environment.auth0Audience,
43
+ }).toString(),
44
+ });
45
+ const body = await readJson(response);
46
+ if (!response.ok) {
47
+ // The response body can echo configuration detail; report the code only.
48
+ throw new CliError(`Auth0 rejected the device-code request for "${environment.name}" ` +
49
+ `(${errorCode(body) ?? `HTTP ${response.status}`}). ` +
50
+ 'Check that the Auth0 application has the Device Code grant enabled.');
51
+ }
52
+ const grant = body;
53
+ return {
54
+ deviceCode: String(grant.device_code),
55
+ userCode: String(grant.user_code),
56
+ verificationUri: String(grant.verification_uri),
57
+ verificationUriComplete: String(grant.verification_uri_complete ?? grant.verification_uri),
58
+ expiresInSeconds: positiveNumberOr(grant.expires_in, DEFAULT_EXPIRES_IN_SECONDS),
59
+ intervalSeconds: positiveNumberOr(grant.interval, DEFAULT_INTERVAL_SECONDS),
60
+ };
61
+ }
62
+ function toCredentials(body, fallbackRefreshToken, nowMs) {
63
+ const token = body;
64
+ return {
65
+ accessToken: String(token.access_token),
66
+ refreshToken: typeof token.refresh_token === 'string' ? token.refresh_token : fallbackRefreshToken,
67
+ expiresAt: nowMs + Number(token.expires_in) * 1000,
68
+ };
69
+ }
70
+ export async function pollForToken(environment, clientId, grant, deps) {
71
+ const now = deps.now ?? Date.now;
72
+ const startedAt = now();
73
+ let intervalSeconds = grant.intervalSeconds;
74
+ for (;;) {
75
+ const response = await deps.fetch(`https://${environment.auth0Domain}/oauth/token`, {
76
+ method: 'POST',
77
+ headers: { 'content-type': 'application/x-www-form-urlencoded' },
78
+ body: new URLSearchParams({
79
+ grant_type: DEVICE_CODE_GRANT,
80
+ device_code: grant.deviceCode,
81
+ client_id: clientId,
82
+ }).toString(),
83
+ });
84
+ const body = await readJson(response);
85
+ if (response.ok) {
86
+ const credentials = toCredentials(body, '', now());
87
+ if (!credentials.refreshToken) {
88
+ // offline_access should always yield one. Storing an empty string here would look like
89
+ // a successful login and then fail every later refresh with no obvious cause.
90
+ throw new CliError('Auth0 returned no refresh token. Check that the Auth0 application allows the ' +
91
+ 'offline_access scope for the Device Code grant.');
92
+ }
93
+ return credentials;
94
+ }
95
+ const code = errorCode(body);
96
+ if (code === 'access_denied') {
97
+ throw new CliError('Authorization was denied. Nothing has been stored.');
98
+ }
99
+ if (code === 'expired_token') {
100
+ throw new CliError('The login request expired before it was approved. Run `bitmagic login` again.');
101
+ }
102
+ if (code !== 'authorization_pending' && code !== 'slow_down') {
103
+ throw new CliError(`Auth0 rejected the login (${code ?? `HTTP ${response.status}`}). Run \`bitmagic login\` again.`);
104
+ }
105
+ if (code === 'slow_down') {
106
+ intervalSeconds += SLOW_DOWN_INCREMENT_SECONDS;
107
+ }
108
+ if (now() - startedAt >= grant.expiresInSeconds * 1000) {
109
+ throw new CliError('The login request expired before it was approved. Run `bitmagic login` again.');
110
+ }
111
+ await deps.sleep(intervalSeconds * 1000);
112
+ }
113
+ }
114
+ export async function refreshAccessToken(environment, clientId, refreshToken, deps) {
115
+ const now = deps.now ?? Date.now;
116
+ const response = await deps.fetch(`https://${environment.auth0Domain}/oauth/token`, {
117
+ method: 'POST',
118
+ headers: { 'content-type': 'application/x-www-form-urlencoded' },
119
+ body: new URLSearchParams({
120
+ grant_type: 'refresh_token',
121
+ client_id: clientId,
122
+ refresh_token: refreshToken,
123
+ }).toString(),
124
+ });
125
+ if (!response.ok) {
126
+ throw new CliError('Your saved session is no longer valid. Run `bitmagic login`.');
127
+ }
128
+ return toCredentials(await readJson(response), refreshToken, now());
129
+ }
130
+ //# sourceMappingURL=device-flow.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"device-flow.js","sourceRoot":"","sources":["../../src/auth/device-flow.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { Environment } from '../config/environments.js';
2
+ import { type DeviceFlowDeps } from './device-flow.js';
3
+ export declare function getAccessToken(environment: Environment, clientId: string, deps: DeviceFlowDeps, baseDir?: string): Promise<string>;
@@ -0,0 +1,19 @@
1
+ import { CliError } from '../errors.js';
2
+ import { readCredentials, writeCredentials, defaultBaseDir } from '../config/credentials.js';
3
+ import { refreshAccessToken } from './device-flow.js';
4
+ /** Refresh slightly early so a request cannot expire mid-flight. */
5
+ const EXPIRY_SKEW_MS = 60_000;
6
+ export async function getAccessToken(environment, clientId, deps, baseDir = defaultBaseDir()) {
7
+ const stored = readCredentials(environment.name, baseDir);
8
+ if (!stored) {
9
+ throw new CliError(`You are not logged in to the "${environment.name}" environment. Run \`bitmagic login\`.`);
10
+ }
11
+ const now = deps.now ?? Date.now;
12
+ if (stored.expiresAt - EXPIRY_SKEW_MS > now()) {
13
+ return stored.accessToken;
14
+ }
15
+ const refreshed = await refreshAccessToken(environment, clientId, stored.refreshToken, deps);
16
+ writeCredentials(environment.name, refreshed, baseDir);
17
+ return refreshed.accessToken;
18
+ }
19
+ //# sourceMappingURL=session.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.js","sourceRoot":"","sources":["../../src/auth/session.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAExC,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC7F,OAAO,EAAE,kBAAkB,EAAuB,MAAM,kBAAkB,CAAC;AAE3E,oEAAoE;AACpE,MAAM,cAAc,GAAG,MAAM,CAAC;AAE9B,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,WAAwB,EACxB,QAAgB,EAChB,IAAoB,EACpB,UAAkB,cAAc,EAAE;IAElC,MAAM,MAAM,GAAG,eAAe,CAAC,WAAW,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAC1D,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,QAAQ,CAChB,iCAAiC,WAAW,CAAC,IAAI,wCAAwC,CAC1F,CAAC;IACJ,CAAC;IAED,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC;IACjC,IAAI,MAAM,CAAC,SAAS,GAAG,cAAc,GAAG,GAAG,EAAE,EAAE,CAAC;QAC9C,OAAO,MAAM,CAAC,WAAW,CAAC;IAC5B,CAAC;IAED,MAAM,SAAS,GAAG,MAAM,kBAAkB,CAAC,WAAW,EAAE,QAAQ,EAAE,MAAM,CAAC,YAAY,EAAE,IAAI,CAAC,CAAC;IAC7F,gBAAgB,CAAC,WAAW,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;IACvD,OAAO,SAAS,CAAC,WAAW,CAAC;AAC/B,CAAC"}
package/dist/bin.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/bin.js ADDED
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env node
2
+ import { runMain } from 'citty';
3
+ import { main } from './cli.js';
4
+ import { CliError } from './errors.js';
5
+ // The actual executable entrypoint — this is what package.json's `bin` points at. It is
6
+ // deliberately the only place that calls `runMain`, and it does so unconditionally: unlike the
7
+ // old path-comparison guard in cli.ts (which broke when invoked through a symlink, e.g. every
8
+ // `npm i -g` install), there is no heuristic here to get wrong. Importing `./cli.js` never runs
9
+ // the CLI by itself, so this file is the sole source of the "actually run it" side effect.
10
+ runMain(main).catch((error) => {
11
+ if (error instanceof CliError) {
12
+ console.error(error.message);
13
+ process.exit(error.exitCode);
14
+ }
15
+ console.error(error instanceof Error ? error.message : String(error));
16
+ process.exit(1);
17
+ });
18
+ //# sourceMappingURL=bin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bin.js","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAChC,OAAO,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAChC,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEvC,wFAAwF;AACxF,+FAA+F;AAC/F,8FAA8F;AAC9F,gGAAgG;AAChG,2FAA2F;AAC3F,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;IACrC,IAAI,KAAK,YAAY,QAAQ,EAAE,CAAC;QAC9B,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC7B,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IAC/B,CAAC;IACD,OAAO,CAAC,KAAK,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IACtE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,70 @@
1
+ import type { ArgsDef, CommandDef } from 'citty';
2
+ export declare function withCleanErrors<T extends ArgsDef>(command: CommandDef<T>): CommandDef<T>;
3
+ /**
4
+ * Structural check for tests: has this command actually passed through `withCleanErrors`? A leaf
5
+ * command (one with its own `run`) is wrapped when that `run` is in `wrappedRuns`. A grouping
6
+ * command with no `run` of its own (see the doc comment above) is wrapped when it has at least
7
+ * one subcommand and every one of them is — recursively, so nesting can go arbitrarily deep.
8
+ */
9
+ export declare function isWrappedWithCleanErrors(command: CommandDef<ArgsDef>): boolean;
10
+ declare const rawCommands: {
11
+ login: CommandDef<{
12
+ readonly env: {
13
+ readonly type: "string";
14
+ readonly description: "Environment to log in to (dev, prod, local).";
15
+ };
16
+ }>;
17
+ logout: CommandDef<{
18
+ readonly env: {
19
+ readonly type: "string";
20
+ readonly description: "Environment to log out of (dev, prod, local).";
21
+ };
22
+ }>;
23
+ whoami: CommandDef<{
24
+ readonly env: {
25
+ readonly type: "string";
26
+ readonly description: "Environment to query (dev, prod, local).";
27
+ };
28
+ }>;
29
+ init: CommandDef<{
30
+ readonly dir: {
31
+ readonly type: "positional";
32
+ readonly required: false;
33
+ readonly description: "Directory to create the project in.";
34
+ };
35
+ readonly template: {
36
+ readonly type: "string";
37
+ readonly description: "Template id (see templates in the engine bundle).";
38
+ };
39
+ readonly name: {
40
+ readonly type: "string";
41
+ readonly description: "Game name.";
42
+ };
43
+ readonly env: {
44
+ readonly type: "string";
45
+ readonly description: "Environment (dev, prod, local).";
46
+ };
47
+ readonly force: {
48
+ readonly type: "boolean";
49
+ readonly description: "Scaffold into a non-empty directory.";
50
+ };
51
+ }>;
52
+ check: CommandDef<ArgsDef>;
53
+ dev: CommandDef<{
54
+ readonly port: {
55
+ readonly type: "string";
56
+ readonly description: "Port to serve on (default 3010).";
57
+ };
58
+ }>;
59
+ verify: CommandDef<{
60
+ readonly timeout: {
61
+ readonly type: "string";
62
+ readonly description: "Settle time in ms (default 15000).";
63
+ };
64
+ }>;
65
+ generate: CommandDef<ArgsDef>;
66
+ };
67
+ type CommandName = keyof typeof rawCommands;
68
+ export declare const subCommands: Record<CommandName, CommandDef<ArgsDef>>;
69
+ export declare const main: CommandDef<ArgsDef>;
70
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,96 @@
1
+ import { defineCommand } from 'citty';
2
+ import { CliError } from './errors.js';
3
+ import { loginCommand } from './commands/login.js';
4
+ import { logoutCommand } from './commands/logout.js';
5
+ import { whoamiCommand } from './commands/whoami.js';
6
+ import { initCommand } from './commands/init.js';
7
+ import { checkCommand } from './commands/check.js';
8
+ import { devCommand } from './commands/dev.js';
9
+ import { verifyCommand } from './commands/verify.js';
10
+ import { generateCommand } from './commands/generate.js';
11
+ // citty's runMain() catches every error thrown from a command's `run()` internally and calls
12
+ // process.exit() itself — it never rejects the promise it returns, so the `.catch()` below
13
+ // never runs for those. For anything that isn't citty's own internal CLIError class, its catch
14
+ // does `console.error(error, '\n')`, which for our CliError (an Error instance) prints the full
15
+ // stack trace. Wrap each command's `run` so a CliError is formatted into a clean, single-line
16
+ // message and the process exits before citty ever gets a chance to dump it.
17
+ // Tracks which `run` functions have already been through `withCleanErrors`, so tests can assert
18
+ // "every entry of subCommands is wrapped" structurally, by reference, instead of maintaining a
19
+ // separate list of command names that could drift out of sync with the real map.
20
+ const wrappedRuns = new WeakSet();
21
+ export function withCleanErrors(command) {
22
+ const originalRun = command.run;
23
+ const wrapped = { ...command };
24
+ if (originalRun) {
25
+ wrapped.run = async (context) => {
26
+ try {
27
+ return await originalRun(context);
28
+ }
29
+ catch (error) {
30
+ if (error instanceof CliError) {
31
+ console.error(error.message);
32
+ process.exit(error.exitCode);
33
+ }
34
+ // Not one of ours: let citty's default handling show it, stack trace included — that's
35
+ // a genuine bug, not a user-facing condition we've designed a message for.
36
+ throw error;
37
+ }
38
+ };
39
+ wrappedRuns.add(wrapped.run);
40
+ }
41
+ // A command that only groups subcommands (e.g. `generate`, whose actual `run`s live one level
42
+ // down on `generate skybox`, `generate sound`, ...) has no `run` of its own to wrap here. Left
43
+ // unrecursed, a CliError thrown from one of those would clear this command's own (nonexistent)
44
+ // run untouched and hit citty's default handling instead — a raw stack trace, the exact thing
45
+ // this wrapper exists to prevent. Recurse so every depth gets the same clean single-line
46
+ // treatment. Every subCommands value in this CLI is a plain synchronous object literal (never a
47
+ // citty Promise/function Resolvable), so narrowing it that way here is safe for this codebase's
48
+ // actual usage even though citty's own type allows more.
49
+ if (command.subCommands && typeof command.subCommands === 'object') {
50
+ const rawSubCommands = command.subCommands;
51
+ wrapped.subCommands = Object.fromEntries(Object.entries(rawSubCommands).map(([name, sub]) => [name, withCleanErrors(sub)]));
52
+ }
53
+ return wrapped;
54
+ }
55
+ /**
56
+ * Structural check for tests: has this command actually passed through `withCleanErrors`? A leaf
57
+ * command (one with its own `run`) is wrapped when that `run` is in `wrappedRuns`. A grouping
58
+ * command with no `run` of its own (see the doc comment above) is wrapped when it has at least
59
+ * one subcommand and every one of them is — recursively, so nesting can go arbitrarily deep.
60
+ */
61
+ export function isWrappedWithCleanErrors(command) {
62
+ if (command.run !== undefined) {
63
+ return wrappedRuns.has(command.run);
64
+ }
65
+ if (command.subCommands && typeof command.subCommands === 'object') {
66
+ const entries = Object.values(command.subCommands);
67
+ return entries.length > 0 && entries.every((sub) => isWrappedWithCleanErrors(sub));
68
+ }
69
+ return false;
70
+ }
71
+ // Wrapping happens once here, over the whole map, rather than per-entry at each call site —
72
+ // a future command added to `rawCommands` is wrapped automatically, so there is no per-command
73
+ // place where someone could forget to apply `withCleanErrors`.
74
+ const rawCommands = {
75
+ login: loginCommand,
76
+ logout: logoutCommand,
77
+ whoami: whoamiCommand,
78
+ init: initCommand,
79
+ check: checkCommand,
80
+ dev: devCommand,
81
+ verify: verifyCommand,
82
+ generate: generateCommand,
83
+ };
84
+ export const subCommands = Object.fromEntries(Object.entries(rawCommands).map(([name, command]) => [name, withCleanErrors(command)]));
85
+ // This module only builds `main` and exports it (along with `subCommands` and `withCleanErrors`)
86
+ // for `bin.ts` to run and for cli.test.ts to import — it must not call `runMain` or otherwise
87
+ // have a side effect when imported, e.g. by cli.test.ts, which needs `subCommands` and
88
+ // `withCleanErrors` without triggering a real citty run against the test runner's own argv.
89
+ export const main = defineCommand({
90
+ meta: {
91
+ name: 'bitmagic',
92
+ description: 'Build Bitmagic games from your own agent tool.',
93
+ },
94
+ subCommands,
95
+ });
96
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export declare const checkCommand: import("citty").CommandDef<import("citty").ArgsDef>;
@@ -0,0 +1,22 @@
1
+ import { defineCommand } from 'citty';
2
+ import { spawnSync } from 'child_process';
3
+ import { CliError } from '../errors.js';
4
+ import { loadProjectContext, projectBin } from '../project/context.js';
5
+ export const checkCommand = defineCommand({
6
+ meta: { name: 'check', description: 'Type-check the game against the vendored engine.' },
7
+ run() {
8
+ const context = loadProjectContext();
9
+ const tsc = projectBin(context.root, 'tsc');
10
+ // stdio inherit: tsc's own diagnostics are what the creator (or their agent) needs to read.
11
+ const result = spawnSync(tsc, ['--noEmit'], { cwd: context.root, stdio: 'inherit' });
12
+ if (result.error) {
13
+ throw new CliError(`Could not run the TypeScript compiler: ${result.error.message}`);
14
+ }
15
+ if (result.status !== 0) {
16
+ // tsc already printed the diagnostics; add nothing but the exit code.
17
+ throw new CliError('Type check failed.', result.status ?? 1);
18
+ }
19
+ console.log('Type check passed.');
20
+ },
21
+ });
22
+ //# sourceMappingURL=check.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check.js","sourceRoot":"","sources":["../../src/commands/check.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC1C,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AACxC,OAAO,EAAE,kBAAkB,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAEvE,MAAM,CAAC,MAAM,YAAY,GAAG,aAAa,CAAC;IACxC,IAAI,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,WAAW,EAAE,kDAAkD,EAAE;IACxF,GAAG;QACD,MAAM,OAAO,GAAG,kBAAkB,EAAE,CAAC;QACrC,MAAM,GAAG,GAAG,UAAU,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QAC5C,4FAA4F;QAC5F,MAAM,MAAM,GAAG,SAAS,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,EAAE,EAAE,GAAG,EAAE,OAAO,CAAC,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC;QACrF,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;YACjB,MAAM,IAAI,QAAQ,CAAC,0CAA0C,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QACvF,CAAC;QACD,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACxB,sEAAsE;YACtE,MAAM,IAAI,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC;QAC/D,CAAC;QACD,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC;IACpC,CAAC;CACF,CAAC,CAAC"}
@@ -0,0 +1,6 @@
1
+ export declare const devCommand: import("citty").CommandDef<{
2
+ readonly port: {
3
+ readonly type: "string";
4
+ readonly description: "Port to serve on (default 3010).";
5
+ };
6
+ }>;
@@ -0,0 +1,89 @@
1
+ import { defineCommand } from 'citty';
2
+ import { spawn, spawnSync } from 'child_process';
3
+ import { CliError } from '../errors.js';
4
+ import { loadProjectContext, projectBin } from '../project/context.js';
5
+ import { waitForServer } from '../verify/wait-for-server.js';
6
+ const DEFAULT_PORT = 3010;
7
+ export const devCommand = defineCommand({
8
+ meta: { name: 'dev', description: 'Build the game, then serve it with live rebuilds.' },
9
+ args: {
10
+ port: { type: 'string', description: `Port to serve on (default ${DEFAULT_PORT}).` },
11
+ },
12
+ async run({ args }) {
13
+ const context = loadProjectContext();
14
+ const tsc = projectBin(context.root, 'tsc');
15
+ const vite = projectBin(context.root, 'vite');
16
+ const port = args.port ?? String(DEFAULT_PORT);
17
+ // Build to completion first. Every alias resolves into dist/, so serving before the first
18
+ // emit 404s every module — a failure that reads as "the engine is broken", not "not built".
19
+ console.log('Building…');
20
+ const build = spawnSync(tsc, [], { cwd: context.root, stdio: 'inherit' });
21
+ if (build.error) {
22
+ throw new CliError(`Could not run the TypeScript compiler: ${build.error.message}`);
23
+ }
24
+ if (build.status !== 0) {
25
+ throw new CliError('Build failed. Fix the errors above, then run `bitmagic dev` again.', build.status ?? 1);
26
+ }
27
+ const children = [
28
+ {
29
+ name: 'tsc --watch',
30
+ process: spawn(tsc, ['--watch', '--preserveWatchOutput'], { cwd: context.root, stdio: 'inherit' }),
31
+ },
32
+ {
33
+ name: 'vite',
34
+ process: spawn(vite, ['--port', port, '--strictPort'], { cwd: context.root, stdio: 'inherit' }),
35
+ },
36
+ ];
37
+ let shuttingDown = false;
38
+ let requestedByUser = false;
39
+ const shutdown = () => {
40
+ if (shuttingDown)
41
+ return;
42
+ shuttingDown = true;
43
+ for (const child of children)
44
+ child.process.kill('SIGTERM');
45
+ };
46
+ process.on('SIGINT', () => {
47
+ requestedByUser = true;
48
+ shutdown();
49
+ });
50
+ process.on('SIGTERM', () => {
51
+ requestedByUser = true;
52
+ shutdown();
53
+ });
54
+ // Print the URL ourselves once vite actually answers, rather than relying on vite's own
55
+ // startup banner — that output is vite's to change, this is ours to keep stable.
56
+ void waitForServer(Number(port), 30_000)
57
+ .then(() => console.log(`\nServing at http://localhost:${port}/`))
58
+ .catch(() => {
59
+ // The child-exit watcher below reports a vite that never comes up; this poll simply has
60
+ // nothing to announce in that case.
61
+ });
62
+ // If a child exits on its own (not because we asked it to via shutdown()), that's a failure:
63
+ // take the other child down too rather than leaving a half-dead stack that serves stale
64
+ // output, and say which one died so an agent gating on the exit code knows where to look.
65
+ const failure = await new Promise((resolve) => {
66
+ let exitedCount = 0;
67
+ for (const child of children) {
68
+ child.process.on('error', (error) => {
69
+ shutdown();
70
+ resolve({ name: child.name, detail: `could not start: ${error.message}` });
71
+ });
72
+ child.process.on('exit', (code, signal) => {
73
+ exitedCount += 1;
74
+ if (!requestedByUser && !shuttingDown) {
75
+ shutdown();
76
+ resolve({ name: child.name, detail: `exited with code ${code}${signal ? ` (signal ${signal})` : ''}` });
77
+ }
78
+ else if (exitedCount === children.length) {
79
+ resolve(undefined);
80
+ }
81
+ });
82
+ }
83
+ });
84
+ if (failure) {
85
+ throw new CliError(`${failure.name} ${failure.detail}. \`bitmagic dev\` stopped.`);
86
+ }
87
+ },
88
+ });
89
+ //# sourceMappingURL=dev.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dev.js","sourceRoot":"","sources":["../../src/commands/dev.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,62 @@
1
+ export interface SkyboxArgs {
2
+ description: string;
3
+ referenceImageUrl?: string;
4
+ }
5
+ export declare function buildSkyboxBody(args: SkyboxArgs): Record<string, unknown>;
6
+ export interface SoundArgs {
7
+ prompt: string;
8
+ duration?: string;
9
+ promptInfluence?: string;
10
+ loop?: boolean;
11
+ }
12
+ export declare function buildSoundBody(args: SoundArgs): Record<string, unknown>;
13
+ export interface ImageArgs {
14
+ name: string;
15
+ prompt: string;
16
+ width?: string;
17
+ height?: string;
18
+ referenceImageUrl?: string;
19
+ }
20
+ export declare function buildImageBody(args: ImageArgs): Record<string, unknown>;
21
+ export interface CharacterArgs {
22
+ name: string;
23
+ prompt: string;
24
+ applyToPlayer?: boolean;
25
+ headScale?: string;
26
+ }
27
+ export declare function buildCharacterBody(args: CharacterArgs): Record<string, unknown>;
28
+ export interface AnimationArgs {
29
+ description: string;
30
+ length?: string;
31
+ cfgScale?: string;
32
+ seed?: string;
33
+ footIk?: boolean;
34
+ inPlace?: boolean;
35
+ loop?: boolean;
36
+ }
37
+ export declare function buildAnimationBody(args: AnimationArgs): Record<string, unknown>;
38
+ export interface GenerateRunDeps {
39
+ fetch: typeof globalThis.fetch;
40
+ sleep: (ms: number) => Promise<void>;
41
+ now?: () => number;
42
+ /** Test-only override; production omits it and loadProjectContext walks up from process.cwd(). */
43
+ startDir?: string;
44
+ /** Test-only override; production omits it and getAccessToken/readDefaultEnvironment read ~/.bitmagic. */
45
+ baseDir?: string;
46
+ log: (message: string) => void;
47
+ }
48
+ /**
49
+ * Runs one `bitmagic generate <subcommand>`: requests the asset, then applies the returned
50
+ * patches to world.json. Every failure path says explicitly whether world.json changed, because
51
+ * a creator (or the agent driving this CLI) who does not know has to go look for themselves:
52
+ *
53
+ * - Anything that fails before a `result`/`error` frame arrives (auth, ownership, spark balance,
54
+ * a network error, ...) — world.json is untouched; the message says so.
55
+ * - A terminal `success: false` outcome — asset-core's `fail()` always carries an empty patch
56
+ * array (see shared/asset-core/src/result.ts), so this is also an unchanged world.json.
57
+ * - A successful outcome whose patches could not be written (a corrupt world.json, an
58
+ * unrecognised patch shape) — distinct from the above: the Forger may already have billed for
59
+ * this generation, so the message says the asset was NOT lost, only not yet applied.
60
+ */
61
+ export declare function runAssetGeneration(type: string, label: string, body: Record<string, unknown>, deps?: GenerateRunDeps): Promise<void>;
62
+ export declare const generateCommand: import("citty").CommandDef<import("citty").ArgsDef>;