@homie-rocks/studio 0.51.4 → 0.51.5

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 (80) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/README.md +10 -29
  3. package/apps/APPS.md +1 -1
  4. package/bin/homie-studio.mjs +28 -30
  5. package/guides/app/SKILL.md +1 -1
  6. package/guides/art/SKILL.md +68 -140
  7. package/guides/art/scripts/art.mjs +6 -79
  8. package/guides/models/SKILL.md +73 -177
  9. package/guides/models/references/RIGHTS.md +1 -1
  10. package/guides/models/scripts/models.mjs +7 -288
  11. package/guides/music/SKILL.md +74 -183
  12. package/guides/music/references/COMPOSITION.md +21 -26
  13. package/guides/music/references/GAME-AUDIO.md +3 -3
  14. package/guides/music/references/RIGHTS.md +12 -15
  15. package/guides/music/scripts/music.mjs +11 -202
  16. package/guides/parts/SKILL.md +1 -1
  17. package/guides/shop/SKILL.md +68 -77
  18. package/guides/sound/SKILL.md +70 -1
  19. package/guides/sound/scripts/player/sound.js +233 -0
  20. package/guides/studio-setup/SKILL.md +11 -27
  21. package/guides/studio-setup/references/OFFICIAL-MEDIA.md +66 -0
  22. package/guides/style/SKILL.md +66 -3
  23. package/guides/video/SKILL.md +83 -0
  24. package/guides/video/references/DELIVERY.md +73 -0
  25. package/guides/video/references/HONESTY.md +40 -0
  26. package/guides/video/references/METHOD.md +144 -0
  27. package/guides/video/references/RECORD.md +137 -0
  28. package/guides/video/references/TRAILER.md +154 -0
  29. package/guides/video/references/examples/site-walkthrough.json +14 -0
  30. package/guides/video/references/examples/studio-play-phone.json +15 -0
  31. package/guides/video/references/examples/studio-play.json +17 -0
  32. package/guides/video/scripts/capture-game.mjs +175 -0
  33. package/guides/video/scripts/clock.js +136 -0
  34. package/guides/video/scripts/drawover/film.js +58 -0
  35. package/guides/video/scripts/drawover/index.html +9 -0
  36. package/guides/video/scripts/drawover/look.js +115 -0
  37. package/guides/video/scripts/drawover/shots.json +12 -0
  38. package/guides/video/scripts/lib/browser.mjs +87 -0
  39. package/guides/video/scripts/lib/frames.mjs +170 -0
  40. package/guides/video/scripts/lib/remix.mjs +159 -0
  41. package/guides/video/scripts/qa.mjs +127 -0
  42. package/guides/video/scripts/record-fixed.mjs +403 -0
  43. package/guides/video/scripts/record-page.mjs +477 -0
  44. package/guides/video/scripts/tap.js +108 -0
  45. package/guides/video/scripts/video.mjs +888 -0
  46. package/lib/build.mjs +12 -9
  47. package/lib/cloudflare.mjs +6 -11
  48. package/lib/dev.mjs +1 -2
  49. package/lib/doctor.mjs +10 -54
  50. package/lib/mcp-tools.mjs +42 -21
  51. package/lib/mcp.mjs +1 -1
  52. package/lib/media-import.mjs +41 -0
  53. package/lib/media-workflows.mjs +13 -0
  54. package/lib/official-media.mjs +15 -0
  55. package/lib/rules-build.mjs +13 -5
  56. package/lib/scaffold.mjs +2 -2
  57. package/lib/shop-links.mjs +54 -18
  58. package/lib/shop-listen.mjs +43 -0
  59. package/lib/shop-local.mjs +1 -1
  60. package/lib/shop-mcp.mjs +58 -0
  61. package/lib/stripe-connect.mjs +36 -15
  62. package/lib/template-history.json +45 -0
  63. package/lib/upgrade-media.mjs +13 -0
  64. package/lib/upgrade.mjs +10 -0
  65. package/package.json +1 -1
  66. package/parts/PARTS.md +1 -1
  67. package/rules/view.ts +4 -0
  68. package/shop/SHOP.md +66 -34
  69. package/worker/build-id.mjs +1 -1
  70. package/worker/index.mjs +5 -1
  71. package/worker/purchase-reconciliation.mjs +16 -1
  72. package/worker/shop-subscriptions.mjs +2 -0
  73. package/worker/shop.mjs +7 -5
  74. package/worker/stripe-ownership.mjs +7 -0
  75. package/worker/version.mjs +1 -1
  76. package/guides/models/references/models.json +0 -175
  77. package/guides/music/scripts/lib/eleven.mjs +0 -260
  78. package/lib/projects-env.mjs +0 -68
  79. package/lib/projects.mjs +0 -311
  80. package/lib/provider-key.mjs +0 -21
