@bitmagic/cli 0.1.65-dev.8 → 0.1.65

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 (55) hide show
  1. package/README.md +101 -15
  2. package/dist/assets/materials.d.ts +9 -0
  3. package/dist/assets/materials.js +10 -2
  4. package/dist/assets/materials.js.map +1 -1
  5. package/dist/cli.d.ts +1 -0
  6. package/dist/cli.js +2 -0
  7. package/dist/cli.js.map +1 -1
  8. package/dist/commands/character.d.ts +18 -0
  9. package/dist/commands/character.js +268 -0
  10. package/dist/commands/character.js.map +1 -0
  11. package/dist/commands/forge.js +7 -1
  12. package/dist/commands/forge.js.map +1 -1
  13. package/dist/commands/generate.d.ts +31 -13
  14. package/dist/commands/generate.js +48 -56
  15. package/dist/commands/generate.js.map +1 -1
  16. package/dist/editor/animation-preview-page.js +88 -1
  17. package/dist/editor/animation-preview-page.js.map +1 -1
  18. package/dist/editor/asset-detail-panel.d.ts +2 -2
  19. package/dist/editor/asset-detail-panel.js +21 -0
  20. package/dist/editor/asset-detail-panel.js.map +1 -1
  21. package/dist/editor/asset-viewer.js +31 -8
  22. package/dist/editor/asset-viewer.js.map +1 -1
  23. package/dist/editor/shell-page.js +63 -1
  24. package/dist/editor/shell-page.js.map +1 -1
  25. package/dist/forge/prune-store.d.ts +46 -0
  26. package/dist/forge/prune-store.js +86 -0
  27. package/dist/forge/prune-store.js.map +1 -0
  28. package/dist/generate/character-body.d.ts +79 -0
  29. package/dist/generate/character-body.js +100 -0
  30. package/dist/generate/character-body.js.map +1 -0
  31. package/dist/mobile/asset-hosts.d.ts +7 -9
  32. package/dist/mobile/asset-hosts.js +7 -16
  33. package/dist/mobile/asset-hosts.js.map +1 -1
  34. package/dist/project/character-search.d.ts +30 -0
  35. package/dist/project/character-search.js +82 -0
  36. package/dist/project/character-search.js.map +1 -0
  37. package/dist/scaffold/asset-pipeline-skill-files.d.ts +42 -0
  38. package/dist/scaffold/asset-pipeline-skill-files.js +207 -0
  39. package/dist/scaffold/asset-pipeline-skill-files.js.map +1 -0
  40. package/dist/scaffold/blender-level-skill-files.js +3 -2
  41. package/dist/scaffold/blender-level-skill-files.js.map +1 -1
  42. package/dist/scaffold/claude-settings.js +4 -0
  43. package/dist/scaffold/claude-settings.js.map +1 -1
  44. package/dist/scaffold/project-files.js +18 -12
  45. package/dist/scaffold/project-files.js.map +1 -1
  46. package/dist/scaffold/project.js +2 -0
  47. package/dist/scaffold/project.js.map +1 -1
  48. package/dist/scaffold/skill-files.js +5 -4
  49. package/dist/scaffold/skill-files.js.map +1 -1
  50. package/dist/telemetry/creator-prompt.d.ts +18 -2
  51. package/dist/telemetry/creator-prompt.js +24 -3
  52. package/dist/telemetry/creator-prompt.js.map +1 -1
  53. package/dist/telemetry/report.js +31 -16
  54. package/dist/telemetry/report.js.map +1 -1
  55. package/package.json +4 -4
@@ -2166,6 +2166,11 @@ ${ASSET_DETAIL_HTML}
2166
2166
  var assetLoadTimer = null;
2167
2167
  // The asset the overlay is showing, for the Edit voxels action. Null whenever it is closed.
2168
2168
  var assetDetailAsset = null;
2169
+ // The rigged GLB the built-in clip pills play on — empty for everything that is not a character.
2170
+ var assetClipUrl = '';
2171
+ // Triangles arrive with the viewer's READY, and the clip pills reopen the viewer more than once
2172
+ // per dialog. Without this the fact list grows a duplicate row on every trip back to Model.
2173
+ var assetTriangleShown = false;
2169
2174
 
2170
2175
  var IMAGE_TYPES = { image: true, skybox: true };
2171
2176
  var AUDIO_TYPES = { audio: true, sound: true, 'sound-effect': true };
@@ -2216,6 +2221,7 @@ ${ASSET_DETAIL_HTML}
2216
2221
  + (asset.type || 'unknown');
2217
2222
 
2218
2223
  assetFacts.textContent = '';
2224
+ assetTriangleShown = false;
2219
2225
  fact('Size', formatSize(asset.size));
