@bitmagic/cli 0.1.55-dev.0 → 0.1.55-dev.2

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 (73) hide show
  1. package/README.md +45 -3
  2. package/dist/cli.d.ts +23 -1
  3. package/dist/cli.js +4 -0
  4. package/dist/cli.js.map +1 -1
  5. package/dist/commands/cover.d.ts +13 -2
  6. package/dist/commands/cover.js +21 -7
  7. package/dist/commands/cover.js.map +1 -1
  8. package/dist/commands/forge.d.ts +14 -0
  9. package/dist/commands/forge.js +41 -3
  10. package/dist/commands/forge.js.map +1 -1
  11. package/dist/commands/generate.d.ts +12 -0
  12. package/dist/commands/generate.js +77 -24
  13. package/dist/commands/generate.js.map +1 -1
  14. package/dist/commands/init.js +4 -0
  15. package/dist/commands/init.js.map +1 -1
  16. package/dist/commands/ios.d.ts +2 -0
  17. package/dist/commands/ios.js +333 -0
  18. package/dist/commands/ios.js.map +1 -0
  19. package/dist/commands/reference.d.ts +41 -0
  20. package/dist/commands/reference.js +398 -0
  21. package/dist/commands/reference.js.map +1 -0
  22. package/dist/generate/stream.d.ts +6 -0
  23. package/dist/generate/stream.js +2 -1
  24. package/dist/generate/stream.js.map +1 -1
  25. package/dist/ios/app-config.d.ts +54 -0
  26. package/dist/ios/app-config.js +109 -0
  27. package/dist/ios/app-config.js.map +1 -0
  28. package/dist/ios/collect-urls.d.ts +73 -0
  29. package/dist/ios/collect-urls.js +230 -0
  30. package/dist/ios/collect-urls.js.map +1 -0
  31. package/dist/ios/icon.d.ts +12 -0
  32. package/dist/ios/icon.js +70 -0
  33. package/dist/ios/icon.js.map +1 -0
  34. package/dist/ios/local-server.d.ts +11 -0
  35. package/dist/ios/local-server.js +88 -0
  36. package/dist/ios/local-server.js.map +1 -0
  37. package/dist/ios/paths.d.ts +21 -0
  38. package/dist/ios/paths.js +23 -0
  39. package/dist/ios/paths.js.map +1 -0
  40. package/dist/ios/record-validate.d.ts +25 -0
  41. package/dist/ios/record-validate.js +98 -0
  42. package/dist/ios/record-validate.js.map +1 -0
  43. package/dist/ios/transform.d.ts +47 -0
  44. package/dist/ios/transform.js +67 -0
  45. package/dist/ios/transform.js.map +1 -0
  46. package/dist/ios/vendor.d.ts +29 -0
  47. package/dist/ios/vendor.js +113 -0
  48. package/dist/ios/vendor.js.map +1 -0
  49. package/dist/ios/xcode-template.d.ts +11 -0
  50. package/dist/ios/xcode-template.js +615 -0
  51. package/dist/ios/xcode-template.js.map +1 -0
  52. package/dist/ios/xcodebuild.d.ts +71 -0
  53. package/dist/ios/xcodebuild.js +216 -0
  54. package/dist/ios/xcodebuild.js.map +1 -0
  55. package/dist/project/jobs.d.ts +1 -1
  56. package/dist/project/reference-file.d.ts +44 -0
  57. package/dist/project/reference-file.js +94 -0
  58. package/dist/project/reference-file.js.map +1 -0
  59. package/dist/project/reference.d.ts +78 -0
  60. package/dist/project/reference.js +175 -0
  61. package/dist/project/reference.js.map +1 -0
  62. package/dist/scaffold/project-files.js +19 -16
  63. package/dist/scaffold/project-files.js.map +1 -1
  64. package/dist/scaffold/reference-guidance.d.ts +20 -0
  65. package/dist/scaffold/reference-guidance.js +79 -0
  66. package/dist/scaffold/reference-guidance.js.map +1 -0
  67. package/dist/update/self-install.d.ts +15 -6
  68. package/dist/update/self-install.js +56 -15
  69. package/dist/update/self-install.js.map +1 -1
  70. package/dist/verify/browser.d.ts +8 -0
  71. package/dist/verify/browser.js +2 -0
  72. package/dist/verify/browser.js.map +1 -1
  73. package/package.json +4 -4
