@homie-rocks/studio 0.51.4 → 0.51.6

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 (94) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/README.md +10 -29
  3. package/apps/APPS.md +16 -1
  4. package/bin/homie-studio.mjs +28 -30
  5. package/guides/app/NEEDS.md +160 -0
  6. package/guides/app/SKILL.md +20 -4
  7. package/guides/art/SKILL.md +68 -140
  8. package/guides/art/scripts/art.mjs +6 -79
  9. package/guides/models/SKILL.md +73 -177
  10. package/guides/models/references/RIGHTS.md +1 -1
  11. package/guides/models/scripts/models.mjs +7 -288
  12. package/guides/music/SKILL.md +74 -183
  13. package/guides/music/references/COMPOSITION.md +21 -26
  14. package/guides/music/references/GAME-AUDIO.md +3 -3
  15. package/guides/music/references/RIGHTS.md +12 -15
  16. package/guides/music/scripts/music.mjs +11 -202
  17. package/guides/parts/SKILL.md +1 -1
  18. package/guides/shop/SKILL.md +68 -77
  19. package/guides/sound/SKILL.md +70 -1
  20. package/guides/sound/scripts/player/sound.js +233 -0
  21. package/guides/studio-setup/SKILL.md +11 -27
  22. package/guides/studio-setup/references/OFFICIAL-MEDIA.md +66 -0
  23. package/guides/style/SKILL.md +66 -3
  24. package/guides/video/SKILL.md +83 -0
  25. package/guides/video/references/DELIVERY.md +73 -0
  26. package/guides/video/references/HONESTY.md +40 -0
  27. package/guides/video/references/METHOD.md +144 -0
  28. package/guides/video/references/RECORD.md +137 -0
  29. package/guides/video/references/TRAILER.md +154 -0
  30. package/guides/video/references/examples/site-walkthrough.json +14 -0
  31. package/guides/video/references/examples/studio-play-phone.json +15 -0
  32. package/guides/video/references/examples/studio-play.json +17 -0
  33. package/guides/video/scripts/capture-game.mjs +175 -0
  34. package/guides/video/scripts/clock.js +136 -0
  35. package/guides/video/scripts/drawover/film.js +58 -0
  36. package/guides/video/scripts/drawover/index.html +9 -0
  37. package/guides/video/scripts/drawover/look.js +115 -0
  38. package/guides/video/scripts/drawover/shots.json +12 -0
  39. package/guides/video/scripts/lib/browser.mjs +87 -0
  40. package/guides/video/scripts/lib/frames.mjs +170 -0
  41. package/guides/video/scripts/lib/remix.mjs +159 -0
  42. package/guides/video/scripts/qa.mjs +127 -0
  43. package/guides/video/scripts/record-fixed.mjs +403 -0
  44. package/guides/video/scripts/record-page.mjs +477 -0
  45. package/guides/video/scripts/tap.js +108 -0
  46. package/guides/video/scripts/video.mjs +888 -0
  47. package/lib/app-needs.mjs +86 -18
  48. package/lib/app-ready.mjs +21 -0
  49. package/lib/build.mjs +12 -9
  50. package/lib/check.mjs +16 -7
  51. package/lib/cloudflare.mjs +6 -11
  52. package/lib/codex.mjs +7 -5
  53. package/lib/dev.mjs +1 -2
  54. package/lib/doctor.mjs +10 -54
  55. package/lib/mcp-tools.mjs +42 -21
  56. package/lib/mcp.mjs +1 -1
  57. package/lib/media-import.mjs +41 -0
  58. package/lib/media-workflows.mjs +13 -0
  59. package/lib/needs-headless.mjs +34 -0
  60. package/lib/official-media.mjs +15 -0
  61. package/lib/rules-build.mjs +13 -5
  62. package/lib/scaffold.mjs +2 -2
  63. package/lib/shop-links.mjs +81 -19
  64. package/lib/shop-listen.mjs +43 -0
  65. package/lib/shop-local.mjs +1 -1
  66. package/lib/shop-mcp.mjs +58 -0
  67. package/lib/stripe-connect.mjs +36 -15
  68. package/lib/template-history.json +87 -0
  69. package/lib/upgrade-media.mjs +13 -0
  70. package/lib/upgrade.mjs +10 -0
  71. package/package.json +1 -1
  72. package/parts/PARTS.md +1 -1
  73. package/rules/view.ts +4 -0
  74. package/shop/SHOP.md +110 -34
  75. package/worker/app-records.mjs +1 -0
  76. package/worker/build-id.mjs +1 -1
  77. package/worker/event-resources.mjs +9 -3
  78. package/worker/index.mjs +5 -1
  79. package/worker/local-test.mjs +4 -0
  80. package/worker/mcp-events.mjs +2 -2
  81. package/worker/mcp-tools.mjs +19 -1
  82. package/worker/mcp.mjs +1 -1
  83. package/worker/purchase-reconciliation.mjs +16 -1
  84. package/worker/shop-links.mjs +1 -1
  85. package/worker/shop-rules.mjs +3 -0
  86. package/worker/shop-subscriptions.mjs +2 -0
  87. package/worker/shop.mjs +58 -8
  88. package/worker/stripe-ownership.mjs +7 -0
  89. package/worker/version.mjs +1 -1
  90. package/guides/models/references/models.json +0 -175
  91. package/guides/music/scripts/lib/eleven.mjs +0 -260
  92. package/lib/projects-env.mjs +0 -68
  93. package/lib/projects.mjs +0 -311
  94. package/lib/provider-key.mjs +0 -21