@@ -1,33 +1,13 @@
1
1
  #!/usr/bin/env node
2
- /**
3
- * art.mjs: a game's cover, painted backdrops and textures, made at build time and never while
4
- * anyone plays. Real frames come from the running game for free; generated images come from fal
5
- * through the creator's own account, priced first, capped by a budget, receipted the moment fal
6
- * accepts them.
7
- *
8
- * check ffmpeg, Chrome, the studio, the fal key (a free check)
9
- * frame <game> --url <site> [--view tv|play] [--seconds 12] [--slug <art job>]
10
- * real frames of the game playing, the busiest one picked
11
- * cover <game> --from <image> [--focus x,y]
12
- * the game's cover: 16:9, 1600x900 JPEG, into the game, named in game.json
13
- * budget <slug> --cap <usd> what the person agreed to spend on this art job
14
- * price --model <fal model> --input <input.json>
15
- * gen <slug> --model <fal model> --input <input.json> --out <file> [--dry-run | --yes]
16
- * one paid image (or a few), inside the cap, resumable, receipted
17
- * tile <image> does a texture tile without a seam? numbers and a 2x2 preview
18
- * fit <image> --out <file> [--width 1024] [--max-kb 300]
19
- * a texture or backdrop at a size a phone can download
20
- * sheet <folder> a labelled contact sheet of every image in a folder
21
- *
22
- * Every command takes --json. Run it from inside the studio (the folder with studio.json).
23
- */
2
+ /** art.mjs: local asset preparation only. Generation, schemas and pricing belong to the official provider MCP.
3
+ * Read ../SKILL.md for the current workflow. */
24
4
  import { spawnSync } from 'node:child_process';