@@ -0,0 +1,71 @@
1
+ /** Refuses early, with the remedy, on machines that cannot build iOS at all. */
2
+ export declare function preflightXcodebuild(platform?: typeof process.platform): void;
3
+ export interface SimulatorBuildOptions {
4
+ xcodeprojDir: string;
5
+ derivedDataDir: string;
6
+ productName: string;
7
+ log: (message: string) => void;
8
+ }
9
+ export interface SimulatorBuildResult {
10
+ appPath: string;
11
+ /** True when the asset-catalog fallback fired and the app shipped without its icon. */
12
+ iconSkipped: boolean;
13
+ }
14
+ /**
15
+ * Build the unsigned Simulator .app and return its path.
16
+ *
17
+ * Deliberately the destination-free `-target -sdk iphonesimulator` form, not `-scheme` with a
18
+ * destination: destination resolution refuses outright on a Mac whose Xcode has no iOS DEVICE
19
+ * platform installed (a fresh install commonly has only simulators), while the target build
20
+ * needs nothing but the simulator SDK.
21
+ *
22
+ * The retry handles the other fresh-Xcode gap: actool refuses to compile an asset catalog when
23
+ * no installed simulator runtime matches the SDK (an SDK-bumped Xcode before the matching
24
+ * runtime download). The app then builds without its icon — cosmetic on a simulator — and the
25
+ * caller says so, with the real remedy.
26
+ */
27
+ export declare function buildSimulatorApp(options: SimulatorBuildOptions): SimulatorBuildResult;
28
+ export interface ArchiveOptions {
29
+ xcodeprojDir: string;
30
+ derivedDataDir: string;
31
+ distDir: string;
32
+ productName: string;
33
+ teamId: string;
34
+ log: (message: string) => void;
35
+ }
36
+ export interface ArchiveResult {
37
+ archivePath: string;
38
+ ipaPath: string;
39
+ }
40
+ /** Archive for device and export a signed .ipa. Signing errors surface with xcodebuild's tail. */
41
+ export declare function archiveAndExportIpa(options: ArchiveOptions): ArchiveResult;
42
+ export interface SubmitOptions {
43
+ archivePath: string;
44
+ distDir: string;
45
+ teamId: string;
46
+ keyPath: string;
47
+ keyId: string;
48
+ issuerId: string;
49
+ log: (message: string) => void;
50
+ }
51
+ /** Upload the archive to App Store Connect — the headless path now that altool is gone. */
52
+ export declare function uploadArchive(options: SubmitOptions): void;
53
+ interface SimctlDevice {
54
+ udid: string;
55
+ name: string;
56
+ state: string;
57
+ isAvailable: boolean;
58
+ }
59
+ /**
60
+ * The simulator to run on: whatever is already booted, else the newest available iPhone.
61
+ * Exported for tests; the runtime keys sort lexicographically into version order within the
62
+ * same major-number width, which is as much precision as "pick a recent phone" needs.
63
+ */
64
+ export declare function chooseSimulator(devicesByRuntime: Record<string, SimctlDevice[]>): {
65
+ udid: string;
66
+ name: string;
67
+ booted: boolean;
68
+ } | null;
69
+ /** Boot (if needed) a simulator, install the app, launch the bundle — the `--run` tail. */
70
+ export declare function runOnSimulator(appPath: string, bundleId: string, log: (m: string) => void): void;
71
+ export {};
@@ -0,0 +1,216 @@
1
+ /**
2
+ * Everything that shells out: xcodebuild (simulator build, archive, export, upload) and simctl
3
+ * (boot/install/launch for `--run`). Preflights name their remedy — an agent told "build
4
+ * failed" with no cause would start editing game code over a missing Xcode.
5
+ */
6
+ import { spawnSync } from 'child_process';
7
+ import * as fs from 'fs';
8
+ import * as path from 'path';
9
+ import { CliError, errnoCode } from '../errors.js';
10
+ import { TARGET_NAME } from './xcode-template.js';
11
+ const MAX_TOOL_OUTPUT = 64 * 1024 * 1024;
12
+ /** How much of a failed tool's output to replay — enough to carry the actual error. */
13
+ const FAILURE_TAIL_LINES = 60;
14
+ function run(command, args, options = {}) {
15
+ const result = spawnSync(command, args, {
16
+ cwd: options.cwd,
17
+ encoding: 'utf-8',
18
+ maxBuffer: MAX_TOOL_OUTPUT,
19
+ });
20
+ if (result.error) {
21
+ if (errnoCode(result.error) === 'ENOENT') {
22
+ throw new CliError(`\`${command}\` is not available. Building for iOS needs Xcode — install it from the `
23
+ + 'App Store, then run `sudo xcode-select -s /Applications/Xcode.app` and try again.', 3);
24
+ }
25
+ throw new CliError(`Could not run ${command}: ${result.error.message}`);
26
+ }
27
+ return { status: result.status ?? 1, output: `${result.stdout ?? ''}${result.stderr ?? ''}` };
28
+ }
29
+ function failWithTail(what, output, exitCode) {
30
+ const tail = output.split('\n').slice(-FAILURE_TAIL_LINES).join('\n');
31
+ throw new CliError(`${what} failed:\n${tail}`, exitCode);
32
+ }
33
+ /** Refuses early, with the remedy, on machines that cannot build iOS at all. */
34
+ export function preflightXcodebuild(platform = process.platform) {
35
+ if (platform !== 'darwin') {
36
+ throw new CliError('Building for iOS needs macOS with Xcode — this machine is not a Mac. '
37
+ + 'Run `bitmagic ios build` on a Mac with Xcode installed.', 3);
38
+ }
39
+ const probe = run('xcodebuild', ['-version']);
40
+ if (probe.status !== 0) {
41
+ throw new CliError('Xcode is present but `xcodebuild -version` failed. If you only have the Command Line '
42
+ + 'Tools, install full Xcode and run `sudo xcode-select -s /Applications/Xcode.app`.\n'
43
+ + probe.output.trim(), 3);
44
+ }
45
+ }
46
+ /**
47
+ * Build the unsigned Simulator .app and return its path.
48
+ *
49
+ * Deliberately the destination-free `-target -sdk iphonesimulator` form, not `-scheme` with a
50
+ * destination: destination resolution refuses outright on a Mac whose Xcode has no iOS DEVICE
51
+ * platform installed (a fresh install commonly has only simulators), while the target build
52
+ * needs nothing but the simulator SDK.
53
+ *
54
+ * The retry handles the other fresh-Xcode gap: actool refuses to compile an asset catalog when
55
+ * no installed simulator runtime matches the SDK (an SDK-bumped Xcode before the matching
56
+ * runtime download). The app then builds without its icon — cosmetic on a simulator — and the
57
+ * caller says so, with the real remedy.
58
+ */
59
+ export function buildSimulatorApp(options) {
60
+ options.log('Building the Simulator app (first build can take a couple of minutes)…');
61
+ const baseArgs = [
62
+ '-project', options.xcodeprojDir,
63
+ '-target', TARGET_NAME,
64
+ '-configuration', 'Release',
65
+ '-sdk', 'iphonesimulator',
66
+ 'build',
67
+ 'CODE_SIGNING_ALLOWED=NO',
68
+ `SYMROOT=${path.join(options.derivedDataDir, 'Build', 'Products')}`,
69
+ ];
70
+ let iconSkipped = false;
71
+ let result = run('xcodebuild', baseArgs);
72
+ if (result.status !== 0 && result.output.includes('No simulator runtime version')) {
73
+ iconSkipped = true;
74
+ result = run('xcodebuild', [
75
+ ...baseArgs,
76
+ 'EXCLUDED_SOURCE_FILE_NAMES=Assets.xcassets',
77
+ 'ASSETCATALOG_COMPILER_APPICON_NAME=',
78
+ ]);
79
+ }
80
+ if (result.status !== 0)
81
+ failWithTail('xcodebuild (simulator)', result.output, 1);
82
+ const appPath = path.join(options.derivedDataDir, 'Build', 'Products', 'Release-iphonesimulator', `${options.productName}.app`);
83
+ if (!fs.existsSync(appPath)) {
84
+ throw new CliError(`xcodebuild reported success but ${appPath} does not exist.`);
85
+ }
86
+ return { appPath, iconSkipped };
87
+ }
88
+ function exportOptionsPlist(teamId, destination) {
89
+ return `<?xml version="1.0" encoding="UTF-8"?>
90
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
91
+ <plist version="1.0">
92
+ <dict>
93
+ \t<key>method</key>
94
+ \t<string>app-store-connect</string>
95
+ \t<key>destination</key>
96
+ \t<string>${destination}</string>
97
+ \t<key>signingStyle</key>
98
+ \t<string>automatic</string>
99
+ \t<key>teamID</key>
100
+ \t<string>${teamId}</string>
101
+ </dict>
102
+ </plist>
103
+ `;
104
+ }
105
+ /** Archive for device and export a signed .ipa. Signing errors surface with xcodebuild's tail. */
106
+ export function archiveAndExportIpa(options) {
107
+ fs.mkdirSync(options.distDir, { recursive: true });
108
+ const archivePath = path.join(options.distDir, `${TARGET_NAME}.xcarchive`);
109
+ options.log('Archiving for devices (signing happens here)…');
110
+ const archived = run('xcodebuild', [
111
+ '-project', options.xcodeprojDir,
112
+ '-scheme', TARGET_NAME,
113
+ '-configuration', 'Release',
114
+ '-destination', 'generic/platform=iOS',
115
+ '-derivedDataPath', options.derivedDataDir,
116
+ '-archivePath', archivePath,
117
+ 'archive',
118
+ `DEVELOPMENT_TEAM=${options.teamId}`,
119
+ '-allowProvisioningUpdates',
120
+ ]);
121
+ if (archived.status !== 0)
122
+ failWithTail('xcodebuild archive', archived.output, 6);
123
+ const plistPath = path.join(options.distDir, 'export-options.plist');
124
+ fs.writeFileSync(plistPath, exportOptionsPlist(options.teamId, 'export'));
125
+ options.log('Exporting the signed .ipa…');
126
+ const exported = run('xcodebuild', [
127
+ '-exportArchive',
128
+ '-archivePath', archivePath,
129
+ '-exportOptionsPlist', plistPath,
130
+ '-exportPath', options.distDir,
131
+ '-allowProvisioningUpdates',
132
+ ]);
133
+ if (exported.status !== 0)
134
+ failWithTail('xcodebuild -exportArchive', exported.output, 6);
135
+ const ipaPath = path.join(options.distDir, `${options.productName}.ipa`);
136
+ if (!fs.existsSync(ipaPath)) {
137
+ throw new CliError(`Export reported success but ${ipaPath} does not exist.`, 6);
138
+ }
139
+ return { archivePath, ipaPath };
140
+ }
141
+ /** Upload the archive to App Store Connect — the headless path now that altool is gone. */
142
+ export function uploadArchive(options) {
143
+ const plistPath = path.join(options.distDir, 'upload-options.plist');
144
+ fs.mkdirSync(options.distDir, { recursive: true });
145
+ fs.writeFileSync(plistPath, exportOptionsPlist(options.teamId, 'upload'));
146
+ options.log('Uploading to App Store Connect (this can take several minutes)…');
147
+ const uploaded = run('xcodebuild', [
148
+ '-exportArchive',
149
+ '-archivePath', options.archivePath,
150
+ '-exportOptionsPlist', plistPath,
151
+ '-authenticationKeyPath', options.keyPath,
152
+ '-authenticationKeyID', options.keyId,
153
+ '-authenticationKeyIssuerID', options.issuerId,
154
+ '-allowProvisioningUpdates',
155
+ ]);
156
+ if (uploaded.status !== 0)
157
+ failWithTail('xcodebuild upload', uploaded.output, 6);
158
+ }
159
+ /**
160
+ * The simulator to run on: whatever is already booted, else the newest available iPhone.
161
+ * Exported for tests; the runtime keys sort lexicographically into version order within the
162
+ * same major-number width, which is as much precision as "pick a recent phone" needs.
163
+ */
164
+ export function chooseSimulator(devicesByRuntime) {
165
+ const runtimes = Object.keys(devicesByRuntime).sort().reverse();
166
+ for (const runtime of runtimes) {
167
+ const booted = devicesByRuntime[runtime].find((device) => device.state === 'Booted');
168
+ if (booted)
169
+ return { udid: booted.udid, name: booted.name, booted: true };
170
+ }
171
+ for (const runtime of runtimes) {
172
+ if (!runtime.includes('iOS'))
173
+ continue;
174
+ const phone = devicesByRuntime[runtime].find((device) => device.isAvailable && device.name.includes('iPhone'));
175
+ if (phone)
176
+ return { udid: phone.udid, name: phone.name, booted: false };
177
+ }
178
+ return null;
179
+ }
180
+ /** Boot (if needed) a simulator, install the app, launch the bundle — the `--run` tail. */
181
+ export function runOnSimulator(appPath, bundleId, log) {
182
+ const listed = run('xcrun', ['simctl', 'list', 'devices', '--json']);
183
+ if (listed.status !== 0)
184
+ failWithTail('simctl list', listed.output, 1);
185
+ let devices;
186
+ try {
187
+ devices = JSON.parse(listed.output).devices;
188
+ }
189
+ catch {
190
+ throw new CliError('Could not parse `simctl list devices --json` output.');
191
+ }
192
+ const chosen = chooseSimulator(devices);
193
+ if (chosen === null) {
194
+ throw new CliError('No iPhone simulator is available. Open Xcode → Settings → Platforms and install an iOS '
195
+ + 'simulator runtime, then run again.', 3);
196
+ }
197
+ if (!chosen.booted) {
198
+ log(`Booting ${chosen.name}…`);
199
+ const booted = run('xcrun', ['simctl', 'boot', chosen.udid]);
200
+ // "Unable to boot device in current state: Booted" is a benign race with the UI.
201
+ if (booted.status !== 0 && !booted.output.includes('current state: Booted')) {
202
+ failWithTail('simctl boot', booted.output, 1);
203
+ }
204
+ }
205
+ // Fronts the Simulator window so the creator actually sees the game; harmless when headless.
206
+ run('open', ['-a', 'Simulator']);
207
+ log(`Installing on ${chosen.name}…`);
208
+ const installed = run('xcrun', ['simctl', 'install', chosen.udid, appPath]);
209
+ if (installed.status !== 0)
210
+ failWithTail('simctl install', installed.output, 1);
211
+ const launched = run('xcrun', ['simctl', 'launch', chosen.udid, bundleId]);
212
+ if (launched.status !== 0)
213
+ failWithTail('simctl launch', launched.output, 1);
214
+ log(`Launched ${bundleId} on ${chosen.name}.`);
215
+ }
216
+ //# sourceMappingURL=xcodebuild.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"xcodebuild.js","sourceRoot":"","sources":["../../src/ios/xcodebuild.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  import type { ForgeProgressSnapshot } from '../forge/progress.js';
2
2
  import type { ForgeLayoutMap } from '@bitmagic/world-forger/pipeline/index.js';