package/CHANGELOG.md CHANGED
@@ -17,6 +17,55 @@ 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.6] - 2026-10-14
21
+
22
+ **Plugin 0.51.6**
23
+
24
+ Keep each customer's exact basket through payment, and check that your studio meets its needs across screens, AI jobs and handover.
25
+
26
+ ### Added
27
+
28
+ - **Check the whole service:** Owner needs can call studio MCP tools as customer, staff or owner, assert returned results and saved records, invoke declared scheduled functions locally, read event feeds and MCP Events, check webhook delivery, and verify handover files. Checks keep role permissions and test-record isolation. Headless-only plans run without Chrome; missing tools, permissions, effects and text explain why a need is not met.
29
+
30
+ ### Fixed
31
+
32
+ - **Exact rounds and baskets:** Opt-in keyless saved baskets keep the phone's exact items and quantities. One custom-amount Payment Link carries the saved total; signed payments are checked against the saved basket and report full payment or a shortfall for staff. Short payments grant no automatic entitlements. The Worker needs no Stripe API key, and existing adjustable Stripe menus keep their behavior.
33
+ - **Clear acceptance and evidence:** Passing assertions mark a need met even when captures are absent or fail. Capture diagnostics identify the surface, explain the failure and show how to collect evidence with `--shots`.
34
+ - **Reliable changing controls:** Click, fill and select actions retry detached elements within a bounded wait. The app skill, packaged guides and Codex template explain how updating controls in place preserves a held press across delayed refreshes.
35
+
36
+ ### Upgrade notes
37
+
38
+ - Saved baskets are opt-in through `shop.json.basket` and require Stripe with tax-inclusive menu prices. Automatic tax and Managed Payments are refused for basket mode. Staff can use the reported basket total, actual payment and settlement status to collect a shortfall.
39
+
40
+ ## [0.51.5] - 2026-10-14
41
+
42
+ **Plugin 0.51.5**
43
+
44
+ Connect your shop and media through official plugins, keep renewals and free bookings working, and see progress while you build.
45
+
46
+ ### Added
47
+
48
+ - **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.
49
+ - **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.
50
+ - **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.
51
+
52
+ ### Changed
53
+
54
+ - **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.
55
+ - **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.
56
+
57
+ ### Fixed
58
+
59
+ - **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.
60
+ - **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.
61
+
62
+ ### Upgrade notes
63
+
64
+ - 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.
65
+ - 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.
66
+ - Run a full build before release, even if you used `build --quick` while iterating.
67
+ - Remove any temporary subscription-routing postinstall workaround for 0.51.3–0.51.4 after upgrading.
68
+
20
69
  ## [0.51.4] - 2026-10-14
