@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
package/CHANGELOG.md CHANGED
@@ -17,6 +17,35 @@ To bring a studio up to date, tell Claude: "Upgrade my studio to the newest Homi
17
17
  `npx -y @homie-rocks/studio@latest upgrade`, which shows what's new since the version your studio pins (from this
18
18
  file) and what the upgrade would change, and changes nothing until you agree.
19
19
 
20
+ ## [0.51.5] - 2026-10-14
21
+
22
+ **Plugin 0.51.5**
23
+
24
+ Connect your shop and media through official plugins, keep renewals and free bookings working, and see progress while you build.
25
+
26
+ ### Added
27
+
28
+ - **Shop setup:** Stripe's official plugin can configure and verify products, prices, item and menu links, paid bookings, monthly giving and the customer portal. Homie plans only the changes your shop needs. No API keys are requested; webhook installation checks the Stripe account and passes the signing secret privately to Wrangler, never through the model. Local webhook forwarding keeps its secret private too.
29
+ - **Build iteration:** Builds report progress, including during JSON output, and show a heartbeat while generated rules play. `build --quick` keeps type and guard checks while skipping play and snapshot smoke checks.
30
+ - **Media placement:** `media import` places completed media, checks scene references and records file hashes, rights, provenance and explicit cost status. Video guidance and local processing tools now ship with the toolkit.
31
+
32
+ ### Changed
33
+
34
+ - **Official media connections:** Art, models, music, sound, video and style use the official fal and ElevenLabs plugins in your AI client. Homie still handles art direction, local processing, placement and provenance. Its own provider generation clients, key-file readers and Stripe Projects credential bridge have been removed.
35
+ - **Connection setup:** Setup reports official connections verified in the current session and explains browser sign-in. Cloudflare inspection uses its official MCP; deployment continues through Wrangler's browser login.
36
+
37
+ ### Fixed
38
+
39
+ - **Recurring gifts and memberships:** Each renewal reaches your shop and notifies apps once, even when the studio also sells parts. Renewals keep the original purchase reference so apps can identify the cause or member. Subscription updates and cancellations reach the shop; parts payments retain their own route.
40
+ - **Free volunteer shifts and services:** Free bookings in a mixed free/paid app work before Stripe is connected. They confirm locally and enforce capacity; paid checkout still requires a connection.
41
+
42
+ ### Upgrade notes
43
+
44
+ - Update the toolkit and Homie plugin together, run upgrade, and connect the official provider plugins in your AI client. Existing media remains usable. Upgrade replaces retired credential instructions without reading or migrating old key files. Available ElevenLabs media capabilities depend on the connected server and are checked before generation.
45
+ - Shop setup starts with `stripe_shop_plan` / `shop plan`, then `stripe_webhook` / `shop webhook`. Private webhook setup may need a separate Stripe CLI browser approval. `shop connect` remains the full CLI alternative; `shop listen` forwards local events privately.
46
+ - Run a full build before release, even if you used `build --quick` while iterating.
47
+ - Remove any temporary subscription-routing postinstall workaround for 0.51.3–0.51.4 after upgrading.
48
+
20
49
  ## [0.51.4] - 2026-10-14
21
50
 
22
51
  **Plugin 0.51.4** · Studio events through experimental MCP Events.
package/README.md CHANGED
@@ -513,17 +513,7 @@ npx homie-studio shop # open (test or live) or what is m
513
513
  npx homie-studio shop refund ord_… # an ASK: the owner taps once (or Refund in /_studio/office/shop)
