@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/shop/SHOP.md CHANGED
@@ -101,26 +101,68 @@ One order stores every line and its item snapshot. `/api/shop/mine` lists the li
101
101
 
102
102
  `homie-studio shop init --supporter` writes an example item and `SELLING.md`, without a policy, cap or refund
103
103
  window. `shop check` validates settings; the build uses the same validation.
104
- `shop connect` (MCP `stripe_login`) starts Stripe's official browser approval, sandbox/test first.
105
- If Stripe CLI is missing, the result gives `npm install -g @stripe/cli@latest` for the AI to run.
106
- The person never supplies a key to the AI. A studio-specific CLI profile avoids borrowing another studio's login.
107
- If the studio is not deployed yet, approval is retained: deploy with `studio_deploy` (and
108
- `cloudflare_login` first if needed), then rerun connect. Webhooks require the deployed HTTPS address.
109
-
110
- **The default is keyless.** Connect syncs one Product and Price for each shop.json item and one
111
- Payment Link for each paid item, adjustable-quantity menus, and one capacity-limited link per published booking slot. Tip Prices use custom amounts; free orders stay local. It creates
112
- an account webhook at `<site>/api/shop/hook`, captures its signing secret without displaying it,
113
- and installs `STRIPE_WEBHOOK_SECRET` and `STRIPE_SHOP_LINKS` through Wrangler stdin. No Stripe
114
- API key goes into the Worker. Repeat connect after editing shop.json: changed prices get new
115
- Price versions, changed links get new versions, and removed objects are archived. Old paid orders
116
- keep their frozen item definitions. Receipts contain no credentials. Unchanged runs create nothing.
117
-
118
- With an existing account and deployed studio, the person approves Stripe CLI once; an already
119
- connected profile needs zero new approvals. An MCP-only grant is separate and is never extracted.
120
- The AI installs tooling, creates/syncs objects and stores the secret. New account verification,
121
- activation, administrator access settings and Cloudflare authorization are separate prerequisites.
122
- `shop connect --live` requires the owner's go-live request. `--renew` retries browser authorization.
123
- Never request a key from the person for the default flow.
104
+ ## Default: Stripe's official plugin
105
+
106
+ Use Stripe's official plugin, authenticated with its browser OAuth approval, for Products,
107
+ Prices, Payment Links, menu links, per-slot booking links, monthly giving and the customer
108
+ portal. Install with `stripe agent setup` if needed; the MCP URL is
109
+ `https://mcp.stripe.com`. Current tools are `stripe_api_search`, `stripe_api_details`,
110
+ `stripe_api_read` and `stripe_api_write`. Discover the operation and its schema before
111
+ calling it; tool argument shapes can change. See https://docs.stripe.com/mcp.
112
+
113
+ 1. Create/check `shop.json`. Read the app's published slot records and supply the complete
114
+ slot manifest described below. Select the owner's account and sandbox in Stripe's plugin;
115
+ read `/v1/account` to record its account ID. Live mode only after the owner requests it.
116
+ 2. Call Homie's `stripe_shop_plan` with `account`, `mode: "test"`, `slots` and
117
+ `observations: []`. CLI equivalent: `homie-studio shop plan --have plan-input.json`.
118
+ That JSON file contains the same fields. A returned `next` is the exact operation:
119
+ method, API path, parameters, API version and (for creates) idempotency key. Updates set desired fields; read before retrying. Metadata
120
+ scopes objects to this studio and origin; prices and links have content revisions.
121
+ 3. Perform each `next` through Stripe's official MCP. For GET, append
122
+ `{ "path": "…", "params": {…}, "body": <Stripe response> }` to `observations`
123
+ and plan again. Follow every pagination request. After a write, discard observations
124
+ and read fresh. Replanning resolves real Stripe IDs; never invent IDs or substitute
125
+ a different price. Reuse the supplied idempotency key where the tool supports it;
126
+ otherwise read before retrying and match the stable product ID/revision metadata.
127
+ 4. When no writes remain, use fresh reads with `stripe_shop_plan` `verify: true`
128
+ (CLI `homie-studio shop verify --have plan-input.json`). It checks the catalog,
129
+ actual link fields and line items, menus, slot limits and portal settings against
130
+ shop.json and the slot manifest. A mismatch blocks verification. It saves only public
131
+ configuration to `.studio/stripe-mcp.json`; local test configuration goes to `.dev.vars`.
132
+ 5. For a deployed HTTPS studio, run `stripe_webhook` (CLI `homie-studio shop webhook`).
133
+ Follow `studio_job`. This uses Stripe CLI only for private webhook installation,
134
+ checks the same account as the MCP, and installs the signing secret and verified
135
+ links through Wrangler stdin. The CLI needs one separate browser approval if its
136
+ studio profile is not already connected. MCP approval cannot be reused or exported.
137
+ Cloudflare authorization and an existing deployment are prerequisites.
138
+ 6. After installation, perform the returned `retire` operations through Stripe MCP to
139
+ archive replaced catalog objects, then verify fresh reads again. Test a sandbox
140
+ purchase and its signed event, order grant, booking capacity or recurring invoice;
141
+ a browser return is never proof. Stripe CLI `trigger` can send a test event, and
142
+ `events resend <event> --webhook-endpoint <endpoint>` retries a delivery. Check the
143
+ endpoint's delivery result and the app's verified records. Do not type a card into
144
+ a page for an automated proof; use Stripe's documented test PaymentMethods/fixtures.
145
+
146
+ Never create a webhook through model-visible Stripe MCP: its response exposes the signing secret.
147
+
148
+ The owner never handles an API key or signing secret. No Homie backend handles payments.
149
+ The Worker needs only public link configuration and the signing secret; buyers pay
150
+ Stripe directly even when the AI and the owner's computer are offline. The old
151
+ `stripe_login` / `shop connect` flow remains an alternative that syncs everything
152
+ through Stripe CLI browser approval. `--renew` renews that approval; `--live` requires
153
+ the owner's go-live request. Never read CLI config or print login-completion output.
154
+
155
+ For local checkout, pass `url: "http://localhost:8787"` (or the private Wi-Fi address)
156
+ and `mode: "test"` to the same plan/verify flow; the official plugin makes the links.
157
+ Run `homie-studio shop listen --profile <sandbox-profile>` in a background terminal.
158
+ Stripe CLI is required for forwarding; one browser approval is enough if needed.
159
+ The command captures and saves the listener secret privately. Start/restart
160
+ `homie-studio dev --lan` once it reports ready and keep the listener running.
161
+ Never copy the listener's raw output into the conversation.
162
+
163
+ For a faster local iteration, `homie-studio build --quick` skips generated rules play
164
+ and the snapshot smoke-run, while retaining compilation, guards and type checks.
165
+ Run a full build before release; quick output is not evidence that play checks passed.
124
166
 
