Agent Skills
› jasonkneen/tiny-world-builder
› tinyworld-integrations
tinyworld-integrations
GitHub定义Tiny World应用的后端集成规范,涵盖Netlify函数路由、身份验证、Solana钱包交互、社区功能及实时通信配置。
Trigger Scenarios
修改API接口或Webhook逻辑
调整后端函数实现
更新集成配置
Install
npx skills add jasonkneen/tiny-world-builder --skill tinyworld-integrations -g -y
SKILL.md
Frontmatter
{
"name": "tinyworld-integrations",
"description": "Use when changing Tiny World Builder API, webhook, SSE, MCP, plugin, or automation examples."
}
Tiny World Integrations
The app has browser-local integration points plus a small Netlify account backend:
- Account/profile/cloud-save functions live under
netlify/functions/.profile.mjs,builds.mjs,share.mjs, andassets.mjsare routed to/api/profile,/api/builds,/api/share, and/api/assetsvia each function's exportedconfig.path. - Auth helpers should resolve the trusted site/Identity base from the same
deploy-origin chain used elsewhere, including
TINYWORLD_SITE_URL, before Netlify deploy URL fallbacks. Do not derive Identity verification targets from request-controlled origins. - PartyKit durable flush buffers must clear every successfully-posted pending
bucket in the
res.okbranch (resources, tax payouts, GOLD events, etc.) so retries do not duplicate already-granted durable rewards. - Worlds MMO grid size: the saved world payload (
data.gridSize) is authoritative. Treatworlds.grid_sizeas cached metadata that can be stale; DTOs, previews, pricing/count derivation, and room entry should prefer/sync the payload size so an 8x8 map is not shown as 20x20 in multiplayer. - Wallet/social functions also live under
netlify/functions/:wallet.mjsverifies Phantom-signed Solana wallet challenges and reads$TINYWORLDbalances/activity from RPC,wallet-payments.mjscreates Solana Pay payment intents,players.mjstracks online presence/search/chat requests/parties, andlivekit-token.mjsissues LiveKit room tokens whenLIVEKIT_URL,LIVEKIT_API_KEY, andLIVEKIT_API_SECRETare configured. - Community functions:
community.mjs(/api/community) backs the/communityDiscord-lite page — rooms, DMs, members, bans, blocks, invites; tables auto-create + seed on first request. Channel names are forced lowercase and the super-owner (TINYWORLD_COMMUNITY_OWNER, defaultjasonkneen) is made an owner of every room each request viaensureCommunityDefaults. Only staff (super-owner /TINYWORLD_COMMUNITY_STAFF) or a room owner can create/delete channels and ban; the sameadminflag gates every privileged action. Members must pass an anti-AI human check (community_verifications) AND have a mandatory Twitter/X handle on their profile (GitHub optional) before they can post/DM/join —saveSocialswrites the bare handles toprofiles.twitter/profiles.github(idempotentALTER TABLE ... ADD COLUMNinensureTables; migration20260615020000_add_profile_socials.sql). Bootstrap returnsme.profileComplete; the page shows a forced "Complete your profile" modal until Twitter is set and renders both handles on profile cards. Members can fully edit their profile (display name, bio, avatar, handles) viasaveProfile(aliassaveSocials). Avatars are an allowlisted preset set underassets/avatars/*.png(keys inAVATAR_KEYS) — no user image uploads, so no NSFW image risk. All user-authored text (display name + bio) is run throughcheckTextSafety, a two-layer filter (hard substrings + whole-word, leet/spacing-normalized) that rejects sexual / nudity / abusive / hateful content. Tested intests/community-profile.test.mjs.community.htmlsigns users in in-page (no bounce to the builder): it loadsvendor/tinyworld-auth.jsvia the import map for Netlify Identity email login/signup and calls/api/walletfor Phantom login, storing the session under the sharedtinyworld:auth:wallet-session.v1key. - Community moderation webhook:
community-webhook.mjs(/api/community/webhook) is a server-to-server endpoint for an agent (Hermes) to ban/unban/block/hide or restore/delete messages/purge spam/delete rooms. Auth is a shared secret (TINYWORLD_COMMUNITY_WEBHOOK_SECRET) viax-tinyworld-signature: sha256=<hmac of raw body>(preferred) orx-webhook-secret. Shared primitives live inlib/community-moderation.mjs;community.mjsalso emits outboundmessage.createdevents toHERMES_COMMUNITY_WEBHOOK_URL(signed, fire-and- forget) so the agent can observe and react. Full reference:docs/community-webhook.md. - User auth is Netlify Identity. The browser bridge is self-hosted through
vendor/tinyworld-auth.jswith an import map to vendored@netlify/identity/gotrue-js; do not reintroduce a remote identity widget script. - The builder should not show working-looking account UI on hosts that cannot
serve Netlify Identity. Treat 404/405
/.netlify/identity/*failures from the browsergetSettings()probe or login calls as "auth unavailable": hide sign-in/account commands, keep local/static building usable, and leave cloud save/share/collab actions gated off. For local account work, use Netlify dev athttp://localhost:8888/tiny-world-builder. - Profile image fields stored through
/api/profile,/api/admin-users, or community preset-avatar saves must be absolutehttp(s)URLs. Preset avatar paths underassets/avatars/*.pngare normalized with the trusted site origin fromTINYWORLD_SITE_URL/ NetlifyURL/ deploy URL envs before validation and persistence; already-absolute URLs are left unchanged. - Account API fetches must send
Authorization: Bearer <nf_jwt>when possible andcredentials: 'same-origin'so Netlify Functions can resolve the current Identity user. Wallet login uses the same bearer path with signedtw-wallet-v1...session tokens stored undertinyworld:auth:*. - For local account/function work, run
npx netlify devand usehttp://localhost:8888/tiny-world-builder; that port keeps the auth/account UI enabled while the plain static dev server remains anonymous. - Cloud worlds are stored as full TinyWorld JSON in Netlify Database
buildsrows. Existing rows update throughPUT /api/builds?id=<id>so named localStorage worlds can stay bound to one cloud row instead of creating duplicates. Public share links create immutable-ish rows inworld_sharesand load through same-origin?share=<id>//api/share?id=<id>. - Multiplayer/shared building uses PartyKit separately from Netlify Functions.
partykit.jsonpoints atparty/index.js, local development runs withnpm run party:devon port1999, and browser rooms connect only when a URL includes?party=,?room=, or?collab=. Collaborate links should reuse a/api/shareid as both the world snapshot id and the PartyKit room id:/tiny-world-builder?share=<id>&party=<id>. - Shared build/collab rooms are public-observer by default: second and later
PartyKit connections are admitted as
viewerseats, not held in a lobby. Host clients heartbeat public room metadata to/api/collabs; the home page feed and/collabspage list those rooms with observer links (observe=1). This public visibility must not grant edit authority; edits still require a host-assigned role plus server-side island/zone checks. - Closing a shared build is a two-layer operation: host clients send
room.closeto PartyKit so every connected peer receivesroom.closedand no replacement host is promoted, and they POST{ action: 'close', roomId }to/api/collabsso the public registry stores a short-lived tombstone incollab_room_closures. Heartbeats for tombstoned rooms must return{ closed: true }instead of recreating the listing. - Admin collab moderation lives on
/collabs: authenticated world-admin sessions call/api/collabswith{ action: 'hide', roomId }to add a short-livedcollab_room_hidestombstone that removes a room from public lists without disconnecting occupants, or{ action: 'adminClose', roomId }to use the close tombstone. When a host sees that close tombstone on its registry heartbeat, the client must send PartyKitroom.closebefore closing its socket so connected peers get the same shutdown event as a manual host stop. - Shared build owners are tracked from the
/api/sharerow./api/collabscopiesworld_shares.owner_auth_id/profile_idintocollab_rooms, exposesGET /api/collabs?mine=1for the builder world-menu "Shared rooms" section, and lets the owner/adminhide(make private),unhide, orownerClose.GET /api/collabs?roomId=<id>&control=1can return a signedtinyworld-collab-controltoken; the builder sendscontrol.claimto PartyKit so the original sharer/admin can reclaim host controls when reopening their own room link instead of staying an observer. - Collaborative build zones are transient PartyKit room permission data, not
saved world cells. Host clients send
zones.set; the server sanitizes zones, stores editorzoneIds, and must gate every non-hostcell.setagainst assigned active zones. Client outlines/labels and local edit checks are UX and desync prevention only; do not rely on them as the authority. - MMO economy/multiplayer extraction lives in
packages/tinyworld-mmo-core/. It is a dependency-free ESM package for shared GOLD allowance, resource tax, ledger, join-command, and interest-snapshot contracts. Use it when wiring the TinyWorld economy guide into PartyKit or Netlify Functions instead of copying constants between runtime files. - Tinyverse published-world navigation no longer uses the
tinyverse-nexushub./api/worldsshould hide that slug from lists and direct loads, published world data should normalize to one centerstargatewithdest: '__world-picker', and PartyKitsafeSpawn()should prefer that gate so players arrive where the in-world picker exit is. - Tinyverse/lobby access is locked to the Jason account allowlist in
netlify/functions/lib/tinyverse-access.mjs. Do not useaccountMeetsCriteria()or a rawprofiles.lobby_accessflag as the authoritative gate; migrations should keeplobby_accessdefault false and clear it for every non-allowlisted profile. - Tinyverse room join/refresh payloads use compact cells. Terrain-only cells may
be
[x,z,terrain]; object/resource cells are[x,z,terrain,kind]. Keep the renderer validator andapplyState()tolerant of both tuple lengths. - Explicit resource-bearing custom assets use object-form cells with
economy: { resource, charges?, label? }. Live resources are currentlyfish,ore,plants, andmeat; normalize throughpackages/tinyworld-mmo-core/normalizeWorldResourceSpec(...)in PartyKit and Netlify code instead of inferring resources from visual materials or copying constants. Compact tuple cells remain the default for ordinary terrain/kind saves. - Tinyverse multiplayer rooms are runtime/play/moderation surfaces only. Do not
add live island building controls,
adminSave, build-role seats, orworld.refreshboard replacement inside PartyKit rooms. Island editing and version publication must live in the dedicated draft/version flow. - Local custom assets are account data too:
/api/assetsstores oneasset_librariesrow per profile containing custom voxel-build stamps and saved asset templates. Browser hooks insaveCustomVoxelBuildStamps()andsaveAssetTemplates()queue a cloud sync after login. - Local Netlify Database failures are expected in some
netlify devsessions. Translate 503Netlify Database is not available...responses into a friendly account/cloud status orwarntoast, never a red production-style error toast, raw database message, or visibleLocal DB offlinewording. - Wallet/player social functions rely on
netlify/database/migrations/20260602120000_wallet_players_social.sql. If those tables are missing in local Netlify dev, classify Postgres42P01withisMissingRelations(...)and return a setup-oriented 503 instead of logging raw missing-relation errors as generic 500s. - Phantom wallet linking and wallet login must stay challenge/response based:
the browser asks Phantom to sign the server-issued message and the function
verifies the Ed25519 signature against the Solana public key before linking
or minting a wallet session. Do not accept a posted wallet address as proof
of ownership. Wallet login requires
TINYWORLD_WALLET_SESSION_SECRET(orTINYWORLD_AUTH_SECRET) for HMAC-signed challenge/session tokens.$TINYWORLDmint/payment values come from env (TINYWORLD_TOKEN_MINT,TINYWORLD_PAYMENT_WALLET, optionalSOLANA_RPC_URL) rather than client constants. - Database schema changes belong in
netlify/database/migrations/*.sql. Deploy previews get their own database branch, so use a preview deploy for real Identity + DB verification; localnetlify devis useful for functions but is not a complete Identity social-login test.
Browser-local integration points:
- Outbound webhooks live in
tiny-world-builder.htmlunder// -------- API / webhooks / SSE bridge --------. - Optional browser-local probes must be opt-in so the static app stays console-clean:
the Cluso in-page embed is LOCAL-DEV-ONLY, injected at runtime by
tools/dev-server.js(assets in gitignoredcluso/); it must never be referenced by committed/shipped HTML; model-stamp API endpoints load only with?modelApi=1,?modelStampApi=1,window.__TWB_MODEL_STAMP_API_ENABLED__ = true, orlocalStorage['tinyworld:features:model-stamp-api']='1'. fireWebhook(event, payload)batches editor mutations and POSTs{ source: 'tiny-world-builder', events }to the configured Developer-panel webhook URL.- Inbound automation uses
EventSourceagainst the configured Developer-panel SSE URL. Each SSEdata:payload must be one JSON command accepted byapplyRemoteCommand. - Supported inbound ops include
place/set_cell,clear,reset, plus runtime-only vehicle controls:vehicle_spawn,vehicle_set_goal,vehicle_controls,vehicle_remove, andvehicle_clear. - Runtime vehicles must not pass through each other. Keep traffic behavior in the runtime layer: collision radius + yield radius, brake when another vehicle is inside the envelope, and reroute around occupied road cells after a short blockage when an alternate road path exists.
- Placed objects on paths are live traffic blockers.
isVehicleDrivableCellshould allow path cells only when the mainkind/extras do not occupy the tile, while bridge cells remain drivable. CallrefreshVehiclesForWorldObstacleChangefrom world edit paths so active auto vehicles reroute immediately when the user drops or removes an obstacle.
Examples live under plugins/examples/:
webhook-receiver.jscaptures outbound webhook batches.sse-command-relay.jsexposes/ssefor the browser and/commandfor external clients.send-command.jsis a small CLI for the relay.mcp-stdio-bridge.jsis a dependency-free MCP stdio server that calls the relay and reads the webhook log.vehicle-road-demo.jsis a dependency-free MCP client/demo runner that talks tomcp-stdio-bridge.js, paints a visible road/water/bridge network, spawns runtime vehicles, and retargets them in a loop so the browser remains watchably active.- The app also supports browser-native shareable vehicle demo URLs:
?demo=vehicles&seed=tide-ridge-428creates the small/default visible road demo.?demo=vehicles-large&seed=metro-culdesac-20&stats=1creates the default 20×20 scale test with arterial/ring roads, bridge crossings, cul-de-sac endpoints, and 36 autonomous vehicles on long routes.- Large-demo params:
size=/mapSize=/grid=/gridSize=accept the nearest valid demo grid size from12through20(12,16,20);cars=/carCount=/vehicles=/vehicleCount=accept1..120and are capped by available unique endpoints. Keep these demos visually self-identifying: show an active badge, hide overlays that cover the road network, and make vehicles obvious with beacons/markers. During local demo work,tools/dev-server.jsshould make barehttp://localhost:3000/and no-queryhttp://localhost:3000/tiny-world-builderredirect to the small seed so the user can simply open the port or remembered app URL and watch it. Use the large URL explicitly for scale/perf checks.
When changing command shape, update the app bridge and these examples together.
Version History
- 2ffaf91 Current 2026-07-06 00:20