514
514
  ```
515
515
 
516
- Connect with `stripe_login` or `shop connect`: the owner approves Stripe's official browser page, sandbox first.
517
- Where the CLI supplies a transferable legacy test key, the toolkit creates the webhook and installs both Worker
518
- secrets privately. It reports the 90-day expiry; `shop connect --renew` obtains approval again. Current CLI OAuth
519
- and live mode cannot export an independent Worker's key, so the result names that limitation. Only if the owner
520
- chooses the fallback does `shop connect --manual` open the old local key page (`--manual --live` for live mode).
521
- Never request a key in chat. Login is not a verified purchase; complete the test purchase, webhook and refund check.
522
-
523
- Stripe's optional agent tools (`stripe agent setup`) provide MCP and skills for catalog work (`shop catalog`),
524
- tax reads and sales questions. They are separate from the Worker's credentials. Never create a webhook through
525
- MCP because its response contains a secret. From 2026-10-31 Stripe MCP accepts only OAuth or Agent-tagged keys;
526
- that change does not affect the shop's ordinary restricted API key. [SHOP.md](shop/SHOP.md) describes the flow.
516
+ Connect through Stripe’s official OAuth plugin: `stripe_shop_plan` gives exact catalog, menu, booking and recurring operations for Stripe MCP and verifies its fresh reads. `stripe_webhook` privately installs the signing secret using one separate Stripe CLI browser approval if needed. No owner handles a key. `shop connect` remains the CLI alternative. [SHOP.md](shop/SHOP.md) describes the flow.
527
517
 
528
518
  In a game: `createShop()` from `@homie-rocks/studio/shop` (`has`, `entitlements`, `on('change')`, `open`, `used`, `add`, `checkout`, `buy`); a
529
519
  supporter's badge rides on their seat (`peer.badge`). Stripe Checkout (hosted), optional Stripe Tax, or Stripe Managed
@@ -1185,26 +1175,17 @@ no Wrangler, no rooms, the game alone and offline with its bots) and shoots that
1185
1175
  there (it needs rooms: use `dev` and `--url`). The result names the build the frames are of, by the hash `build`
1186
1176
  printed and the hashed bundle the page loaded.
1187
1177
 
1188
- ## Another way to Cloudflare: Stripe Projects (a prototype, 0.24.3)
1178
+ ## Official media and hosting connections
1189
1179
 
1190
- `npx wrangler login` stays the way. For a creator with no Cloudflare account (and no ElevenLabs), Stripe Projects
1191
- (Stripe's CLI plugin, docs.stripe.com/projects) can make or link both on the person's own Stripe sign-in, on free
1192
- plans, and hand the credentials to the studio folder:
1180
+ Connect fal and ElevenLabs through their official plugins in your AI client and approve
1181
+ one browser sign-in per provider. Homie never handles their credentials. Cloudflare's
1182
+ plugin provides account inspection; deployment keeps pinned Wrangler's own browser login.
1183
+ See [connection and upgrade guidance](guides/studio-setup/references/OFFICIAL-MEDIA.md).
1193
1184
 
1194
- ```sh
1195
- npx homie-studio setup --via stripe-projects [--with elevenlabs] --dry-run # what it would do; changes nothing
1196
- npx homie-studio setup --via stripe-projects [--with elevenlabs] --accept-tos # after the person accepted the providers' terms
1197
- ```
1198
-
1199
- It stops with a `needs` step at each thing that is the person's (the Stripe CLI and its Projects plugin, Stripe's
1200
- sign-in, the providers' terms, Cloudflare's own approval page for an existing account); it never adds a paid plan or
1201
- a payment method. The Cloudflare account id goes in studio.json with `"auth": "stripe-projects"`; the token stays in
1202
- Projects' vault and the git-ignored `.env` it syncs, and every Wrangler run in the studio takes it from there
1203
- (`deploy`, `dev`, secrets, the office). It checks read-only what the token can do (Wrangler signed in, D1, Workers AI;
1204
- R2 is optional) and that `.gitignore` keeps `.env`, `.env.*`, `.projects/vault/` and `.projects/cache/` out of git.
1205
- With `--with elevenlabs` the music skill finds the ElevenLabs key there too (ElevenLabs' free plan has no commercial
1206
- licence). Unverified until a real run: the exact JSON of the Projects commands, and the Cloudflare token's permission
1207
- groups, which the checks measure instead of assuming.
1185
+ `homie-studio media import --file <download> --receipt <receipt.json> --out <studio-path>
1186
+ --used-by <scene-file>` places a completed asset and records its hash, rights, style and
1187
+ cost status in `media/provenance.json`. It makes no provider calls. The scene file must
1188
+ reference the delivered path; also verify the asset in the running scene.
1208
1189
 
1209
1190
  ## No payment method needed
1210
1191
 
package/apps/APPS.md CHANGED
@@ -114,7 +114,7 @@ information in a separate private collection. Public roles must not have write
114
114
  capabilities for slots or bookings. Staff movement is a versioned record update
115
115
  containing only `slot`; confirmation and release follow verified Checkout state.
116
116
  The default keyless connection uses one Payment Link per published slot. Call
117
- `stripe_bookings_sync` after publishing/changing slots. Pending checkout does not
117
+ `stripe_shop_plan` with the complete slot manifest after publishing/changing slots. Pending checkout does not
118
118
  block others; payment secures the seat. Use record.booking.full/available/synced
119
119
  for slot display. Read SHOP.md for the complete manifest and limitations.
120
120
 
@@ -17,9 +17,9 @@
17
17
  * homie-studio app role <id> <role> [--player <account>] [--revoke] [--url <site>]
18
18
  * homie-studio game new <id> [--from gem-rush] [--name "<Game Name>"]
19
19
  * homie-studio games
20
- * homie-studio build [<id>] [--maps] [--types] [--long-check] (--maps: also keep each bundle's source map and
20
+ * homie-studio build [<id>] [--maps] [--types] [--long-check] [--quick] (--maps: also keep each bundle's source map and
21
21
  * module sizes in .studio/maps/<id>/, never in site/dist: what `perf` reads a
22
- * CPU profile through; --types: check the games' TypeScript first, with the
22
+ * CPU profile through; --quick: skip rules play and smoke checks; --types: check the games' TypeScript first, with the
23
23
  * studio's own `typescript`; a type error stops the build; --long-check: play
24
24
  * each rules game eight times as long before it is written (a rules game is
25
25
  * always type-checked and played; this is more of the same play, for a
@@ -183,10 +183,11 @@
183
183
  * homie-studio shop is the studio's shop selling (its own Stripe), and if not, what is missing
184
184
  * homie-studio shop init [--supporter|--giving] [--currency <code>] [--price 500] [--managed] shop.json and SELLING.md
185
185
  * homie-studio shop check shop.json against studio settings and provider requirements
186
- * homie-studio shop connect [--live] [--renew] [--manual] sync Payment Links and webhook through Stripe browser approval; --manual chooses the local page for the studio's
187
- * restricted Stripe key; with it this makes the webhook (0.24.3), and the key
188
- * and the webhook's secret go straight to the Worker secrets, never a chat or a
189
- * file (test keys only unless --live)
186
+ * homie-studio shop listen --profile <sandbox> [--port 8787] private local webhook forwarding
187
+ * homie-studio shop plan --have <file> exact next operation for Stripe's official MCP (public reads only)
188
+ * homie-studio shop verify --have <file> verify fresh MCP reads and save the public link configuration
189
+ * homie-studio shop webhook [--live] [--renew] privately install webhook using one Stripe CLI browser approval
190
+ * homie-studio shop connect [--live] [--renew] alternative: full sync through Stripe CLI approval
190
191
  * homie-studio shop catalog [--have <file>|-] [--mode test|live] the items as Products in the studio's Stripe,
191
192
  * made by the AI through Stripe's own MCP: the read first, then (with what it
192
193
  * answered) the exact writes; in sync, shop.json "catalog" records the mode
@@ -226,14 +227,9 @@
226
227
  * homie-studio setup attach <hs_…> [--client claude|codex|grok]
227
228
  * (this repository is the studio the chat's setup card is making: say so, once,
228
229
  * learn its live address, and give a template copy its real name)
229
- * homie-studio setup --via stripe-projects [--with elevenlabs] [--accept-tos] [--dry-run]
230
- * (a prototype, 0.24.3: Cloudflare, and ElevenLabs with --with, made or linked
231
- * through Stripe Projects on their free plans; the person accepts the providers'
232
- * terms (then --accept-tos) and signs in on Stripe's and the provider's own
233
- * pages; the account id goes into studio.json, the token stays in Projects'
234
- * vault and its git-ignored .env, and Wrangler runs with it here. The usual
235
- * way, npx wrangler login, stays the default. lib/projects.mjs)
236
230
  * homie-studio domain <hostname> AI configures DNS/TLS for the next deploy
231
+ * homie-studio media import --file <download> --receipt <receipt.json> --out <studio-path> --used-by <scene-file>
232
+ * Place completed official MCP output with provenance (no provider calls)
237
233
  * homie-studio setup prepare AI installs a private Node/npm if missing
238
234
  * homie-studio setup status [--connector yes|no] [--client claude|codex|grok] (also: homie-studio doctor)
239
235
  * what this computer and the person's accounts have for a studio: Node, the Homie
@@ -325,6 +321,7 @@
325
321
  *
326
322
  * Every command prints a few lines for a person; --json prints the result.
327
323
  */
324
+ import { importMedia } from '../lib/media-import.mjs';
328
325
  // FIRST, before any other file of the toolkit is evaluated: a Node.js that is too old ends here, with the reason.
329
326
  import '../lib/node-version.mjs';
330
327
  import { spawn, spawnSync } from 'node:child_process';
@@ -362,8 +359,6 @@ import { agentsBrain, agentsBrainKey, agentsPass, agentsPasses, agentsRevoke, ag
362
359
  import { AgentSeat } from '../lib/agent-seat.mjs';
363
360
  import { shopCheck, shopConnect, shopDisconnect, shopInit, shopLines, shopOrders, shopRefund, shopStatus } from '../lib/shop.mjs';
364
361
  import { catalogLines, catalogPlan } from '../lib/shop-catalog.mjs';
365
- import { projectsLines, setupViaProjects } from '../lib/projects.mjs';
366
- import { projectsCloudflareEnv } from '../lib/projects-env.mjs';
367
362
  import { Feed, currentFeed, currentId, flushProgress, publicFeed, readFeed, recordChange, startProgress } from '../lib/progress.mjs';
368
363
  import { formatStatus, setupStatus } from '../lib/doctor.mjs';
369
364
  import { codexTarget, newCodex, writeCodexPage } from '../lib/codex.mjs';
@@ -384,22 +379,23 @@ import { standaloneDeployNotes } from '../lib/standalone.mjs';
384
379
 
385
380
  const argv = process.argv.slice(2);
386
381
  const flags = new Map();
387
- const BOOL_FLAGS = ['public', 'lan', 'off', 'revoke', 'json', 'yes', 'detach', 'no-install', 'plan', 'stop', 'share', 'apply', 'diff', 'template', 'ci', 'fresh', 'install', 'remove', 'replace', 'artifact', 'open', 'reopen', 'kids', 'remote-ai', 'dry-run', 'verify', 'maps', 'profile', 'hands-on', 'automatic', 'unlock', 'confirm', 'no-library', 'no-validate', 'rigged', 'no-rig', 'supporter', 'giving', 'managed', 'live', 'manual', 'renew', 'send', 'accept-tos', 'quiet', 'no-local-ai', 'timestamps', 'overwrite', 'own-route', 'before', 'release', 'device'];
382
+ // build --quick keeps compilation and type checks, skips rules generated play/smoke checks.
383
+ const BOOL_FLAGS = ['quick', 'public', 'lan', 'off', 'revoke', 'json', 'yes', 'detach', 'no-install', 'plan', 'stop', 'share', 'apply', 'diff', 'template', 'ci', 'fresh', 'install', 'remove', 'replace', 'artifact', 'open', 'reopen', 'kids', 'remote-ai', 'dry-run', 'verify', 'maps', 'profile', 'hands-on', 'automatic', 'unlock', 'confirm', 'no-library', 'no-validate', 'rigged', 'no-rig', 'supporter', 'giving', 'managed', 'live', 'manual', 'renew', 'send', 'accept-tos', 'quiet', 'no-local-ai', 'timestamps', 'overwrite', 'own-route', 'before', 'release', 'device'];
388
384
  const positional = [];
389
385
  for (let i = 0; i < argv.length; i++) {
390
386
  const a = argv[i];
391
387
  if (a.startsWith('--')) {
392
388
  const [k, v] = a.slice(2).split(/=(.*)/s, 2);
393
389
  if (v !== undefined) flags.set(k, v);
394
- else if (argv[i + 1] !== undefined && !argv[i + 1].startsWith('--') && !BOOL_FLAGS.includes(k)) flags.set(k, argv[++i]);
390
+ else if (argv[i + 1] !== undefined && !argv[i + 1].startsWith('--') && (!BOOL_FLAGS.includes(k) || k === 'profile' && argv[0] === 'shop')) flags.set(k, argv[++i]);
395
391
  else flags.set(k, true);
396
392
  } else positional.push(a);
397
393
  }
398
394
  const asJson = flags.has('json');
399
- const log = asJson ? () => {} : (line) => process.stderr.write(`${line}\n`);
395
+ const log = asJson && positional[0] !== 'build' ? () => {} : (line) => process.stderr.write(`${line}\n`);
400
396
 
401
397
  function print(result) {
402
- if (asJson) { process.stdout.write(`${JSON.stringify(result, null, 2)}\n`); return; }
398
+ if (asJson || /^shop (plan|verify)$/.test(result.command ?? '')) { process.stdout.write(`${JSON.stringify(result, null, 2)}\n`); return; }
403
399
  // A standalone plan or build says every target, built or not, and what a standalone copy does not have.
404
400
  if (/^standalone( |$)/.test(String(result.command ?? ''))) { process.stdout.write(`${standaloneLines(result).join('\n')}\n`); return; }
405
401
  // A check that found problems still prints them (its rows say what to fix), never a bare "failed".
@@ -635,9 +631,6 @@ function print(result) {
635
631
  case 'shop catalog':
636
632
  lines.push(...catalogLines(result));
637
633
  break;
638
- case 'setup':
639
- lines.push(...projectsLines(result));
640
- break;
641
634
  case 'chat rules':
642
635
  case 'chat budget':
643
636
  case 'lounge rules':
@@ -846,6 +839,8 @@ const OUTWARD_FLAGS = {
846
839
  'chat remove': ['url', 'all'], 'chat budget': ['url'],
847
840
  'servers close': ['url', 'reopen'], 'servers level': ['url'], 'servers member': ['url', 'role', 'remove'],
848
841
  'parts reissue': ['claim-hash', 'receipt-verified', 'buyer'], 'parts retire': [], 'parts keys': [], 'parts refund': ['game'],
842
+ 'shop listen': ['profile','port'],
843
+ 'shop webhook': ['live','renew','profile'],
849
844
  'shop connect': ['local', 'profile', 'url', 'managed', 'live', 'manual', 'renew', 'from-clipboard'], 'shop disconnect': ['url'], 'shop refund': ['url', 'reason', 'note', 'manual-transaction'],
850
845
  'agents pass': ['url', 'label', 'server', 'hands', 'role', 'days'], 'agents revoke': ['url'], 'agents brain': ['url', 'budget', 'remove'],
851
846
  'agents sit': ['url', 'server', 'pass', 'label', 'brain'],
@@ -936,12 +931,7 @@ async function main() {
936
931
  if (cmd === 'progress') return progressCommand(root, sub);
937
932
  if (cmd === 'domain') return studioDomain(root, sub);
938
933
  if (cmd === 'setup' && sub === 'attach') return setupAttach(root, positional[2], { homie: flags.get('homie'), client: flags.get('client') });
939
- if (cmd === 'setup' && flags.has('via')) {
940
- if (flags.get('via') !== 'stripe-projects') return { ok: false, command: 'setup', why: '--via stripe-projects is the one other way this version knows (the default is npx wrangler login)' };
941
- const extra = String(flags.get('with') ?? '').split(',').map((x) => x.trim()).filter(Boolean);
942
- if (extra.some((x) => x !== 'elevenlabs')) return { ok: false, command: 'setup', why: '--with takes elevenlabs (Cloudflare is always set up)' };
943
- return setupViaProjects(root, { withElevenlabs: extra.includes('elevenlabs'), acceptTos: flags.has('accept-tos'), dryRun: flags.has('dry-run'), log });
944
- }
934
+ if (cmd === 'media' && sub === 'import') return importMedia(root, { file: flags.get('file'), receipt: flags.get('receipt'), out: flags.get('out'), usedBy: flags.get('used-by') });
945
935
  if (cmd === 'handoff') return handoff(root, sub, { homie: flags.get('homie'), client: flags.get('client') });
946
936
  if (cmd === 'port' && sub === 'import') return importPort(root, positional[2], flags.get('id'), { name: flags.get('name'), mode: flags.get('mode') });
947
937
  if (cmd === 'port' && sub === 'check') {
@@ -963,7 +953,7 @@ async function main() {
963
953
  // go, so an old note, an old card or an agent's memory of the toolkit does not end at "unknown command".
964
954
  if (cmd === 'game' && sub === 'remix') return { ok: false, command: 'game remix', why: 'remix was retired: a game is no longer handed over whole, and no studio serves one. Games build on each other through parts, the pieces of a game its studio chose to share: find one with `homie-studio parts find <words>` and bring it in with `homie-studio parts add` (in chat: parts_find, part_add; parts/PARTS.md)' };
965
955
  if (cmd === 'games') return { ok: true, command: 'games', games: listGames(root).map(({ dir, ...g }) => ({ ...g, dir: relative(root, dir) })) };
966
- if (cmd === 'build') return tracked(root, 'build', () => build(root, { only: positional[1] ?? null, log, maps: flags.has('maps'), types: flags.has('types'), longCheck: flags.has('long-check') }), 'build');
956
+ if (cmd === 'build') return tracked(root, 'build', () => build(root, { only: positional[1] ?? null, log, maps: flags.has('maps'), types: flags.has('types'), longCheck: flags.has('long-check'), quick: flags.has('quick') }), 'build');
967
957
  if (cmd === 'preview') return preview(root, positional[1] ?? (listGames(root).length === 1 ? listGames(root)[0].id : null));
968
958
  if (cmd === 'perf' && sub === 'sizes') return perfSizes(root, positional[2] ?? listGames(root)[0]?.id);
969
959
  if (cmd === 'lab') return labCommand(root, sub);
@@ -1114,7 +1104,15 @@ async function main() {
1114
1104
  const url = flags.get('url');
1115
1105
  if (!sub) return shopStatus(root, { url });
1116
1106
  if (sub === 'init') return shopInit(root, { supporter: flags.has('supporter'), giving: flags.has('giving'), currency: flags.get('currency') ?? null, price: flags.get('price') ?? 500, managed: flags.has('managed') });
1107
+ if(sub==='listen')return (await import('../lib/shop-listen.mjs')).shopListen(root,{profile:flags.get('profile'),port:flags.get('port')??8787,log});
1117
1108
  if (sub === 'check') return shopCheck(root);
1109
+ if (sub === 'plan' || sub === 'verify') {
1110
+ const {shopMcpPlan,saveMcpVerification}=await import('../lib/shop-mcp.mjs');
1111
+ const file=flags.get('have');
1112
+ const input=file?JSON.parse(readFileSync(file==='-'?0:file,'utf8')):{};
1113
+ return (sub==='verify'?saveMcpVerification:shopMcpPlan)(root,input);
1114
+ }
1115
+ if (sub === 'webhook') return shopConnect(root,{webhookOnly:true,live:flags.has('live'),renew:flags.has('renew'),profile:flags.get('profile'),log:line=>process.stderr.write(`${line}\n`)});
1118
1116
  if (sub === 'connect' && flags.has('local')) {
1119
1117
  if(flags.has('live'))throw new Error('Local checkout requires test mode');
1120
1118
  return (await import('../lib/shop-local.mjs')).shopConnectLocal(root,{url:flags.get('url'),profile:flags.get('profile')});
@@ -1362,7 +1360,7 @@ function mediaPut(root, file, as) {
1362
1360
  const key = as ?? (/^(music|videos)\//.test(rel) && !rel.includes('..') ? rel : `${folder}/${basename(file)}`);
1363
1361
  const bin = wranglerBin(root);
1364
1362
  return new Promise((done) => {
1365
- const p = spawn(bin, ['r2', 'object', 'put', `${r2}/${key}`, '--file', resolve(file), '--content-type', typeOf(file), '--remote'], { cwd: workerDir(root), env: { ...process.env, ...projectsCloudflareEnv(root), CI: '1' }, stdio: ['ignore', 'ignore', 'inherit'] });
1363
+ const p = spawn(bin, ['r2', 'object', 'put', `${r2}/${key}`, '--file', resolve(file), '--content-type', typeOf(file), '--remote'], { cwd: workerDir(root), env: { ...process.env, CI: '1' }, stdio: ['ignore', 'ignore', 'inherit'] });
1366
1364
  p.on('close', (code) => {
1367
1365
  if (code !== 0) return done({ ok: false, command: 'media put', why: 'wrangler r2 object put failed' });
1368
1366
  // The manifest is committed: it names the file by its path on the site, never the workers.dev address.
@@ -183,7 +183,7 @@ Scaffold businesses with `new <folder> --kind business`; causes with `--kind cau
183
183
 