125
167
  At purchase time, the Worker saves an opaque order reference and sends it as the link's
126
168
  `client_reference_id`. The buyer confirms quantity or tip amount on Stripe. A signed snapshot
@@ -142,17 +184,7 @@ items; failed refunds restore the financial booking. A lost dispute revokes good
142
184
  | Managed Payments | Payment Links support managed_payments.enabled; connect uses it when the studio selects Stripe as seller, subject to Stripe eligibility and terms. |
143
185
  | Currency display | Payment Links always enable Stripe Adaptive Pricing. Bookkeeping uses integration currency; presentment details describe what the customer saw. |
144
186
 
145
- A key alone never selects the fuller path. It requires explicit `STRIPE_CHECKOUT_MODE=sessions` as well as a Worker key. Only if requested, explain: “The optional session connection adds exact app-built carts, spending reservations, expiring holds and runtime refunds.”
146
- The explicit `shop connect --manual` path remains available, including `--live`, but requires the
147
- owner's existing Dashboard key step; it does not satisfy one approval. Never choose it automatically.
148
- Running default connect again saves links, removes the API key, and keeps signed-event fulfillment.
149
- When switching to spending restrictions, the AI must deactivate previously issued Payment Links through
150
- the approved CLI and resolve in-flight checkouts before relying on those restrictions; switching credentials
151
- does not cancel old Stripe sessions. The runtime OAuth finding and sources are in `docs/stripe-connect-research.md` in the source checkout.
152
-
153
- `shop`, `shop orders`, and `/_studio/office/shop` show readiness and sales. Stripe's optional MCP
154
- is useful for owner-requested refunds and analytics; honor its approval links. Do not create a
155
- secret-returning webhook through a conversation tool: the private connect process owns that call.
187
+ `shop`, `shop orders`, and `/_studio/office/shop` show readiness and sales. Use Stripe's official MCP for owner-requested refunds and analytics; honor its approval links.
156
188
 