3
3
  /** Which command is running. Shown as-is in the dev view, so these are creator-facing words. */
4
- export type JobKind = 'skybox' | 'background' | 'block-type' | 'sound' | 'image' | 'character' | 'animation' | 'prop' | 'vehicle' | 'forge' | 'cover' | 'hq' | 'judge' | 'upload' | 'model';
4
+ export type JobKind = 'skybox' | 'background' | 'block-type' | 'sound' | 'image' | 'character' | 'animation' | 'prop' | 'vehicle' | 'forge' | 'cover' | 'reference' | 'hq' | 'judge' | 'upload' | 'model';
5
5
  export interface JobRecord {
6
6
  jobId: string;
7
7
  kind: JobKind;
@@ -0,0 +1,44 @@
1
+ import type { CommandRunDeps } from '../commands/run-deps.js';
2
+ import { type ReferenceFlagArgs, type ResolvedReference } from './reference.js';
3
+ /** Far above any sane reference image; the cap exists so a mispassed video fails with a size, not a timeout. */
4
+ export declare const MAX_REFERENCE_FILE_BYTES: number;
5
+ /** The three formats the whole reference lane round-trips (api-server's forge fetch enforces the same set). */
6
+ export declare function sniffImageContentType(bytes: Buffer): {
7
+ contentType: string;
8
+ extension: string;
9
+ } | null;
10
+ /**
11
+ * Read a local image and re-host it under the game's bucket, returning the public URL.
12
+ *
13
+ * Every refusal happens BEFORE the network: an unreadable path, a non-image, an oversized file
14
+ * each cost nothing and name the fix. Content-addressed filename, so re-sending the same picture
15
+ * twice at least reads as the same picture in the bucket listing (the upload key still gets a
16
+ * server-side timestamp prefix, so this is legibility, not dedup).
17
+ */
18
+ export declare function uploadReferenceImageFile(options: {
19
+ root: string;
20
+ gameId: string;
21
+ filePath: string;
22
+ deps: CommandRunDeps;
23
+ }): Promise<{
24
+ url: string;
25
+ contentType: string;
26
+ }>;
27
+ /** The file-flag sibling of ReferenceFlagArgs — commands that take `--reference-image <file>`. */
28
+ export interface ReferenceFileFlagArgs extends ReferenceFlagArgs {
29
+ 'reference-image'?: string;
30
+ }
31
+ /**
32
+ * The full per-call resolution, file flag included: `--reference-image <file>` is uploaded and
33
+ * wins (it IS the caller choosing, like the URL flag), then the URL-flag/`--no-reference`/project
34
+ * precedence of {@link resolveReferenceImageUrl}. Both flags together is two answers to the same
35
+ * question and is refused.
36
+ */
37
+ export declare function resolveReferenceForCall(options: {
38
+ args: ReferenceFileFlagArgs;
39
+ root: string;
40
+ gameId: string;
41
+ deps: CommandRunDeps;
42
+ /** One line when a file was uploaded, so the agent sees the URL its picture became. */
43
+ log: (message: string) => void;
44
+ }): Promise<ResolvedReference>;
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Per-call reference images from LOCAL FILES — the "make a world like this [image]" case.
3
+ *
4
+ * The accepted project reference (reference.ts) answers "what does this game look like"; this
5
+ * module answers "what should THIS generation look like", when the creator hands their agent an
6
+ * actual picture. The generators upstream take only URLs, so a local file has to become one
7
+ * first: it is uploaded to the game's own bucket (the same signed-upload lane `bitmagic assets
8
+ * add` and `reference accept` use) and the resulting public URL rides the call as
9
+ * `referenceImageUrl` — outranking the accepted project reference exactly as an explicit
10
+ * `--reference-image-url` does, because both are the caller choosing.
11
+ *
12
+ * Nothing is recorded: a per-call reference is a one-off by definition. The command that wants
13
+ * the picture to BECOME the project's look is `bitmagic reference accept <file>`.
14
+ */
15
+ import { createHash } from 'crypto';
16
+ import * as fs from 'fs';
17
+ import { CliError } from '../errors.js';
18
+ import { resolveEnvironmentForCommand, resolveAuth0ClientId } from '../config/environments.js';
19
+ import { getAccessToken } from '../auth/session.js';
20
+ import { uploadProjectFile } from '../http/signed-upload.js';
21
+ import { resolveReferenceImageUrl } from './reference.js';
22
+ /** Far above any sane reference image; the cap exists so a mispassed video fails with a size, not a timeout. */
23
+ export const MAX_REFERENCE_FILE_BYTES = 20 * 1024 * 1024;
24
+ /** The three formats the whole reference lane round-trips (api-server's forge fetch enforces the same set). */
25
+ export function sniffImageContentType(bytes) {
26
+ if (bytes.length >= 4 && bytes[0] === 0x89 && bytes[1] === 0x50 && bytes[2] === 0x4e && bytes[3] === 0x47) {
27
+ return { contentType: 'image/png', extension: 'png' };
28
+ }
29
+ if (bytes.length >= 3 && bytes[0] === 0xff && bytes[1] === 0xd8 && bytes[2] === 0xff) {
30
+ return { contentType: 'image/jpeg', extension: 'jpg' };
31
+ }
32
+ if (bytes.length >= 12 && bytes.toString('ascii', 0, 4) === 'RIFF' && bytes.toString('ascii', 8, 12) === 'WEBP') {
33
+ return { contentType: 'image/webp', extension: 'webp' };
34
+ }
35
+ return null;
36
+ }
37
+ /**
38
+ * Read a local image and re-host it under the game's bucket, returning the public URL.
39
+ *
40
+ * Every refusal happens BEFORE the network: an unreadable path, a non-image, an oversized file
41
+ * each cost nothing and name the fix. Content-addressed filename, so re-sending the same picture
42
+ * twice at least reads as the same picture in the bucket listing (the upload key still gets a
43
+ * server-side timestamp prefix, so this is legibility, not dedup).
44
+ */
45
+ export async function uploadReferenceImageFile(options) {
46
+ const { root, gameId, filePath, deps } = options;
47
+ let bytes;
48
+ try {
49
+ bytes = fs.readFileSync(filePath);
50
+ }
51
+ catch (error) {
52
+ const message = error instanceof Error ? error.message : String(error);
53
+ throw new CliError(`Could not read the reference image ${filePath}: ${message}`);
54
+ }
55
+ const sniffed = sniffImageContentType(bytes);
56
+ if (!sniffed) {
57
+ throw new CliError(`${filePath} is not a PNG, JPEG or WebP image — those are the formats the generators accept as a reference.`);
58
+ }
59
+ if (bytes.byteLength > MAX_REFERENCE_FILE_BYTES) {
60
+ throw new CliError(`${filePath} is ${Math.round(bytes.byteLength / (1024 * 1024))}MB — a reference image must be at most ` +
61
+ `${Math.floor(MAX_REFERENCE_FILE_BYTES / (1024 * 1024))}MB.`);
62
+ }
63
+ const environment = resolveEnvironmentForCommand({ cwd: root, baseDir: deps.baseDir });
64
+ const clientId = resolveAuth0ClientId(environment);
65
+ const token = await getAccessToken(environment, clientId, { fetch: deps.fetch, sleep: deps.sleep, now: deps.now }, deps.baseDir);
66
+ const hash = createHash('sha256').update(bytes).digest('hex').slice(0, 8);
67
+ const upload = await uploadProjectFile(environment, token, {
68
+ gameId,
69
+ filename: `reference-${hash}.${sniffed.extension}`,
70
+ contentType: sniffed.contentType,
71
+ bytes,
72
+ }, { fetch: deps.fetch });
73
+ return { url: upload.url, contentType: sniffed.contentType };
74
+ }
75
+ /**
76
+ * The full per-call resolution, file flag included: `--reference-image <file>` is uploaded and
77
+ * wins (it IS the caller choosing, like the URL flag), then the URL-flag/`--no-reference`/project
78
+ * precedence of {@link resolveReferenceImageUrl}. Both flags together is two answers to the same
79
+ * question and is refused.
80
+ */
81
+ export async function resolveReferenceForCall(options) {
82
+ const { args, root, gameId, deps, log } = options;
83
+ const filePath = args['reference-image'];
84
+ if (filePath !== undefined && filePath !== '') {
85
+ if (args['reference-image-url'] !== undefined && args['reference-image-url'] !== '') {
86
+ throw new CliError('--reference-image and --reference-image-url both name the reference for this call — pass one or the other.');
87
+ }
88
+ const uploaded = await uploadReferenceImageFile({ root, gameId, filePath, deps });
89
+ log(`Using ${filePath} as the reference for this call: ${uploaded.url}`);
90
+ return { url: uploaded.url, source: 'flag' };
91
+ }
92
+ return resolveReferenceImageUrl(args, root);
93
+ }
94
+ //# sourceMappingURL=reference-file.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reference-file.js","sourceRoot":"","sources":["../../src/project/reference-file.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,78 @@
1
+ export declare const REFERENCE_DIR: string;
2
+ export declare function referenceDirPath(root: string): string;
3
+ export declare function acceptedReferencePath(root: string): string;
4
+ export declare function candidateImagePath(root: string, id: string): string;
5
+ /** One generated candidate — a picture the creator may accept, iterate from, or ignore. */
6
+ export interface CandidateRecord {
7
+ id: string;
8
+ /** The Asset Forger URL the generation returned. Short-lived; the accept re-hosts it. */
9
+ url: string;
10
+ /** The prompt actually sent, truncated — enough to recognise a candidate in a list. */
11
+ prompt: string;
12
+ /** The candidate this one was iterated from (`--from`), if any. */
13
+ from?: string;
14
+ /** An explicit `--reference-image-url` this candidate was steered by, if any. */
15
+ referenceImageUrl?: string;
16
+ createdAt: string;
17
+ }
18
+ /** The accepted reference — what every generator and forge inherits. */
19
+ export interface AcceptedReference {
20
+ /** The URL generators are given: the re-hosted public URL, or the source URL when re-hosting failed. */
21
+ url: string;
22
+ /** False when re-hosting failed and `url` is the (expiring) source URL — worth re-accepting. */
23
+ hosted: boolean;
24
+ /** What was accepted, before any re-hosting. */
25
+ sourceUrl: string;
26
+ /** Absent when a bare URL was accepted rather than a generated candidate. */
27
+ candidateId?: string;
28
+ /** The local copy, when bytes were available to keep. */
29
+ imagePath?: string;
30
+ acceptedAt: string;
31
+ }
32
+ export declare function readAcceptedReference(root: string): AcceptedReference | null;
33
+ export declare function writeAcceptedReference(root: string, accepted: AcceptedReference): void;
34
+ /** Remove the accepted reference. Candidates stay — clearing a decision is not deleting the work. */
35
+ export declare function clearAcceptedReference(root: string): boolean;
36
+ export declare function readCandidate(root: string, id: string): CandidateRecord | null;
37
+ export declare function writeCandidate(root: string, record: CandidateRecord): void;
38
+ /** Every readable candidate, newest first (ids sort chronologically by construction). */
39
+ export declare function listCandidates(root: string): CandidateRecord[];
40
+ /** `ref-<yyyymmdd-hhmmss>-<4 rand>` — short enough to type, sortable by creation time. */
41
+ export declare function newCandidateId(now?: Date): string;
42
+ /** Candidate ids become file names, so anything path-shaped is refused, never sanitised. */
43
+ export declare function isSafeCandidateId(id: string): boolean;
44
+ /** The two flags every reference-aware command declares (see REFERENCE_IMAGE_FLAG / NO_REFERENCE_FLAG). */
45
+ export interface ReferenceFlagArgs {
46
+ 'reference-image-url'?: string;
47
+ /** citty's negative boolean: `--no-reference` arrives as `false`; the flag has no positive form. */
48
+ reference?: boolean;
49
+ }
50
+ export interface ResolvedReference {
51
+ url?: string;
52
+ source: 'flag' | 'disabled' | 'project' | 'none';
53
+ }
54
+ /**
55
+ * The reference a generating command should send, in precedence order: an explicit
56
+ * `--reference-image-url` wins outright (it IS a reference choice), `--no-reference` disables,
57
+ * and otherwise the project's accepted reference applies — that inheritance being the whole
58
+ * point of accepting one.
59
+ */
60
+ export declare function resolveReferenceImageUrl(args: ReferenceFlagArgs, root: string): ResolvedReference;
61
+ /**
62
+ * The one line a command logs when the PROJECT's reference was applied — so the agent driving
63
+ * the CLI can see the default took effect (and knows the switch that turns it off). Explicit
64
+ * flags echo nothing: the caller chose, and repeating their own flag back is noise.
65
+ */
66
+ export declare function describeAppliedReference(resolved: ResolvedReference): string | null;
67
+ export declare const REFERENCE_IMAGE_FLAG: {
68
+ readonly type: "string";
69
+ readonly description: "Style/composition reference image URL forwarded to the image model. Overrides the accepted project reference for this call.";
70
+ };
71
+ export declare const NO_REFERENCE_FLAG: {
72
+ readonly type: "boolean";
73
+ readonly negativeDescription: "Do not apply the accepted project reference image (`bitmagic reference`) to this call.";
74
+ };
75
+ export declare const REFERENCE_FILE_FLAG: {
76
+ readonly type: "string";
77
+ readonly description: "A local image file (PNG/JPEG/WebP) to use as the reference for this call — uploaded to the game's storage first. Overrides the accepted project reference.";
78
+ };