2220
2226
  fact('Length', typeof asset.durationSeconds === 'number' && asset.durationSeconds > 0
2221
2227
  ? asset.durationSeconds.toFixed(1) + 's' : '');
@@ -2235,6 +2241,14 @@ ${ASSET_DETAIL_HTML}
2235
2241
  // The asset's own file, for the types a browser can show without a renderer.
2236
2242
  var direct = typeof asset.url === 'string' ? asset.url : '';
2237
2243
 
2244
+ // A built-in clip needs a rigged body to play on, and a character asset is exactly that: a
2245
+ // GLB on the standard Mixamo skeleton. Every other type keeps the row out of sight rather
2246
+ // than disabled, for the reason Edit voxels does.
2247
+ var characterJob = type === 'character' ? glbJobFor(asset) : null;
2248
+ assetClipUrl = characterJob === null ? '' : characterJob.url;
2249
+ document.getElementById('asset-clips').classList.toggle('hidden', assetClipUrl === '');
2250
+ setActiveClip('');
2251
+
2238
2252
  // '=== true' rather than a truthy read: a type of 'constructor' would otherwise find
2239
2253
  // Object.prototype's own property and pass for an image.
2240
2254
  if (IMAGE_TYPES[type] === true && direct) {
@@ -2312,6 +2326,7 @@ ${ASSET_DETAIL_HTML}
2312
2326
  function closeAssetDetail() {
2313
2327
  if (!assetOverlay.classList.contains('show')) return false;
2314
2328
  assetDetailAsset = null;
2329
+ assetClipUrl = '';
2315
2330
  assetOverlay.classList.remove('show');
2316
2331
  if (assetLoadTimer) clearTimeout(assetLoadTimer);
2317
2332
  // about:blank rather than hiding it: an idle iframe would hold a WebGL context and a render
@@ -2346,7 +2361,8 @@ ${ASSET_DETAIL_HTML}
2346
2361
  if (message.type === 'ASSET_PREVIEW_READY') {
2347
2362
  if (assetLoadTimer) clearTimeout(assetLoadTimer);
2348
2363
  assetNote.classList.add('hidden');
2349
- if (typeof message.triangles === 'number' && message.triangles > 0) {
2364
+ if (typeof message.triangles === 'number' && message.triangles > 0 && !assetTriangleShown) {
2365
+ assetTriangleShown = true;
2350
2366
  fact('Triangles', message.triangles.toLocaleString());
2351
2367
  }
2352
2368
  } else if (message.type === 'ASSET_PREVIEW_FAILED') {
@@ -2394,6 +2410,52 @@ ${ASSET_DETAIL_HTML}
2394
2410
  post('EDIT_VOXEL_ASSET', { assetId: assetId });
2395
2411
  }
2396
2412
 
2413
+ /**
2414
+ * The built-in animation pills — the dev view's answer to "what does this character look like
2415
+ * moving?", which a still mesh cannot show.
2416
+ *
2417
+ * The pills carry NAMES, not URLs, and /animation-preview resolves them against the project's
2418
+ * own engine/AnimationAssets.js. That is deliberate: the animation library lives in a
2419
+ * datetime-stamped folder that changes on every publish, and which of the two tables is live is
2420
+ * the engine's decision. A copy of either here would go stale with nothing to catch it, and
2421
+ * would show a creator a clip their game does not actually play.
2422
+ *
2423
+ * The empty name is the Model pill: back to the still asset viewer, which is what the dialog
2424
+ * opens on. Reset view goes with it — it speaks to the orbit viewer only, and a dead button on
2425
+ * a playing clip is worse than no button.
2426
+ */
2427
+ function setActiveClip(name) {
2428
+ var pills = document.querySelectorAll('#asset-clip-row button');
2429
+ for (var i = 0; i < pills.length; i++) {
2430
+ // The accent, not --pressed: this is a tab-like selector, and DESIGN.md spends aquamarine on
2431
+ // the active tab. --pressed reads as the same dark pill as its neighbours at this size, which
2432
+ // left the creator with no way to tell which clip was playing. Edit voxels is hidden for a
2433
+ // character asset, so this is still the one primary on the surface.
2434
+ pills[i].classList.toggle('bm-pill--primary', pills[i].getAttribute('data-clip') === name);
2435
+ }
2436
+ }
2437
+
2438
+ function playAssetClip(name) {
2439
+ if (assetClipUrl === '') return;
2440
+ setActiveClip(name);
2441
+ assetNote.classList.add('hidden');
2442
+ if (assetLoadTimer) clearTimeout(assetLoadTimer);
2443
+ assetImage.classList.add('hidden');
2444
+ assetResetButton.classList.toggle('hidden', name !== '');
2445
+ assetFrame.classList.remove('hidden');
2446
+ assetFrame.src = name === ''
2447
+ ? '/asset-preview?kind=glb&url=' + encodeURIComponent(assetClipUrl)
2448
+ : '/animation-preview?character=' + encodeURIComponent(assetClipUrl)
2449
+ + '&clip=' + encodeURIComponent(name);
2450
+ assetLoadTimer = setTimeout(function () {
2451
+ showAssetNote('Still loading. If this does not clear, check the dev server output.');
2452
+ }, 12000);
2453
+ }
2454
+
2455
+ document.getElementById('asset-clip-row').addEventListener('click', function (event) {
2456
+ var button = event.target.closest('button[data-clip]');
2457
+ if (button) playAssetClip(button.getAttribute('data-clip'));
2458
+ });
2397
2459
  document.getElementById('asset-edit-voxels').addEventListener('click', function () {
2398
2460
  void startVoxelEditFromOverlay();
2399
2461
  });
@@ -1 +1 @@
1
- {"version":3,"file":"shell-page.js","sourceRoot":"","sources":["../../src/editor/shell-page.ts"],"names":[],"mappings":"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EAAE;;;;;;;;0BAQF,IAAI,CAAC,SAAS,CAAC,qBAAqB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;wBA4hCvC,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA01C1C,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmChB,CAAC;AACF,CAAC"}
1
+ {"version":3,"file":"shell-page.js","sourceRoot":"","sources":["../../src/editor/shell-page.ts"],"names":[],"mappings":"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EAAE;;;;;;;;0BAQF,IAAI,CAAC,SAAS,CAAC,qBAAqB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;wBA4hCvC,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAw5C1C,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmChB,CAAC;AACF,CAAC"}
@@ -0,0 +1,46 @@
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[];
@@ -0,0 +1,86 @@
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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prune-store.js","sourceRoot":"","sources":["../../src/forge/prune-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,79 @@
1
+ export interface CharacterArgs {
2
+ name: string;
3
+ prompt: string;
4
+ applyToPlayer?: boolean;
5
+ headScale?: string;
6
+ /** `--fresh`: forge a new one, never substitute a stored body. */
7
+ fresh?: boolean;
8
+ /**
9
+ * citty's negative boolean: `--no-library` arrives as `false`; the flag has no positive form.
10
+ * The deprecated spelling of `--fresh`, kept working because every scaffolded AGENTS.md
11
+ * written before the rename teaches it.
12
+ */
13
+ library?: boolean;
14
+ /** `--library-only`: take the closest stored body, never forge, never charge. */
15
+ libraryOnly?: boolean;
16
+ /** A row a prior search returned, which `bitmagic character add --id` adds verbatim. */
17
+ libraryPick?: {
18
+ sid: string;
19
+ assetUrl: string;
20
+ description?: string;
21
+ similarity: number;
22
+ };
23
+ /** Absent = the generator's default (true); the command fills it from the project's art style. */
24
+ voxelize?: boolean;
25
+ /** Asset Forger image-to-mesh model; absent = the Forger default. */
26
+ meshModel?: string;
27
+ }
28
+ /**
29
+ * The character default a project's art style implies: a low-poly game keeps the textured rig.
30
+ * Read from the cwd's project like every other generate command; outside a project the answer
31
+ * is "declares nothing", so the generator's own default (voxelize) stands and `loadProjectContext`
32
+ * reports the missing project on the very next line.
33
+ */
34
+ export declare function defaultVoxelizeForProject(): boolean | undefined;
35
+ export declare function buildCharacterBody(args: CharacterArgs): Record<string, unknown>;
36
+ export declare const NAME_ARG: {
37
+ readonly type: "string";
38
+ readonly required: true;
39
+ readonly description: "Unique name for the character.";
40
+ };
41
+ export declare const PROMPT_ARG: {
42
+ readonly type: "string";
43
+ readonly required: true;
44
+ readonly description: "Description of the character.";
45
+ };
46
+ export declare const APPLY_TO_PLAYER_ARG: {
47
+ readonly type: "boolean";
48
+ readonly description: "Set this character as the PLAYER. Omit to use the generator default (true).";
49
+ };
50
+ export declare const HEAD_SCALE_ARG: {
51
+ readonly type: "string";
52
+ readonly description: "Head size as a percentage of normal, 50-300. Omit to use the generator default.";
53
+ };
54
+ export declare const FRESH_ARG: {
55
+ readonly type: "boolean";
56
+ readonly description: string;
57
+ };
58
+ export declare const VOXELIZE_ARG: {
59
+ readonly type: "boolean";
60
+ readonly description: "Voxelize the rigged body. Omit to follow game.json artStyle: voxel games voxelize, low-poly games keep the textured rig.";
61
+ readonly negativeDescription: "Keep the textured low-poly rig instead of voxelizing it.";
62
+ };
63
+ export declare const MESH_MODEL_ARG: {
64
+ readonly type: "string";
65
+ readonly description: "Image-to-mesh model. Low-poly characters already default to 'runware-rodin-gen-2', which bills 60 sparks and takes ~3 min; pass 'unified-trellis' to put one back on the cheap lane at 15 sparks. Voxel characters ignore the default and use the Forger's.";
66
+ };
67
+ /**
68
+ * The deprecated spelling of `--fresh`.
69
+ *
70
+ * Kept declared because citty derives `--no-library` from an argument NAMED
71
+ * `library`, so there is no way to accept the old flag without it — and no way
72
+ * to hide it either: citty's `ArgDef` has no `hidden` field (only `CommandMeta`
73
+ * does). So it stays visible and says what it is.
74
+ */
75
+ export declare const LIBRARY_ARG: {
76
+ readonly type: "boolean";
77
+ readonly description: "Deprecated and unnecessary — the library is consulted by default. Use --fresh to skip it.";
78
+ readonly negativeDescription: "Deprecated spelling of --fresh. Always makes a new character.";
79
+ };
@@ -0,0 +1,100 @@
1
+ /**
2
+ * The character request: its flags, and the body they map onto.
3
+ *
4
+ * Lives here rather than in either command because TWO commands present it —
5
+ * `bitmagic character generate` and the deprecated `bitmagic generate character`
6
+ * it replaced — and a deprecated alias whose flags have drifted from the command
7
+ * it aliases is worse than no alias at all. One definition, imported by both.
8
+ *
9
+ * Field names are exactly what `characterParamsSchema`
10
+ * (shared/asset-core/src/generators/character.ts) expects, and a flag the
11
+ * creator did not pass is OMITTED rather than sent as `undefined` or as a
12
+ * restated default — the generator owns its defaults.
13
+ */
14
+ import { findProjectRootOrNull } from '../project/context.js';
15
+ import { readArtStyleDeclaration } from '../project/art-style.js';
16
+ import { parseNumberFlag } from '../commands/number-flag.js';
17
+ /** Drops the keys an absent flag left `undefined`; `false` and `0` are deliberate values. */
18
+ function withoutAbsentFlags(fields) {
19
+ return Object.fromEntries(Object.entries(fields).filter(([, value]) => value !== undefined));
20
+ }
21
+ /**
22
+ * The character default a project's art style implies: a low-poly game keeps the textured rig.
23
+ * Read from the cwd's project like every other generate command; outside a project the answer
24
+ * is "declares nothing", so the generator's own default (voxelize) stands and `loadProjectContext`
25
+ * reports the missing project on the very next line.
26
+ */
27
+ export function defaultVoxelizeForProject() {
28
+ const root = findProjectRootOrNull();
29
+ if (root === null)
30
+ return undefined;
31
+ return readArtStyleDeclaration(root).artStyle === 'low-poly' ? false : undefined;
32
+ }
33
+ export function buildCharacterBody(args) {
34
+ return withoutAbsentFlags({
35
+ name: args.name,
36
+ prompt: args.prompt,
37
+ applyToPlayer: args.applyToPlayer,
38
+ headScale: parseNumberFlag('head-scale', args.headScale, { min: 50, max: 300 }),
39
+ // `--fresh` and the deprecated `--no-library` are the same request. Neither
40
+ // passed leaves the field absent, so the generator's default (search first)
41
+ // stands rather than being restated here.
42
+ useLibrary: args.fresh === true ? false : args.library,
43
+ libraryOnly: args.libraryOnly,
44
+ libraryPick: args.libraryPick,
45
+ voxelize: args.voxelize,
46
+ meshModel: args.meshModel,
47
+ });
48
+ }
49
+ // ============================================================================
50
+ // Flag definitions, shared by `character generate` and the deprecated alias
51
+ // ============================================================================
52
+ export const NAME_ARG = {
53
+ type: 'string',
54
+ required: true,
55
+ description: 'Unique name for the character.',
56
+ };
57
+ export const PROMPT_ARG = {
58
+ type: 'string',
59
+ required: true,
60
+ description: 'Description of the character.',
61
+ };
62
+ export const APPLY_TO_PLAYER_ARG = {
63
+ type: 'boolean',
64
+ description: 'Set this character as the PLAYER. Omit to use the generator default (true).',
65
+ };
66
+ export const HEAD_SCALE_ARG = {
67
+ type: 'string',
68
+ description: 'Head size as a percentage of normal, 50-300. Omit to use the generator default.',
69
+ };
70
+ export const FRESH_ARG = {
71
+ type: 'boolean',
72
+ description: 'Forge a new character even when the library has a close match. '
73
+ + 'Use it when you want this exact brief made rather than the nearest ready-made body.',
74
+ };
75
+ export const VOXELIZE_ARG = {
76
+ type: 'boolean',
77
+ description: 'Voxelize the rigged body. Omit to follow game.json artStyle: voxel games voxelize, low-poly games keep the textured rig.',
78
+ negativeDescription: 'Keep the textured low-poly rig instead of voxelizing it.',
79
+ };
80
+ export const MESH_MODEL_ARG = {
81
+ type: 'string',
82
+ description: "Image-to-mesh model. Low-poly characters already default to 'runware-rodin-gen-2', which bills 60 sparks and takes ~3 min; pass 'unified-trellis' to put one back on the cheap lane at 15 sparks. Voxel characters ignore the default and use the Forger's.",
83
+ };
84
+ /**
85
+ * The deprecated spelling of `--fresh`.
86
+ *
87
+ * Kept declared because citty derives `--no-library` from an argument NAMED
88
+ * `library`, so there is no way to accept the old flag without it — and no way
89
+ * to hide it either: citty's `ArgDef` has no `hidden` field (only `CommandMeta`
90
+ * does). So it stays visible and says what it is.
91
+ */
92
+ export const LIBRARY_ARG = {
93
+ type: 'boolean',
94
+ // Both forms get a description, because citty renders a positive row for any
95
+ // boolean arg whether or not the flag is meant to be used. Left undescribed it
96
+ // was a blank line in `--help` sitting next to `--fresh`.
97
+ description: 'Deprecated and unnecessary — the library is consulted by default. Use --fresh to skip it.',
98
+ negativeDescription: 'Deprecated spelling of --fresh. Always makes a new character.',
99
+ };
100
+ //# sourceMappingURL=character-body.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"character-body.js","sourceRoot":"","sources":["../../src/generate/character-body.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAC9D,OAAO,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAClE,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAE7D,6FAA6F;AAC7F,SAAS,kBAAkB,CAAC,MAA+B;IACzD,OAAO,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC;AAC/F,CAAC;AAyBD;;;;;GAKG;AACH,MAAM,UAAU,yBAAyB;IACvC,MAAM,IAAI,GAAG,qBAAqB,EAAE,CAAC;IACrC,IAAI,IAAI,KAAK,IAAI;QAAE,OAAO,SAAS,CAAC;IACpC,OAAO,uBAAuB,CAAC,IAAI,CAAC,CAAC,QAAQ,KAAK,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AACnF,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,IAAmB;IACpD,OAAO,kBAAkB,CAAC;QACxB,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,aAAa,EAAE,IAAI,CAAC,aAAa;QACjC,SAAS,EAAE,eAAe,CAAC,YAAY,EAAE,IAAI,CAAC,SAAS,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;QAC/E,4EAA4E;QAC5E,4EAA4E;QAC5E,0CAA0C;QAC1C,UAAU,EAAE,IAAI,CAAC,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO;QACtD,WAAW,EAAE,IAAI,CAAC,WAAW;QAC7B,WAAW,EAAE,IAAI,CAAC,WAAW;QAC7B,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,SAAS,EAAE,IAAI,CAAC,SAAS;KAC1B,CAAC,CAAC;AACL,CAAC;AAED,+EAA+E;AAC/E,4EAA4E;AAC5E,+EAA+E;AAE/E,MAAM,CAAC,MAAM,QAAQ,GAAG;IACtB,IAAI,EAAE,QAAQ;IACd,QAAQ,EAAE,IAAI;IACd,WAAW,EAAE,gCAAgC;CACrC,CAAC;AAEX,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,IAAI,EAAE,QAAQ;IACd,QAAQ,EAAE,IAAI;IACd,WAAW,EAAE,+BAA+B;CACpC,CAAC;AAEX,MAAM,CAAC,MAAM,mBAAmB,GAAG;IACjC,IAAI,EAAE,SAAS;IACf,WAAW,EAAE,6EAA6E;CAClF,CAAC;AAEX,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,IAAI,EAAE,QAAQ;IACd,WAAW,EAAE,iFAAiF;CACtF,CAAC;AAEX,MAAM,CAAC,MAAM,SAAS,GAAG;IACvB,IAAI,EAAE,SAAS;IACf,WAAW,EAAE,iEAAiE;UAC1E,qFAAqF;CACjF,CAAC;AAEX,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,IAAI,EAAE,SAAS;IACf,WAAW,EAAE,0HAA0H;IACvI,mBAAmB,EAAE,0DAA0D;CACvE,CAAC;AAEX,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,IAAI,EAAE,QAAQ;IACd,WAAW,EAAE,6PAA6P;CAClQ,CAAC;AAEX;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,IAAI,EAAE,SAAS;IACf,6EAA6E;IAC7E,+EAA+E;IAC/E,0DAA0D;IAC1D,WAAW,EAAE,2FAA2F;IACxG,mBAAmB,EAAE,+DAA+D;CAC5E,CAAC"}
@@ -12,18 +12,16 @@
12
12
  * that redirection: which hosts are ours to fetch on the browser's behalf, and the reversible path
13
13
  * that carries one.
14
14
  *
15
- * The host suffixes are the same environment classification `rehost-imported-assets.ts` uses in
16
- * game-play-agent: `bitmagic.ai` is prod (`mini.bitmagic.ai`, `forged-assets.bitmagic.ai`, ...) and
17
- * `bitmagic.cloud` is dev/sandbox (`beta.portal.bitmagic.cloud`,
18
- * `magic-mesh-gen.sandbox.dev.bitmagic.cloud`, ...). Everything else is deliberately left alone:
19
- * `esm.sh` serves the import map's modules and already answers `*`, and an unrecognised host is
20
- * more likely to be a creator's own endpoint than an asset we should be relaying.
15
+ * The host allowlist itself lives in `@bitmagic/asset-core` (`managed-hosts.ts`) and is
16
+ * re-exported here, because api-server needs the same answer when it decides whether a
17
+ * caller-supplied library `assetUrl` may be stored — and two copies of a security-relevant
18
+ * allowlist drift. `esm.sh` and everything else is deliberately left alone: it serves the import
19
+ * map's modules and already answers `*`.
21
20
  */
21
+ import { isManagedAssetHost, MANAGED_HOST_SUFFIXES } from '@bitmagic/asset-core';
22
+ export { isManagedAssetHost, MANAGED_HOST_SUFFIXES };
22
23
  /** The bridge route every proxied asset is fetched through. Trailing slash included. */
23
24
  export declare const CDN_PROXY_PREFIX = "/__bm/cdn/";
24
- /** Host suffixes that identify a bitmagic-controlled asset host. See the file header. */
25
- export declare const MANAGED_HOST_SUFFIXES: readonly ["bitmagic.ai", "bitmagic.cloud"];
26
- export declare function isManagedAssetHost(hostname: string): boolean;
27
25
  /**
28
26
  * The same-origin path that stands in for `url`, or null when the URL is not ours to proxy.
29
27
  *
@@ -12,25 +12,16 @@
12
12
  * that redirection: which hosts are ours to fetch on the browser's behalf, and the reversible path
13
13
  * that carries one.
14
14
  *
15
- * The host suffixes are the same environment classification `rehost-imported-assets.ts` uses in
16
- * game-play-agent: `bitmagic.ai` is prod (`mini.bitmagic.ai`, `forged-assets.bitmagic.ai`, ...) and
17
- * `bitmagic.cloud` is dev/sandbox (`beta.portal.bitmagic.cloud`,
18
- * `magic-mesh-gen.sandbox.dev.bitmagic.cloud`, ...). Everything else is deliberately left alone:
19
- * `esm.sh` serves the import map's modules and already answers `*`, and an unrecognised host is
20
- * more likely to be a creator's own endpoint than an asset we should be relaying.
15
+ * The host allowlist itself lives in `@bitmagic/asset-core` (`managed-hosts.ts`) and is
16
+ * re-exported here, because api-server needs the same answer when it decides whether a
17
+ * caller-supplied library `assetUrl` may be stored — and two copies of a security-relevant
18
+ * allowlist drift. `esm.sh` and everything else is deliberately left alone: it serves the import
19
+ * map's modules and already answers `*`.
21
20
  */
21
+ import { isManagedAssetHost, MANAGED_HOST_SUFFIXES } from '@bitmagic/asset-core';
22
+ export { isManagedAssetHost, MANAGED_HOST_SUFFIXES };
22
23
  /** The bridge route every proxied asset is fetched through. Trailing slash included. */
23
24
  export const CDN_PROXY_PREFIX = '/__bm/cdn/';
24
- /** Host suffixes that identify a bitmagic-controlled asset host. See the file header. */
25
- export const MANAGED_HOST_SUFFIXES = ['bitmagic.ai', 'bitmagic.cloud'];
26
- /** A hostname with no port and no credentials — what a proxy path's first segment may hold. */
27
- const HOSTNAME = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/;
28
- export function isManagedAssetHost(hostname) {
29
- const host = hostname.toLowerCase();
30
- if (!HOSTNAME.test(host))
31
- return false;
32
- return MANAGED_HOST_SUFFIXES.some((suffix) => host === suffix || host.endsWith(`.${suffix}`));
33
- }
34
25
  /**
35
26
  * The same-origin path that stands in for `url`, or null when the URL is not ours to proxy.
36
27
  *
@@ -1 +1 @@
1
- {"version":3,"file":"asset-hosts.js","sourceRoot":"","sources":["../../src/mobile/asset-hosts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,wFAAwF;AACxF,MAAM,CAAC,MAAM,gBAAgB,GAAG,YAAY,CAAC;AAE7C,yFAAyF;AACzF,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,aAAa,EAAE,gBAAgB,CAAU,CAAC;AAEhF,+FAA+F;AAC/F,MAAM,QAAQ,GAAG,mEAAmE,CAAC;AAErF,MAAM,UAAU,kBAAkB,CAAC,QAAgB;IACjD,MAAM,IAAI,GAAG,QAAQ,CAAC,WAAW,EAAE,CAAC;IACpC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IACvC,OAAO,qBAAqB,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,KAAK,MAAM,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,MAAM,EAAE,CAAC,CAAC,CAAC;AAChG,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,WAAW,CAAC,GAAW;IACrC,IAAI,MAAW,CAAC;IAChB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;IACxB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,MAAM,CAAC,QAAQ,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,KAAK,OAAO;QAAE,OAAO,IAAI,CAAC;IAC7E,IAAI,MAAM,CAAC,IAAI,KAAK,EAAE,IAAI,MAAM,CAAC,QAAQ,KAAK,EAAE,IAAI,MAAM,CAAC,QAAQ,KAAK,EAAE;QAAE,OAAO,IAAI,CAAC;IACxF,IAAI,CAAC,kBAAkB,CAAC,MAAM,CAAC,QAAQ,CAAC;QAAE,OAAO,IAAI,CAAC;IACtD,OAAO,GAAG,gBAAgB,GAAG,MAAM,CAAC,QAAQ,GAAG,MAAM,CAAC,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC;AACnF,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,aAAa,CAAC,WAAmB;IAC/C,IAAI,CAAC,WAAW,CAAC,UAAU,CAAC,gBAAgB,CAAC;QAAE,OAAO,IAAI,CAAC;IAC3D,MAAM,IAAI,GAAG,WAAW,CAAC,KAAK,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC;IACxD,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAChC,IAAI,KAAK,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAC5B,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,WAAW,EAAE,CAAC;IAChD,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAC3C,OAAO,WAAW,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;AAC/C,CAAC"}
1
+ {"version":3,"file":"asset-hosts.js","sourceRoot":"","sources":["../../src/mobile/asset-hosts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAEjF,OAAO,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,CAAC;AAErD,wFAAwF;AACxF,MAAM,CAAC,MAAM,gBAAgB,GAAG,YAAY,CAAC;AAE7C;;;;;;;GAOG;AACH,MAAM,UAAU,WAAW,CAAC,GAAW;IACrC,IAAI,MAAW,CAAC;IAChB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;IACxB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,MAAM,CAAC,QAAQ,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,KAAK,OAAO;QAAE,OAAO,IAAI,CAAC;IAC7E,IAAI,MAAM,CAAC,IAAI,KAAK,EAAE,IAAI,MAAM,CAAC,QAAQ,KAAK,EAAE,IAAI,MAAM,CAAC,QAAQ,KAAK,EAAE;QAAE,OAAO,IAAI,CAAC;IACxF,IAAI,CAAC,kBAAkB,CAAC,MAAM,CAAC,QAAQ,CAAC;QAAE,OAAO,IAAI,CAAC;IACtD,OAAO,GAAG,gBAAgB,GAAG,MAAM,CAAC,QAAQ,GAAG,MAAM,CAAC,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC;AACnF,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,aAAa,CAAC,WAAmB;IAC/C,IAAI,CAAC,WAAW,CAAC,UAAU,CAAC,gBAAgB,CAAC;QAAE,OAAO,IAAI,CAAC;IAC3D,MAAM,IAAI,GAAG,WAAW,CAAC,KAAK,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC;IACxD,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAChC,IAAI,KAAK,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAC5B,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,WAAW,EAAE,CAAC;IAChD,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAC3C,OAAO,WAAW,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;AAC/C,CAAC"}
@@ -0,0 +1,30 @@
1
+ export declare function characterSearchPath(root: string): string;
2
+ /** One row as the search displayed it. The shape `libraryPick` needs, plus the caption. */
3
+ export interface CharacterCandidate {
4
+ sid: string;
5
+ assetUrl: string;
6
+ description: string;
7
+ subject: string | null;
8
+ similarity: number;
9
+ }
10
+ export interface CharacterSearchRecord {
11
+ /** The prompt that produced this list — `add --id` reuses it as the asset's brief. */
12
+ prompt: string;
13
+ /** Which shelf was searched. `add` sends the matching `voxelize` so the media cannot cross. */
14
+ representation: 'voxel' | 'lowpoly';
15
+ /** The floor automatic substitution would have applied, for the "auto" column. */
16
+ threshold: number;
17
+ searchedAt: string;
18
+ candidates: CharacterCandidate[];
19
+ }
20
+ /** The last search, or null when there is none to trust. */
21
+ export declare function readCharacterSearch(root: string): CharacterSearchRecord | null;
22
+ export declare function writeCharacterSearch(root: string, record: CharacterSearchRecord): void;
23
+ /**
24
+ * The cached row with this id, or null.
25
+ *
26
+ * Case-insensitive on the id because a creator reads it off a table and retypes
27
+ * it, and the corpus uses one case throughout — so a capital letter is a typo
28
+ * with an obvious intent, not a different character.
29
+ */
30
+ export declare function findCachedCandidate(record: CharacterSearchRecord, sid: string): CharacterCandidate | null;
@@ -0,0 +1,82 @@
1
+ /**
2
+ * The candidates the last `bitmagic character search` returned, so
3
+ * `bitmagic character add --id <sid>` can name one of them.
4
+ *
5
+ * The search already fetched every row it displayed — sid, URL, caption and
6
+ * score. Keeping them means `add --id` needs nothing but the id: no second
7
+ * query, no re-ranking, and no asking the creator to retype the prompt that
8
+ * produced the list they are looking at. It also means `add` cannot drift from
9
+ * what was on screen, which a re-search could (the catalogue grows).
10
+ *
11
+ * Lives under `.bitmagic/` like `reference.json` and `cover.json`: generated,
12
+ * machine-written state. NOT in `bitmagic.json`, deliberately — that file is
13
+ * inside the publish fingerprint (shared/asset-core's publish-source.ts), so
14
+ * recording a search there would invalidate a passing `bitmagic verify` every
15
+ * time somebody browsed the shelf.
16
+ *
17
+ * Readers are lenient, like `readCoverRecord` and the reference records: a
18
+ * corrupt or absent file reads as "no search yet", because the worst case is one
19
+ * re-run of a free command, and a hard error would wedge the very command that
20
+ * would have replaced the bad file.
21
+ *
22
+ * There is deliberately no expiry. The catalogue only changes when someone
23
+ * promotes to it, and a stale row fails loudly and harmlessly (the URL 404s) —
24
+ * where a timeout would expire a list the creator is still reading. `add` prints
25
+ * the search's own timestamp instead, so age is visible rather than enforced.
26
+ */
27
+ import * as path from 'path';
28
+ import { readJsonFile, writeJsonAtomic } from '../atomic-json.js';
29
+ const SEARCH_FILE = path.join('.bitmagic', 'character-search.json');
30
+ export function characterSearchPath(root) {
31
+ return path.join(root, SEARCH_FILE);
32
+ }
33
+ function isCandidate(value) {
34
+ if (typeof value !== 'object' || value === null)
35
+ return false;
36
+ const row = value;
37
+ return typeof row.sid === 'string' && row.sid !== ''
38
+ && typeof row.assetUrl === 'string' && row.assetUrl !== ''
39
+ && typeof row.similarity === 'number';
40
+ }
41
+ /** The last search, or null when there is none to trust. */
42
+ export function readCharacterSearch(root) {
43
+ const raw = readJsonFile(characterSearchPath(root));
44
+ if (typeof raw !== 'object' || raw === null)
45
+ return null;
46
+ const record = raw;
47
+ if (typeof record.prompt !== 'string')
48
+ return null;
49
+ if (record.representation !== 'voxel' && record.representation !== 'lowpoly')
50
+ return null;
51
+ if (!Array.isArray(record.candidates))
52
+ return null;
53
+ const candidates = record.candidates.filter(isCandidate).map((row) => ({
54
+ sid: row.sid,
55
+ assetUrl: row.assetUrl,
56
+ description: typeof row.description === 'string' ? row.description : '',
57
+ subject: typeof row.subject === 'string' ? row.subject : null,
58
+ similarity: row.similarity,
59
+ }));
60
+ return {
61
+ prompt: record.prompt,
62
+ representation: record.representation,
63
+ threshold: typeof record.threshold === 'number' ? record.threshold : 0,
64
+ searchedAt: typeof record.searchedAt === 'string' ? record.searchedAt : '',
65
+ candidates,
66
+ };
67
+ }
68
+ export function writeCharacterSearch(root, record) {
69
+ writeJsonAtomic(characterSearchPath(root), record);
70
+ }
71
+ /**
72
+ * The cached row with this id, or null.
73
+ *
74
+ * Case-insensitive on the id because a creator reads it off a table and retypes
75
+ * it, and the corpus uses one case throughout — so a capital letter is a typo
76
+ * with an obvious intent, not a different character.
77
+ */
78
+ export function findCachedCandidate(record, sid) {
79
+ const wanted = sid.trim().toLowerCase();
80
+ return record.candidates.find((row) => row.sid.toLowerCase() === wanted) ?? null;
81
+ }
82
+ //# sourceMappingURL=character-search.js.map