21
70
 
22
71
  **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
 
@@ -181,3 +181,18 @@ The phone remains its own surface; assert its confirmed gift after checkout. The
181
181
  checker brings each page forward before reading or capturing it. Use the real
182
182
  provider's controls, never a test-only app action. App sharing defaults to the top
183
183
  right, clear of bottom tabs; `screen.share` remains an author-controlled override.
184
+
185
+ ## Every owner need is executable
186
+
187
+ The Codex `needs` block supports existing UI steps plus named-role MCP calls,
188
+ returned-result assertions, simulated schedule ticks, record/results reads,
189
+ event feed/MCP Events/webhook checks and handover file contents. See the packaged
190
+ [needs format and examples](../guides/app/NEEDS.md). Passing assertions are **met**;
191
+ captures are additional evidence. Missing captures name their surface, reason and
192
+ how to rerun with `--shots .checks`. Detached action elements are re-queried for a
193
+ bounded ten seconds; ambiguous post-dispatch failures are not replayed.
194
+
195
+ Keep buttons connected during delayed refreshes: create the button once and set
196
+ `button.textContent = label` when data arrives. Never replace a held button with
197
+ `innerHTML` or `replaceWith`; that can cancel navigation between pointer down and
198
+ up. The needs guide has a short complete example and explains outcome assertions.
@@ -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.
@@ -0,0 +1,160 @@
1
+ # Check every owner need
2
+
3
+ The JSON array in the app's `CODEX.md` fenced `needs` block is the acceptance
4
+ source. Keep every owner statement, including AI jobs, reminders, results and
5
+ handover. Each need has a unique `id`, `statement`, optional `role` and `steps`,
6
+ and nonempty `assertions`. An unavailable capability stays as a need with an
7
+ empty assertion list and a prose explanation; check reports it not met.
8
+
9
+ Run `homie-studio check <app> --url http://localhost:8787 --shots .checks` with
10
+ `homie-studio dev` running in the same studio. Headless-only needs require no
11
+ Chrome. UI steps and headless steps can be combined in one need and run in order.
12
+ Each need starts with its own saved results; records are shared across needs in
13
+ the run. The default isolated record scope is shared by browsers, MCP calls and
14
+ scheduled effects. `--no-isolate` explicitly uses existing records. Isolation is
15
+ for app records, not a sandbox for external email/SMS/payment calls: use the
16
+ service's test connection for those jobs.
17
+
18
+ ## Passing assertions and capture evidence
19
+
20
+ Passing assertions mean **met**, with or without screenshots. A missing capture
21
+ never changes that verdict. For every UI surface without a capture, the result
22
+ names the surface, why it was unavailable (including the browser's error), and
23
+ tells you to rerun with `--shots .checks`. Fix the reported disk/browser error
24
+ first. Successful captures are listed in the saved needs report. Inspect them
25
+ against the owner's scene: automation proves the assertions, not visual quality.
26
+ Headless needs have no screenshots to collect.
27
+
28
+ Click, fill and select actions re-query their selector for up to ten seconds when
29
+ the browser reports a detached/stale element. A permanently unstable control
30
+ reports its surface and selector. Other action failures are not replayed: a
31
+ navigation error after dispatch does not prove that a purchase did not happen.
32
+
33
+ ## Customer and staff AI calls and returned results
34
+
35
+ Headless actions require an explicit `role`, inherited from the need or overridden
36
+ on a headless step/assertion. Use an app's declared role, or `owner` for owner-only
37
+ operations. The dev-only signed identity enforces real MCP audiences and record
38
+ capabilities; a customer is not silently promoted to owner. Production OAuth
39
+ approval is unchanged. Missing dev credentials, tools or role permissions produce
40
+ plain not-met reasons.
41
+
42
+ ```needs
43
+ [
44
+ {"id":"ai-book","statement":"A customer's AI books in one call","role":"customer",
45
+ "steps":[{"mcp":{"tool":"book","arguments":{"name":"Ada"}},"save":"booking"}],
46
+ "assertions":[{"result":"booking","contains":{"ok":true}}]},
47
+ {"id":"open-roles","statement":"Staff AI can ask which roles are still open","role":"staff",
48
+ "steps":[{"mcp":{"tool":"open_roles"},"save":"open"}],
49
+ "assertions":[{"result":"open","path":"roles","contains":["cook"]}]}
50
+ ]
51
+ ```
52
+
53
+ Use actual tool names, arguments and returned fields from this studio. `save`
54
+ names the returned result; duplicate names fail. `result` reads it. `path` selects
55
+ dot-separated object keys or array indexes, such as `records.0.data.name`.
56
+ `equals` compares the full selected JSON value; `contains` matches a subset of
57
+ object fields, each expected array entry against an actual entry, or a substring.
58
+ `text` checks a string for one substring or every string in an array. Use `{"$result":"booking.id"}` anywhere in a subsequent step or assertion
59
+ to refer to a saved value, without string interpolation. A missing
60
+ result/path, wrong value or malformed assertion stays not met, naming the failed
61
+ condition. Standard studio tool envelopes are unwrapped to their `data` value;
62
+ other tools retain their structured content or MCP result.
63
+
64
+ ## Schedules, records and results
65
+
66
+ A `schedule` step invokes a named declared scheduled function through the local
67
+ MCP dispatcher, with a simulated timestamp in epoch milliseconds. It runs the
68
+ handler headless and reports handler failures. This is a simulated tick, not
69
+ proof that a deployed cron fired or an external message reached a person. Assert
70
+ the actual resulting record or queue entry; do not just assert the trigger's ID.
71
+ The handler still needs a declared role with the relevant record capabilities.
72
+
73
+ ```needs
74
+ [
75
+ {"id":"remind","statement":"Daily reminders queue for booked guests","role":"owner",
76
+ "steps":[{"schedule":{"name":"reminders","at":1800000000000}}],
77
+ "assertions":[{"role":"staff","records":{"collection":"reminders"},
78
+ "contains":{"records":[{"data":{"name":"Ada","state":"queued"}}]}}]},
79
+ {"id":"results","statement":"Staff can read the completed result","role":"staff",
80
+ "assertions":[{"records":{"collection":"results","id":"one"},
81
+ "contains":{"data":{"status":"complete"}}}]}
82
+ ]
83
+ ```
84
+
85
+ `records` uses the real `app_records` read tool, bound to this app, the named role
86
+ and this check's isolation scope. Supply collection and optional ID as accepted by that tool. Use its actual response shape (a single record versus
87
+ a list). Create fixtures with steps before asserting them; fresh runs start empty.
88
+
89
+ ## Owner AI events and webhook handoff
90
+
91
+ Call `studio_events_read` to prove the feed contains the event, or use MCP Events
92
+ `events/poll` with an explicit cursor to prove event retrieval. `events/list`
93
+ checks discovery. `resources/read` also works and preserves its MCP `contents`
94
+ array. These prove the studio exposes an event to the named role; they do not
95
+ claim an external AI held a live connection or acted on it.
96
+
97
+ ```needs
98
+ [
99
+ {"id":"owner-feed","statement":"The owner's AI can read new bookings","role":"owner",
100
+ "steps":[{"mcp":{"tool":"book","arguments":{"name":"Ada"}},"role":"customer"}],
101
+ "assertions":[{"mcp":{"tool":"studio_events_read","arguments":{"feed":"bookings"}},
102
+ "contains":{"events":[{"type":"record.changed","data":{"data":{"name":"Ada"}}}]}}]},
103
+ {"id":"mcp-events","statement":"Owner AI retrieves booking events through MCP Events","role":"owner",
104
+ "assertions":[{"mcp":{"method":"events/poll","params":{"name":"bookings","cursor":"v1:bookings:0"}},
105
+ "contains":{"events":[{"name":"bookings"}]}}]},
106
+ {"id":"webhook","statement":"The declared owner webhook accepts a signed test event","role":"owner",
107
+ "steps":[{"mcp":{"tool":"studio_events_webhook_test","arguments":{"feed":"bookings"}},"save":"delivery"}],
108
+ "assertions":[{"mcp":{"tool":"studio_events_webhook_status","arguments":{"feed":"bookings"}},
109
+ "waitMs":5000,"contains":{"deliveries":[{"event":{"$result":"delivery.id"},"state":"complete"}]}}]}
110
+ ]
111
+ ```
112
+
113
+ Declare the feed/audience and webhook first using the existing functions guide.
114
+ The webhook test sends an explicitly synthetic event to the declared receiver;
115
+ match the saved delivery ID to avoid mistaking an older delivery for this test.
116
+ Use `studio_events_webhook_status` for subsequent attempts and errors.
117
+ A status assertion proves the receiver's acknowledgement, not its downstream
118
+ email/SMS or AI work. Read-only MCP/records assertions make one call by default. For asynchronous
119
+ effects, set `waitMs` (0–10000) to re-read until the expected value appears.
120
+ Tool/permission errors fail immediately. Saved results are immutable and are
121
+ never retried. Never put a mutating tool in an assertion; use a step once and
122
+ assert its saved result or durable effect. Unknown methods/tools and denied feeds
123
+ fail explicitly. Secrets do not belong in the needs block.
124
+
125
+ ## Handover files
126
+
127
+ ```needs
128
+ [
129
+ {"id":"handover","statement":"The owner has AI connection and reminder instructions",
130
+ "assertions":[{"file":"HANDOVER.md","text":["/mcp","Reminders run daily"]}]}
131
+ ]
132
+ ```
133
+
134
+ `file` is relative to the studio root. The file must exist and be readable; `text`
135
+ checks all requested contents. Without `text` it proves only existence/readability.
136
+ Use this for handover instructions and exported text results; use `records` for
137
+ live structured results. Missing files and missing text name the exact failure.
138
+
139
+ ## Keep controls connected during delayed refreshes
140
+
141
+ Never replace a button under a press. A delayed fetch can finish between pointer
142
+ down and pointer up; replacing the node cancels its click/navigation and loses
143
+ focus. Create interactive nodes once, key repeated items by stable record IDs,
144
+ and update text/attributes in place. Keep pending/disabled state deliberate.
145
+
146
+ ```js
147
+ const choose = document.createElement('button');
148
+ choose.textContent = 'Choose crew';
149
+ choose.addEventListener('click', () => selectRole('crew'));
150
+ controls.append(choose);
151
+ async function refresh() {
152
+ const { open } = await loadRoles();
153
+ choose.textContent = `Choose crew (${open} places)`;
154
+ // Keep this same button connected; do not replace controls.innerHTML here.
155
+ }
156
+ ```
157
+
158
+ The runner can retry a detected stale handle before dispatch; it cannot infer
159
+ that an app silently cancelled a press after dispatch. Both stable DOM updates
160
+ and an assertion on the navigation's outcome are necessary.
@@ -100,7 +100,7 @@ The Codex's `needs` fenced block is a JSON array, one entry per need from the re
100
100
 