25
5
  import { appendFileSync, existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
26
6
  import { dirname, extname, join, relative, resolve } from 'node:path';
27
7
  import { fileURLToPath } from 'node:url';
28
8
  import { experienceDir, experienceJson, isServerRules, SLUG, checkBudget, findStudio, readBudget, readJson, setBudget, writeJson } from '../../music/scripts/lib/studio.mjs';
29
9
  import { GPU_FLAGS, chromePath, htmlToPng, loadPuppeteer } from '../../video/scripts/lib/browser.mjs';
30
- import { checkKey, falKey, priceOf, run as falRun } from '../../video/scripts/lib/fal.mjs';
10
+
31
11
  import { decode, stats } from '../../playtest/scripts/lib/pixels.mjs';
32
12
 
33
13
  const argv = process.argv.slice(2);
@@ -61,29 +41,15 @@ function gameDir(root, game) {
61
41
  }
62
42
 
63
43
  /** A receipt line: the job's budget (running total), art/receipts.jsonl, and HOMIE_SPEND_LEDGER when set. */
64
- function receipt(root, dir, line) {
65
- const at = new Date().toISOString();
66
- const b = readBudget(dir);
67
- if (b) {
68
- b.spent = +(b.spent + (Number(line.cost) || 0)).toFixed(6);
69
- b.calls.push({ at, provider: line.provider, model: line.model, cost: line.cost, unit: line.unit, requestId: line.requestId ?? null, artifact: line.artifact ?? null });
70
- writeJson(join(dir, 'budget.json'), b);
71
- }
72
- mkdirSync(join(root, 'art'), { recursive: true });
73
- appendFileSync(join(root, 'art', 'receipts.jsonl'), `${JSON.stringify({ schema: 'homie.media-receipt/1', at, ...line })}\n`);
74
- const ledger = process.env.HOMIE_SPEND_LEDGER;
75
- if (ledger) {
76
- try { appendFileSync(ledger, `${JSON.stringify({ schema: 'homie.brand-spend/1', at, provider: line.provider, model: line.model, jobId: line.requestId ?? null, units: line.units ?? null, unit: line.unitName ?? line.unit, usd: line.cost, credits: null, unpriced: null, brand: process.env.HOMIE_SPEND_BRAND ?? null, attribution: process.env.HOMIE_SPEND_ATTRIBUTION ?? null, artifact: line.artifact ?? null })}\n`); } catch (e) { process.stderr.write(`could not write the ledger: ${e.message}\n`); }
77
- }
78
- }
44
+
79
45
 
80
46
  /* ---------------------------------------------------------------- check */
81
47
 
82
48
  async function check() {
83
49
  const ff = spawnSync('ffmpeg', ['-version']).status === 0;
84
50
  const root = findStudio();
85
- const key = falKey() ? await checkKey() : { ok: false, why: 'FAL_KEY is not set (only generated images need it; frames, covers, tiles and fitting are free)' };
86
- return { ok: ff, command: 'check', ffmpeg: ff, chrome: chromePath(), puppeteer: Boolean(loadPuppeteer(root)), studio: root, fal: key.ok ? `the key works (checked free; from ${key.source})` : key.why };
51
+ return { ok: ff, command: 'check', ffmpeg: ff, chrome: chromePath(), puppeteer: Boolean(loadPuppeteer(root)), studio: root, generation: 'Use the official fal MCP in your AI client' };
52
+
87
53
  }
88
54
 
89
55
  /* ---------------------------------------------------------------- frame */
@@ -175,43 +141,6 @@ function budget(root) {
175
141
  return { ok: true, command: 'budget', slug: pos[1], cap: b.cap, spent: b.spent };
176
142
  }
177
143
 
178
- function inputOf(file) {
179
- const j = readJson(resolve(String(file ?? '')), null);
180
- if (!j) throw new Error('--input <file.json>: the model\'s input (its schema is on the model\'s fal page, or the fal MCP get_model_schema)');
181
- return j;
182
- }
183
-
184
- async function price() {
185
- const model = String(flags.get('model') ?? '');
186
- if (!model) throw new Error('--model <fal model id, e.g. fal-ai/flux/dev>');
187
- const p = await priceOf(model, inputOf(flags.get('input')));
188
- return { ok: true, command: 'price', ...p };
189
- }
190
-
191
- async function gen(root) {
192
- const slug = pos[1];
193
- const dir = artDir(root, slug);
194
- const model = String(flags.get('model') ?? '');
195
- if (!model) throw new Error('--model <fal model id>');
196
- if (!flags.get('out')) throw new Error('--out <file in this art job, e.g. cover-painted.png>');
197
- const input = inputOf(flags.get('input'));
198
- const out = resolve(dir, String(flags.get('out')));
199
- if (!out.startsWith(`${dir}/`)) throw new Error('--out must stay inside the art job\'s folder');
200
- if (existsSync(out)) return { ok: true, command: 'gen', slug, already: rel(root, out) };
201
- const p = await priceOf(model, input);
202
- if (flags.has('dry-run')) return { ok: true, command: 'gen', dryRun: true, slug, price: p };
203
- const resuming = existsSync(`${out}.request`);
204
- if (!resuming) {
205
- const b = checkBudget(dir, p.usd, { provider: 'fal', unit: 'usd' });
206
- if (!flags.has('yes')) return { ok: false, command: 'gen', needs: 'approval', slug, price: p, budget: { cap: b.cap, spent: b.spent }, why: `This call costs about US$${p.usd.toFixed(3)} (${p.basis}); US$${b.spent.toFixed(2)} of US$${b.cap.toFixed(2)} spent so far. Run it with --yes once that is fine.` };
207
- }
208
- const r = await falRun(model, input, {
209
- out, base: dir, uploads: join(dir, 'uploads.json'), price: p, log: (m) => { if (!JSON_OUT) process.stderr.write(`${m}\n`); },
210
- onAccepted: async (q) => { receipt(root, dir, { provider: 'fal', model, requestId: q.request_id, cost: p.usd, unit: 'usd', units: p.units, unitName: p.unit, basis: p.basis, artifact: rel(root, out) }); },
211
- });
212
- return { ok: true, command: 'gen', slug, files: r.files.map((f) => rel(root, f)), requestId: r.requestId, usd: p.usd, spent: readJson(join(dir, 'budget.json'), {}).spent, next: 'look at it before anything else; a second call only with a changed prompt, never a loop' };
213
- }
214
-
215
144
  /* ---------------------------------------------------------------- tile / fit / sheet */
216
145
 
217
146
  function tile() {
@@ -281,14 +210,12 @@ async function main() {
281
210
  return { ok: true, command: 'help', usage: head.replace(/^#!.*\n\/\*\*?/, '').replace(/^ \* ?/gm, '').trim() };
282
211
  }
283
212
  if (cmd === 'check') return check();
284
- if (cmd === 'price') return price();
285
213
  if (cmd === 'tile') return tile();
286
214
  if (cmd === 'fit') return fit();
287
215
  const root = studioRoot();
288
216
  if (cmd === 'frame') return frame(root);
289
217
  if (cmd === 'cover') return cover(root);
290
218
  if (cmd === 'budget') return budget(root);
291
- if (cmd === 'gen') return gen(root);
292
219
  if (cmd === 'sheet') return sheet(root);
293
220
  return { ok: false, command: cmd, why: `unknown command "${cmd}" (art.mjs help)` };
294
221
  }
@@ -1,198 +1,94 @@
1
1
  ---
2
2
  name: models
3
- description: Give a game real 3D models that fit together and run on a phone — free CC0 models from Homie's starter library (Kenney, KayKit, Poly Haven, ambientCG) copied in with their licence, the person's own files checked and made phone-sized, and generated props (a concept image in the game's locked style, then Tripo P1 image-to-3D) and generated rigged characters (an A-pose concept, then Meshy 7.1 with its auto-rig, the library's clips retargeted onto it) on the person's OWN fal account under a hard budget with a receipt per call — every asset recorded with its provenance and licence (RIGHTS.md, the credits page), checked against phone budgets, shown in a lineup at true scale, and loaded through one safe loader. Use when someone wants 3D models, props, characters, scenery or "free assets" for a game, asks where a model came from or whether it may be used, or says the models look like they are from different games.
4
- compatibility: Node 22. Generated props use Tripo's models, and generated characters Meshy's, on the creator's own fal account (a local FAL_KEY for this skill's script, or a job run through a signed-in fal connector and brought in with `models.mjs import`). fal's own MCP server (https://mcp.fal.ai/mcp-relay) reads schemas and prices.
3
+ description: "Make coherent 3D assets through the official fal MCP, then optimize, register and check them in the studio."
4
+ compatibility: Node 22 and Tripo models through the official fal plugin in the AI client. Local media processing uses ffmpeg and Chrome where needed.
5
5
  metadata:
6
6
  providers: fal tripo
7
7
  ---
8
8
 
9
- # Models for a game: free first, everything licensed, one look
9
+ # Models that fit the game
10
10
 
11
- Read the `style` skill first: a game's models are made under its decisions (render style, palette, scale,
12
- budgets). Routes, cheapest coherent one first:
11
+ Start with Homie's CC0 starter library (`homie-studio assets`) when it fits. Read the
12
+ locked style, character proportions, scale and performance budgets before generating.
13
+ For a new prop, generate and inspect one clean concept first, then a mesh. For a
14
+ character, specify a full body A-pose, readable silhouette and the required skeleton.
15
+ Use a coherent family; avoid mixing unrelated material styles and proportions.
13
16
 
14
- 1. **The engine** (free): procedural geometry, tinted from the palette. The house's best looks are procedural;
15
- never replace one with generated meshes without the owner's ask.
16
- 2. **The starter library** (free, CC0): `assets find`, then `assets add`.
17
- 3. **The person's own files**: `assets add --file … --license …` (ask whose it is).
18
- 4. **Generated** (paid, their own fal account, under a budget): a concept, then a mesh: props, and rigged characters
19
- (below). A character's rig, clips and feel are the `animate` skill's.
17
+ Use fal's `recommend_model`/`search_models`, `get_model_schema` and `get_pricing` for
18
+ each image, mesh and rig step. No model or price is permanently pinned in Homie.
19
+ Use `run_model` for the concept and `submit_job`, `check_job`, `get_job_result` for 3D.
20
+ Inspect the concept before paying for a mesh. Request only schema-supported rig fields.
20
21
 
21
- Commands run from inside the studio: `npx --no-install homie-studio assets …` (free) and this skill's script for
22
- the paid route: `node "${CLAUDE_PLUGIN_ROOT}/skills/models/scripts/models.mjs" <command>` (in an app with Homie's
23
- tools: `assets_find`, `asset_add`, `asset_make`, `asset_check`, `asset_lineup`, `asset_rights`).
24
-
25
- ## The starter library (free)
26
-
27
- ```sh
28
- npx --no-install homie-studio assets find "fox" # CC0 items, phone-sized, with triangles and size
29
- npx --no-install homie-studio assets find "pine tree" --family kenney
30
- npx --no-install homie-studio assets add <id> kenney-cube-pets/animal-fox --height 0.7 --card "Characters/Fox"
31
- ```
32
-
33
- One library family per game (`cast.family`): mixing Kenney with KayKit shows. An added item is COPIED into
34
- `games/<id>/public/models/` (never loaded from homie.rocks at runtime), recorded in `assets/manifest.json` with its
35
- pack, origin and SHA-256, and its pack is credited on the landing's credits page (kindly: CC0 needs no credit).
36
- `HOMIE_LIBRARY` points at another copy of the library (a folder or an address).
37
-
38
- ## The person's own files
39
-
40
- ```sh
41
- npx --no-install homie-studio assets add <id> --file /path/to/lantern.glb --license own --as lantern --height 0.6
42
- ```
43
-
44
- Ask whose it is and under what licence: `own`, `cc0`, `cc-by-4.0` (with `--attribution "<who, where>"`),
45
- `eula:<store>`, `market:<listing>`, `other` (with `--notes`). The file is refused before anything reads it when it
46
- loads anything from an address (an external URI), declares a buffer over the import cap, or is over 64 MB; a
47
- .gltf may bring its .bin and pictures from its own folder only. Then it is made phone-sized (its tier's
48
- triangles and picture size, the pivot at the bottom centre, its height in metres, meshopt geometry, WebP
49
- pictures); the raw file stays in `art/<slug>/raw/` (git-ignored; the studio's R2 with `media move` once it has
50
- storage), never shipped and never committed. FBX, OBJ and .blend: export a .glb first (Blender: File > Export >
51
- glTF 2.0, binary).
52
-
53
- ## Generated props (paid): price, budget, look, then the mesh
22
+ Import a completed GLB with `homie-studio assets add`; use its current `--help` for
23
+ scale, rig and clip options. Retain original generation receipts and register the
24
+ optimized result's licence and provenance. `scripts/models.mjs import` also accepts
25
+ an already completed connector receipt and local GLB; it never runs a provider.
26
+ For a completed connector job, the local import command is:
54
27
 
55
28
  ```sh
56
- node <models.mjs> check # the LOCAL fal key (a free check) and the registry
57
- node <models.mjs> quote <id> --count 3 # concept + mesh, priced live (about US$0.54 a prop)
58
- node <models.mjs> budget <id> --cap 3 # the cap the person agreed to, for this game's models
59
- node <models.mjs> prop <id> lantern --card "Items/Ember Lantern" --what "an iron lantern with a warm ember inside" --height 0.6 --dry-run
60
- node <models.mjs> prop <id> lantern --card "Items/Ember Lantern" --what "…" --height 0.6 --yes # the concept only
61
- # LOOK at art/lantern/concept.*: the thing, whole, in the game's style, on a plain background?
62
- node <models.mjs> prop <id> lantern --mesh --yes # Tripo P1, textured, face_limit 1500; then optimised and recorded
63
- npx --no-install homie-studio assets lineup <id> # true scale, silhouettes, palette drift
29
+ node <path-to-models.mjs> import <id> <asset> --receipt <job.json> --file <model.glb>
64
30
  ```
65
31
 
66
- - Tell the person the price first and agree a cap; `prop` refuses with no budget, refuses past the cap, and asks
67
- again without `--yes`. The receipt is written the moment fal accepts the job (`art/receipts.jsonl`, the
68
- game's `art/<id>-models/budget.json`); running the same step again resumes it and never pays twice.
69
- - The concept starts from the derived style prompt (`homie-studio style prompt <id>`) with the golden images as
70
- references, on a plain background with even light (baked light is the top quality problem of image-to-3D).
71
- - **Two sign-ins, one set of books.** `FAL_KEY` in the shell is what this script pays with. A fal connector (fal's
72
- MCP server signed in from the app) is a separate sign-in the script cannot see: `check` reports the local key only,
73
- and "no local FAL_KEY" does not mean the connector is signed out. With a key, generate here. With only a connector,
74
- generate there (ask the price first and hold it to the same cap), save the job's receipt as JSON the moment it is
75
- accepted (`{ "requestId", "endpoint", "cost" }`) and bring the finished file in:
76
- `node <models.mjs> import <id> lantern --receipt art/lantern/mesh-job.json --file <the .glb> [--concept <image> --concept-receipt <job.json>] [--kind character] [--height 0.6]`.
77
- It is refused with no budget or past the cap, writes one receipt line a request id (never twice), keeps the raw file
78
- in `art/<asset>/raw/` and records the asset with every step, exactly as a job made here. Never copy a key out of
79
- the connector, and never bring a connector's file in with a bare `assets add`: the spend and the provenance would
80
- be missing.
81
- - **Never a loop.** Look at the concept before the mesh, and at the model in the lineup before the next one. A
82
- bad concept: change the words and `--concept-again` once. Spend the riskiest prop first.
83
- - `references/models.json` is the dated registry of endpoints, prices and schemas. Endpoints retire and reprice
84
- monthly: `node <models.mjs> registry` reads every price again for free; update the registry (`--write`) when one
85
- moved, and never hard-code an endpoint anywhere else. fal's own MCP server (`get_model_schema`, `get_pricing`,
86
- `search_models` for a newer Tripo version; the `art` skill says how to connect it) reads the same facts. A paid
87
- run goes through `models.mjs` when there is a local key; a run made through the connector instead is only finished
88
- when `models.mjs import` has recorded it (above).
89
- - Tripo's models run **on fal** here (`tripo3d/...` endpoints): one account, prices read live, the receipts above.
90
- Tripo's own CLI and MCP server (`tripo-cli`, `tripo mcp`) bill a separate Tripo account in Tripo credits, outside
91
- this skill's budget and receipts, and a free Tripo plan makes its outputs public under CC BY 4.0
92
- (`references/RIGHTS.md`): keep a game's models on the fal route. The Homie mod (Claude Code) and Homie's hooks (Codex)
93
- hold a `tripo` generating command or a Tripo MCP tool as a call whose cost cannot be read first.
94
- - A painted mood image for the style board: `node <models.mjs> mood <id> b --yes` (about US$0.035; a target).
95
-
96
- ## Generated characters (paid): an A-pose concept, then a rigged mesh
97
-
98
- Library characters first (`assets find "knight" --kind character`: KayKit's adventurers and skeletons, Kenney's mini
99
- and blocky characters, all CC0 and animated). For one the library cannot cover:
100
-
101
- ```sh
102
- node <models.mjs> character <id> ranger --card "Players/Ranger" --what "a forest ranger: green hooded cloak, leather tunic, a bow on the back" --height 1.45 --like kaykit-adventurers/rogue --dry-run
103
- node <models.mjs> character <id> ranger --card "Players/Ranger" --what "…" --height 1.45 --like kaykit-adventurers/rogue --yes # the concept only
104
- # LOOK at art/ranger/concept.*: one character, whole, in an A-pose, in the game's style, on a plain background?
105
- node <models.mjs> character <id> ranger --mesh --yes # Meshy 7.1 + auto-rig, about US$1.40; then free: optimised, rig mapped, clips retargeted
106
- npx --no-install homie-studio anim preview <id> --asset ranger # LOOK at the preview sheet
32
+ If a registered asset is deliberately not placed, mark it accurately with
33
+ `homie-studio assets use <id> <asset> unused`. Never report an unused model as visible.
34
+
35
+ Inspect bounds, normals, textures, silhouettes and pivot; run `assets check`, check
36
+ triangles/draw calls/texture memory on a phone, and test animation in the actual scene.
37
+ A generated rig is not proof that Homie's clips retarget correctly.
38
+
39
+ ## Official generation workflow
40
+
41
+ The owner's AI uses the provider's official plugin/MCP in its own client. Homie never
42
+ asks for, stores, reads or prints provider credentials. Read the current provider
43
+ skill and discover its available tools and input schemas; host prefixes vary.
44
+ Connection instructions and verified sources: [official integrations](../studio-setup/references/OFFICIAL-MEDIA.md).
45
+ If tools are missing, connect that official plugin once in the client's browser flow.
46
+ Do not fall back to a Homie client, key file, environment key or CLI generation wrapper.
47
+
48
+ Before spending, state the model, count/duration, estimated cost and remaining approved
49
+ budget. Use the owner's existing authorization; ask only if the spend is outside it.
50
+ Record the request ID as soon as accepted. Poll that same request; never resubmit to
51
+ retry a slow job. Unknown cost is unknown, never zero. Provider billing is authoritative;
52
+ Homie's local receipt cannot enforce spending in another client.
53
+
54
+ ## Place and prove the result
55
+
56
+ Download the completed tool's output using the AI client's file/download facilities.
57
+ Keep temporary signed URLs out of committed records. Keep the original in the studio's
58
+ art/job folder; put the optimized delivered asset beside its game/app, or in music/ or
59
+ videos/ for a published work. Record a receipt with these fields:
60
+
61
+ ```json
62
+ {
63
+ "provider": "fal",
64
+ "tool": "run_model",
65
+ "model": "exact endpoint returned by the provider",
66
+ "requestId": "actual request ID",
67
+ "prompt": "the actual prompt, including style constraints",
68
+ "styleRevision": "revision from the studio's style",
69
+ "rights": {"source": "provider/model terms URL", "checkedAt": "YYYY-MM-DD", "commercial": false, "note": "record the actual plan and permitted use"},
70
+ "cost": {"status": "unknown"}
71
+ }
107
72
  ```
108
73
 
109
- `--like` takes a library character's own thumbnail as the style reference (so a generated hero stands beside the
110
- library's in one look); golden images, when the game has them, come first. The concept is a full body in an A-pose,
111
- front view, plain background (what auto-rigging needs). Humanoids with clear limbs only. The quote reads fal's pricing
112
- API and the model page's add-ons (textures, rigging) and takes the higher. The raw rigged file stays in
113
- `art/<asset>/raw/`; the shipped character is phone-sized with its skeleton mapped to the standard and the library's
114
- CC0 clips (idle, run, jump, attack, hit, die, ...) retargeted onto it, recorded with both receipts.
74
+ For ElevenLabs use its actual discovered tool and model, and its flow/session or request
75
+ identifier. A measured cost uses `{"status":"actual","amount":1,"unit":"credits"}`;
76
+ a quote uses `estimate`. Never claim commercial rights from the fact that generation
77
+ succeeded. Read the provider and model terms for the account's plan; record attribution,
78
+ reference-image permissions and any restrictions. Mark test fixtures `"fixture": true`.
115
79
 
116
- ## Check, lineup, rights
80
+ Wire the delivered path into the scene or manifest, then run:
117
81
 
118
82
  ```sh
119
- npx --no-install homie-studio assets check <id> # phone budgets, the glTF-Validator, licences, staleness, big files
120
- npx --no-install homie-studio assets use <id> <asset> unused # a file the game no longer draws (or cast, prop, environment)
121
- npx --no-install homie-studio assets add <id> --file <model.glb> --license own --lo 0.2 # also <asset>_lo.glb: the same prop at a fifth of its triangles, for the far band (LodProps)
122
- npx --no-install homie-studio assets lineup <id> # pictures in .studio/art/<id>/; look at them
123
- npx --no-install homie-studio assets rights <id> # games/<id>/assets/RIGHTS.md and the credits
124
- npx --no-install homie-studio assets redo <id> <asset> # made again from its kept raw file, free
83
+ npx --no-install homie-studio media import --file <downloaded-file> --receipt <receipt.json> --out games/<id>/assets/<name> --used-by games/<id>/view.ts
125
84
  ```
126
85
 
127
- `assets redo` runs only the free steps again (today's budgets and render style, from `art/<slug>/raw/`). A model
128
- drawn black in the game is metal with nothing to reflect: every style but `pbr` makes models non-metal, and a redo
129
- applies it to one made before. A palette or shape change needs a new concept and mesh (paid), not a redo.
130
-
131
- Phone budgets (per asset): a prop 1,500 triangles and 512 px pictures, a hero 8,000 and 1,024 px, one material;
132
- the scene: about 100 draw calls, 150,000 triangles, 48 MB of picture memory, 5 MB of shipped payload. `assets check`
133
- holds the game to them, and what it adds up is an **inventory estimate**: the recorded files' own numbers times their
134
- `placements`. It never ran the game, so procedural geometry, repeated instances, effects and shadow passes are not in
135
- it, and "shipped payload" is every built file gzipped, not what a browser fetched before the first round. Say
136
- "inventory estimate" when you report it; the `playtest` and `perf` skills measure the running game and are the real
137
- gate on a phone.
138
-
139
- - A game that needs heavier models says so once, in game.json: `"assets": { "budgets": { "triangles": 4000, "bytes": 600000 } }`
140
- (the loader's warnings and the check both read it; hard caps still hold).
141
- - Standalone textures and skies are measured too (size, hash, decode, mipmapped memory once a picture). Record every
142
- picture the game loads as a texture (`kind: "texture"` or `"sky"`, with its licence and SHA-256): the check lists
143
- shipped pictures that have no record and leaves them out of the sum.
144
- - A model's normal and roughness maps count unless the manifest declares the game draws base colour only
145
- (`"inGame": { "maps": "base" }` on a record or at the top of the manifest). Declare it only when the game really
146
- calls `stylize()` with a non-PBR material model on those models; never to make a number pass.
147
- - The lineup is three pictures: the inventory (everything recorded), the cast (what is in play) and the environment.
148
- Mark a file the game no longer draws `unused`, and give a reviewer the cast and environment lineups, not the folder.
149
-
150
- After the lineup, have a FRESH reviewer (the `playtest` skill's blind review: a new agent that never saw your
151
- reasoning) look at the style board, the golden images and the lineup pictures and score "does every model look
152
- like one game?" from 1 to 10, naming outliers; record it: `assets review <id> --score <n> --outliers a,b`.
153
- Under 7, or any outlier, those assets need another look.
154
-
155
- ## Loading them in the game
156
-
157
- Every model loads through `@homie-rocks/studio/assets`:
158
-
159
- ```ts
160
- import { createModels } from '@homie-rocks/studio/assets';
161
- const models = createModels();
162
- const fox = await models.instance('./models/fox.glb').catch(() => models.placeholder({ x: 0.5, y: 0.7, z: 0.8 }));
163
- ```
86
+ This copies bytes, verifies a static reference, hashes the asset and records allowlisted
87
+ provenance in `media/provenance.json`. Reimporting the same output is idempotent; it
88
+ never generates or charges. Include each generation step's receipt for derived assets.
89
+ Run the scene and look/listen: a file and static reference are not proof that it renders
90
+ or plays. Report the placed path, usage evidence, rights and actual/estimated cost.
164
91
 
165
- It refuses an unsafe or oversized file (an external URI, a buffer over the cap, an extension three.js cannot
166
- read) before parsing it, decodes meshopt geometry, clones skinned models properly, and warns in development
167
- when a model is over its budget. Draw each loaded model the way the decisions say, once, before copying it:
168
- `stylize((await models.load(url)).scene, style)` with the game's `style.json` (its material model and ink outline,
169
- as the style board drew them). The `gem-rush-3d` starter shows the whole pattern.
170
-
171
- ## Licences, plainly
172
-
173
- - `references/RIGHTS.md` keeps what the providers' terms say, with read dates. Read the live pages again before
174
- anything commercial is published (`registry` re-reads prices; terms you read by hand).
175
- - The assets whose licence lets the file itself be handed on are CC0, CC BY with credit, the studio's own, and
176
- generated ones: only those may be in a part the studio shares. With anything else the game may serve the file
177
- to its players, and the studio's repository must stay private while it holds the raw file. `publish` refuses a game with an asset that
178
- has no licence record.
179
- - Never fetch from Poly Pizza or ShareTextures by script, never use Quaternius, Mixamo, Synty, Fab, Unity Asset
180
- Store or TurboSquid files in a public game's repository, never use a model or dataset under non-commercial
181
- terms.
182
-
183
- ## Never
184
-
185
- - Never spend without a budget, past it, or without a receipt; never run paid generation in a loop; never print
186
- or store a key.
187
- - Never ship a raw provider file, never commit one, never ship an asset with no licence record.
188
- - Never replace a model the person made without asking.
189
-
190
- ### Existing provider credentials
191
-
192
- The tools read `FAL_KEY` or `ELEVENLABS_API_KEY` first, then the plain single-line
193
- key in `~/.homie/keys/fal-key` or `~/.homie/keys/elevenlabs-key`. They also accept
194
- `~/.homie/workspace/fal-key` and `~/.homie/workspace/elevenlabs-key` from earlier
195
- installs. Checks name the source, never the key. Keep these files private (mode
196
- 0600); never copy them into a studio. ElevenLabs also accepts its CLI browser
197
- sign-in and the existing Stripe Projects connection. Run the tool’s `check`
198
- before declaring a provider disconnected.
92
+ The toolkit’s `lib/media-workflows.mjs` declares this skill’s verified tool references.
93
+ Validate them against the host’s current tool inventory before execution; tool discovery
94
+ is required for generation capabilities that have no stable documented name.
@@ -11,7 +11,7 @@ and in `@homie-rocks/studio`'s `lib/asset-manifest.mjs` (`PROVIDER_TERMS`), whic
11
11
  Output to the customer; fal "does not represent, warrant, or covenant that any Output Content will be original,
12
12
  will not infringe rights of any third party … or otherwise entitle Company to any intellectual property rights
13
13
  in any Output Content". Third-party model outputs may not be used to train models that compete with them. The
14
- endpoints in `models.json` carry fal's "Commercial use" label.
14
+ model-specific terms must be checked through the official fal plugin before use; a catalogue label is not a licence record.
15
15
  - **Tripo** through fal (developer terms, https://developers.tripo3d.ai/en/terms, read 2026-10-02, last updated
16
16
  2025-07-11): "you may use Outputs for lawful commercial or non-commercial purposes". Calls through fal are paid
17
17
  calls. Tripo's FREE plan makes outputs public under CC BY 4.0 (credit Tripo); this skill never uses a free plan.