@bitmagic/asset-core 0.1.1 → 0.1.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.
@@ -0,0 +1,74 @@
1
+ /**
2
+ * The published game's browser-tab icon: the one `<link>` element, the one storage-key
3
+ * derivation, and the one function that swaps the default out for a game's own.
4
+ *
5
+ * Import path matters: this module is published as the `@bitmagic/asset-core/favicon`
6
+ * subpath so its consumers do not drag in the root barrel's zod / three / forger client.
7
+ * It is node-only (`node:crypto`) — the three consumers are game-play-agent (web publish
8
+ * lane), api-server (CLI publish lane) and the CLI itself. The Creator never imports it;
9
+ * it uploads a source image and lets publish do the deriving.
10
+ *
11
+ * Why this lives in a shared package rather than being duplicated per lane: the two
12
+ * publish lanes must agree on the marker id and on the exact `<link>` they emit, or a
13
+ * game's icon silently reverts to the Bitmagic mark in whichever lane drifted. The pieces
14
+ * where drift breaks correctness are shared here; the sharp resize is NOT (it would drag
15
+ * a heavy native dep into a package the browser-facing subpaths live beside), and is
16
+ * deliberately mirrored in each lane instead.
17
+ */
18
+ /**
19
+ * The id on the default `<link rel="icon">` baked into every game bundle — `game/index.html`
20
+ * for the web lane, `renderIndexHtml()` for CLI projects. Publish finds the element by this
21
+ * id and replaces it wholesale.
22
+ *
23
+ * Replacing rather than appending a second link is deliberate. Two `sizes`-less `rel="icon"`
24
+ * links is ambiguous input: "the last one wins" is browser convention, not spec, and Safari
25
+ * has historically picked differently from Chrome. After `applyFaviconLink` the document
26
+ * carries exactly one icon link, which is something a test can assert.
27
+ */
28
+ export declare const FAVICON_LINK_ID = "bm-favicon";
29
+ /**
30
+ * Edge length of the derived icon. A retina browser tab renders a 32 CSS px icon at 2x, so
31
+ * 64 is the real ceiling that matters; it also covers bookmark and taskbar use at 48.
32
+ * Browsers downscale cleanly, so a single size beats emitting several `sizes=` variants.
33
+ */
34
+ export declare const FAVICON_PX = 64;
35
+ /**
36
+ * 32 hex chars of sha256 over the SOURCE URL — not over the source bytes.
37
+ *
38
+ * Hashing the URL is what makes the cache free: the storage key is computable without
39
+ * fetching anything, so a cache hit costs one existence probe and no download. It is sound
40
+ * because every cover/upload URL in both lanes is already timestamp-unique per upload, so
41
+ * new art always produces a new URL and therefore a new key.
42
+ */
43
+ export declare function faviconContentHash(sourceUrl: string): string;
44
+ /**
45
+ * Where the web lane stores an icon derived from `sourceUrl`, relative to the storage
46
+ * prefix (`worlds/v3`). The CLI lane composes its own game-scoped key from
47
+ * `faviconContentHash` instead, because its bucket layout is per-game rather than shared.
48
+ *
49
+ * The `v1` segment and the `-64` suffix are load-bearing: changing the crop or the size
50
+ * must produce a NEW key rather than silently serving the old bytes from an immutable
51
+ * `max-age=31536000` object. Same reasoning as the hand-rolled `-v2` on the watermark logo.
52
+ */
53
+ export declare function faviconStorageKey(sourceUrl: string): string;
54
+ /** The exact element every lane emits. One definition so the lanes cannot disagree. */
55
+ export declare function renderFaviconLink(href: string): string;
56
+ /**
57
+ * Point the document's icon at `href`.
58
+ *
59
+ * Replaces the element carrying `id="bm-favicon"`. When that marker is absent — an older
60
+ * cached bundle, or a CLI project that has not run `bitmagic upgrade` since the marker
61
+ * shipped — falls back to inserting before `</head>`, which is best-effort but never worse
62
+ * than the un-marked status quo.
63
+ *
64
+ * Returns `html` unchanged when `href` is missing or not an allowed scheme, and never
65
+ * throws: a tab icon must not be able to fail a publish.
66
+ *
67
+ * `indexOf` rather than a regex on purpose. The default href is a ~700-char base64 data URI
68
+ * sitting inside a bundle that reaches 20 MB, and the surrounding code (`injectIntoHtml`)
69
+ * is string-insertion for the same reason. Anchored `indexOf` scans are O(n) with tiny
70
+ * constants and have obvious failure semantics; a backtracking regex over that string does
71
+ * not.
72
+ */
73
+ export declare function applyFaviconLink(html: string, href: string | null | undefined): string;
74
+ //# sourceMappingURL=favicon.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"favicon.d.ts","sourceRoot":"","sources":["../src/favicon.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAIH;;;;;;;;;GASG;AACH,eAAO,MAAM,eAAe,eAAe,CAAC;AAE5C;;;;GAIG;AACH,eAAO,MAAM,UAAU,KAAK,CAAC;AAqB7B;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAE5D;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAE3D;AAED,uFAAuF;AACvF,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAuBtF"}
@@ -0,0 +1,130 @@
1
+ /**
2
+ * The published game's browser-tab icon: the one `<link>` element, the one storage-key
3
+ * derivation, and the one function that swaps the default out for a game's own.
4
+ *
5
+ * Import path matters: this module is published as the `@bitmagic/asset-core/favicon`
6
+ * subpath so its consumers do not drag in the root barrel's zod / three / forger client.
7
+ * It is node-only (`node:crypto`) — the three consumers are game-play-agent (web publish
8
+ * lane), api-server (CLI publish lane) and the CLI itself. The Creator never imports it;
9
+ * it uploads a source image and lets publish do the deriving.
10
+ *
11
+ * Why this lives in a shared package rather than being duplicated per lane: the two
12
+ * publish lanes must agree on the marker id and on the exact `<link>` they emit, or a
13
+ * game's icon silently reverts to the Bitmagic mark in whichever lane drifted. The pieces
14
+ * where drift breaks correctness are shared here; the sharp resize is NOT (it would drag
15
+ * a heavy native dep into a package the browser-facing subpaths live beside), and is
16
+ * deliberately mirrored in each lane instead.
17
+ */
18
+ import { createHash } from 'node:crypto';
19
+ /**
20
+ * The id on the default `<link rel="icon">` baked into every game bundle — `game/index.html`
21
+ * for the web lane, `renderIndexHtml()` for CLI projects. Publish finds the element by this
22
+ * id and replaces it wholesale.
23
+ *
24
+ * Replacing rather than appending a second link is deliberate. Two `sizes`-less `rel="icon"`
25
+ * links is ambiguous input: "the last one wins" is browser convention, not spec, and Safari
26
+ * has historically picked differently from Chrome. After `applyFaviconLink` the document
27
+ * carries exactly one icon link, which is something a test can assert.
28
+ */
29
+ export const FAVICON_LINK_ID = 'bm-favicon';
30
+ /**
31
+ * Edge length of the derived icon. A retina browser tab renders a 32 CSS px icon at 2x, so
32
+ * 64 is the real ceiling that matters; it also covers bookmark and taskbar use at 48.
33
+ * Browsers downscale cleanly, so a single size beats emitting several `sizes=` variants.
34
+ */
35
+ export const FAVICON_PX = 64;
36
+ /** Attribute-safe. The href reaches a public page's `<head>` and may be creator-supplied. */
37
+ function escapeAttr(value) {
38
+ return value
39
+ .replace(/&/g, '&amp;')
40
+ .replace(/"/g, '&quot;')
41
+ .replace(/</g, '&lt;')
42
+ .replace(/>/g, '&gt;');
43
+ }
44
+ /**
45
+ * Only these two forms may become an icon href. Escaping alone is not the whole guard — a
46
+ * `javascript:` URL in a `<link href>` is inert in modern browsers, but this string is
47
+ * assembled from creator-controlled data and baked into a public page, so the allowlist is
48
+ * the guard and the escaping is defence in depth.
49
+ */
50
+ function isAllowedHref(href) {
51
+ return /^https:\/\//i.test(href) || /^data:image\//i.test(href);
52
+ }
53
+ /**
54
+ * 32 hex chars of sha256 over the SOURCE URL — not over the source bytes.
55
+ *
56
+ * Hashing the URL is what makes the cache free: the storage key is computable without
57
+ * fetching anything, so a cache hit costs one existence probe and no download. It is sound
58
+ * because every cover/upload URL in both lanes is already timestamp-unique per upload, so
59
+ * new art always produces a new URL and therefore a new key.
60
+ */
61
+ export function faviconContentHash(sourceUrl) {
62
+ return createHash('sha256').update(sourceUrl).digest('hex').slice(0, 32);
63
+ }
64
+ /**
65
+ * Where the web lane stores an icon derived from `sourceUrl`, relative to the storage
66
+ * prefix (`worlds/v3`). The CLI lane composes its own game-scoped key from
67
+ * `faviconContentHash` instead, because its bucket layout is per-game rather than shared.
68
+ *
69
+ * The `v1` segment and the `-64` suffix are load-bearing: changing the crop or the size
70
+ * must produce a NEW key rather than silently serving the old bytes from an immutable
71
+ * `max-age=31536000` object. Same reasoning as the hand-rolled `-v2` on the watermark logo.
72
+ */
73
+ export function faviconStorageKey(sourceUrl) {
74
+ return `assets/favicons/v1/${faviconContentHash(sourceUrl)}-${FAVICON_PX}.png`;
75
+ }
76
+ /** The exact element every lane emits. One definition so the lanes cannot disagree. */
77
+ export function renderFaviconLink(href) {
78
+ return `<link id="${FAVICON_LINK_ID}" rel="icon" type="image/png" href="${escapeAttr(href)}">`;
79
+ }
80
+ /**
81
+ * Point the document's icon at `href`.
82
+ *
83
+ * Replaces the element carrying `id="bm-favicon"`. When that marker is absent — an older
84
+ * cached bundle, or a CLI project that has not run `bitmagic upgrade` since the marker
85
+ * shipped — falls back to inserting before `</head>`, which is best-effort but never worse
86
+ * than the un-marked status quo.
87
+ *
88
+ * Returns `html` unchanged when `href` is missing or not an allowed scheme, and never
89
+ * throws: a tab icon must not be able to fail a publish.
90
+ *
91
+ * `indexOf` rather than a regex on purpose. The default href is a ~700-char base64 data URI
92
+ * sitting inside a bundle that reaches 20 MB, and the surrounding code (`injectIntoHtml`)
93
+ * is string-insertion for the same reason. Anchored `indexOf` scans are O(n) with tiny
94
+ * constants and have obvious failure semantics; a backtracking regex over that string does
95
+ * not.
96
+ */
97
+ export function applyFaviconLink(html, href) {
98
+ if (!href || !isAllowedHref(href))
99
+ return html;
100
+ const link = renderFaviconLink(href);
101
+ const needle = `id="${FAVICON_LINK_ID}"`;
102
+ for (let at = html.indexOf(needle); at !== -1; at = html.indexOf(needle, at + needle.length)) {
103
+ const open = html.lastIndexOf('<', at);
104
+ const close = html.indexOf('>', at);
105
+ // A marker with no enclosing element is malformed input, not something to guess at.
106
+ if (open === -1 || close === -1)
107
+ continue;
108
+ // It has to be an actual <link> element. The id is also NAMED in prose — the explanatory
109
+ // comment above the default link in game/index.html, this doc block, docs — and matching the
110
+ // first occurrence anywhere replaced a comment instead of the link, which left the real
111
+ // default in place and shipped a page with two icon links. Verified the hard way.
112
+ if (!/^<link[\s/>]/i.test(html.slice(open, open + 6)))
113
+ continue;
114
+ if (isInsideComment(html, open))
115
+ continue;
116
+ return html.slice(0, open) + link + html.slice(close + 1);
117
+ }
118
+ const headClose = html.indexOf('</head>');
119
+ if (headClose === -1)
120
+ return html;
121
+ return html.slice(0, headClose) + link + '\n' + html.slice(headClose);
122
+ }
123
+ /** Is `at` inside an HTML comment? True when the nearest `<!--` behind it is not yet closed. */
124
+ function isInsideComment(html, at) {
125
+ const open = html.lastIndexOf('<!--', at);
126
+ if (open === -1)
127
+ return false;
128
+ return html.lastIndexOf('-->', at) < open;
129
+ }
130
+ //# sourceMappingURL=favicon.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"favicon.js","sourceRoot":"","sources":["../src/favicon.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ import { z } from 'zod';
2
+ import type { AssetResultBase, GenerateDeps } from '../types.js';
3
+ /**
4
+ * A 3D prop mesh, from a prompt.
5
+ *
6
+ * The odd one out among the generators: it returns NO world.json patches. Every other generator
7
+ * produces a finished asset the host can write straight away, but a prop is only half made when
8
+ * the Asset Forger hands back a GLB — the engine still has to voxelize it, and voxelizing happens
9
+ * in a browser with the game loaded, which is the caller's side of the seam in both lanes. So this
10
+ * returns the GLB URL and stops, and the host decides what to do with it.
11
+ *
12
+ * That is what makes the same code serve the CLI's `bitmagic generate prop` (voxelize in a
13
+ * headless Chrome, upsert the asset locally) and, in principle, the web lane's HQ job — which
14
+ * today reimplements this call inline in `hq-asset-jobs.ts`.
15
+ *
16
+ * The generation is billable the moment the Forger accepts the job: the vendor charges on
17
+ * acceptance, so a downstream failure to produce a URL still costs.
18
+ */
19
+ export declare const propParamsSchema: z.ZodObject<{
20
+ prompt: z.ZodString;
21
+ /**
22
+ * `prop` matches what the HQ path has always sent. Named rather than hardcoded because the
23
+ * Forger distinguishes props from characters, and a mis-typed model produces a rigged humanoid
24
+ * where a rock was wanted.
25
+ */
26
+ modelType: z.ZodDefault<z.ZodOptional<z.ZodEnum<["prop"]>>>;
27
+ }, "strict", z.ZodTypeAny, {
28
+ prompt: string;
29
+ modelType: "prop";
30
+ }, {
31
+ prompt: string;
32
+ modelType?: "prop" | undefined;
33
+ }>;
34
+ export type PropParams = z.input<typeof propParamsSchema>;
35
+ export type PropResult = AssetResultBase & {
36
+ /** Where the generated mesh landed. The host voxelizes this. */
37
+ glbUrl?: string;
38
+ billable?: boolean;
39
+ };
40
+ export declare function generateProp(params: PropParams, deps: GenerateDeps & {
41
+ imageToMeshModel?: string;
42
+ }): Promise<PropResult>;
43
+ //# sourceMappingURL=prop.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prop.d.ts","sourceRoot":"","sources":["../../src/generators/prop.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAIjE;;;;;;;;;;;;;;;GAeG;AAEH,eAAO,MAAM,gBAAgB;;IAI3B;;;;OAIG;;;;;;;;EAEM,CAAC;AAEZ,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE1D,MAAM,MAAM,UAAU,GAAG,eAAe,GAAG;IACzC,gEAAgE;IAChE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAcF,wBAAsB,YAAY,CAChC,MAAM,EAAE,UAAU,EAClB,IAAI,EAAE,YAAY,GAAG;IAAE,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAAE,GACjD,OAAO,CAAC,UAAU,CAAC,CAsDrB"}
@@ -0,0 +1,80 @@
1
+ import { z } from 'zod';
2
+ import { fail, ok } from '../result.js';
3
+ import { formatZodIssues } from '../validation.js';
4
+ /**
5
+ * A 3D prop mesh, from a prompt.
6
+ *
7
+ * The odd one out among the generators: it returns NO world.json patches. Every other generator
8
+ * produces a finished asset the host can write straight away, but a prop is only half made when
9
+ * the Asset Forger hands back a GLB — the engine still has to voxelize it, and voxelizing happens
10
+ * in a browser with the game loaded, which is the caller's side of the seam in both lanes. So this
11
+ * returns the GLB URL and stops, and the host decides what to do with it.
12
+ *
13
+ * That is what makes the same code serve the CLI's `bitmagic generate prop` (voxelize in a
14
+ * headless Chrome, upsert the asset locally) and, in principle, the web lane's HQ job — which
15
+ * today reimplements this call inline in `hq-asset-jobs.ts`.
16
+ *
17
+ * The generation is billable the moment the Forger accepts the job: the vendor charges on
18
+ * acceptance, so a downstream failure to produce a URL still costs.
19
+ */
20
+ export const propParamsSchema = z.object({
21
+ prompt: z.string()
22
+ .min(1)
23
+ .describe('What the object should be (e.g. "a weathered stone archway covered in moss")'),
24
+ /**
25
+ * `prop` matches what the HQ path has always sent. Named rather than hardcoded because the
26
+ * Forger distinguishes props from characters, and a mis-typed model produces a rigged humanoid
27
+ * where a rock was wanted.
28
+ */
29
+ modelType: z.enum(['prop']).optional().default('prop'),
30
+ }).strict();
31
+ const ENDPOINT = '/ai/v1/forge3d/jobs';
32
+ /**
33
+ * Kept in step with `hq-asset-jobs.ts`, which is the proven configuration for this endpoint.
34
+ * `rigging: 'false'` is a STRING because that is what the Forger's API accepts.
35
+ */
36
+ const DEFAULT_IMAGE_TO_MESH_MODEL = 'unified-trellis';
37
+ export async function generateProp(params, deps) {
38
+ const { forger, logger, onProgress } = deps;
39
+ const parsed = propParamsSchema.safeParse(params);
40
+ if (!parsed.success) {
41
+ logger.warn({ err: parsed.error }, '[prop] Invalid parameters');
42
+ return fail(formatZodIssues(parsed.error));
43
+ }
44
+ const input = parsed.data;
45
+ if (!forger.hasCredentials()) {
46
+ logger.error('[prop] Missing Asset Forger credentials');
47
+ return fail(forger.getMissingCredentialsMessage());
48
+ }
49
+ onProgress(`Generating a mesh for: ${input.prompt}`);
50
+ logger.info(`[prop] Starting forge3d for "${input.prompt}"`);
51
+ let response;
52
+ try {
53
+ response = await forger.postJobAndWait(ENDPOINT, {
54
+ model: 'lab',
55
+ prompt: input.prompt,
56
+ parameters: {
57
+ imageToMeshModel: deps.imageToMeshModel ?? DEFAULT_IMAGE_TO_MESH_MODEL,
58
+ modelType: input.modelType,
59
+ rigging: 'false',
60
+ },
61
+ }, 'prop');
62
+ }
63
+ catch (error) {
64
+ const message = error instanceof Error ? error.message : 'Unknown error occurred';
65
+ logger.error({ err: error }, '[prop] Error during generation');
66
+ return fail(message);
67
+ }
68
+ const glbUrl = response.data?.fileUrl;
69
+ if (!glbUrl) {
70
+ // Billable: the Forger accepted and ran the job, and was paid for it, whatever it answered.
71
+ return { ...fail('Asset Forger returned no file URL for the generated mesh'), billable: true };
72
+ }
73
+ logger.info(`[prop] Generated ${glbUrl}`);
74
+ return ok(`Mesh generated: ${glbUrl}`,
75
+ // Deliberately empty — see the file header. The asset does not exist until the host has
76
+ // voxelized this, and writing a half-made entry would leave world.json describing an asset the
77
+ // engine cannot load.
78
+ [], { glbUrl, billable: true });
79
+ }
80
+ //# sourceMappingURL=prop.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prop.js","sourceRoot":"","sources":["../../src/generators/prop.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC;AACxC,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEnD;;;;;;;;;;;;;;;GAeG;AAEH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAC,MAAM,CAAC;IACvC,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE;SACf,GAAG,CAAC,CAAC,CAAC;SACN,QAAQ,CAAC,8EAA8E,CAAC;IAC3F;;;;OAIG;IACH,SAAS,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC;CACvD,CAAC,CAAC,MAAM,EAAE,CAAC;AAcZ,MAAM,QAAQ,GAAG,qBAAqB,CAAC;AAEvC;;;GAGG;AACH,MAAM,2BAA2B,GAAG,iBAAiB,CAAC;AAEtD,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,MAAkB,EAClB,IAAkD;IAElD,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,GAAG,IAAI,CAAC;IAE5C,MAAM,MAAM,GAAG,gBAAgB,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IAClD,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,KAAK,EAAE,EAAE,2BAA2B,CAAC,CAAC;QAChE,OAAO,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IAC7C,CAAC;IACD,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC;IAE1B,IAAI,CAAC,MAAM,CAAC,cAAc,EAAE,EAAE,CAAC;QAC7B,MAAM,CAAC,KAAK,CAAC,yCAAyC,CAAC,CAAC;QACxD,OAAO,IAAI,CAAC,MAAM,CAAC,4BAA4B,EAAE,CAAC,CAAC;IACrD,CAAC;IAED,UAAU,CAAC,0BAA0B,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;IACrD,MAAM,CAAC,IAAI,CAAC,gCAAgC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC;IAE7D,IAAI,QAAyB,CAAC;IAC9B,IAAI,CAAC;QACH,QAAQ,GAAG,MAAM,MAAM,CAAC,cAAc,CACpC,QAAQ,EACR;YACE,KAAK,EAAE,KAAK;YACZ,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,UAAU,EAAE;gBACV,gBAAgB,EAAE,IAAI,CAAC,gBAAgB,IAAI,2BAA2B;gBACtE,SAAS,EAAE,KAAK,CAAC,SAAS;gBAC1B,OAAO,EAAE,OAAO;aACjB;SACF,EACD,MAAM,CACP,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,wBAAwB,CAAC;QAClF,MAAM,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE,gCAAgC,CAAC,CAAC;QAC/D,OAAO,IAAI,CAAC,OAAO,CAAC,CAAC;IACvB,CAAC;IAED,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACtC,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,4FAA4F;QAC5F,OAAO,EAAE,GAAG,IAAI,CAAC,0DAA0D,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IACjG,CAAC;IAED,MAAM,CAAC,IAAI,CAAC,oBAAoB,MAAM,EAAE,CAAC,CAAC;IAC1C,OAAO,EAAE,CACP,mBAAmB,MAAM,EAAE;IAC3B,wFAAwF;IACxF,+FAA+F;IAC/F,sBAAsB;IACtB,EAAE,EACF,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,CAC3B,CAAC;AACJ,CAAC"}
package/dist/index.d.ts CHANGED
@@ -11,6 +11,8 @@ export { generateImageAsset, imageAssetParamsSchema } from './generators/image.j
11
11
  export type { ImageAssetParams, ImageAssetResult } from './generators/image.js';
12
12
  export { generateCharacter, characterParamsSchema } from './generators/character.js';
13
13
  export type { CharacterParams, CharacterResult } from './generators/character.js';
14
+ export { generateProp, propParamsSchema } from './generators/prop.js';
15
+ export type { PropParams, PropResult } from './generators/prop.js';
14
16
  export { generateAnimationAsset, animationParamsSchema, getConfiguredAnimationModel, ANIMATION_MODEL_TEXT_TO_MOTION_3, ANIMATION_MODEL_TEXT_TO_MOTION_2, ANIMATION_MODEL_OLDER, } from './generators/animation.js';
15
17
  export type { AnimationParams, AnimationResult } from './generators/animation.js';
16
18
  export { fetchContentLength } from './http.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,eAAe,EACf,UAAU,EACV,cAAc,EACd,YAAY,EACZ,YAAY,EACZ,eAAe,EACf,UAAU,GACX,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAC/F,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,YAAY,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AACjE,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAC5E,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACzE,OAAO,EAAE,mBAAmB,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAC5F,YAAY,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACzF,OAAO,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AACnF,YAAY,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAChF,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AACrF,YAAY,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAClF,OAAO,EACL,sBAAsB,EACtB,qBAAqB,EACrB,2BAA2B,EAC3B,gCAAgC,EAChC,gCAAgC,EAChC,qBAAqB,GACtB,MAAM,2BAA2B,CAAC;AACnC,YAAY,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAClF,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAMnG,OAAO,EACL,sBAAsB,EACtB,wBAAwB,EACxB,oBAAoB,EACpB,qBAAqB,EACrB,kBAAkB,EAClB,oBAAoB,GACrB,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EACV,yBAAyB,EACzB,eAAe,EACf,eAAe,EACf,mBAAmB,EACnB,qBAAqB,GACtB,MAAM,uBAAuB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,eAAe,EACf,UAAU,EACV,cAAc,EACd,YAAY,EACZ,YAAY,EACZ,eAAe,EACf,UAAU,GACX,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAC/F,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,YAAY,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AACjE,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAC5E,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACzE,OAAO,EAAE,mBAAmB,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAC5F,YAAY,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACzF,OAAO,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AACnF,YAAY,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAChF,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AACrF,YAAY,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAClF,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACtE,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACnE,OAAO,EACL,sBAAsB,EACtB,qBAAqB,EACrB,2BAA2B,EAC3B,gCAAgC,EAChC,gCAAgC,EAChC,qBAAqB,GACtB,MAAM,2BAA2B,CAAC;AACnC,YAAY,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAClF,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAMnG,OAAO,EACL,sBAAsB,EACtB,wBAAwB,EACxB,oBAAoB,EACpB,qBAAqB,EACrB,kBAAkB,EAClB,oBAAoB,GACrB,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EACV,yBAAyB,EACzB,eAAe,EACf,eAAe,EACf,mBAAmB,EACnB,qBAAqB,GACtB,MAAM,uBAAuB,CAAC"}
package/dist/index.js CHANGED
@@ -5,6 +5,7 @@ export { generateSkybox, skyboxParamsSchema } from './generators/skybox.js';
5
5
  export { generateSoundEffect, soundEffectParamsSchema } from './generators/sound-effect.js';
6
6
  export { generateImageAsset, imageAssetParamsSchema } from './generators/image.js';
7
7
  export { generateCharacter, characterParamsSchema } from './generators/character.js';
8
+ export { generateProp, propParamsSchema } from './generators/prop.js';
8
9
  export { generateAnimationAsset, animationParamsSchema, getConfiguredAnimationModel, ANIMATION_MODEL_TEXT_TO_MOTION_3, ANIMATION_MODEL_TEXT_TO_MOTION_2, ANIMATION_MODEL_OLDER, } from './generators/animation.js';
9
10
  export { fetchContentLength } from './http.js';
10
11
  export { PIXAR_VOXEL_STYLE, MAX_COVER_DESIGN_CHARS, buildCoverArtPrompt } from './cover-prompt.js';
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAC/F,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAEvD,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAE5E,OAAO,EAAE,mBAAmB,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAE5F,OAAO,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAEnF,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAErF,OAAO,EACL,sBAAsB,EACtB,qBAAqB,EACrB,2BAA2B,EAC3B,gCAAgC,EAChC,gCAAgC,EAChC,qBAAqB,GACtB,MAAM,2BAA2B,CAAC;AAEnC,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAEnG,oEAAoE;AACpE,gFAAgF;AAChF,gFAAgF;AAChF,oEAAoE;AACpE,OAAO,EACL,sBAAsB,EACtB,wBAAwB,EACxB,oBAAoB,EACpB,qBAAqB,EACrB,kBAAkB,EAClB,oBAAoB,GACrB,MAAM,uBAAuB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAC/F,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAEvD,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAE5E,OAAO,EAAE,mBAAmB,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAE5F,OAAO,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAEnF,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAErF,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAEtE,OAAO,EACL,sBAAsB,EACtB,qBAAqB,EACrB,2BAA2B,EAC3B,gCAAgC,EAChC,gCAAgC,EAChC,qBAAqB,GACtB,MAAM,2BAA2B,CAAC;AAEnC,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAEnG,oEAAoE;AACpE,gFAAgF;AAChF,gFAAgF;AAChF,oEAAoE;AACpE,OAAO,EACL,sBAAsB,EACtB,wBAAwB,EACxB,oBAAoB,EACpB,qBAAqB,EACrB,kBAAkB,EAClB,oBAAoB,GACrB,MAAM,uBAAuB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bitmagic/asset-core",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -13,6 +13,10 @@
13
13
  "types": "./dist/terminal-failure.d.ts",
14
14
  "default": "./dist/terminal-failure.js"
15
15
  },
16
+ "./favicon": {
17
+ "types": "./dist/favicon.d.ts",
18
+ "default": "./dist/favicon.js"
19
+ },
16
20
  "./animation/*": {
17
21
  "default": "./src/animation/*"
18
22
  },