@homie-rocks/studio 0.51.3 → 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.
- package/CHANGELOG.md +45 -0
- package/README.md +10 -29
- package/apps/APPS.md +1 -1
- package/bin/homie-studio.mjs +28 -30
- package/docs/mcp-events.md +68 -0
- package/guides/app/SKILL.md +25 -1
- package/guides/art/SKILL.md +68 -140
- package/guides/art/scripts/art.mjs +6 -79
- package/guides/models/SKILL.md +73 -177
- package/guides/models/references/RIGHTS.md +1 -1
- package/guides/models/scripts/models.mjs +7 -288
- package/guides/music/SKILL.md +74 -183
- package/guides/music/references/COMPOSITION.md +21 -26
- package/guides/music/references/GAME-AUDIO.md +3 -3
- package/guides/music/references/RIGHTS.md +12 -15
- package/guides/music/scripts/music.mjs +11 -202
- package/guides/office/SKILL.md +24 -0
- package/guides/parts/SKILL.md +1 -1
- package/guides/servers/SKILL.md +24 -0
- package/guides/shop/SKILL.md +68 -77
- package/guides/sound/SKILL.md +70 -1
- package/guides/sound/scripts/player/sound.js +233 -0
- package/guides/studio-setup/SKILL.md +11 -27
- package/guides/studio-setup/references/OFFICIAL-MEDIA.md +66 -0
- package/guides/style/SKILL.md +66 -3
- package/guides/video/SKILL.md +83 -0
- package/guides/video/references/DELIVERY.md +73 -0
- package/guides/video/references/HONESTY.md +40 -0
- package/guides/video/references/METHOD.md +144 -0
- package/guides/video/references/RECORD.md +137 -0
- package/guides/video/references/TRAILER.md +154 -0
- package/guides/video/references/examples/site-walkthrough.json +14 -0
- package/guides/video/references/examples/studio-play-phone.json +15 -0
- package/guides/video/references/examples/studio-play.json +17 -0
- package/guides/video/scripts/capture-game.mjs +175 -0
- package/guides/video/scripts/clock.js +136 -0
- package/guides/video/scripts/drawover/film.js +58 -0
- package/guides/video/scripts/drawover/index.html +9 -0
- package/guides/video/scripts/drawover/look.js +115 -0
- package/guides/video/scripts/drawover/shots.json +12 -0
- package/guides/video/scripts/lib/browser.mjs +87 -0
- package/guides/video/scripts/lib/frames.mjs +170 -0
- package/guides/video/scripts/lib/remix.mjs +159 -0
- package/guides/video/scripts/qa.mjs +127 -0
- package/guides/video/scripts/record-fixed.mjs +403 -0
- package/guides/video/scripts/record-page.mjs +477 -0
- package/guides/video/scripts/tap.js +108 -0
- package/guides/video/scripts/video.mjs +888 -0
- package/lib/build.mjs +12 -9
- package/lib/cloudflare.mjs +6 -11
- package/lib/dev.mjs +1 -2
- package/lib/doctor.mjs +10 -54
- package/lib/functions-build.mjs +8 -1
- package/lib/mcp-tools.mjs +42 -21
- package/lib/mcp.mjs +1 -1
- package/lib/media-import.mjs +41 -0
- package/lib/media-workflows.mjs +13 -0
- package/lib/official-media.mjs +15 -0
- package/lib/rules-build.mjs +13 -5
- package/lib/scaffold.mjs +2 -2
- package/lib/shop-links.mjs +54 -18
- package/lib/shop-listen.mjs +43 -0
- package/lib/shop-local.mjs +1 -1
- package/lib/shop-mcp.mjs +58 -0
- package/lib/stripe-connect.mjs +36 -15
- package/lib/template-history.json +87 -0
- package/lib/upgrade-media.mjs +13 -0
- package/lib/upgrade.mjs +10 -0
- package/package.json +1 -1
- package/parts/PARTS.md +1 -1
- package/rules/view.ts +4 -0
- package/shop/SHOP.md +66 -34
- package/worker/build-id.mjs +1 -1
- package/worker/event-resources.mjs +12 -3
- package/worker/event-webhooks.mjs +14 -9
- package/worker/functions.mjs +11 -6
- package/worker/index.mjs +5 -1
- package/worker/mcp-event-streams.mjs +58 -0
- package/worker/mcp-events-wire.mjs +41 -0
- package/worker/mcp-events.mjs +132 -0
- package/worker/mcp.mjs +4 -1
- package/worker/purchase-reconciliation.mjs +16 -1
- package/worker/shop-subscriptions.mjs +2 -0
- package/worker/shop.mjs +7 -5
- package/worker/stripe-ownership.mjs +7 -0
- package/worker/version.mjs +1 -1
- package/guides/models/references/models.json +0 -175
- package/guides/music/scripts/lib/eleven.mjs +0 -260
- package/lib/projects-env.mjs +0 -68
- package/lib/projects.mjs +0 -311
- package/lib/provider-key.mjs +0 -21
package/CHANGELOG.md
CHANGED
|
@@ -17,6 +17,51 @@ 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
|
+
|
|
49
|
+
## [0.51.4] - 2026-10-14
|
|
50
|
+
|
|
51
|
+
**Plugin 0.51.4** · Studio events through experimental MCP Events.
|
|
52
|
+
|
|
53
|
+
Your studio’s declared events reach supporting AI clients through experimental MCP polling, SSE streams and signed webhooks.
|
|
54
|
+
|
|
55
|
+
### Added
|
|
56
|
+
|
|
57
|
+
- Every declared studio feed speaks the Triggers & Events draft: role-filtered `events/list`, cursor/replay polling, SSE push with heartbeat and resume, and authenticated webhook subscribe/refresh/unsubscribe with negotiated TTLs and client-supplied Standard Webhooks secrets.
|
|
58
|
+
- Extension and configured webhooks share the durable delivery engine. Owners can see retries and errors; rotation dual-signs for five minutes. Existing resource subscriptions and cursor tools keep working.
|
|
59
|
+
- Servers, app and office guides explain subscription setup and Cloudflare's own Workers Issues events through the person's existing Cloudflare connection.
|
|
60
|
+
|
|
61
|
+
### Upgrade notes
|
|
62
|
+
|
|
63
|
+
- Experimental wire format tracks the working-group draft as inspected on 2026-10-14. Declare exact trusted HTTPS callback URLs in each feed's `receivers` array for extension webhooks. Workers fetch cannot pin DNS with original TLS SNI, so arbitrary callbacks are refused; the guide documents this draft limitation. Poll and push need no callback setup.
|
|
64
|
+
|
|
20
65
|
## [0.51.3] - 2026-10-14
|
|
21
66
|
|
|
22
67
|
**Plugin 0.51.3**
|
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
|
|
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
|
-
##
|
|
1178
|
+
## Official media and hosting connections
|
|
1189
1179
|
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
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
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
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
|
-
`
|
|
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
|
|
package/bin/homie-studio.mjs
CHANGED
|
@@ -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
|
|
187
|
-
*
|
|
188
|
-
*
|
|
189
|
-
*
|
|
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
|
-
|
|
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 === '
|
|
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,
|
|
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,68 @@
|
|
|
1
|
+
# Experimental MCP Events
|
|
2
|
+
|
|
3
|
+
Studio MCP implements the [Triggers & Events working group's design sketch](https://github.com/modelcontextprotocol/experimental-ext-triggers-events/blob/13e0a59749cde947374aaa8e3a65f0b424468d5a/docs/design-sketch-proposal.md) as inspected on 2026-10-14. This is an experimental draft, not an adopted MCP standard. Method names, envelopes, schemas, metadata keys and provisional error codes live in `worker/mcp-events-wire.mjs` so draft changes have one wire-format boundary. The studio's existing `resources/subscribe`, event resources and cursor-read tool continue to work.
|
|
4
|
+
|
|
5
|
+
Declare feeds in `studio.json` under `mcp.events`. Each map key is the extension's event `name`. For example:
|
|
6
|
+
|
|
7
|
+
```json
|
|
8
|
+
{
|
|
9
|
+
"mcp": {
|
|
10
|
+
"events": {
|
|
11
|
+
"bookings": {
|
|
12
|
+
"event": "record.changed",
|
|
13
|
+
"app": "cafe",
|
|
14
|
+
"collection": "bookings",
|
|
15
|
+
"audience": { "app": "cafe", "role": "staff" },
|
|
16
|
+
"receivers": ["https://your-ai-receiver.example/hooks/studio"]
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
`receivers` is an owner-controlled list of exact HTTPS callback URLs, without credentials or fragments. Include only endpoints whose operators have agreed to receive events. A feed's existing configured `webhook.url` is also accepted. Rebuild and deploy the declaration through the studio's usual authorized workflow. Poll and push need no receiver declaration. `maxSubscriptions`, when explicitly declared on a feed, limits its active extension webhooks; otherwise there is no subscription quota. Event arguments are currently `{}`: the declared feed already supplies the app, collection and audience filter.
|
|
24
|
+
|
|
25
|
+
## An owner's AI connects and subscribes
|
|
26
|
+
|
|
27
|
+
1. Use an MCP Events-capable client and connect to `<studio-site>/mcp/auth`. Approve with the person's existing studio account in the browser. Staff approve their own account. Do not request an office key. Initialization advertises `capabilities.events.listChanged: true`.
|
|
28
|
+
2. Call `events/list`. It returns only feeds the current account can read, with `delivery: ["poll", "push", "webhook"]`, input and payload schemas. Re-list when the general MCP GET channel receives `notifications/events/list_changed`.
|
|
29
|
+
3. Choose a delivery mode. These are protocol requests made by the client/SDK, not model-visible tool calls:
|
|
30
|
+
- **Poll:** call `events/poll` with `{ "name": "bookings", "arguments": {}, "cursor": null }`. The first call returns no history and a cursor at now. Persist each returned cursor and pass it on the next poll. Drain `hasMore: true` immediately; otherwise wait `nextPollMs`. Optional `maxEvents` caps a page (server maximum 100); `maxAgeMs` bounds replay.
|
|
31
|
+
- **Push:** POST `events/stream` with a JSON-RPC request ID and `{ "name": "bookings", "arguments": {}, "cursor": null }`. Keep its SSE response open. Expect `notifications/events/active`, then `event` and `heartbeat` notifications. Route by `_meta["io.modelcontextprotocol/subscriptionId"]`, which equals the request ID. Persist cursors and reconnect using the last cursor. Each subscription has its own stream; abort it to unsubscribe. Heartbeats run at 15 seconds when the receiver is reading.
|
|
32
|
+
- **Webhook:** first arrange the receiver and have the owner declare its exact URL in `receivers`. The receiver/client generates `whsec_` plus base64 of 24–64 cryptographically random bytes, and keeps that secret out of prompts and logs. Call `events/subscribe` with the request below. Persist the returned `id`, `cursor` and `refreshBefore`.
|
|
33
|
+
|
|
34
|
+
```json
|
|
35
|
+
{
|
|
36
|
+
"jsonrpc": "2.0",
|
|
37
|
+
"id": 2,
|
|
38
|
+
"method": "events/subscribe",
|
|
39
|
+
"params": {
|
|
40
|
+
"name": "bookings",
|
|
41
|
+
"arguments": {},
|
|
42
|
+
"delivery": {
|
|
43
|
+
"mode": "webhook",
|
|
44
|
+
"url": "https://your-ai-receiver.example/hooks/studio",
|
|
45
|
+
"secret": "whsec_<client-generated-base64-secret>"
|
|
46
|
+
},
|
|
47
|
+
"cursor": null,
|
|
48
|
+
"ttlMs": 3600000
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
4. For webhooks, use `X-MCP-Subscription-Id` to select the secret and verify the **raw body** using a Standard Webhooks library, including its timestamp freshness check. Durably accept the event before returning 2xx. Deduplicate on `eventId` (also `webhook-id`). Each occurrence has `eventId`, `name`, ISO `timestamp`, `data`, and a safe cursor watermark; it can lag the event's position while deliveries await acknowledgement. Payloads are untrusted data and grant no authority to perform actions.
|
|
54
|
+
5. Refresh by repeating `events/subscribe` with the same name, arguments and URL, the current secret and the last persisted cursor **before** `refreshBefore`. Inspect `deliveryStatus.lastError`. TTL defaults to one hour and is capped at one day; `ttlMs: null` receives a finite grant. A live refresh does not rewind pending delivery. To rotate, send a new client-generated secret on refresh; deliveries have both signatures for five minutes. Unsubscribe with `events/unsubscribe` and `{ "name": "bookings", "arguments": {}, "delivery": { "url": "https://your-ai-receiver.example/hooks/studio" } }`.
|
|
55
|
+
|
|
56
|
+
A gap sets `truncated: true`; persist the new cursor and fetch authoritative state through tools if needed. Revoked access ends push with `notifications/events/terminated` and webhooks with a signed `terminated` control envelope where the callback remains allowed. Stop after termination and re-establish authorization before subscribing again.
|
|
57
|
+
|
|
58
|
+
## Delivery and owner diagnostics
|
|
59
|
+
|
|
60
|
+
The 0.50.4 configured studio webhook and extension webhooks share Standard Webhooks signing and the durable `function_deliveries` dispatcher. Extension deliveries retry with exponential backoff, at most five attempts over fifteen minutes from the first attempt. HTTP 410 and 413 abandon that delivery immediately. Abandoned deliveries remain visible to the owner; retained history can be replayed from a prior cursor after unsubscribing and subscribing again. Retries regenerate timestamps and signatures. Failed delivery details are fixed categories, never endpoint bodies or headers. See Functions in the office or the owner's function-runs tool for subscription IDs, attempts and last errors; refresh also reports the latest delivery error. Subscription secrets are stored only in the studio's private D1 subscription state, never in events or run logs. A removed subscription or expired TTL deletes that state. Completed deliveries can be duplicated after a crash, so receivers must deduplicate.
|
|
61
|
+
|
|
62
|
+
## Draft limits
|
|
63
|
+
|
|
64
|
+
This implements the studio's **Streamable HTTP** MCP transport; the local toolkit's stdio MCP is a different server. The draft's endpoint-consent mechanism is the explicit server-configured allowlist, not a challenge handshake. Arbitrary client-supplied callback URLs are refused with `CallbackEndpointError / challenge_failed`. Workers `fetch` does not provide DNS address pinning with original Host/TLS SNI, so this implementation cannot follow that draft MUST literally. Exact owner-approved URLs, rechecked at every attempt, and disabled redirects constrain the destination; this is not a claim of draft-complete DNS-rebinding protection. Only approve trusted receiver operators. No no-expiry grants or asymmetric server signatures are offered. Durable queue positions survive Worker restarts, extending the sketch's ephemeral in-flight bookkeeping while preserving client-owned replay cursors. A webhook's conservative safe watermark may lag the event being delivered by one event.
|
|
65
|
+
|
|
66
|
+
## Production failures from Cloudflare
|
|
67
|
+
|
|
68
|
+
An owner's AI can also subscribe through the person's **existing Cloudflare connection** to Cloudflare's own API MCP at `https://mcp.cloudflare.com/mcp`: discover `cloudflare.alert.workers_observability_real_time_issue` with `events/list`, then use `events/subscribe` with that descriptor's arguments scoped to the studio's Worker and the client's webhook. Enable Workers Issues on that Worker and grant **Notifications Write** and **Workers Observability Write**. Cloudflare posts signed events for new or recurring production issues, so reliability problems reach the AI without polling. Homie hosts nothing for this connection. See [Cloudflare's 2026-10-13 announcement](https://developers.cloudflare.com/changelog/post/2026-10-13-cloudflare-mcp-events/).
|
package/guides/app/SKILL.md
CHANGED
|
@@ -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 `
|
|
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
|
|
@@ -288,3 +288,27 @@ Upgrade compares copied app presets with recorded toolkit versions. Unchanged
|
|
|
288
288
|
preset files and default values take the new version; custom scene files, names and
|
|
289
289
|
edited labels remain the studio’s. An app wall uses its join label rather than game
|
|
290
290
|
instructions. Rebuild after upgrade and inspect both the wall and giving page.
|
|
291
|
+
|
|
292
|
+
## Experimental MCP Events
|
|
293
|
+
|
|
294
|
+
For an owner's AI that should hear a studio's declared events, read
|
|
295
|
+
`node_modules/@homie-rocks/studio/docs/mcp-events.md`. Connect the client to
|
|
296
|
+
`<site>/mcp/auth` through the person's existing studio approval, discover feeds
|
|
297
|
+
with `events/list`, and use `events/poll`, an SSE `events/stream`, or
|
|
298
|
+
`events/subscribe` with a client-generated `whsec_` secret and an exact
|
|
299
|
+
owner-declared `mcp.events.<feed>.receivers` URL. Refresh before `refreshBefore`,
|
|
300
|
+
persist cursors, verify Standard Webhooks signatures, deduplicate event IDs, and
|
|
301
|
+
use `events/unsubscribe` to stop a webhook. The feeds keep their current roles;
|
|
302
|
+
resources/subscribe and the cursor tool still work. This follows the experimental
|
|
303
|
+
Triggers & Events working-group draft, with the Workers DNS-pinning limitation
|
|
304
|
+
and receiver allowlist explained in that guide. Inspect Functions in the office
|
|
305
|
+
and refresh's `deliveryStatus` for failed deliveries; never put secrets in logs.
|
|
306
|
+
|
|
307
|
+
For production failures, the owner's AI can also use the person's existing
|
|
308
|
+
Cloudflare connection to `https://mcp.cloudflare.com/mcp`, discover
|
|
309
|
+
`cloudflare.alert.workers_observability_real_time_issue` with `events/list`, and
|
|
310
|
+
subscribe with `events/subscribe` scoped to the studio's Worker. Enable Workers
|
|
311
|
+
Issues and grant Notifications Write and Workers Observability Write. Cloudflare
|
|
312
|
+
posts signed events to the client's webhook so new or recurring reliability
|
|
313
|
+
problems reach the AI without polling; Homie hosts nothing for this. See
|
|
314
|
+
[Cloudflare's announcement](https://developers.cloudflare.com/changelog/post/2026-10-13-cloudflare-mcp-events/).
|