184
184
  For giving, set the studio currency and run `shop init --giving --currency CAD` (use the organisation’s currency). It creates one-time/monthly suggested gifts. The owner supplies `giving.receiptText` in shop.json; Homie supplies no legal or tax text. Before Stripe is connected, local /shop/ previews items and prices with checkout disabled. Run the connection step yourself; the owner approves their Stripe account in the browser.
185
185
 
186
- Use `shop.book` and the booking collection declaration in SHOP.md. Default keyless booking links show pending while paying; an unpaid guest does not block others. Stripe limits completed sessions to capacity. The owner’s AI syncs published/changed slots with `stripe_bookings_sync`. Say “Pay to secure your seat.” Exclusive unpaid holds require the explicitly selected optional keyed connection.
186
+ Use `shop.book` and the booking collection declaration in SHOP.md. Default keyless booking links show pending while paying; an unpaid guest does not block others. Stripe limits completed sessions to capacity. The owner’s AI syncs published/changed slots with `stripe_shop_plan` and the complete slot manifest. Say “Pay to secure your seat.” Exclusive unpaid holds require the explicitly selected optional keyed connection.
187
187
 
188
188
 
189
189
  ## Tell my AI when a booking comes in
@@ -1,154 +1,82 @@
1
1
  ---
2
2
  name: art