157
189
  ## Payment guarantees and schema
158
190
 
@@ -220,7 +252,7 @@ For referrals, use Stripe Connect to split payments or a Stripe-based affiliate
220
252
 
221
253
  ## Keyless menus, paid seats and monthly giving (0.51.0)
222
254
 
223
- Use `shop connect` by default. All objects belong to the owner's Stripe account and
255
+ Use the official MCP plan/verify flow by default. All objects belong to the owner's Stripe account and
224
256
  are created through its approved CLI session. Homie has no platform key, OAuth
225
257
  backend, payment intermediary or cut. A desktop is needed to sync, not to take money.
226
258
 
@@ -251,7 +283,7 @@ data goes in that reference. A return URL is never proof of payment.
251
283
  collection with integer `capacity` and a server-managed `bookings` collection with
252
284
  `"booking":{"slots":"slots","item":"full-tune"}`. Publish slot records, then have
253
285
  the owner's AI write the complete manifest to `.studio/booking-slots.json` and run
254
- `shop connect`, or call local MCP `stripe_bookings_sync` with the same `slots` array:
286
+ `stripe_shop_plan` with the same `slots` array (the CLI alternative is `stripe_bookings_sync`):
255
287
 
256
288
  ```json
257
289
  [{"app":"tune","collection":"bookings","slots":"slots","slot":"sat-0900","item":"full-tune","capacity":1}]
@@ -303,7 +335,7 @@ Signed `subscription.started`, `.renewed`, `.failed`, `.cancelled` events drive
303
335
  membership and giving. Membership access ends at the paid invoice line's period end;
304
336
  failed invoices cannot extend it and cancellation ends access. Unknown Prices grant
305
337
  nothing. Keyless portal configuration supports cancellation, payment-method updates
306
- and invoice history. `shop connect` enables `login_page.enabled` through the CLI API
338
+ and invoice history. The official MCP plan enables `login_page.enabled` through Stripe’s API
307
339
  and stores Stripe's no-code login URL; **no Dashboard toggle is required**. Givers
308
340
  verify their email with Stripe to manage/cancel. The buyer-owned `/api/shop/portal`
309
341
  returns this URL. Plan changes require deliberate portal configuration and fulfilment
@@ -1,2 +1,2 @@
1
1
  // Generated by lib/build-identity.mjs; identifies the bundled runtime sources.
2
- export const STUDIO_BUILD = Object.freeze({"version":"0.51.4","sha256":"6286b3013c1ad438111eaed4338ccbffa2f85bf2a765f9f007155a809c414f71"});
2
+ export const STUDIO_BUILD = Object.freeze({"version":"0.51.5","sha256":"1e11f3b33959ff475ed31b5e5e0b5b4e7a155be104d47db80901422409830b23"});
package/worker/index.mjs CHANGED
@@ -189,7 +189,11 @@ async function availableShellShop(env, cat, game, options) {
189
189
  // The shell can show a disabled catalog before payment setup is complete.
190
190
  // Checkout readiness is enforced by the shop API, separately from visibility.
191
191
  const preview = (cat.games ?? []).find(g => g.id === game)?.screen?.shopPreview === true;
192
- return (preview && !shopOf(cat)?.broken || await sellingShop(env, cat)) ? shellShop(cat, game, options) : null;
192
+ const shop = shopOf(cat);
193
+ // Free services must retain their bridge in a mixed free/paid catalogue.
194
+ // Paid readiness, policy and atomic booking capacity remain API decisions.
195
+ const free = shop?.open && !shop.broken && shop.items.some(i => i.price === 0 && !i.interval && (!i.game || i.game === game));
196
+ return (preview && !shop?.broken || free || await sellingShop(env, cat)) ? shellShop(cat, game, options) : null;
193
197
  }
194
198
 
195
199
  async function postsOf(env, origin) {
@@ -1,3 +1,4 @@
1
+ import { stripeEventOwner } from './stripe-ownership.mjs';
1
2
  import { hasStripeKey, shopMode, rememberMoneyEvent, rememberedMoney } from './shop-links.mjs';
2
3
  import { paymentRefunds } from './shop-refunds.mjs';
3
4
  import { orderFact, bindHostedSession } from './purchase-core.mjs';
@@ -303,11 +304,25 @@ export async function reconcileOrder(env, o) {
303
304
 
304
305
  /** Called only AFTER the existing shop webhook signature/mode check. Return null for player orders. */
305
306
  export async function purchasePaymentEvent(env, event) {
307
+ const obj = event.data?.object ?? {};
308
+ const owner = stripeEventOwner(obj);
309
+ if (owner === 'shop') return null;
310
+ // Stripe can omit metadata on lifecycle events; an established shop subscription
311
+ // still belongs to the shop. Explicit ownership takes precedence over ID lookup.
312
+ if (!owner && (event.type.startsWith('invoice.') || event.type.startsWith('customer.subscription.'))) {
313
+ const subscription = event.type.startsWith('customer.subscription.') ? obj.id : idOf(obj.parent?.subscription_details?.subscription ?? obj.subscription);
314
+ if (subscription) {
315
+ try {
316
+ if (await env.DB.prepare('SELECT 1 FROM shop_subscriptions WHERE id = ?1').bind(subscription).first()) return null;
317
+ } catch (error) {
318
+ if (!/no such table: (?:main\.)?shop_subscriptions\b/.test(String(error?.message))) throw error;
319
+ }
320
+ }
321
+ }
306
322
  // An existing player shop may receive webhooks before purchase migrations run.
307
323
  try { await env.DB.prepare("SELECT 1 FROM purchase_orders LIMIT 0").first(); }
308
324
  catch (error) { if (/no such table/.test(error.message)) return null; throw error; }
309
325
 
310
- const obj = event.data?.object ?? {};
311
326
  if (!event.type.startsWith('checkout.session.')) await rememberMoneyEvent(env,event);
312
327
  if (obj.payment_link && obj.metadata?.homie === 'purchase-links-v1') return reconcileLinkSession(env,obj);
313
328
  if (!hasStripeKey(env) && event.type.startsWith('customer.subscription.')) {
@@ -1,3 +1,4 @@
1
+ import { stripeEventOwner } from './stripe-ownership.mjs';
1
2
  import { linkConfig, hasStripeKey } from './shop-links.mjs';
2
3
  import { KEY_SHAPE, stripeCall } from './stripe.mjs';
3
4
  /** Only verified shop Checkout sessions establish ownership. Provider customer IDs
@@ -48,6 +49,7 @@ export async function bindSubscription(env, order, session, shop) {
48
49
  export async function subscriptionEvent(env, event, shop) {
49
50
  if (!['invoice.paid','invoice.payment_failed','customer.subscription.updated','customer.subscription.deleted'].includes(event.type)) return null;
50
51
  const obj=event.data.object;
52
+ if (stripeEventOwner(obj) === 'parts') return null;
51
53
  const subscription=event.type.startsWith('customer.subscription.') ? obj.id : idOf(obj.parent?.subscription_details?.subscription ?? obj.subscription);
52
54
  if (!subscription) return 'ignored';
53
55
  let row;
package/worker/shop.mjs CHANGED
@@ -1,3 +1,4 @@
1
+ import { stripeEventOwner } from './stripe-ownership.mjs';
1
2
  import { localSession } from './local-test.mjs';
2
3
  import { bindSubscription, subscriptionEvent, subscriptionPortal } from './shop-subscriptions.mjs';
3
4
  import { bookingStatement } from './booking-store.mjs';
@@ -162,10 +163,10 @@ export const MISSING_WORDS = Object.freeze({
162
163
  'shop.json-refused': 'shop.json conflicts with the studio settings or payment requirements (homie-studio shop check says which), so nothing is sold.',
163
164
  'till-off': 'shop.json says "till": "off".',
164
165
  items: 'shop.json has no items.',
165
- 'payment-links': 'Run homie-studio shop connect to sync your items and connect Stripe; approve its browser page if asked.',
166
- 'fuller-checkout': 'Your spending allowance or checkout expiry needs the fuller checkout connection. Ask your AI to connect it.',
167
- 'stripe-key': 'The Worker has no Stripe key yet (homie-studio shop connect: approve Stripe in your browser; the command names any remaining Worker credential step).',
168
- 'webhook-secret': 'The Worker has no webhook signing secret yet (run homie-studio shop connect to create and store it automatically).',
166
+ 'payment-links': 'Ask your AI to plan and verify this shop through Stripe’s official plugin, then install its private webhook (homie-studio shop plan, shop verify, shop webhook).',
167
+ 'fuller-checkout': 'These spending allowance, expiry or recurring amount settings are not supported by Payment Links. Ask your AI to review them with you before setup.',
168
+ 'stripe-key': 'This older checkout connection is unavailable. Ask your AI to set up Payment Links through Stripe’s official plugin.',
169
+ 'webhook-secret': 'Ask your AI to run homie-studio shop webhook after verifying the Stripe plugin setup; it installs the signing secret privately.',
169
170
  database: 'This Worker has no database.',
170
171
  migration: 'The shop\'s tables are not in the studio\'s D1 yet: npm run deploy applies migrations 0008_studio_shop.sql, 0010_shop_reservations.sql and 0012_shop_lines.sql.',
171
172
  });
@@ -832,6 +833,7 @@ async function hook(request, env, cat, shop, ready) {
832
833
  if (payment) await syncRefunds(env, payment);
833
834
  await env.DB.prepare('INSERT INTO shop_events (id, type, at) VALUES (?1, ?2, ?3) ON CONFLICT(id) DO NOTHING').bind(ev.id, ev.type, Date.now()).run(); return json({ ok: true, did: purchase }); }
834
835
  }
836
+ if (stripeEventOwner(obj) === 'parts') return json({ ok: true, did: 'ignored' });
835
837
  needed.push('payment_link.updated','invoice.paid','invoice.payment_failed','customer.subscription.updated','customer.subscription.deleted');
836
838
  if (!needed.includes(ev.type)) return json({ ok: true, did: 'ignored' });
837
839
  if (typeof ev.livemode === 'boolean' && (hasStripeKey(env) || linkConfig(env)) && ev.livemode !== (shopMode(env) === 'live')) return json({ ok: true, ignored: 'mode' });
@@ -1194,7 +1196,7 @@ async function disputeClosed(env, payment, status) {
1194
1196
  }
1195
1197
 
1196
1198
  /** What the owner reads when Stripe holds a refund for approval (the shop's key is an Agent key). */
1197
- export const REFUND_HELD = 'Stripe is holding this refund until a person approves it in Stripe (Settings, Approvals, Requests): the shop\'s Stripe key is marked as an Agent key, and Stripe asks a person before an agent\'s refund. Approve it there; Stripe then refunds, and the item leaves the player\'s account when Stripe says so. To refund with one tap again, connect the shop with a plain restricted key (homie-studio shop connect), not one made for "Authorizing agent access".';
1199
+ export const REFUND_HELD = 'Stripe is holding this refund for your approval in Stripe (Settings, Approvals, Requests). Approve it there; the item leaves the player’s account after Stripe confirms the refund. You can also manage refunds through your connected Stripe AI; no credential change is needed.';
1198
1200
 
1199
1201
  /** Refund selected lines through Stripe, revoking each only after success. */
1200
1202
  export async function refundOrder(env, o, { reason = 'requested_by_customer', by = 'owner', line: lineId = null } = {}) {
@@ -0,0 +1,7 @@
1
+ /** Subscription metadata takes precedence over invoice-level metadata. */
2
+ export function stripeEventOwner(object) {
3
+ const metadata = object.parent?.subscription_details?.metadata ?? object.subscription_details?.metadata ?? object.metadata;
4
+ if (['shop-links-v1', 'shop-v1'].includes(metadata?.homie)) return 'shop';
5
+ if (['purchase-links-v1', 'purchase-v1'].includes(metadata?.homie)) return 'parts';
6
+ return null;
7
+ }
@@ -1,2 +1,2 @@
1
1
  /** The @homie-rocks/studio version this Worker code is (package.json's; a test keeps them equal). Cache-busts /_homie/site.js. */
2
- export const STUDIO_VERSION_TAG = '0.51.4';
2
+ export const STUDIO_VERSION_TAG = '0.51.5';
@@ -1,175 +0,0 @@
1
- {
2
- "v": 1,
3
- "checked": "2026-10-03",
4
- "note": "The endpoints the models skill uses, with the prices and schemas read live on the date above (fal's pricing API, its OpenAPI pages, the providers' terms). Endpoints retire and reprice monthly: `models.mjs registry` reads every price again for free and flags what moved; every receipt names the exact endpoint. Never hard-code one of these anywhere else.",
5
- "concept": {
6
- "withRefs": {
7
- "endpoint": "fal-ai/bytedance/seedream/v5/lite/edit",
8
- "price": {
9
- "usd": 0.035,
10
- "per": "image",
11
- "read": "2026-10-02"
12
- },
13
- "input": {
14
- "num_images": 1,
15
- "image_size": {
16
- "width": 2048,
17
- "height": 2048
18
- }
19
- },
20
- "refs": "image_urls",
21
- "maxRefs": 10,
22
- "why": "the cheapest multi-reference editor: the golden images keep every prop in one style"
23
- },
24
- "noRefs": {
25
- "endpoint": "fal-ai/bytedance/seedream/v5/lite/text-to-image",
26
- "price": {
27
- "usd": 0.035,
28
- "per": "image",
29
- "read": "2026-10-02"
30
- },
31
- "input": {
32
- "num_images": 1,
33
- "image_size": {
34
- "width": 2048,
35
- "height": 2048
36
- }
37
- },
38
- "why": "before there are golden images; the derived style prompt carries the look"
39
- },
40
- "minPixels": 3686400
41
- },
42
- "mesh": {
43
- "prop": {
44
- "endpoint": "tripo3d/p1/image-to-3d",
45
- "price": {
46
- "usd": 0.5,
47
- "per": "model with textures (0.40 without)",
48
- "read": "2026-10-02"
49
- },
50
- "credits": {
51
- "base": 40,
52
- "texture": 10,
53
- "defaults": {
54
- "texture": true
55
- }
56
- },
57
- "input": {
58
- "texture": true
59
- },
60
- "faceLimit": "face_limit",
61
- "output": "model_mesh",
62
- "why": "Tripo P1: built for low-poly game meshes, a face_limit that reaches a phone budget, $0.50 textured"
63
- },
64
- "fallbacks": [
65
- {
66
- "endpoint": "fal-ai/hyper3d/rodin/v2.5",
67
- "price": {
68
- "usd": 0.4,
69
- "per": "generation",
70
- "read": "2026-10-02"
71
- },
72
- "why": "quad presets and texture_delight; not wired by default"
73
- },
74
- {
75
- "endpoint": "fal-ai/trellis",
76
- "price": {
77
- "usd": 0.02,
78
- "per": "generation",
79
- "read": "2026-10-02"
80
- },
81
- "why": "drafts for exploring, then local simplify; not wired by default"
82
- }
83
- ]
84
- },
85
- "mood": {
86
- "endpoint": "fal-ai/bytedance/seedream/v5/lite/text-to-image",
87
- "price": {
88
- "usd": 0.035,
89
- "per": "image",
90
- "read": "2026-10-02"
91
- },
92
- "input": {
93
- "num_images": 1,
94
- "image_size": {
95
- "width": 2560,
96
- "height": 1440
97
- }
98
- }
99
- },
100
- "terms": [
101
- {
102
- "who": "fal",
103
- "url": "https://fal.ai/terms",
104
- "read": "2026-10-02",
105
- "lastUpdated": "2026-09-08",
106
- "says": "Customer owns its input; no express assignment of output; output not promised original or non-infringing; no training competing models on third-party outputs."
107
- },
108
- {
109
- "who": "Tripo (developer terms)",
110
- "url": "https://developers.tripo3d.ai/en/terms",
111
- "read": "2026-10-02",
112
- "lastUpdated": "2025-07-11",
113
- "says": "\"you may use Outputs for lawful commercial or non-commercial purposes\"."
114
- },
115
- {
116
- "who": "Meshy (through fal)",
117
- "url": "https://fal.ai/models/meshy/v7.1/image-to-3d",
118
- "read": "2026-10-03",
119
- "says": "fal labels the endpoint \"Commercial use\"; calls through fal are paid calls on the person's own fal account (fal's terms apply to the output). Meshy's own free plan (CC BY 4.0, Meshy owns output) does not apply: this skill never uses a Meshy free plan."
120
- }
121
- ],
122
- "character": {
123
- "mesh": {
124
- "endpoint": "meshy/v7.1/image-to-3d",
125
- "price": {
126
- "usd": 1.4,
127
- "per": "textured model with auto-rigging: US$0.80 base + US$0.40 textures + US$0.20 rigging (+US$0.12 an animation preset, not used)",
128
- "read": "2026-10-03",
129
- "api": "the pricing API says US$0.80 a generation (the base); the page's add-ons are added on top"
130
- },
131
- "addons": {
132
- "base": 0.8,
133
- "should_texture": 0.4,
134
- "enable_rigging": 0.2,
135
- "enable_animation": 0.12,
136
- "defaults": {
137
- "should_texture": true
138
- }
139
- },
140
- "input": {
141
- "model_type": "standard",
142
- "topology": "triangle",
143
- "target_polycount": 7000,
144
- "should_remesh": true,
145
- "should_texture": true,
146
- "enable_pbr": false,
147
- "pose_mode": "a-pose",
148
- "enable_rigging": true,
149
- "enable_animation": false,
150
- "symmetry_mode": "on"
151
- },
152
- "polycount": "target_polycount",
153
- "height": "rigging_height_meters",
154
- "output": "rigged_character_glb",
155
- "why": "Meshy 7.1 through fal: a textured mesh, a humanoid auto-rig and basic walk and run clips in one call, on the person's own fal account; the library's clips are retargeted onto its skeleton for free"
156
- },
157
- "rig": {
158
- "endpoint": "fal-ai/meshy/rigging",
159
- "price": {
160
- "usd": 0.8,
161
- "per": "request (the page says US$0.20; the pricing API says US$0.80: quoted at the higher until they agree)",
162
- "read": "2026-10-03"
163
- },
164
- "addons": {
165
- "base": 0.2,
166
- "enable_animation": 0.12
167
- },
168
- "input": {
169
- "enable_animation": false
170
- },
171
- "why": "rig a humanoid mesh made elsewhere (a Tripo prop-style mesh, the person's own); humanoids with clear limbs only; not wired by default"
172
- },
173
- "concept": "uses concept.withRefs or concept.noRefs: one full-body image in an A-pose, front view, plain background"
174
- }
175
- }