@bitmagic/cli 0.1.65-dev.24 → 0.1.65-dev.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/README.md +18 -106
  2. package/dist/assets/materials.d.ts +0 -9
  3. package/dist/assets/materials.js +2 -10
  4. package/dist/assets/materials.js.map +1 -1
  5. package/dist/cli.d.ts +0 -1
  6. package/dist/cli.js +0 -2
  7. package/dist/cli.js.map +1 -1
  8. package/dist/commands/forge.js +23 -39
  9. package/dist/commands/forge.js.map +1 -1
  10. package/dist/commands/generate.d.ts +13 -31
  11. package/dist/commands/generate.js +56 -48
  12. package/dist/commands/generate.js.map +1 -1
  13. package/dist/commands/trailer.js +1 -1
  14. package/dist/commands/trailer.js.map +1 -1
  15. package/dist/editor/animation-preview-page.js +1 -88
  16. package/dist/editor/animation-preview-page.js.map +1 -1
  17. package/dist/editor/asset-detail-panel.d.ts +2 -2
  18. package/dist/editor/asset-detail-panel.js +0 -21
  19. package/dist/editor/asset-detail-panel.js.map +1 -1
  20. package/dist/editor/asset-viewer.js +8 -31
  21. package/dist/editor/asset-viewer.js.map +1 -1
  22. package/dist/editor/shell-page.js +1 -63
  23. package/dist/editor/shell-page.js.map +1 -1
  24. package/dist/mobile/asset-hosts.d.ts +9 -7
  25. package/dist/mobile/asset-hosts.js +16 -7
  26. package/dist/mobile/asset-hosts.js.map +1 -1
  27. package/dist/scaffold/blender-level-skill-files.js +2 -3
  28. package/dist/scaffold/blender-level-skill-files.js.map +1 -1
  29. package/dist/scaffold/claude-settings.js +0 -4
  30. package/dist/scaffold/claude-settings.js.map +1 -1
  31. package/dist/scaffold/project-files.js +12 -18
  32. package/dist/scaffold/project-files.js.map +1 -1
  33. package/dist/scaffold/project.js +1 -4
  34. package/dist/scaffold/project.js.map +1 -1
  35. package/dist/scaffold/skill-files.d.ts +5 -32
  36. package/dist/scaffold/skill-files.js +63 -102
  37. package/dist/scaffold/skill-files.js.map +1 -1
  38. package/dist/telemetry/creator-prompt.d.ts +2 -18
  39. package/dist/telemetry/creator-prompt.js +3 -24
  40. package/dist/telemetry/creator-prompt.js.map +1 -1
  41. package/dist/telemetry/report.js +16 -31
  42. package/dist/telemetry/report.js.map +1 -1
  43. package/package.json +4 -4
  44. package/assets/skills/trailer-craft.md +0 -180
  45. package/dist/commands/character.d.ts +0 -18
  46. package/dist/commands/character.js +0 -268
  47. package/dist/commands/character.js.map +0 -1
  48. package/dist/forge/prune-store.d.ts +0 -46
  49. package/dist/forge/prune-store.js +0 -86
  50. package/dist/forge/prune-store.js.map +0 -1
  51. package/dist/forge/remote-store.d.ts +0 -36
  52. package/dist/forge/remote-store.js +0 -113
  53. package/dist/forge/remote-store.js.map +0 -1
  54. package/dist/generate/character-body.d.ts +0 -79
  55. package/dist/generate/character-body.js +0 -100
  56. package/dist/generate/character-body.js.map +0 -1
  57. package/dist/project/character-search.d.ts +0 -30
  58. package/dist/project/character-search.js +0 -82
  59. package/dist/project/character-search.js.map +0 -1
  60. package/dist/scaffold/asset-pipeline-skill-files.d.ts +0 -42
  61. package/dist/scaffold/asset-pipeline-skill-files.js +0 -207
  62. package/dist/scaffold/asset-pipeline-skill-files.js.map +0 -1