3
- description: Make a studio's game look like something at build time — a cover from a real frame of the game (free), painted covers, backdrops, textures and character plates from image models through the creator's OWN fal account under a hard budget with a receipt for every call, checked (does the texture tile, is the file small enough for a phone, does the cover promise only what the game contains) and loaded by the game — plus the art-direction method (a style sheet and paintovers of real frames as the target the game's own rendering is changed to reach). Use when someone asks for cover art, key art, a thumbnail, a hero image, backgrounds, textures, sprites, or says the game looks rough, flat or unfinished.
4
- compatibility: Node 22, ffmpeg and Chrome. Generated images use the creator's own fal account (FAL_KEY); fal's own MCP server (https://mcp.fal.ai/mcp-relay) finds models and reads prices.
3
+ description: "Make game and app art using the official fal MCP, studio art direction, local capture and asset checks."
4
+ compatibility: Node 22 and the official fal plugin in the AI client. Local media processing uses ffmpeg and Chrome where needed.
5
5
  metadata:
6
6
  providers: fal
7
7
  ---
8
8
 
9
- # Art for a studio's game
10
-
11
- **Build time, never a frame of play.** Everything here happens once, before anybody plays, and costs
12
- nothing at runtime: never call an image model from a game's loop or from anything a person waits on.
13
- If you want one there, you want a baked asset instead.
14
-
15
- One script: `scripts/art.mjs` in this skill's folder (Claude Code:
16
- `node "${CLAUDE_PLUGIN_ROOT}/skills/art/scripts/art.mjs" <command>`), from inside the studio. Node 22,
17
- ffmpeg and Chrome; `--json` on every command. Art jobs live in `art/<slug>/` (the images, the
18
- `budget.json`, the `.request` and `.json` receipts beside each generated file); every paid call is
19
- also a line in `art/receipts.jsonl`.
20
-
21
- ```sh
22
- node <art.mjs> check # ffmpeg, Chrome, the studio; the fal key checked for free (only generated images need it)
23
- ```
24
-
25
- ## 1. The cover, first, always
26
-
27
- The cover is what a stranger sees before pressing anything: the studio's game page, the directory
28
- card, a link preview. Start from the game itself:
29
-
30
- ```sh
31
- npm run dev # background task
32
- node <art.mjs> frame <game> --url http://127.0.0.1:8787 # real game frames, the most detailed picked
33
- node <art.mjs> cover <game> --from art/frames/frame-04.png [--focus 0.6,0.4]
34
- ```
35
-
36
- `frame` uses the play view for server rules so the camera's seat keeps the room
37
- running, and the big screen for older games. It hides the platform's furniture
38
- (join QR, status chip, result cards) and keeps the frame with the most visible detail.
39
- For `--view tv` in a rules game, keep a real player in that same room: a TV alone
40
- does not keep a server match running. A paused-room message means use `--view play`
41
- or open a player, not change hosting mode.
42
- `cover` crops it to 16:9 around the focus, writes `cover.jpg` (1600x900, under 400 KB) into the game
43
- and names it in `game.json` (`"cover": "cover.jpg"`): the site and the directory use it until the game's
44
- landing has a still of its own (`hero/wide.jpg`), which then is its picture everywhere.
45
-
46
- **Open the cover and look.** A cover is a picture of the GAME: no QR code, no share panel, no debug
47
- text, not the waiting screen (a picture of nothing happening). Dark or featureless frames get a
48
- warning: a black card on a page reads as a black box.
49
-
50
- **A painted cover** from that frame, when the person wants one (paid, below): write the prompt from
51
- the game you just played, never from its title. Describe what is on screen: the palette, the time of
52
- day, what the player is doing, the shapes. A cover that promises something the game does not contain
53
- is the one failure here that matters: a person presses Play expecting that picture and meets
54
- something else. Use the real frame as the image reference (`"@file:art/frames/frame-04.png"` in the
55
- input) so the painting keeps the game's layout.
56
-
57
- ## 2. Paid images: price, budget, one call at a time
58
-
59
- Use an already connected fal account for paid generation; never ask the person to find or set a key.
60
- The local paid script discovers an existing fal key using the lookup below. If none is available, continue
61
- with local assets or captured footage and explain that this paid-script connection is not ready.
62
- Do not make account configuration a prerequisite for a live studio. **fal's own MCP server** is how you find a
63
- model and read its input schema and price (`search_models`, `recommend_model`, `get_model_schema`,
64
- `get_pricing`): use it rather than memory, because endpoints retire and reprice. Not connected? Offer
65
- it, and the person signs in on fal's own page (no key): in Claude Code `claude mcp add --transport http
66
- --scope user fal https://mcp.fal.ai/mcp-relay`, then `/mcp`; in Codex `codex mcp add fal --url
67
- https://mcp.fal.ai/mcp-relay`, then `codex mcp login fal`. **Paid calls go through `art.mjs gen`,
68
- never the MCP's `run_model` or `submit_job`**: `gen` prices, caps, receipts and resumes; the MCP's run
69
- does none of that (the Homie mod in Claude Code, and Homie's hooks in Codex, hold one as a call whose cost cannot be read first).
70
-
71
- ```sh
72
- node <art.mjs> price --model fal-ai/flux/dev --input art/cover/input.json # free: fal's unit price x this input
73
- node <art.mjs> budget cover --cap <dollars the person agreed to>
74
- node <art.mjs> gen cover --model fal-ai/flux/dev --input art/cover/input.json --out cover-painted.jpg --dry-run
75
- node <art.mjs> gen cover --model fal-ai/flux/dev --input art/cover/input.json --out cover-painted.jpg --yes
9
+ # Art for this studio
10
+
11
+ Read the game's or app's style, codex and existing scene before writing a prompt.
12
+ Use its palette, shape language, materials, lighting and camera. Keep approved references
13
+ and the style revision. Covers must describe what the playable experience really offers.
14
+ A frame from the running game is free and often the best cover.
15
+
16
+ For generated art use fal's `recommend_model` or `search_models`, `get_model_schema`
17
+ and `get_pricing`, then `run_model`. Continue a pending request with `check_job` and
18
+ `get_job_result`. Use `upload_file` only as documented by the official plugin for references.
19
+ Set an explicit image count. Inspect the first result before any dependent generation.
20
+
21
+ Local `scripts/art.mjs` keeps `check`, `frame`, `cover`, `tile`, `fit` and `sheet`.
22
+ Use `cover <game> --from <image>` for a 1600×900 cover and game.json reference;
23
+ `fit <image> --out <file>` for phone-sized delivery. Check tile seams with a repeated
24
+ 2×2 preview, transparency against light and dark, readability at phone size, and
25
+ compression after fitting. Do not replace an engine effect with a large image by default.
26
+
27
+ ## Official generation workflow
28
+
29
+ The owner's AI uses the provider's official plugin/MCP in its own client. Homie never
30
+ asks for, stores, reads or prints provider credentials. Read the current provider
31
+ skill and discover its available tools and input schemas; host prefixes vary.
32
+ Connection instructions and verified sources: [official integrations](../studio-setup/references/OFFICIAL-MEDIA.md).
33
+ If tools are missing, connect that official plugin once in the client's browser flow.
34
+ Do not fall back to a Homie client, key file, environment key or CLI generation wrapper.
35
+
36
+ Before spending, state the model, count/duration, estimated cost and remaining approved
37
+ budget. Use the owner's existing authorization; ask only if the spend is outside it.
38
+ Record the request ID as soon as accepted. Poll that same request; never resubmit to
39
+ retry a slow job. Unknown cost is unknown, never zero. Provider billing is authoritative;
40
+ Homie's local receipt cannot enforce spending in another client.
41
+
42
+ ## Place and prove the result
43
+
44
+ Download the completed tool's output using the AI client's file/download facilities.
45
+ Keep temporary signed URLs out of committed records. Keep the original in the studio's
46
+ art/job folder; put the optimized delivered asset beside its game/app, or in music/ or
47
+ videos/ for a published work. Record a receipt with these fields:
48
+
49
+ ```json
50
+ {
51
+ "provider": "fal",
52
+ "tool": "run_model",
53
+ "model": "exact endpoint returned by the provider",
54
+ "requestId": "actual request ID",
55
+ "prompt": "the actual prompt, including style constraints",
56
+ "styleRevision": "revision from the studio's style",
57
+ "rights": {"source": "provider/model terms URL", "checkedAt": "YYYY-MM-DD", "commercial": false, "note": "record the actual plan and permitted use"},
58
+ "cost": {"status": "unknown"}
59
+ }
76
60
  ```
77
61
 
78
- - Tell the person the price and ask for a cap before the first call. `gen` refuses with no budget,
79
- refuses past the cap, and asks again without `--yes`.
80
- - The receipt is written the moment fal accepts the job. Running the same `gen` again resumes it from
81
- `<out>.request`: it never pays twice. A cut-off call is resumed, not re-sent.
82
- - **Never run paid generation in a loop** to "try until it is good". Look at what came back, change
83
- the prompt, run it once more. Spend the riskiest image first.
84
- - No real people, no real brands, no copyrighted characters, no "in the style of" a living artist, in
85
- prompts or references.
86
- - **Every prompt starts from the game's derived style prompt** (`homie-studio style prompt <id>`: medium,
87
- shape, palette hexes, light, framing; computed from the decisions, never edited by hand) and uses the golden
88
- images as references on a multi-reference model. A picture made under an older palette is listed stale when
89
- the palette changes (the `style` skill's blast radius).
90
- - What the provider's terms say about the pictures, with read dates: `references/RIGHTS.md`. Read the live page
91
- again before anything commercial is published.
92
- - 3D models (props, characters) are the `models` skill's; mood images for the style board are too
93
- (`models.mjs mood`).
62
+ For ElevenLabs use its actual discovered tool and model, and its flow/session or request
63
+ identifier. A measured cost uses `{"status":"actual","amount":1,"unit":"credits"}`;
64
+ a quote uses `estimate`. Never claim commercial rights from the fact that generation
65
+ succeeded. Read the provider and model terms for the account's plan; record attribution,
66
+ reference-image permissions and any restrictions. Mark test fixtures `"fixture": true`.
94
67
 
95
- ## 3. Backdrops, textures, plates
96
-
97
- A procedural gradient sky is the tell that a game was generated; a painted one costs one image at build
98
- time and loads like any other asset. Good candidates: a skybox or far matte, a ground or wall texture,
99
- a character plate, a title card. Bad: anything the game generates procedurally for a reason.
68
+ Wire the delivered path into the scene or manifest, then run:
100
69
 
101
70
  ```sh
102
- node <art.mjs> tile art/floor/floor.png # does it tile? seam numbers and a 2x2 preview
103
- node <art.mjs> fit art/floor/floor.png --out games/<id>/public/art/floor.jpg --width 1024 --max-kb 250
104
- node <art.mjs> sheet art/floor # everything in a job, labelled, to look at
71
+ npx --no-install homie-studio media import --file <downloaded-file> --receipt <receipt.json> --out games/<id>/assets/<name> --used-by games/<id>/view.ts
105
72
  ```
106
73
 
107
- - **Models are bad at tiling.** Ask for a texture, then check it: a seam over 2 shows as a grid of
108
- lines in the game. Mirror-blend the edges or make it tile yourself before it ships.
109
- - **Size is a feature.** A phone downloads every byte before the first round. Fit backdrops to the
110
- size they are drawn at, JPEG (or WebP where ffmpeg has it) for pictures, PNG only for hard-edged
111
- sprites with transparency. Then play it again: the `playtest` skill's first row times the load; an
112
- asset that doubled the load time made the game worse.
113
- - Put art where the game's own build puts assets (`games/<id>/public/` for a bundled game, the
114
- game's folder for a static one) and load it with a relative path.
115
-
116
- ## 4. Art direction: paintovers, then change the game
117
-
118
- Images do not fix a game that looks unfinished; the best-loved browser games often ship no texture
119
- files at all. What makes a frame look finished is light, contrast between the subject and the ground,
120
- detail at more than one scale, and a clear horizon or depth. The method:
121
-
122
- 1. **The style bible is the game's decisions** (the `style` skill): render style, palette with its jobs,
123
- light, shape language, camera, fonts, drawn in the codex's Art direction tab. An older studio's
124
- `art/STYLE.md` retires into it: carry what it says into `style set` / `style steer`, then keep only the
125
- decisions. Paint with the LOCKED palette and light.
126
- 2. **Paintovers**: real frames of the game (`frame`), painted over by an image model with the derived style
127
- prompt (`npx --no-install homie-studio style prompt <id>`) and the frame as the reference, plus the golden
128
- images (`style golden`) where there are any. These are the TARGET, never shipped as the game.
129
- 3. **Change the game's own rendering** (lights, materials, fog, the floor, the camera) until frames of
130
- the running game match the paintover; compare them side by side.
131
- 4. **Judge blind**: frames of the old build, the new one and a reference game the person admires,
132
- shuffled under random labels, scored by a fresh reviewer before the labels are revealed (the
133
- `playtest` skill's review). A reference is a bar, not a template.
134
-
135
- ## Tell the person
136
-
137
- What was made and where it is used, the file sizes, what it cost (from the receipts) against the cap,
138
- what is a real frame and what is generated, and the pictures themselves.
139
-
140
- ## Never
141
-
142
- - Never ship generated art as a screenshot of gameplay, or a cover that shows what the game does not have.
143
- - Never spend without a budget, past it, or without a receipt; never print or store a key.
144
- - Never replace art a person made without asking.
145
-
146
- ### Existing provider credentials
74
+ This copies bytes, verifies a static reference, hashes the asset and records allowlisted
75
+ provenance in `media/provenance.json`. Reimporting the same output is idempotent; it
76
+ never generates or charges. Include each generation step's receipt for derived assets.
77
+ Run the scene and look/listen: a file and static reference are not proof that it renders
78
+ or plays. Report the placed path, usage evidence, rights and actual/estimated cost.
147
79
 
148
- The tools read `FAL_KEY` or `ELEVENLABS_API_KEY` first, then the plain single-line
149
- key in `~/.homie/keys/fal-key` or `~/.homie/keys/elevenlabs-key`. They also accept
150
- `~/.homie/workspace/fal-key` and `~/.homie/workspace/elevenlabs-key` from earlier
151
- installs. Checks name the source, never the key. Keep these files private (mode
152
- 0600); never copy them into a studio. ElevenLabs also accepts its CLI browser
153
- sign-in and the existing Stripe Projects connection. Run the tool’s `check`
154
- before declaring a provider disconnected.
80
+ The toolkit’s `lib/media-workflows.mjs` declares this skill’s verified tool references.
81
+ Validate them against the host’s current tool inventory before execution; tool discovery
82
+ is required for generation capabilities that have no stable documented name.