@inneranimalmedia/agentsam-sdk 2.6.7 → 2.6.9
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/README.md +37 -0
- package/apps/README.md +25 -0
- package/apps/cad-creator/shared/cad/package.json +1 -1
- package/apps/client-cms-editor/backend/package.json +4 -6
- package/apps/client-cms-editor/frontend/package.json +6 -8
- package/apps/client-cms-editor/frontend/tsconfig.json +27 -8
- package/apps/client-cms-editor/package.json +1 -1
- package/apps/client-cms-editor/shared/cms/package.json +4 -6
- package/docs/LOCAL_ENGINE_COMPUTE_AUDIT_PLAN.md +299 -0
- package/docs/REPO_FILE_MAP.txt +132 -0
- package/migrations/d1/0018_goap_control_plane_remaster.sql +40 -0
- package/migrations/d1/0019_agentsam_agent_run_plan_todo.sql +14 -0
- package/migrations/d1/README_PORTABLE_CONTROL_PLANE.md +20 -9
- package/migrations/runtime/0002_goap_blackboard_v1.sql +15 -0
- package/migrations/runtime/0004_agent_run_plan_linkage.sql +8 -0
- package/native/agentsam-machine/cli/Cargo.toml +8 -1
- package/native/agentsam-machine/cli/README.md +21 -0
- package/native/agentsam-machine/core/Cargo.toml +7 -0
- package/native/agentsam-machine/core/README.md +14 -0
- package/native/agentsam-machine/core/build.rs +33 -0
- package/native/agentsam-machine/core/contracts/source-types.v1.json +353 -0
- package/native/agentsam-machine/core/src/lib.rs +6 -0
- package/native/agentsam-machine/core/src/source_types.rs +2 -2
- package/package.json +31 -10
- package/packages/agentsam-brand/package.json +1 -1
- package/packages/agentsam-contracts/package.json +71 -20
- package/packages/agentsam-contracts/scripts/build.mjs +18 -0
- package/packages/agentsam-contracts/test/contracts.test.mjs +4 -1
- package/packages/agentsam-contracts/test/repository.test.mjs +4 -1
- package/packages/agentsam-contracts/tsconfig.build.json +10 -0
- package/packages/agentsam-errors/package.json +3 -2
- package/packages/agentsam-errors/src/index.js +1 -0
- package/packages/agentsam-errors/src/tool.js +56 -0
- package/packages/agentsam-errors/test/tool.test.mjs +58 -0
- package/packages/agentsam-goap/README.md +52 -0
- package/packages/agentsam-goap/package.json +37 -0
- package/packages/agentsam-goap/src/adapters/d1-sqlite.js +570 -0
- package/packages/agentsam-goap/src/adapters/memory.js +199 -0
- package/packages/agentsam-goap/src/contracts.js +226 -0
- package/packages/agentsam-goap/src/control-plane.js +112 -0
- package/packages/agentsam-goap/src/errors.js +157 -0
- package/packages/agentsam-goap/src/index.js +5 -0
- package/packages/agentsam-goap/src/world.js +202 -0
- package/packages/agentsam-goap/test/goap-control-plane.test.mjs +245 -0
- package/packages/agentsam-goap/test/world.test.mjs +79 -0
- package/packages/agentsam-knowledge/package.json +3 -2
- package/packages/agentsam-nav/package.json +1 -1
- package/packages/agentsam-queue-control/package.json +1 -1
- package/packages/agentsam-rapid-rust/src/main.rs +1 -0
- package/packages/agentsam-repository/package.json +3 -2
- package/packages/agentsam-repository/src/goap.js +13 -14
- package/packages/agentsam-repository/src/tickets.js +15 -22
- package/packages/agentsam-repository/test/goap-tickets-workspace-state.test.mjs +19 -8
- package/packages/agentsam-vault/package.json +8 -5
- package/packages/agentsam-workbench/package.json +44 -17
- package/packages/connectors/cloudflare/package.json +4 -2
- package/packages/heuristic-theme/package.json +1 -1
- package/packages/identity/package.json +4 -3
- package/packages/identity/tests/lane1-packed-consumer.test.mjs +10 -2
- package/packages/providers/completeful/package.json +1 -1
- package/packages/theme-church-site/README.md +2 -2
- package/packages/theme-church-site/package.json +3 -4
- package/packages/theme-church-site/src/index.js +1 -1
- package/packages/theme-church-site/test/theme.test.mjs +1 -1
- package/packages/theme-companions-site/README.md +2 -2
- package/packages/theme-companions-site/package.json +3 -4
- package/packages/theme-companions-site/src/index.js +1 -1
- package/packages/theme-companions-site/test/theme.test.mjs +1 -1
- package/packages/theme-floors-site/README.md +2 -2
- package/packages/theme-floors-site/package.json +3 -4
- package/packages/theme-floors-site/src/index.js +1 -1
- package/packages/theme-floors-site/test/theme.test.mjs +1 -1
- package/packages/theme-fuelnfree-site/README.md +2 -2
- package/packages/theme-fuelnfree-site/package.json +3 -4
- package/packages/theme-fuelnfree-site/src/index.js +1 -1
- package/packages/theme-fuelnfree-site/test/theme.test.mjs +1 -1
- package/packages/theme-handyman-site/README.md +2 -2
- package/packages/theme-handyman-site/package.json +3 -4
- package/packages/theme-handyman-site/src/index.js +1 -1
- package/packages/theme-handyman-site/test/theme.test.mjs +1 -1
- package/packages/theme-insurance-site/README.md +2 -2
- package/packages/theme-insurance-site/package.json +3 -4
- package/packages/theme-insurance-site/src/index.js +1 -1
- package/packages/theme-insurance-site/test/theme.test.mjs +1 -1
- package/packages/theme-scenes/package.json +1 -1
- package/packages/theme-shinshu-site/README.md +2 -2
- package/packages/theme-shinshu-site/package.json +3 -4
- package/packages/theme-shinshu-site/src/index.js +1 -1
- package/packages/theme-shinshu-site/test/theme.test.mjs +1 -1
- package/packages/work-graph/package.json +3 -2
- package/protocol/local-engine/benchmark.v1.schema.json +14 -0
- package/protocol/local-engine/engine.v1.schema.json +17 -0
- package/python/agentsam_sdk/repository/intelligence/discovery.py +27 -2
- package/python/agentsam_sdk/repository/intelligence/git.py +4 -0
- package/python/tests/test_repository_intelligence.py +30 -0
- package/src/cli/command-catalog.js +3 -1
- package/src/cli.js +20 -1
- package/src/commands/app.js +10 -0
- package/src/commands/engine.js +10 -0
- package/src/commands/goap-world.js +134 -0
- package/src/commands/goap.js +11 -4
- package/src/commands/machine-binary.js +33 -27
- package/src/commands/machine-runtime.js +266 -0
- package/src/commands/machine.js +155 -51
- package/src/commands/package.js +537 -0
- package/src/commands/plan-ledger.js +370 -0
- package/src/commands/plan.js +183 -0
- package/src/commands/runtime.js +25 -10
- package/src/commands/setup.js +30 -6
- package/src/commands/skill.js +78 -17
- package/src/commands/terminal.js +20 -8
- package/src/errors/contract.js +4 -4
- package/src/errors/diagnostic.js +1 -1
- package/src/errors/index.js +1 -1
- package/src/index.js +20 -0
- package/src/indexing/ingest/materials.js +237 -30
- package/src/knowledge/source.js +1 -1
- package/src/lib/setup/runtime.js +4 -2
- package/src/lib/slash-commands.js +1 -1
- package/src/local/runtime-store.js +24 -2
- package/src/local/sqlite.js +17 -0
- package/src/local-engine/assets.js +37 -0
- package/src/local-engine/audit.js +133 -0
- package/src/local-engine/benchmark.js +26 -0
- package/src/local-engine/contracts.js +101 -0
- package/src/local-engine/engines.js +142 -0
- package/src/local-engine/index.js +18 -0
- package/src/local-engine/runtime.js +39 -0
- package/src/sam/operations/package-audit.js +41 -0
- package/src/sam/seed.js +2 -0
- package/skills/grok-auth/SKILL.md +0 -110
- package/skills/grok-auth/agentsam.skill.json +0 -25
- package/skills/grok-auth/references/grok-identity.md +0 -64
- package/skills/grok-auth/references/per-user-data.md +0 -49
- package/skills/grok-auth/references/prewired-and-env.md +0 -54
- package/skills/grok-auth/references/session-ui.md +0 -133
- package/skills/grok-auth/references/sign-in-methods.md +0 -55
- package/skills/grok-auth/references/wiring.md +0 -71
- package/skills/grok-building-games/SKILL.md +0 -213
- package/skills/grok-building-games/agentsam.skill.json +0 -25
- package/skills/grok-building-games/references/3d-libs.md +0 -64
- package/skills/grok-building-games/references/ai-pathfinding.md +0 -122
- package/skills/grok-building-games/references/audio.md +0 -129
- package/skills/grok-building-games/references/babylon.md +0 -202
- package/skills/grok-building-games/references/collision-physics.md +0 -122
- package/skills/grok-building-games/references/ecs-architecture.md +0 -111
- package/skills/grok-building-games/references/game-feel-juice.md +0 -110
- package/skills/grok-building-games/references/genres/board-card-chess.md +0 -89
- package/skills/grok-building-games/references/genres/endless-runner.md +0 -83
- package/skills/grok-building-games/references/genres/fps.md +0 -105
- package/skills/grok-building-games/references/genres/platformer-2d.md +0 -97
- package/skills/grok-building-games/references/genres/puzzle-match3-tetris.md +0 -131
- package/skills/grok-building-games/references/genres/racing-kart.md +0 -106
- package/skills/grok-building-games/references/genres/topdown-twin-stick.md +0 -97
- package/skills/grok-building-games/references/genres/tower-defense.md +0 -84
- package/skills/grok-building-games/references/genres/voxel-minecraft.md +0 -89
- package/skills/grok-building-games/references/input.md +0 -139
- package/skills/grok-building-games/references/phaser.md +0 -277
- package/skills/grok-building-games/references/procedural-generation.md +0 -141
- package/skills/grok-building-games/references/save-persistence.md +0 -108
- package/skills/grok-building-games/references/threejs-foundational.md +0 -148
- package/skills/grok-controls/SKILL.md +0 -274
- package/skills/grok-controls/agentsam.skill.json +0 -25
- package/skills/grok-design-ui/SKILL.md +0 -122
- package/skills/grok-design-ui/agentsam.skill.json +0 -25
- package/skills/grok-design-ui/references/animations.md +0 -379
- package/skills/grok-design-ui/references/performance.md +0 -88
- package/skills/grok-design-ui/references/refined-ui.md +0 -384
- package/skills/grok-design-ui/references/surfaces.md +0 -247
- package/skills/grok-design-ui/references/typography.md +0 -123
- package/skills/grok-game-animation-frames/SKILL.md +0 -84
- package/skills/grok-game-animation-frames/agentsam.skill.json +0 -25
- package/skills/grok-game-asset-core/SKILL.md +0 -69
- package/skills/grok-game-asset-core/agentsam.skill.json +0 -25
- package/skills/grok-game-character-consistency/SKILL.md +0 -69
- package/skills/grok-game-character-consistency/agentsam.skill.json +0 -25
- package/skills/grok-game-tilesets/SKILL.md +0 -65
- package/skills/grok-game-tilesets/agentsam.skill.json +0 -25
- package/skills/grok-game-ui-icons/SKILL.md +0 -56
- package/skills/grok-game-ui-icons/agentsam.skill.json +0 -25
- package/skills/grok-generate2dmap/LICENSE +0 -21
- package/skills/grok-generate2dmap/SKILL.md +0 -120
- package/skills/grok-generate2dmap/SOURCE.md +0 -6
- package/skills/grok-generate2dmap/agentsam.skill.json +0 -25
- package/skills/grok-generate2dmap/references/deliverables.md +0 -101
- package/skills/grok-generate2dmap/references/layered-map-contract.md +0 -209
- package/skills/grok-generate2dmap/references/map-strategies.md +0 -341
- package/skills/grok-generate2dmap/references/object-production-gate.md +0 -24
- package/skills/grok-generate2dmap/references/pipeline.md +0 -149
- package/skills/grok-generate2dmap/references/prop-pack-contract.md +0 -174
- package/skills/grok-generate2dmap/references/side-scroll-stages.md +0 -29
- package/skills/grok-generate2dmap/scripts/compose_layered_preview.py +0 -157
- package/skills/grok-generate2dmap/scripts/extract_prop_pack.py +0 -369
- package/skills/grok-generate2dsprite/LICENSE +0 -21
- package/skills/grok-generate2dsprite/SKILL.md +0 -313
- package/skills/grok-generate2dsprite/SOURCE.md +0 -6
- package/skills/grok-generate2dsprite/agentsam.skill.json +0 -25
- package/skills/grok-generate2dsprite/references/modes.md +0 -124
- package/skills/grok-generate2dsprite/references/prompt-rules.md +0 -315
- package/skills/grok-generate2dsprite/scripts/generate2dsprite.py +0 -1031
- package/skills/grok-generate2dsprite/scripts/make_layout_guide.py +0 -109
- package/skills/grok-imagine/SKILL.md +0 -201
- package/skills/grok-imagine/agentsam.skill.json +0 -25
- package/skills/grok-multiplayer-p2p/SKILL.md +0 -111
- package/skills/grok-multiplayer-p2p/agentsam.skill.json +0 -25
- package/skills/grok-multiplayer-p2p/references/react-binding.md +0 -144
- package/skills/grok-multiplayer-p2p/references/signaling-relay.md +0 -269
- package/skills/grok-neon/SKILL.md +0 -157
- package/skills/grok-neon/agentsam.skill.json +0 -25
- package/skills/grok-og/SKILL.md +0 -102
- package/skills/grok-og/agentsam.skill.json +0 -25
- package/skills/grok-og/references/brand-pass.md +0 -53
- package/skills/grok-og/references/custom-card.md +0 -107
- package/skills/grok-og/references/favicon-and-icons.md +0 -48
- package/skills/grok-og/references/og-type-contract.md +0 -18
- package/skills/grok-og/references/placeholder-card.md +0 -21
- package/skills/grok-og/references/x-banner.md +0 -41
- package/skills/grok-threejs/SKILL.md +0 -67
- package/skills/grok-threejs/agentsam.skill.json +0 -25
- package/skills/grok-threejs/references/llms-full.txt +0 -2780
- package/skills/grok-video2dsprite/LICENSE +0 -21
- package/skills/grok-video2dsprite/SKILL.md +0 -236
- package/skills/grok-video2dsprite/SOURCE.md +0 -6
- package/skills/grok-video2dsprite/agentsam.skill.json +0 -25
- package/skills/grok-video2dsprite/references/pipeline.md +0 -90
- package/skills/grok-video2dsprite/references/prompt-rules.md +0 -63
- package/skills/grok-video2dsprite/scripts/video2dsprite.py +0 -490
- package/skills/grok-xai-api/SKILL.md +0 -152
- package/skills/grok-xai-api/agentsam.skill.json +0 -25
- package/test/account-session.test.mjs +0 -101
- package/test/agent-capabilities.test.mjs +0 -67
- package/test/app-authority-context.test.mjs +0 -32
- package/test/app-building-skills.test.mjs +0 -61
- package/test/apps-scaffold-contract.test.mjs +0 -115
- package/test/blender-cad.test.mjs +0 -146
- package/test/cad-docker.test.mjs +0 -96
- package/test/capabilities.test.mjs +0 -133
- package/test/cli/api-key-env-whoami.test.mjs +0 -129
- package/test/cli/catalog-authority.test.mjs +0 -64
- package/test/cli/codebaseindex-plan-ux.test.mjs +0 -37
- package/test/cli/eval-live.test.mjs +0 -219
- package/test/cli/go.test.mjs +0 -128
- package/test/cli/local-vault-and-provider-setup.test.mjs +0 -268
- package/test/cli/machine-command.test.mjs +0 -34
- package/test/cli/mcp-client.test.mjs +0 -256
- package/test/cli/preferences-runtime.test.mjs +0 -11
- package/test/cli/runtime-persist.test.mjs +0 -56
- package/test/cli/runtime-ui.test.mjs +0 -141
- package/test/cli/rust-command.test.mjs +0 -21
- package/test/cli/skill-npm-and-env.test.mjs +0 -52
- package/test/cli/status-actions.test.mjs +0 -88
- package/test/cli/wireframes-go-registry.test.mjs +0 -139
- package/test/cli-preferences.test.mjs +0 -46
- package/test/cloudflare-connector.test.mjs +0 -93
- package/test/cloudflare-runtime.test.mjs +0 -82
- package/test/context.test.mjs +0 -159
- package/test/credentials-audit.test.mjs +0 -113
- package/test/deploy-health-scan.test.mjs +0 -95
- package/test/deploy-receipt.test.mjs +0 -91
- package/test/dockerize.test.mjs +0 -93
- package/test/error-diagnostics.test.mjs +0 -124
- package/test/eval-context.test.mjs +0 -37
- package/test/execution-approvals.test.mjs +0 -27
- package/test/fixtures/cad/courtyard-house.json +0 -208
- package/test/fixtures/knowledge-rpc-worker.mjs +0 -16
- package/test/fixtures/project-authority/customer-app/.agentsam/app.json +0 -5
- package/test/fixtures/project-authority/customer-app/apps/my-product/index.ts +0 -1
- package/test/fixtures/project-authority/customer-app/wrangler.toml +0 -17
- package/test/gcloud-auth.test.mjs +0 -111
- package/test/go/build-source-identity.test.mjs +0 -31
- package/test/go/cloudflare-probe.test.mjs +0 -317
- package/test/go/distribution.test.mjs +0 -28
- package/test/google-cloud-connection.test.mjs +0 -83
- package/test/google-cloud-inventory.test.mjs +0 -64
- package/test/indexing-provider.test.mjs +0 -29
- package/test/init-options.test.mjs +0 -35
- package/test/integration/ai-access-onboarding.test.mjs +0 -47
- package/test/integration/app-command.test.mjs +0 -85
- package/test/integration/autorag-project-authority.integration.test.mjs +0 -51
- package/test/integration/autorag-runtime-discovery.test.mjs +0 -107
- package/test/integration/cad-discovery.test.mjs +0 -158
- package/test/integration/cad-docker-executor.test.mjs +0 -249
- package/test/integration/cad-execution.test.mjs +0 -184
- package/test/integration/cad-project-app.test.mjs +0 -31
- package/test/integration/cad-project-d1.test.mjs +0 -25
- package/test/integration/cad-project-native.test.mjs +0 -24
- package/test/integration/cad-project-workflow.test.mjs +0 -9
- package/test/integration/cad-project.test.mjs +0 -85
- package/test/integration/cli-help.test.mjs +0 -41
- package/test/integration/cms-app-scaffold.test.mjs +0 -77
- package/test/integration/cms-site-tenancy-contract.test.mjs +0 -268
- package/test/integration/codebaseindex-ingest.test.mjs +0 -255
- package/test/integration/core-client.integration.test.mjs +0 -55
- package/test/integration/database-hosted-honesty.test.mjs +0 -49
- package/test/integration/desktop-keychain-boundary.test.mjs +0 -45
- package/test/integration/error-runtime-rpc.test.mjs +0 -71
- package/test/integration/features-resolve.test.mjs +0 -147
- package/test/integration/hosted-work-host-authority.test.mjs +0 -47
- package/test/integration/icon-registry.test.mjs +0 -67
- package/test/integration/ingest-discover-models.test.mjs +0 -30
- package/test/integration/inspect-path-cli.test.mjs +0 -46
- package/test/integration/install-script.test.mjs +0 -110
- package/test/integration/inventory-core.test.mjs +0 -53
- package/test/integration/inventory-eligibility.test.mjs +0 -59
- package/test/integration/inventory-evidence.test.mjs +0 -125
- package/test/integration/knowledge-rpc.test.mjs +0 -112
- package/test/integration/local-fs.test.mjs +0 -113
- package/test/integration/local-provider-bridge.test.mjs +0 -65
- package/test/integration/local-studio-settings-production.test.mjs +0 -40
- package/test/integration/lockfile-sync.test.mjs +0 -26
- package/test/integration/merkle-cli.test.mjs +0 -61
- package/test/integration/merkle-persistence-identity.test.mjs +0 -48
- package/test/integration/plugins-cloudflare.integration.test.mjs +0 -28
- package/test/integration/provider-env-cli.test.mjs +0 -60
- package/test/integration/provider-factory.test.mjs +0 -197
- package/test/integration/repository-company-graph.test.mjs +0 -90
- package/test/integration/runtime-migrations.test.mjs +0 -87
- package/test/integration/runtime-status.integration.test.mjs +0 -227
- package/test/integration/sam-activity-recovery.test.mjs +0 -147
- package/test/integration/sam-decision.test.mjs +0 -584
- package/test/integration/sam-kernel.test.mjs +0 -99
- package/test/integration/sam-planning-astar.test.mjs +0 -279
- package/test/integration/scaffold-cloudflare.test.mjs +0 -46
- package/test/integration/skill-runtime.test.mjs +0 -240
- package/test/integration/storage-architecture.test.mjs +0 -154
- package/test/integration/store-discovery.test.mjs +0 -44
- package/test/integration/studio-fs-pty-e2e.test.mjs +0 -294
- package/test/integration/terminal-exec.integration.test.mjs +0 -86
- package/test/integration/theme-package-registry.test.mjs +0 -31
- package/test/integration/tool-runtime.test.mjs +0 -124
- package/test/integration/tool-schema.test.mjs +0 -59
- package/test/jr-dev-skill.test.mjs +0 -26
- package/test/knowledge-context-pack.test.mjs +0 -19
- package/test/knowledge-postgres.test.mjs +0 -40
- package/test/knowledge-service.test.mjs +0 -110
- package/test/knowledge.test.mjs +0 -206
- package/test/live/terminal-transport.live.test.mjs +0 -24
- package/test/local-sessions.test.mjs +0 -57
- package/test/local-studio-deploy.test.mjs +0 -83
- package/test/loopback-oauth.test.mjs +0 -53
- package/test/merkle-render.test.mjs +0 -31
- package/test/mini.test.mjs +0 -140
- package/test/model-catalog.test.mjs +0 -70
- package/test/models.test.mjs +0 -309
- package/test/npm10-lock.test.mjs +0 -29
- package/test/ollama.test.mjs +0 -137
- package/test/open-url.test.mjs +0 -64
- package/test/openai-responses.test.mjs +0 -155
- package/test/portable-context.test.mjs +0 -94
- package/test/project-config.test.mjs +0 -81
- package/test/project-rules.test.mjs +0 -44
- package/test/provider-credentials.test.mjs +0 -106
- package/test/rehydrate.test.mjs +0 -25
- package/test/release-hygiene.test.mjs +0 -42
- package/test/repository-snapshot-view.test.mjs +0 -112
- package/test/responses-runner.test.mjs +0 -465
- package/test/runtime-activity.test.mjs +0 -98
- package/test/sdk-worker-contract.test.mjs +0 -72
- package/test/security.test.mjs +0 -266
- package/test/setup-planner.test.mjs +0 -74
- package/test/setup-runtime.test.mjs +0 -51
- package/test/shell.test.mjs +0 -281
- package/test/skills.test.mjs +0 -22
- package/test/smoke.mjs +0 -122
- package/test/telemetry.test.mjs +0 -79
- package/test/terminal/local-pty.mock.test.mjs +0 -153
- package/test/terminal-machine-identity.test.mjs +0 -88
- package/test/theme-portability.test.mjs +0 -14
- package/test/tools-search.test.mjs +0 -73
- package/test/unit/website-assets-bindings.test.mjs +0 -60
- package/test/whoami-resume.test.mjs +0 -157
|
@@ -1,269 +0,0 @@
|
|
|
1
|
-
# Signaling relay and API route (create once)
|
|
2
|
-
|
|
3
|
-
The relay is **yours**, not part of the kit: copy it as-is or serve the same
|
|
4
|
-
`RtcPollResponse` shape from any store. Only rendezvous traffic (roster +
|
|
5
|
-
SDP/ICE) passes through it; game data flows peer-to-peer.
|
|
6
|
-
|
|
7
|
-
## Schema — nothing to do by default
|
|
8
|
-
|
|
9
|
-
The relay below creates its two tables on first use
|
|
10
|
-
(`CREATE TABLE IF NOT EXISTS`, once per process) — nothing ships in
|
|
11
|
-
`migrations/` and the template itself never touches your database. If you'd
|
|
12
|
-
rather own or extend the schema (extra columns, your own migration ordering),
|
|
13
|
-
copy this into one of your app migrations; `IF NOT EXISTS` makes the runtime
|
|
14
|
-
ensure and your migration coexist safely:
|
|
15
|
-
|
|
16
|
-
```sql
|
|
17
|
-
-- CREATE TABLE IF NOT EXISTS webrtc_peers (
|
|
18
|
-
-- room TEXT NOT NULL,
|
|
19
|
-
-- peer_id TEXT NOT NULL,
|
|
20
|
-
-- name TEXT NOT NULL DEFAULT '',
|
|
21
|
-
-- last_seen TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
22
|
-
-- PRIMARY KEY (room, peer_id)
|
|
23
|
-
-- );
|
|
24
|
-
-- CREATE TABLE IF NOT EXISTS webrtc_signals (
|
|
25
|
-
-- id BIGSERIAL PRIMARY KEY,
|
|
26
|
-
-- room TEXT NOT NULL,
|
|
27
|
-
-- to_peer TEXT NOT NULL,
|
|
28
|
-
-- from_peer TEXT NOT NULL,
|
|
29
|
-
-- kind TEXT NOT NULL,
|
|
30
|
-
-- payload JSONB NOT NULL,
|
|
31
|
-
-- created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
32
|
-
-- );
|
|
33
|
-
-- CREATE INDEX IF NOT EXISTS webrtc_signals_inbox
|
|
34
|
-
-- ON webrtc_signals (room, to_peer, id);
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
## `src/lib/multiplayer/signaling.server.ts`
|
|
38
|
-
|
|
39
|
-
Copy this file as-is (or adapt it — it is yours, not part of the kit):
|
|
40
|
-
|
|
41
|
-
```ts
|
|
42
|
-
// src/lib/multiplayer/signaling.server.ts
|
|
43
|
-
/**
|
|
44
|
-
* WebRTC signaling over the app database (Neon deployed, PGLite in preview).
|
|
45
|
-
* Only rendezvous traffic passes through here — roster + SDP/ICE relay while a
|
|
46
|
-
* mesh forms; game data then flows peer-to-peer. DB-backed so any serverless
|
|
47
|
-
* instance can serve any poll. Mount at /api/rtc (see the multiplayer-p2p
|
|
48
|
-
* skill); the client side lives in `@/lib/multiplayer`.
|
|
49
|
-
*
|
|
50
|
-
* The GET poll is the whole peer lifecycle: the first poll (since=0) IS the
|
|
51
|
-
* join — it registers the peer, returns the roster, and prunes stale rows.
|
|
52
|
-
* Peer ids are random per mount, so a fresh inbox never has old signals to
|
|
53
|
-
* skip and no join/cursor handshake is needed.
|
|
54
|
-
*/
|
|
55
|
-
import { z } from "zod";
|
|
56
|
-
import { getSql, type Sql } from "@/lib/db";
|
|
57
|
-
import type { PeerRow, RtcPollResponse, SignalRow } from "./p2p";
|
|
58
|
-
|
|
59
|
-
const ID = z.string().regex(/^[a-zA-Z0-9_-]{1,64}$/);
|
|
60
|
-
const signalSchema = z.object({
|
|
61
|
-
op: z.literal("signal"),
|
|
62
|
-
room: ID,
|
|
63
|
-
from: ID,
|
|
64
|
-
to: ID,
|
|
65
|
-
kind: z.enum(["offer", "answer", "ice"]),
|
|
66
|
-
// SDP offers are typically 3–10KB; the cap only blocks abuse (payload is
|
|
67
|
-
// re-serialized at insert — cheap at this size). An absent
|
|
68
|
-
// payload is rejected here (JSON.stringify(undefined) has no .length).
|
|
69
|
-
payload: z.unknown().refine((v) => v !== undefined && JSON.stringify(v).length <= 32_768, {
|
|
70
|
-
message: "payload too large",
|
|
71
|
-
}),
|
|
72
|
-
});
|
|
73
|
-
const leaveSchema = z.object({ op: z.literal("leave"), room: ID, peer: ID });
|
|
74
|
-
const postSchema = z.discriminatedUnion("op", [signalSchema, leaveSchema]);
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const PEER_TTL_SECONDS = 30;
|
|
78
|
-
const SIGNAL_TTL_SECONDS = 60;
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* The kit ships no migration: tables are created on first use (IF NOT EXISTS)
|
|
82
|
-
* so the app's migrations/ namespace stays fully in the agent's hands. Agents
|
|
83
|
-
* who want to own/extend the schema can copy the DDL from the multiplayer-p2p
|
|
84
|
-
* skill into their own migration — both coexist safely. Memoized on globalThis
|
|
85
|
-
* (the db.ts pattern) so dev HMR never runs two ensures concurrently; a failed
|
|
86
|
-
* ensure clears the slot so the next request retries.
|
|
87
|
-
*/
|
|
88
|
-
const globalRef = globalThis as typeof globalThis & {
|
|
89
|
-
__rtcSchemaPromise__?: Promise<void>;
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
function ensureSchema(sql: Sql): Promise<void> {
|
|
93
|
-
globalRef.__rtcSchemaPromise__ ??= (async () => {
|
|
94
|
-
await sql.query(
|
|
95
|
-
`CREATE TABLE IF NOT EXISTS webrtc_peers (
|
|
96
|
-
room TEXT NOT NULL,
|
|
97
|
-
peer_id TEXT NOT NULL,
|
|
98
|
-
name TEXT NOT NULL DEFAULT '',
|
|
99
|
-
last_seen TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
100
|
-
PRIMARY KEY (room, peer_id)
|
|
101
|
-
)`,
|
|
102
|
-
);
|
|
103
|
-
await sql.query(
|
|
104
|
-
`CREATE TABLE IF NOT EXISTS webrtc_signals (
|
|
105
|
-
id BIGSERIAL PRIMARY KEY,
|
|
106
|
-
room TEXT NOT NULL,
|
|
107
|
-
to_peer TEXT NOT NULL,
|
|
108
|
-
from_peer TEXT NOT NULL,
|
|
109
|
-
kind TEXT NOT NULL,
|
|
110
|
-
payload JSONB NOT NULL,
|
|
111
|
-
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
112
|
-
)`,
|
|
113
|
-
);
|
|
114
|
-
await sql.query(
|
|
115
|
-
`CREATE INDEX IF NOT EXISTS webrtc_signals_inbox
|
|
116
|
-
ON webrtc_signals (room, to_peer, id)`,
|
|
117
|
-
);
|
|
118
|
-
})().catch((err) => {
|
|
119
|
-
globalRef.__rtcSchemaPromise__ = undefined;
|
|
120
|
-
throw err;
|
|
121
|
-
});
|
|
122
|
-
return globalRef.__rtcSchemaPromise__;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
async function roster(sql: Sql, room: string): Promise<PeerRow[]> {
|
|
126
|
-
// LIMIT bounds the blast radius of room-stuffing; the mesh caps out ~8.
|
|
127
|
-
const rows = await sql.query<{ peer_id: string; name: string }>(
|
|
128
|
-
`SELECT peer_id, name FROM webrtc_peers
|
|
129
|
-
WHERE room = $1 AND last_seen > now() - make_interval(secs => $2)
|
|
130
|
-
ORDER BY peer_id LIMIT 32`,
|
|
131
|
-
[room, PEER_TTL_SECONDS],
|
|
132
|
-
);
|
|
133
|
-
return rows.map((r) => ({ id: r.peer_id, name: r.name }));
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
async function touchPeer(sql: Sql, room: string, peer: string, name: string) {
|
|
137
|
-
await sql.query(
|
|
138
|
-
`INSERT INTO webrtc_peers (room, peer_id, name, last_seen)
|
|
139
|
-
VALUES ($1, $2, $3, now())
|
|
140
|
-
ON CONFLICT (room, peer_id)
|
|
141
|
-
DO UPDATE SET last_seen = now(), name = EXCLUDED.name`,
|
|
142
|
-
[room, peer, name],
|
|
143
|
-
);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* Rows are ephemeral; GC rides the polls instead of a cron: joins (since=0)
|
|
148
|
-
* always prune, and ~2% of all other polls do too — so a busy room whose
|
|
149
|
-
* cursors always advance still gets swept, without every heartbeat paying
|
|
150
|
-
* the two DELETEs.
|
|
151
|
-
*/
|
|
152
|
-
async function prune(sql: Sql) {
|
|
153
|
-
await Promise.all([
|
|
154
|
-
sql.query(`DELETE FROM webrtc_signals WHERE created_at < now() - make_interval(secs => $1)`, [
|
|
155
|
-
SIGNAL_TTL_SECONDS,
|
|
156
|
-
]),
|
|
157
|
-
sql.query(`DELETE FROM webrtc_peers WHERE last_seen < now() - make_interval(secs => $1)`, [
|
|
158
|
-
PEER_TTL_SECONDS,
|
|
159
|
-
]),
|
|
160
|
-
]);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
function json(body: unknown, status = 200): Response {
|
|
164
|
-
return new Response(JSON.stringify(body), {
|
|
165
|
-
status,
|
|
166
|
-
headers: { "content-type": "application/json", "cache-control": "no-store" },
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
/** GET /api/rtc?room&peer&name&since — join (since=0), heartbeat, and inbox. */
|
|
171
|
-
async function handleGet(url: URL): Promise<Response> {
|
|
172
|
-
const parsed = z
|
|
173
|
-
.object({
|
|
174
|
-
room: ID,
|
|
175
|
-
peer: ID,
|
|
176
|
-
name: z.string().max(64).default(""),
|
|
177
|
-
since: z.coerce.number().int().min(0).default(0),
|
|
178
|
-
})
|
|
179
|
-
.safeParse({
|
|
180
|
-
room: url.searchParams.get("room"),
|
|
181
|
-
peer: url.searchParams.get("peer"),
|
|
182
|
-
name: url.searchParams.get("name") ?? "",
|
|
183
|
-
since: url.searchParams.get("since") ?? 0,
|
|
184
|
-
});
|
|
185
|
-
if (!parsed.success) return json({ error: "invalid query" }, 400);
|
|
186
|
-
const { room, peer, name, since } = parsed.data;
|
|
187
|
-
|
|
188
|
-
const sql = await getSql();
|
|
189
|
-
await ensureSchema(sql);
|
|
190
|
-
if (since === 0 || Math.random() < 0.02) await prune(sql);
|
|
191
|
-
await touchPeer(sql, room, peer, name);
|
|
192
|
-
const rows = await sql.query<{
|
|
193
|
-
id: number;
|
|
194
|
-
from_peer: string;
|
|
195
|
-
kind: SignalRow["kind"];
|
|
196
|
-
payload: unknown;
|
|
197
|
-
}>(
|
|
198
|
-
`SELECT id, from_peer, kind, payload FROM webrtc_signals
|
|
199
|
-
WHERE room = $1 AND to_peer = $2 AND id > $3
|
|
200
|
-
ORDER BY id LIMIT 200`,
|
|
201
|
-
[room, peer, since],
|
|
202
|
-
);
|
|
203
|
-
const body: RtcPollResponse = {
|
|
204
|
-
peers: await roster(sql, room),
|
|
205
|
-
signals: rows.map((r) => ({
|
|
206
|
-
id: r.id,
|
|
207
|
-
from: r.from_peer,
|
|
208
|
-
kind: r.kind,
|
|
209
|
-
payload: r.payload,
|
|
210
|
-
})),
|
|
211
|
-
};
|
|
212
|
-
return json(body);
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
async function handlePost(request: Request): Promise<Response> {
|
|
216
|
-
let body: unknown;
|
|
217
|
-
try {
|
|
218
|
-
body = await request.json();
|
|
219
|
-
} catch {
|
|
220
|
-
return json({ error: "invalid JSON" }, 400);
|
|
221
|
-
}
|
|
222
|
-
const parsed = postSchema.safeParse(body);
|
|
223
|
-
if (!parsed.success) return json({ error: "invalid request" }, 400);
|
|
224
|
-
const msg = parsed.data;
|
|
225
|
-
const sql = await getSql();
|
|
226
|
-
await ensureSchema(sql);
|
|
227
|
-
|
|
228
|
-
if (msg.op === "signal") {
|
|
229
|
-
await sql.query(
|
|
230
|
-
`INSERT INTO webrtc_signals (room, to_peer, from_peer, kind, payload)
|
|
231
|
-
VALUES ($1, $2, $3, $4, $5)`,
|
|
232
|
-
[msg.room, msg.to, msg.from, msg.kind, JSON.stringify(msg.payload)],
|
|
233
|
-
);
|
|
234
|
-
} else {
|
|
235
|
-
await sql.query(`DELETE FROM webrtc_peers WHERE room = $1 AND peer_id = $2`, [
|
|
236
|
-
msg.room,
|
|
237
|
-
msg.peer,
|
|
238
|
-
]);
|
|
239
|
-
}
|
|
240
|
-
return json({ ok: true });
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
/** Request entrypoint for the /api/rtc route (GET poll, POST signal/leave). */
|
|
244
|
-
export async function handleSignaling(request: Request): Promise<Response> {
|
|
245
|
-
try {
|
|
246
|
-
if (request.method === "GET") return await handleGet(new URL(request.url));
|
|
247
|
-
if (request.method === "POST") return await handlePost(request);
|
|
248
|
-
return json({ error: "method not allowed" }, 405);
|
|
249
|
-
} catch (error) {
|
|
250
|
-
console.error("[rtc] signaling error:", error);
|
|
251
|
-
return json({ error: "signaling failed" }, 500);
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
```
|
|
255
|
-
|
|
256
|
-
## Mount the API route
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
```ts
|
|
260
|
-
// src/routes/api/rtc.ts
|
|
261
|
-
import { createFileRoute } from "@tanstack/react-router";
|
|
262
|
-
import { handleSignaling } from "@/lib/multiplayer/signaling.server";
|
|
263
|
-
|
|
264
|
-
const handle = ({ request }: { request: Request }) => handleSignaling(request);
|
|
265
|
-
|
|
266
|
-
export const Route = createFileRoute("/api/rtc")({
|
|
267
|
-
server: { handlers: { GET: handle, POST: handle } },
|
|
268
|
-
});
|
|
269
|
-
```
|
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: neon
|
|
3
|
-
description: >
|
|
4
|
-
Use Neon Postgres (the database) in this TanStack Start app. Use when the app
|
|
5
|
-
needs to store or query data, persist state, or keep per-user data. Triggers on
|
|
6
|
-
"database", "Postgres", "Neon", "save data", "store data", "persist", "tables",
|
|
7
|
-
"SQL", "query", "migrations".
|
|
8
|
-
metadata:
|
|
9
|
-
short-description: "Neon Postgres (with a local PGLite fallback) for this template"
|
|
10
|
-
user-invocable: false
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
# Neon Postgres
|
|
14
|
-
|
|
15
|
-
**The database is opt-in** (AGENTS.md §0.5): use it only when the app needs data
|
|
16
|
-
that outlives a browser session or is shared across devices. Otherwise ship no
|
|
17
|
-
migrations, don't import `@/lib/db`, and keep state in `localStorage` / zustand.
|
|
18
|
-
|
|
19
|
-
This template ships a ready-made, **dual-mode** database integration:
|
|
20
|
-
|
|
21
|
-
- **Configured** (env var set, e.g. deployed): real **Neon Postgres**.
|
|
22
|
-
- **Not configured** (sandbox live preview): the DB falls back to a local
|
|
23
|
-
**PGLite** (embedded WASM Postgres), so the preview always renders. Build
|
|
24
|
-
against the `@/lib/db` helper; both modes work with the same API.
|
|
25
|
-
|
|
26
|
-
Packages are **preinstalled** — do not `npm install` them: `pg` (node-postgres,
|
|
27
|
-
the regular Postgres driver) and `@electric-sql/pglite` (local DB fallback).
|
|
28
|
-
|
|
29
|
-
For **user accounts, sign-in, and reading the current user**, see the separate
|
|
30
|
-
**`auth` skill** — this skill is just the database.
|
|
31
|
-
|
|
32
|
-
## Turning the database on
|
|
33
|
-
|
|
34
|
-
Set `deploy.database` to `true` in `.grok/app-env.json`:
|
|
35
|
-
|
|
36
|
-
```json
|
|
37
|
-
{ "VITE_AUTH_ENABLED": "false", "deploy": { "database": true } }
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
That is what tells the platform to provision Neon for the deployed app; leave it
|
|
41
|
-
`false` and the deploy gets no `DATABASE_URL`, so the app silently runs on a
|
|
42
|
-
throwaway PGLite that loses its rows. Shipping `migrations/*.sql`, or sign-in,
|
|
43
|
-
provisions one regardless — this flag is for an app that queries `@/lib/db`
|
|
44
|
-
without either. It is not a `VITE_` key and never reaches the browser.
|
|
45
|
-
|
|
46
|
-
## Env vars — do **not** create a `.env` file
|
|
47
|
-
|
|
48
|
-
**Never write a `.env` / `.env.local` / `.env.example` for the database.** In
|
|
49
|
-
the sandbox live preview, leave `DATABASE_URL` unset — `@/lib/db` automatically
|
|
50
|
-
uses embedded PGLite. When the app is deployed, the platform injects
|
|
51
|
-
`DATABASE_URL` (Neon); you do not provision or write it yourself.
|
|
52
|
-
|
|
53
|
-
| Var | Where | Purpose |
|
|
54
|
-
|---|---|---|
|
|
55
|
-
| `DATABASE_URL` | server | Neon connection string when deployed (optional — PGLite fallback if unset) |
|
|
56
|
-
|
|
57
|
-
Never hardcode it; never expose non-`VITE_` vars to the client.
|
|
58
|
-
|
|
59
|
-
## Database (server-only)
|
|
60
|
-
|
|
61
|
-
`@/lib/db` exports `getSql()` and `dbSource`: a **regular Postgres driver**
|
|
62
|
-
(node-postgres, `pg`) against `DATABASE_URL`, or a local **PGLite** fallback when
|
|
63
|
-
unset. Same API either way — a tagged template (and `.query()`) resolving to
|
|
64
|
-
`rows[]`. Call ONLY from a `createServerFn` handler / server loader, never a
|
|
65
|
-
client component. Define schema in `migrations/`, not inline.
|
|
66
|
-
|
|
67
|
-
```ts
|
|
68
|
-
import { createServerFn } from "@tanstack/react-start";
|
|
69
|
-
import { getSql } from "@/lib/db";
|
|
70
|
-
|
|
71
|
-
export const listPosts = createServerFn({ method: "GET" }).handler(async () => {
|
|
72
|
-
const sql = await getSql();
|
|
73
|
-
// Type the row shape — a server fn's return must be provably serializable.
|
|
74
|
-
return sql<{ id: number; title: string }>`select id, title from posts order by id desc`;
|
|
75
|
-
// or: return sql.query<{ id: number; title: string }>("select id, title from posts where id = $1", [id]);
|
|
76
|
-
});
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
**Per-user data (only once the app has sign-in).** A regular driver has full DB
|
|
80
|
-
access, so scope **every** query to the authenticated user server-side — never
|
|
81
|
-
trust a client-sent id. Use the prewired **`authMiddleware`** to get a verified
|
|
82
|
-
`context.userId`, then filter by it. Full pattern (middleware, calling from
|
|
83
|
-
client code, fail-closed semantics) is in the **`auth` skill**:
|
|
84
|
-
|
|
85
|
-
```ts
|
|
86
|
-
import { authMiddleware } from "@/lib/auth/middleware";
|
|
87
|
-
|
|
88
|
-
export const listTodos = createServerFn({ method: "GET" })
|
|
89
|
-
.middleware([authMiddleware])
|
|
90
|
-
.handler(async ({ context }) => {
|
|
91
|
-
const sql = await getSql();
|
|
92
|
-
return sql<{ id: number; title: string }>`select id, title from todos where user_id = ${context.userId} order by id desc`;
|
|
93
|
-
});
|
|
94
|
-
// mutations must scope writes too: `... where id = ${id} and user_id = ${context.userId}`
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
**Without sign-in (the default), do NOT use `authMiddleware` / `requireUserId`.**
|
|
98
|
-
The dev user they fall back to is a preview-only convenience. A deployed app's
|
|
99
|
-
`VITE_AUTH_ENABLED` is set by the platform, not by this workspace (today the
|
|
100
|
-
deployer always sets it to `"true"`), so deployed, both reject every visitor —
|
|
101
|
-
and an auth-off app ships no sign-in route for them to recover with. A
|
|
102
|
-
database-only app keeps its rows unowned: no `user_id` column, or one literal
|
|
103
|
-
constant. Add the middleware as part of turning sign-in on (the `auth` skill's
|
|
104
|
-
upgrade steps), and re-scope or drop those rows then.
|
|
105
|
-
|
|
106
|
-
Unowned rows are world-readable and world-writable through your public server
|
|
107
|
-
functions: never persist personal or sensitive data (names, emails, free text
|
|
108
|
-
about a person) in this mode, and leave out destructive bulk mutations
|
|
109
|
-
(delete-all, overwrite-all) — if the app needs them, propose sign-in instead.
|
|
110
|
-
|
|
111
|
-
## Migrations
|
|
112
|
-
|
|
113
|
-
`migrations/*.sql` are the single schema source. They apply to **Neon on deploy**
|
|
114
|
-
(`npm run build` runs `db:migrate` against `DATABASE_URL`, so Vercel ships with
|
|
115
|
-
the schema ready) and to the **PGLite** preview **automatically on startup**, so
|
|
116
|
-
dev matches prod.
|
|
117
|
-
|
|
118
|
-
Neither applier descends into subdirectories, so the Better Auth schema at
|
|
119
|
-
`migrations/auth/0001_auth.sql` is **not** applied unless the app turns sign-in
|
|
120
|
-
on and copies it up (**do not edit** it — see the `auth` skill). Put your app's
|
|
121
|
-
schema in NEW ordered files starting at `0002`:
|
|
122
|
-
|
|
123
|
-
```sql
|
|
124
|
-
-- migrations/0002_schema.sql — example for a todos app; use YOUR app's tables
|
|
125
|
-
create table if not exists todos (
|
|
126
|
-
id serial primary key,
|
|
127
|
-
user_id text not null,
|
|
128
|
-
title text not null,
|
|
129
|
-
done boolean not null default false,
|
|
130
|
-
created_at timestamptz not null default now()
|
|
131
|
-
);
|
|
132
|
-
create index if not exists todos_user_id_idx on todos (user_id);
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
Never edit an applied file — it is tracked by name in `_migrations` and will not
|
|
136
|
-
re-run (add a new file instead; new files apply to the running preview on the
|
|
137
|
-
next request). Prefer idempotent statements (`… if not exists`). Tables with
|
|
138
|
-
per-user data should carry a `user_id text not null` column (TEXT, not UUID — the
|
|
139
|
-
preview dev user id is the string `'dev-user'`).
|
|
140
|
-
|
|
141
|
-
## Preview ↔ production parity
|
|
142
|
-
|
|
143
|
-
`getSql()` normalizes result types so both backends return identical, JSON-safe
|
|
144
|
-
shapes: `bigint`/`count(*)` → `number`, `date` → `'YYYY-MM-DD'` string,
|
|
145
|
-
`interval` → text, `numeric` → string. Remaining differences to respect:
|
|
146
|
-
|
|
147
|
-
- **`bigint` past 2^53 loses precision** as a number — cast `::text` if you
|
|
148
|
-
ever need huge integers (row counts are fine).
|
|
149
|
-
- **Preview DB is in-memory**: wiped on dev-server restart, single-connection
|
|
150
|
-
(no lock contention or concurrent-write conflicts), and loads **no
|
|
151
|
-
extensions** — do not `create extension`; stick to core Postgres.
|
|
152
|
-
- **Neon's pooled endpoint keeps no session state** — don't rely on `SET`,
|
|
153
|
-
`LISTEN/NOTIFY`, or session advisory locks.
|
|
154
|
-
- **Keep `user_id` columns `text`** — preview uses `'dev-user'`, production uses
|
|
155
|
-
Better Auth's text ids; a `uuid` column breaks preview inserts.
|
|
156
|
-
- Deployed Neon queries traverse the network (and may cold-resume) — avoid
|
|
157
|
-
N+1 query patterns that feel free against the in-process preview DB.
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"schema": "agentsam.skill.v1",
|
|
3
|
-
"id": "grok-neon",
|
|
4
|
-
"name": "Grok \u00b7 neon",
|
|
5
|
-
"description": "Neon Postgres (with a local PGLite fallback) for this template",
|
|
6
|
-
"icon": "skill",
|
|
7
|
-
"slash": {
|
|
8
|
-
"suggested": "/grok-neon"
|
|
9
|
-
},
|
|
10
|
-
"instructions": {
|
|
11
|
-
"source": "local_file",
|
|
12
|
-
"ref": "SKILL.md"
|
|
13
|
-
},
|
|
14
|
-
"execution": {
|
|
15
|
-
"mode": "turn",
|
|
16
|
-
"tools": [],
|
|
17
|
-
"operations": [],
|
|
18
|
-
"capabilities": []
|
|
19
|
-
},
|
|
20
|
-
"provenance": {
|
|
21
|
-
"source": "apps/local-studio/.grok/skills",
|
|
22
|
-
"vendor": "grok",
|
|
23
|
-
"status": "imported"
|
|
24
|
-
}
|
|
25
|
-
}
|
package/skills/grok-og/SKILL.md
DELETED
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: og
|
|
3
|
-
description: >
|
|
4
|
-
Share-link previews and app identity for apps on *.grok.me: the injector-owned
|
|
5
|
-
og:image card, the SVG favicon, and PWA icons for installable apps.
|
|
6
|
-
Use when scaffolding, renaming, or restyling the app — and for share /
|
|
7
|
-
unfurl / OG / Twitter card questions. A custom 1200×630 card from the app's
|
|
8
|
-
own art is the default — games of every kind (DOM board/word games
|
|
9
|
-
included), whimsical apps, creative tools, and brand-forward pages; only
|
|
10
|
-
plain utilities keep the placeholder. Always run the brand-asset pass as a
|
|
11
|
-
`task` subagent and never wait for it.
|
|
12
|
-
Triggers on "share", "rename", "app name", "OG", "Open Graph",
|
|
13
|
-
"twitter card", "unfurl", "og:image", "og:type", "x:game:image",
|
|
14
|
-
"x-banner", "link preview", "social card", "thumbnail", "preview image",
|
|
15
|
-
"favicon", "app icon", "PWA", "manifest", "installable", "home screen",
|
|
16
|
-
"SEO", "meta description".
|
|
17
|
-
metadata:
|
|
18
|
-
short-description: "Brand assets: og.jpg card, X feed banner, SVG favicon, PWA icons — always a non-blocking `task` subagent"
|
|
19
|
-
user-invocable: false
|
|
20
|
-
---
|
|
21
|
-
|
|
22
|
-
# Share cards, favicon, and app icons
|
|
23
|
-
|
|
24
|
-
A deployed app (`https://{name}.grok.me`) unfurls with a 1200×630 card; every app (preview included) shows a
|
|
25
|
-
favicon in the tab. **Share-card `<meta>` tags are not authored in `__root.tsx`** — the injector
|
|
26
|
-
(`scripts/grok-pwa-shared.mjs`) overwrites `og:*` and `twitter:card` on every HTML response. Identity data is
|
|
27
|
-
the only thing anyone writes, and the pass writes all of it — dispatching, you seed none of it:
|
|
28
|
-
|
|
29
|
-
- `src/lib/og/site.json` — not pre-seeded, created only if needed: `{ "title", "type"?: "x:game", "card"?: "custom", "color"?: "RRGGBB" }`; title defaults to the host slug.
|
|
30
|
-
- `public/og.jpg` — custom 1200×630 card (optional; placeholder otherwise)
|
|
31
|
-
- `public/x-banner.jpg` — games only: 50:11 (1200×264) X feed card
|
|
32
|
-
- `public/favicon.svg` — linked from root `head()`; until the pass lands the tab just shows the browser default, which fails nothing
|
|
33
|
-
|
|
34
|
-
**Extend `__root.tsx`; never replace it wholesale** (auth SSR, redesigns, skill excerpts): dropping the
|
|
35
|
-
favicon link ships a blank tab icon no local check catches.
|
|
36
|
-
|
|
37
|
-
## Decide: which card this app gets
|
|
38
|
-
|
|
39
|
-
**Default: a custom card** from the app's own art — games of every kind and rendering tech (Canvas/WebGL *and*
|
|
40
|
-
DOM board, card, word, puzzle, quiz: a tic-tac-toe grid of divs is still a game), whimsical apps, creative
|
|
41
|
-
tools, content- and brand-forward pages. **When in doubt, make the custom card.**
|
|
42
|
-
|
|
43
|
-
**Plain utility apps only** (converters, CRUD trackers, dashboards, notes/admin — apps whose face is the data)
|
|
44
|
-
keep the default `og.grok.me` placeholder: no `public/og.jpg`. Its URL, the `"color"` knob and the rename
|
|
45
|
-
rule: `references/placeholder-card.md`.
|
|
46
|
-
|
|
47
|
-
## `og:type` for games
|
|
48
|
-
|
|
49
|
-
**A game of any kind** carries `"type": "x:game"` in `src/lib/og/site.json` — the pass writes it, owning that
|
|
50
|
-
file. No hostname, never gated on a custom card, never "corrected" to `website`, bare `game`, `twitter:card`
|
|
51
|
-
or an invented `x:type`: X's pipeline keys off that exact value. Non-games omit it. Your check, not your
|
|
52
|
-
edit — missing it, or missing `public/x-banner.jpg` once a custom card exists, is a **BRAND WARNING**.
|
|
53
|
-
|
|
54
|
-
## Brand-asset pass: always a subagent, never waited for
|
|
55
|
-
|
|
56
|
-
**Have the `task` tool? Dispatch this pass as a subagent and never generate card art yourself** — inline it
|
|
57
|
-
puts minutes of generation latency in front of the user. As soon as name and palette settle (AGENTS.md §
|
|
58
|
-
"Parallel work"), dispatch this prompt verbatim. It is complete — do not open the references below to
|
|
59
|
-
enrich it; the pass reads them itself:
|
|
60
|
-
|
|
61
|
-
> You are the brand-asset pass. Follow the `og` skill, which tells you where to start. App `<NAME>`,
|
|
62
|
-
> `og:type` `<TYPE>`, palette `<PALETTE>`. You solely own `public/` brand assets and `src/lib/og/site.json`.
|
|
63
|
-
|
|
64
|
-
Keep building. Stay sequential only if the user is art-directing or the art to reuse doesn't exist yet.
|
|
65
|
-
|
|
66
|
-
**Never wait for it.** No `wait_tasks`, and **never `get_task_output` on the brand task**: reading a task's
|
|
67
|
-
output consumes it, and a consumed task sends no completion notification, so the card's result — a failure
|
|
68
|
-
included — would reach nobody. Answer as soon as the app renders; the pass wakes you later, and that turn is
|
|
69
|
-
**one short sentence at most** — one that asks for a republish, since `public/og.jpg` ships in the build
|
|
70
|
-
and a card that lands after a publish never reaches the live app on its own:
|
|
71
|
-
"Added the share card — publish again if you already did." / "The card failed; the default one stands."
|
|
72
|
-
|
|
73
|
-
While the pass keeps `/workspace/.grok/og-pending` fresh, brand checks say nothing about the card: in flight
|
|
74
|
-
is not a finding. The marker goes stale after 10 minutes, so a very long pass lets the warning through — but
|
|
75
|
-
a brand warning while it runs is never a cue to redo its work.
|
|
76
|
-
|
|
77
|
-
**No `task` tool? Then you are the pass** — build the assets now; nothing else will. Whoever runs it claims
|
|
78
|
-
that marker, stages files under `/workspace/.grok/` — never inside `public/`, which `vite build` copies
|
|
79
|
-
verbatim into the deployed app — hands it over with `scripts/write-atomic.mjs` so no build reads half a JPEG,
|
|
80
|
-
and self-checks with `node scripts/brand-check.mjs --game`.
|
|
81
|
-
|
|
82
|
-
## Build the assets — the pass reads these, the parent does not
|
|
83
|
-
|
|
84
|
-
**Dispatching? Do not open them** — the prompt above is complete. One read carries this procedure in your
|
|
85
|
-
context every later turn while the subagent does the work anyway.
|
|
86
|
-
|
|
87
|
-
**You are the pass?** Start at `references/brand-pass.md`, then read
|
|
88
|
-
per asset you owe: `references/custom-card.md` for `public/og.jpg`, `references/x-banner.md` for the
|
|
89
|
-
games-only `public/x-banner.jpg`, `references/favicon-and-icons.md` for `public/favicon.svg` plus the PWA
|
|
90
|
-
raster icons — those only when the user asked for installable/PWA, never invent a manifest. **Hand-author
|
|
91
|
-
that SVG, never `imagine_text_to_image`**: it must stay crisp at 16px. Writing `site.json` for a game?
|
|
92
|
-
`references/og-type-contract.md` argues the spellings X rejects.
|
|
93
|
-
|
|
94
|
-
Regenerate on rename or a material identity change — `APP_NAME`, the `site.json` `title` and the baked-in card
|
|
95
|
-
title move together. Without `imagine_text_to_image` or the xAI Images API, keep the `og.grok.me` card; never
|
|
96
|
-
ship a broken `og:image` URL.
|
|
97
|
-
|
|
98
|
-
## Not supported
|
|
99
|
-
|
|
100
|
-
No `/api/og` route, no runtime image renderer, no per-route cards — the card is one static site-wide image
|
|
101
|
-
(`public/og.jpg` or the placeholder service). If you add `robots.txt`, never blanket `Disallow: /`: crawlers
|
|
102
|
-
must fetch `/` to read the tags.
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"schema": "agentsam.skill.v1",
|
|
3
|
-
"id": "grok-og",
|
|
4
|
-
"name": "Grok \u00b7 og",
|
|
5
|
-
"description": "Brand assets: og.jpg card, X feed banner, SVG favicon, PWA icons \u2014 always a non-blocking `task` subagent",
|
|
6
|
-
"icon": "skill",
|
|
7
|
-
"slash": {
|
|
8
|
-
"suggested": "/grok-og"
|
|
9
|
-
},
|
|
10
|
-
"instructions": {
|
|
11
|
-
"source": "local_file",
|
|
12
|
-
"ref": "SKILL.md"
|
|
13
|
-
},
|
|
14
|
-
"execution": {
|
|
15
|
-
"mode": "turn",
|
|
16
|
-
"tools": [],
|
|
17
|
-
"operations": [],
|
|
18
|
-
"capabilities": []
|
|
19
|
-
},
|
|
20
|
-
"provenance": {
|
|
21
|
-
"source": "apps/local-studio/.grok/skills",
|
|
22
|
-
"vendor": "grok",
|
|
23
|
-
"status": "imported"
|
|
24
|
-
}
|
|
25
|
-
}
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
# Running the brand-asset pass (the pass's own contract)
|
|
2
|
-
|
|
3
|
-
Read this when you *are* the pass — dispatched as the brand subagent, or
|
|
4
|
-
building the assets inline because no `task` tool exists. The parent keeps
|
|
5
|
-
building and never waits, so everything below is yours to get right unobserved.
|
|
6
|
-
|
|
7
|
-
## 1. Claim the marker, keep it fresh, always release it
|
|
8
|
-
|
|
9
|
-
`touch /workspace/.grok/og-pending` before generating and again before each
|
|
10
|
-
`imagine_*` call — while that marker is fresh `brand-check.mjs` suppresses the
|
|
11
|
-
missing-card warnings the parent would otherwise act on, and it goes stale after
|
|
12
|
-
10 minutes so a killed pass cannot silence the check forever.
|
|
13
|
-
`rm -f /workspace/.grok/og-pending` on every exit path, success or not.
|
|
14
|
-
|
|
15
|
-
## 2. Hand every file over atomically
|
|
16
|
-
|
|
17
|
-
`public/og.jpg`, `public/x-banner.jpg` and `src/lib/og/site.json` alike. The
|
|
18
|
-
parent may be mid-`npm run build` and would then read a half-written JPEG. Write
|
|
19
|
-
to a staged path under `/workspace/.grok/` — never inside `public/`, which
|
|
20
|
-
`vite build` copies verbatim into the deployed app, and never on another
|
|
21
|
-
filesystem such as `/tmp`, where the hand-over cannot be a rename — then:
|
|
22
|
-
|
|
23
|
-
```sh
|
|
24
|
-
node scripts/write-atomic.mjs /workspace/.grok/og.jpg.tmp public/og.jpg
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
`src/lib/og/site.json` is the only file this pass writes under `src/`. Hand it
|
|
28
|
-
over **once**, with the finished card — never a field at a time: its
|
|
29
|
-
`"card": "custom"` flag has to land *with* the card and not before, because the
|
|
30
|
-
bake trusts the flag on its own and would emit an `og:image` URL for a file that
|
|
31
|
-
does not exist.
|
|
32
|
-
|
|
33
|
-
## 3. Verify your own work, because nobody waits for it
|
|
34
|
-
|
|
35
|
-
Run the card checks in `custom-card.md`, then `node scripts/brand-check.mjs --game`
|
|
36
|
-
(drop `--game` for non-games), which prints a JSON verdict and exits non-zero
|
|
37
|
-
on any `BRAND WARNING`. That run judges the files on disk and reports
|
|
38
|
-
`"pending": true` — the marker only demotes the parent's gates — and it treats a
|
|
39
|
-
missing `public/og.jpg` as a failure whatever kind of app this is, because
|
|
40
|
-
producing one is what this pass is normally for.
|
|
41
|
-
|
|
42
|
-
**The one exception**: a pass launched for a plain utility that keeps the
|
|
43
|
-
`og.grok.me` card (favicon, PWA icons, and title only — SKILL.md § "Decide:
|
|
44
|
-
which card this app gets") is not there to produce a card, so it adds
|
|
45
|
-
`--placeholder-ok` and the missing card is then the expected verdict rather than
|
|
46
|
-
a failure:
|
|
47
|
-
|
|
48
|
-
```sh
|
|
49
|
-
node scripts/brand-check.mjs --placeholder-ok
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
Use it only for that launch — passing it on a custom-card app hides the one
|
|
53
|
-
thing that pass owes. Report pass or fail in your answer text.
|