@@ -1,268 +0,0 @@
1
- /**
2
- * `bitmagic character` — make one, look through the ready-made ones, or add one.
3
- *
4
- * Its own noun group rather than three flags on `bitmagic generate character`,
5
- * because the shelf and the forge are two different requests that happened to
6
- * share a command:
7
- *
8
- * generate costs sparks, makes a new body, may substitute a close stored one
9
- * search free, read-only, shows what the shelf has and how close it is
10
- * add free, puts a stored body in the game, never forges
11
- *
12
- * The old surface expressed "add" as `generate character --library-only`, a
13
- * command that generates nothing, and it carried two flags that existed only to
14
- * be refused on that path (`--head-scale` was accepted, reported and dropped;
15
- * `--mesh-model` was rejected outright). Both refusals disappear here, because a
16
- * command that never forges simply does not offer the flags that pick how a
17
- * forge runs.
18
- *
19
- * `search` exists because no similarity floor can do this job. Measured against
20
- * the live index, genuine matches score 0.624-0.831 while humanoid prompts the
21
- * shelf cannot answer score 0.552-0.698 — overlapping, so a threshold that keeps
22
- * the good ones keeps "a humanoid bee" for "a beekeeper" too. A person reading
23
- * three captions settles in a second what a number cannot settle at all.
24
- */
25
- import { defineCommand } from 'citty';
26
- import { CliError } from '../errors.js';
27
- import { createOutput } from '../output.js';
28
- import { apiGet } from '../http/client.js';
29
- import { resolveArtStyle } from '../project/art-style.js';
30
- import { findCachedCandidate, readCharacterSearch, writeCharacterSearch, } from '../project/character-search.js';
31
- import { ORIGINAL_PROMPT_FLAG, withParent } from '../telemetry/creator-prompt.js';
32
- import { JSON_FLAG, resolveRun, runAssetGeneration } from './generate.js';
33
- import { APPLY_TO_PLAYER_ARG, buildCharacterBody, defaultVoxelizeForProject, FRESH_ARG, HEAD_SCALE_ARG, LIBRARY_ARG, MESH_MODEL_ARG, NAME_ARG, PROMPT_ARG, VOXELIZE_ARG, } from '../generate/character-body.js';
34
- const generateCommand = defineCommand({
35
- meta: {
36
- name: 'generate',
37
- description: 'Forge a new rigged character and add it to world.json assets. Costs sparks.',
38
- },
39
- args: {
40
- name: NAME_ARG,
41
- prompt: PROMPT_ARG,
42
- 'apply-to-player': APPLY_TO_PLAYER_ARG,
43
- 'head-scale': HEAD_SCALE_ARG,
44
- fresh: FRESH_ARG,
45
- library: LIBRARY_ARG,
46
- voxelize: VOXELIZE_ARG,
47
- 'mesh-model': MESH_MODEL_ARG,
48
- 'original-prompt': ORIGINAL_PROMPT_FLAG,
49
- json: JSON_FLAG,
50
- },
51
- async run({ args }) {
52
- const body = buildCharacterBody({
53
- name: args.name,
54
- prompt: args.prompt,
55
- applyToPlayer: args['apply-to-player'],
56
- headScale: args['head-scale'],
57
- fresh: args.fresh,
58
- library: args.library,
59
- voxelize: args.voxelize ?? defaultVoxelizeForProject(),
60
- meshModel: args['mesh-model'],
61
- });
62
- await runAssetGeneration('character', 'character', withParent(body), args.json === true);
63
- },
64
- });
65
- /** `62%` — the only form a similarity is ever shown in, so the table lines up. */
66
- function percent(value) {
67
- return `${Math.round(value * 100)}%`;
68
- }
69
- function renderCandidates(candidates, threshold, log) {
70
- const width = Math.max(2, ...candidates.map((row) => row.sid.length));
71
- log('');
72
- log(` # ${'id'.padEnd(width)} match auto description`);
73
- candidates.forEach((row, index) => {
74
- // The "auto" column is why the floor is reported rather than applied: it
75
- // says which of these automatic substitution would have taken, so a creator
76
- // can see both that a body cleared the bar and that a weaker one exists.
77
- const auto = row.similarity >= threshold ? ' yes' : ' -';
78
- const label = row.subject ?? row.description ?? '';
79
- log(` ${String(index + 1).padStart(1)} ${row.sid.padEnd(width)} ${percent(row.similarity).padStart(5)} ${auto} ${label.slice(0, 60)}`);
80
- });
81
- log('');
82
- }
83
- const searchCommand = defineCommand({
84
- meta: {
85
- name: 'search',
86
- description: 'List the ready-made characters closest to a description. Free; changes nothing.',
87
- },
88
- args: {
89
- prompt: {
90
- type: 'string',
91
- required: true,
92
- description: 'What the character should look like.',
93
- },
94
- limit: {
95
- type: 'string',
96
- description: 'How many candidates to list, 1-16. Default 5.',
97
- },
98
- representation: {
99
- type: 'string',
100
- description: "Which shelf to search: 'voxel' or 'lowpoly'. Omit to follow the project's art style.",
101
- },
102
- json: JSON_FLAG,
103
- },
104
- async run({ args }) {
105
- const json = args.json === true;
106
- const { output, runDeps, context, environment, token } = await resolveRun(json);
107
- const representation = (() => {
108
- const asked = args.representation;
109
- // `resolveArtStyle` also reports a game.json value that is neither word,
110
- // which is the same note every other art-style-aware command prints.
111
- if (asked === undefined)
112
- return resolveArtStyle(context.root, output.info) === 'low-poly' ? 'lowpoly' : 'voxel';
113
- if (asked !== 'voxel' && asked !== 'lowpoly') {
114
- throw new CliError("--representation must be 'voxel' or 'lowpoly'.", 1, 'invalid_flag');
115
- }
116
- return asked;
117
- })();
118
- const query = new URLSearchParams({
119
- gameId: context.metadata.gameId,
120
- prompt: args.prompt,
121
- representation,
122
- });
123
- if (args.limit !== undefined)
124
- query.set('limit', args.limit);
125
- const response = await apiGet(environment, `/api/cli/v1/characters/search?${query.toString()}`, token, runDeps);
126
- // Written even when empty: "the shelf has nothing like this" is a result a
127
- // re-run should not have to rediscover, and an empty cache makes `add --id`
128
- // say "search first" rather than "no such id".
129
- writeCharacterSearch(context.root, {
130
- prompt: response.prompt,
131
- representation,
132
- threshold: response.threshold,
133
- searchedAt: new Date().toISOString(),
134
- candidates: response.candidates,
135
- });
136
- if (response.candidates.length === 0) {
137
- const nearest = response.topSimilarity !== undefined
138
- ? ` The closest was ${percent(response.topSimilarity)}, which had no usable file.`
139
- : '';
140
- output.info(`No ready-made ${representation} character matches "${response.prompt}".${nearest}`);
141
- output.info(`Forge one instead: bitmagic character generate --name <name> --prompt "${response.prompt}"`);
142
- output.result({ ok: true, kind: 'character search', representation, threshold: response.threshold, candidates: [], ...(response.reason ? { reason: response.reason } : {}) });
143
- return;
144
- }
145
- output.info(`${response.candidates.length} ready-made ${representation} character(s) for "${response.prompt}":`);
146
- renderCandidates(response.candidates, response.threshold, output.info);
147
- const best = response.candidates[0];
148
- if (best.similarity < response.threshold) {
149
- // Said plainly rather than hidden: on this corpus a confident-looking
150
- // caption can still be the wrong character, and the creator is the only
151
- // one who can tell.
152
- output.info(`None clears the ${percent(response.threshold)} bar automatic substitution requires — read the captions before adding one.`);
153
- }
154
- output.info(`Add one: bitmagic character add --id ${best.sid} --name <name>`);
155
- output.result({
156
- ok: true,
157
- kind: 'character search',
158
- representation,
159
- threshold: response.threshold,
160
- candidates: response.candidates,
161
- });
162
- },
163
- });
164
- /**
165
- * What `bitmagic character add` will ask for, from its flags and the cached search.
166
- *
167
- * Pure, and separated from the command so the branch that matters — which of the
168
- * two forms was asked for, and whether the cache can answer it — is testable
169
- * without a project, a token or a network. It returns the character args rather
170
- * than a body so the shared `buildCharacterBody` still owns the wire shape.
171
- */
172
- export function resolveAddRequest(args) {
173
- if (args.id === undefined) {
174
- if (args.prompt === undefined) {
175
- throw new CliError('Pass --id (from `bitmagic character search`) or --prompt to take the closest match.', 1, 'invalid_flag');
176
- }
177
- // The no-id form: the server searches with no floor and adds the best usable
178
- // body, or fails without charging. Deliberately still available — "any of
179
- // these will do" is a real request, and it is one call rather than two.
180
- return {
181
- name: args.name,
182
- prompt: args.prompt,
183
- applyToPlayer: args.applyToPlayer,
184
- libraryOnly: true,
185
- voxelize: defaultVoxelizeForProject(),
186
- };
187
- }
188
- if (args.record === null) {
189
- throw new CliError('No character search to pick from. Run `bitmagic character search --prompt "..."` first.', 1, 'no_search');
190
- }
191
- const candidate = findCachedCandidate(args.record, args.id);
192
- if (candidate === null) {
193
- const known = args.record.candidates.map((row) => row.sid).join(', ');
194
- throw new CliError(`"${args.id}" was not in the last character search`
195
- + (known === '' ? ' (it found nothing).' : `. It offered: ${known}.`)
196
- + ' Run `bitmagic character search` again to pick from a fresh list.', 1, 'unknown_candidate');
197
- }
198
- return {
199
- name: args.name,
200
- // The CREATOR's brief, not the library row's own caption: this is what a
201
- // regeneration re-drives, so it must describe what they asked for.
202
- prompt: args.prompt ?? args.record.prompt,
203
- applyToPlayer: args.applyToPlayer,
204
- libraryPick: {
205
- sid: candidate.sid,
206
- assetUrl: candidate.assetUrl,
207
- similarity: candidate.similarity,
208
- ...(candidate.description ? { description: candidate.description } : {}),
209
- },
210
- // The shelf that produced the row decides the medium, not the project's art
211
- // style at the moment of adding: mixing them would hand a low-poly game a
212
- // pile of cubes.
213
- voxelize: args.record.representation === 'voxel',
214
- };
215
- }
216
- const addCommand = defineCommand({
217
- meta: {
218
- name: 'add',
219
- description: 'Add a ready-made character from the library to world.json assets. Free; never forges.',
220
- },
221
- args: {
222
- name: NAME_ARG,
223
- id: {
224
- type: 'string',
225
- description: 'The id of a candidate from `bitmagic character search`. Omit to take the closest match to --prompt.',
226
- },
227
- prompt: {
228
- type: 'string',
229
- description: 'What the character should look like. Required without --id; with --id it defaults to the search that found it.',
230
- },
231
- 'apply-to-player': APPLY_TO_PLAYER_ARG,
232
- 'original-prompt': ORIGINAL_PROMPT_FLAG,
233
- json: JSON_FLAG,
234
- },
235
- async run({ args }) {
236
- const json = args.json === true;
237
- const { context } = await resolveRun(json);
238
- const output = createOutput(json);
239
- // Only the --id form consults the cache; the --prompt form is a fresh ask.
240
- const record = args.id === undefined ? null : readCharacterSearch(context.root);
241
- const request = resolveAddRequest({
242
- name: args.name,
243
- id: args.id,
244
- prompt: args.prompt,
245
- applyToPlayer: args['apply-to-player'],
246
- record,
247
- });
248
- if (request.libraryPick) {
249
- // The search's own date, not an expiry: age is worth seeing, and the
250
- // catalogue only changes when somebody promotes to it.
251
- output.info(`Adding ${request.libraryPick.sid} (${percent(request.libraryPick.similarity)} match`
252
- + `${record?.searchedAt ? `, from the search on ${record.searchedAt.slice(0, 10)}` : ''}).`);
253
- }
254
- await runAssetGeneration('character', 'character', withParent(buildCharacterBody(request)), json);
255
- },
256
- });
257
- export const characterCommand = defineCommand({
258
- meta: {
259
- name: 'character',
260
- description: 'Forge a character, search the ready-made ones, or add one to your game.',
261
- },
262
- subCommands: {
263
- generate: generateCommand,
264
- search: searchCommand,
265
- add: addCommand,
266
- },
267
- });
268
- //# sourceMappingURL=character.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"character.js","sourceRoot":"","sources":["../../src/commands/character.ts"],"names":[],"mappings":"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"}
@@ -1,46 +0,0 @@
1
- /**
2
- * Forge-store housekeeping — the job directories under `.bitmagic/forge` that
3
- * nothing will read again are removed once a forge has landed.
4
- *
5
- * Every edit is a NEW job and the level keeps only its newest job's id, so a
6
- * level edited a dozen times leaves a dozen finished job directories behind
7
- * (dev-games/alien held 13, 6 MB, after one afternoon). A job is kept while
8
- * anything could still want it:
9
- *
10
- * - an asset of the world names it as its forge job (`parentForgeJobId`, the
11
- * same reading `--edit` uses) — that is the parent an edit reuses bakes from;
12
- * - it is the job that just ran;
13
- * - it never finished (no `persisted` record) — an unfinished job is what
14
- * `--resume` continues, and its local bakes are the progress a resume keeps.
15
- *
16
- * The server's copy of every job is untouched: it is what a resume or an edit
17
- * from another machine reads, and its lifetime is the server's call. The
18
- * resolution is pure over world.json and a listing, so the rules are testable
19
- * without a project on disk; the removal is best-effort, since a directory
20
- * that will not delete must never fail a forge that has already landed.
21
- */
22
- import { type JsonObject } from '../project/world-json.js';
23
- export interface ForgeJobDir {
24
- jobId: string;
25
- /** The job wrote its `persisted` record: world.json has what it built. */
26
- finished: boolean;
27
- }
28
- /** The job ids the world's assets name — the jobs an edit could reuse bakes from. */
29
- export declare function referencedForgeJobs(world: JsonObject): Set<string>;
30
- /** Of `jobs`, the finished ones neither the world nor `keep` refers to, in listing order. */
31
- export declare function staleForgeJobs(world: JsonObject, jobs: ReadonlyArray<ForgeJobDir>, keep: ReadonlyArray<string>): string[];
32
- /** `<root>/.bitmagic/forge/worlds/v3/forged-work` — the store's job directories, by the shared key layout. */
33
- export declare function forgeJobsDir(projectRoot: string): string;
34
- /** The store's job directories, finished or not; an absent store lists nothing. */
35
- export declare function listForgeJobDirs(projectRoot: string): ForgeJobDir[];
36
- /**
37
- * Remove the finished jobs the project no longer refers to. One line when
38
- * something was removed, a warning when something would not go; silent otherwise.
39
- */
40
- export declare function pruneForgeStore(options: {
41
- projectRoot: string;
42
- world: JsonObject;
43
- /** Jobs to keep whatever the world says — the one that just ran. */
44
- keep: ReadonlyArray<string>;
45
- log: (message: string) => void;
46
- }): string[];
@@ -1,86 +0,0 @@
1
- /**
2
- * Forge-store housekeeping — the job directories under `.bitmagic/forge` that
3
- * nothing will read again are removed once a forge has landed.
4
- *
5
- * Every edit is a NEW job and the level keeps only its newest job's id, so a
6
- * level edited a dozen times leaves a dozen finished job directories behind
7
- * (dev-games/alien held 13, 6 MB, after one afternoon). A job is kept while
8
- * anything could still want it:
9
- *
10
- * - an asset of the world names it as its forge job (`parentForgeJobId`, the
11
- * same reading `--edit` uses) — that is the parent an edit reuses bakes from;
12
- * - it is the job that just ran;
13
- * - it never finished (no `persisted` record) — an unfinished job is what
14
- * `--resume` continues, and its local bakes are the progress a resume keeps.
15
- *
16
- * The server's copy of every job is untouched: it is what a resume or an edit
17
- * from another machine reads, and its lifetime is the server's call. The
18
- * resolution is pure over world.json and a listing, so the rules are testable
19
- * without a project on disk; the removal is best-effort, since a directory
20
- * that will not delete must never fail a forge that has already landed.
21
- */
22
- import * as fs from 'fs';
23
- import * as path from 'path';
24
- import { forgeArtifactKey, parentForgeJobId } from '@bitmagic/world-forger/pipeline/index.js';
25
- import { FORGE_STORE_DIR } from './local-store.js';
26
- import { isJsonObject } from '../project/world-json.js';
27
- /** The job ids the world's assets name — the jobs an edit could reuse bakes from. */
28
- export function referencedForgeJobs(world) {
29
- const assets = Array.isArray(world.assets) ? world.assets.filter(isJsonObject) : [];
30
- const jobs = new Set();
31
- for (const asset of assets) {
32
- const jobId = parentForgeJobId(asset);
33
- if (jobId !== null)
34
- jobs.add(jobId);
35
- }
36
- return jobs;
37
- }
38
- /** Of `jobs`, the finished ones neither the world nor `keep` refers to, in listing order. */
39
- export function staleForgeJobs(world, jobs, keep) {
40
- const referenced = referencedForgeJobs(world);
41
- for (const jobId of keep)
42
- referenced.add(jobId);
43
- return jobs.filter((job) => job.finished && !referenced.has(job.jobId)).map((job) => job.jobId);
44
- }
45
- /** `<root>/.bitmagic/forge/worlds/v3/forged-work` — the store's job directories, by the shared key layout. */
46
- export function forgeJobsDir(projectRoot) {
47
- const oneJob = path.dirname(forgeArtifactKey('job', 'persisted'));
48
- return path.join(projectRoot, FORGE_STORE_DIR, path.dirname(oneJob));
49
- }
50
- /** The store's job directories, finished or not; an absent store lists nothing. */
51
- export function listForgeJobDirs(projectRoot) {
52
- const dir = forgeJobsDir(projectRoot);
53
- if (!fs.existsSync(dir))
54
- return [];
55
- return fs.readdirSync(dir, { withFileTypes: true })
56
- .filter((entry) => entry.isDirectory())
57
- .map((entry) => ({
58
- jobId: entry.name,
59
- finished: fs.existsSync(path.join(projectRoot, FORGE_STORE_DIR, forgeArtifactKey(entry.name, 'persisted'))),
60
- }));
61
- }
62
- /**
63
- * Remove the finished jobs the project no longer refers to. One line when
64
- * something was removed, a warning when something would not go; silent otherwise.
65
- */
66
- export function pruneForgeStore(options) {
67
- const { projectRoot, world, keep, log } = options;
68
- const stale = staleForgeJobs(world, listForgeJobDirs(projectRoot), keep);
69
- const removed = [];
70
- for (const jobId of stale) {
71
- const dir = path.join(forgeJobsDir(projectRoot), jobId);
72
- try {
73
- fs.rmSync(dir, { recursive: true, force: true });
74
- removed.push(jobId);
75
- }
76
- catch (error) {
77
- const detail = error instanceof Error ? error.message : String(error);
78
- log(`warning: could not remove the finished forge job ${jobId} from ${FORGE_STORE_DIR} (${detail}); it is safe to delete by hand.`);
79
- }
80
- }
81
- if (removed.length > 0) {
82
- log(`Removed ${removed.length} finished forge job${removed.length === 1 ? '' : 's'} from ${FORGE_STORE_DIR} that no level refers to any more.`);
83
- }
84
- return removed;
85
- }
86
- //# sourceMappingURL=prune-store.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"prune-store.js","sourceRoot":"","sources":["../../src/forge/prune-store.ts"],"names":[],"mappings":"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"}
@@ -1,36 +0,0 @@
1
- /**
2
- * The forge store with a server behind it.
3
- *
4
- * The CLI keeps every forge artifact in the project's `.bitmagic/forge` (see
5
- * local-store.ts). This wraps that storage so the artifacts the BROWSER steps
6
- * write are also PUT to api-server, and anything missing locally is fetched
7
- * from it — so a job can be resumed, and a forged level edited, from a
8
- * machine that never ran the original forge. The server owns `input`, `spec`
9
- * and `forge` and refuses a client write of them; those are written locally
10
- * only (they arrive on the forge's terminal frame anyway).
11
- *
12
- * The mirror is best-effort by design: the local write is the one the run
13
- * depends on, and a server that cannot be reached must not fail a bake that
14
- * has already been paid for. A failed mirror is said once, plainly, because
15
- * its cost is real — the artifact will not be there for another machine.
16
- * Reads that fall through to the server are cached locally, so a resumed job
17
- * reads from disk from then on.
18
- */
19
- import type { ForgeStorageEnv } from '@bitmagic/world-forger/pipeline/index.js';
20
- import type { Environment } from '../config/environments.js';
21
- export interface RemoteForgeStoreDeps {
22
- environment: Environment;
23
- /**
24
- * Resolved on the FIRST remote call, not when the store is built: the store
25
- * is created before the command's cheap pre-request checks, and a run that
26
- * never leaves the machine (a fresh forge with every artifact local) must
27
- * not demand a login it would not otherwise need until the request.
28
- */
29
- accessToken: () => Promise<string>;
30
- fetch: typeof globalThis.fetch;
31
- /** Where a failed mirror or fetch is said. */
32
- warn: (message: string) => void;
33
- }
34
- export declare function forgeArtifactUrl(environment: Environment, jobId: string, name: string): string;
35
- /** Wrap a local storage env so forged-work artifacts are mirrored to, and fetched from, the server. */
36
- export declare function createMirroredForgeStorage(local: ForgeStorageEnv, deps: RemoteForgeStoreDeps): ForgeStorageEnv;
@@ -1,113 +0,0 @@
1
- /**
2
- * The forge store with a server behind it.
3
- *
4
- * The CLI keeps every forge artifact in the project's `.bitmagic/forge` (see
5
- * local-store.ts). This wraps that storage so the artifacts the BROWSER steps
6
- * write are also PUT to api-server, and anything missing locally is fetched
7
- * from it — so a job can be resumed, and a forged level edited, from a
8
- * machine that never ran the original forge. The server owns `input`, `spec`
9
- * and `forge` and refuses a client write of them; those are written locally
10
- * only (they arrive on the forge's terminal frame anyway).
11
- *
12
- * The mirror is best-effort by design: the local write is the one the run
13
- * depends on, and a server that cannot be reached must not fail a bake that
14
- * has already been paid for. A failed mirror is said once, plainly, because
15
- * its cost is real — the artifact will not be there for another machine.
16
- * Reads that fall through to the server are cached locally, so a resumed job
17
- * reads from disk from then on.
18
- */
19
- import { CLIENT_FORGE_ARTIFACTS, parseForgeArtifactKey } from '@bitmagic/world-forger/pipeline/index.js';
20
- import { apiServerHeaders } from '../telemetry/request-headers.js';
21
- export function forgeArtifactUrl(environment, jobId, name) {
22
- return `${environment.apiUrl}/api/cli/v1/forge/jobs/${encodeURIComponent(jobId)}/artifacts/${encodeURIComponent(name)}`;
23
- }
24
- /** Wrap a local storage env so forged-work artifacts are mirrored to, and fetched from, the server. */
25
- export function createMirroredForgeStorage(local, deps) {
26
- const said = new Set();
27
- const warnOnce = (key, message) => {
28
- if (said.has(key))
29
- return;
30
- said.add(key);
31
- deps.warn(message);
32
- };
33
- const headers = async () => ({
34
- ...apiServerHeaders(),
35
- Authorization: `Bearer ${await deps.accessToken()}`,
36
- });
37
- const failure = (error) => (error instanceof Error ? error.message : String(error));
38
- const client = {
39
- async putObject(params) {
40
- const written = await local.storageClient.putObject(params);
41
- const parsed = parseForgeArtifactKey(params.key);
42
- if (parsed === null || !CLIENT_FORGE_ARTIFACTS.includes(parsed.name))
43
- return written;
44
- const body = typeof params.body === 'string' ? params.body : new TextDecoder().decode(params.body);
45
- try {
46
- const response = await deps.fetch(forgeArtifactUrl(deps.environment, parsed.jobId, parsed.name), {
47
- method: 'PUT',
48
- headers: { ...(await headers()), 'Content-Type': 'application/json' },
49
- body,
50
- });
51
- if (!response.ok) {
52
- warnOnce(`put:${parsed.name}`, `Could not mirror the forge's "${parsed.name}" record to api-server (HTTP ${response.status}). The run continues from `
53
- + 'the local copy, but resuming or editing this level from another machine will re-bake it.');
54
- }
55
- }
56
- catch (error) {
57
- warnOnce(`put:${parsed.name}`, `Could not mirror the forge's "${parsed.name}" record to api-server (${failure(error)}). The run continues from `
58
- + 'the local copy, but resuming or editing this level from another machine will re-bake it.');
59
- }
60
- return written;
61
- },
62
- async headObject(params) {
63
- if (await local.storageClient.headObject(params))
64
- return true;
65
- const parsed = parseForgeArtifactKey(params.key);
66
- if (parsed === null)
67
- return false;
68
- try {
69
- const response = await deps.fetch(forgeArtifactUrl(deps.environment, parsed.jobId, parsed.name), {
70
- method: 'HEAD',
71
- headers: await headers(),
72
- });
73
- return response.ok;
74
- }
75
- catch (error) {
76
- warnOnce('head', `Could not ask api-server for the job's saved state (${failure(error)}) — working from the local copy only.`);
77
- return false;
78
- }
79
- },
80
- async getObject(params) {
81
- const here = await local.storageClient.getObject(params);
82
- if (here !== null)
83
- return here;
84
- const parsed = parseForgeArtifactKey(params.key);
85
- if (parsed === null)
86
- return null;
87
- try {
88
- const response = await deps.fetch(forgeArtifactUrl(deps.environment, parsed.jobId, parsed.name), {
89
- method: 'GET',
90
- headers: await headers(),
91
- });
92
- if (response.status === 404)
93
- return null;
94
- if (!response.ok) {
95
- warnOnce(`get:${parsed.name}`, `api-server refused the job's "${parsed.name}" record (HTTP ${response.status}) — working from the local copy only.`);
96
- return null;
97
- }
98
- const bytes = new Uint8Array(await response.arrayBuffer());
99
- // Cached locally so the rest of the run, and the next one, read from disk.
100
- await local.storageClient.putObject({
101
- bucket: params.bucket, key: params.key, body: bytes, contentType: 'application/json', cacheControl: 'no-store, max-age=0',
102
- });
103
- return bytes;
104
- }
105
- catch (error) {
106
- warnOnce('get', `Could not fetch the job's saved state from api-server (${failure(error)}) — working from the local copy only.`);
107
- return null;
108
- }
109
- },
110
- };
111
- return { ...local, storageClient: client };
112
- }
113
- //# sourceMappingURL=remote-store.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"remote-store.js","sourceRoot":"","sources":["../../src/forge/remote-store.ts"],"names":[],"mappings":"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"}