101
101
  Steps support `fill`, `select`, `click: true` and `reload: true`; surfaces are `wall`, `phone`, `phone-two`, `tablet`, `kiosk` and `staff` (a tablet viewport). Set a need’s `role` to the declared role to prove. Each assertion names its surface; only the named screens are required. Assertions use a visible selector with optional contained text, or an observed effect. An honestly unavailable capability remains in the block with no assertions and a plain explanation in the prose; check reports it not met. Do not delete, weaken or replace a need to get green. Static needs such as accurate prices may have no action steps. Use semantic attributes on real visible scene elements for testability, never hidden QA-only counters.
102
102
 
103
- Run build and the shared-action check, then the needs step runs on those real browsers. Save captures with `--shots .checks`. The result contains each statement, met/partly/not, the assertion result and captures of all named surfaces; `.studio/checks/<id>-needs.json` drives the Codex's Needs status. Redraw `codex <id>` after checking. Passing a selector assertion is evidence, not a visual judgement: read every capture and perform the full flows as owner and customer using the playtest skill. If the picture fails the words, it is not done even with green assertions.
103
+ Run build and the shared-action check, then the needs step runs on those real browsers. Save captures with `--shots .checks`. The result contains each statement, met/partly/not, the assertion result and available captures of named surfaces; `.studio/checks/<id>-needs.json` drives the Codex's Needs status. Redraw `codex <id>` after checking. Passing a selector assertion is evidence, not a visual judgement: read every capture and perform the full flows as owner and customer using the playtest skill. If the picture fails the words, it is not done even with green assertions.
104
104
 
105
105
  For staff flows, sign in through the existing local owner link and prove the transition in real Chrome. Keep evidence in the Codex alongside the automated flows. Confirm unauthorized writes are refused and records remain after all screens close. Never call an untested privileged path passed. Before final handover, show the need table with evidence and explicit remaining gaps. If the starter or a board of boxes is still visible, return to the scene design and build it.
106
106
 
@@ -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
@@ -252,8 +252,8 @@ a notification or a person heard audio. Observations reset before each need.
252
252
  After reloads and rebuilt frames, check waits for online state, completed arrival,
253
253
  a hidden loading cover and two frames before assertions/captures. Timeout evidence
254
254
  names the surface and selector/effect it awaited. Dialogs are automatically accepted
255
- and reported. Read retries recover detached frames; dispatched actions are not
256
- silently repeated. `dev` rebuilds root HTML, CSS and JS as well as source and map
255
+ and reported. Read retries recover detached frames. Click/fill/select retry detached elements by
256
+ re-querying for up to ten seconds; other dispatched action failures are not replayed. `dev` rebuilds root HTML, CSS and JS as well as source and map
257
257
  changes. Unavailable sandbox Web Storage uses frame-local memory; use app records
258
258
  or `net.prefs` for persistence across reloads.
259
259
 
@@ -312,3 +312,19 @@ Issues and grant Notifications Write and Workers Observability Write. Cloudflare
312
312
  posts signed events to the client's webhook so new or recurring reliability
313
313
  problems reach the AI without polling; Homie hosts nothing for this. See
314
314
  [Cloudflare's announcement](https://developers.cloudflare.com/changelog/post/2026-10-13-cloudflare-mcp-events/).
315
+
316
+ ## Every need has a check
317
+
318
+ Read [NEEDS.md](NEEDS.md) for the complete executable format and examples for UI,
319
+ customer/staff MCP tools, owner event feeds and MCP Events, webhook delivery,
320
+ simulated scheduled functions, records/results and handover files. Keep existing
321
+ UI steps. Headless steps use an explicit named role and the local dev credential;
322
+ assertions check actual returned values and effects. Missing capabilities stay
323
+ not met with their reason. Passing assertions are met even without captures;
324
+ missing evidence names each surface, its error and the `--shots .checks` remedy.
325
+
326
+ Keep interactive DOM nodes connected across delayed refreshes. Update a button's
327
+ text and attributes in place; never replace a button under a press. The short
328
+ example in NEEDS.md preserves its click listener and focus. A stale-element retry
329
+ helps a replaced handle before dispatch; it cannot recover a silently cancelled
330
+ press. Assert the resulting navigation as well as the button's presence.