@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,490 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""Postprocess Grok imagine_image_to_video clips into dense 2D sprites.
|
|
3
|
-
|
|
4
|
-
Pipeline steps (deterministic only — no creative generation):
|
|
5
|
-
extract → ffmpeg frames from mp4
|
|
6
|
-
clean → magenta flood-fill chroma + light despill
|
|
7
|
-
sample → even-index frame sets + feet/center normalize
|
|
8
|
-
process → extract + clean + sample in one shot
|
|
9
|
-
|
|
10
|
-
This skill is designed for Grok Build (imagine_text_to_image + imagine_image_to_video).
|
|
11
|
-
The script itself only needs ffmpeg, Pillow, and numpy.
|
|
12
|
-
"""
|
|
13
|
-
|
|
14
|
-
from __future__ import annotations
|
|
15
|
-
|
|
16
|
-
import argparse
|
|
17
|
-
import json
|
|
18
|
-
import math
|
|
19
|
-
import shutil
|
|
20
|
-
import subprocess
|
|
21
|
-
import sys
|
|
22
|
-
from collections import deque
|
|
23
|
-
from pathlib import Path
|
|
24
|
-
from typing import Sequence
|
|
25
|
-
|
|
26
|
-
import numpy as np
|
|
27
|
-
from PIL import Image
|
|
28
|
-
|
|
29
|
-
MAGENTA = np.array([255, 0, 255], dtype=np.float32)
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
def _ensure_dir(path: Path) -> Path:
|
|
33
|
-
path.mkdir(parents=True, exist_ok=True)
|
|
34
|
-
return path
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def _parse_counts(text: str) -> list[int]:
|
|
38
|
-
counts: list[int] = []
|
|
39
|
-
for part in text.split(","):
|
|
40
|
-
part = part.strip()
|
|
41
|
-
if not part:
|
|
42
|
-
continue
|
|
43
|
-
n = int(part)
|
|
44
|
-
if n < 1:
|
|
45
|
-
raise ValueError(f"frame count must be >= 1, got {n}")
|
|
46
|
-
counts.append(n)
|
|
47
|
-
if not counts:
|
|
48
|
-
raise ValueError("at least one frame count required")
|
|
49
|
-
return counts
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
def sample_indices(n_total: int, n_want: int) -> list[int]:
|
|
53
|
-
if n_total <= 0:
|
|
54
|
-
return []
|
|
55
|
-
if n_want >= n_total:
|
|
56
|
-
return list(range(n_total))
|
|
57
|
-
if n_want == 1:
|
|
58
|
-
return [0]
|
|
59
|
-
return [int(round(i * (n_total - 1) / (n_want - 1))) for i in range(n_want)]
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
def extract_frames(video: Path, out_dir: Path, fps: float = 0.0) -> list[Path]:
|
|
63
|
-
_ensure_dir(out_dir)
|
|
64
|
-
for old in out_dir.glob("frame_*.png"):
|
|
65
|
-
old.unlink()
|
|
66
|
-
ffmpeg = shutil.which("ffmpeg")
|
|
67
|
-
if not ffmpeg:
|
|
68
|
-
raise RuntimeError("ffmpeg not found on PATH. Install ffmpeg to extract video frames.")
|
|
69
|
-
pattern = str(out_dir / "frame_%04d.png")
|
|
70
|
-
cmd = [ffmpeg, "-y", "-i", str(video)]
|
|
71
|
-
if fps and fps > 0:
|
|
72
|
-
cmd += ["-vf", f"fps={fps}"]
|
|
73
|
-
else:
|
|
74
|
-
cmd += ["-vsync", "0"]
|
|
75
|
-
cmd.append(pattern)
|
|
76
|
-
proc = subprocess.run(cmd, capture_output=True, text=True)
|
|
77
|
-
if proc.returncode != 0:
|
|
78
|
-
raise RuntimeError("ffmpeg failed:\n" + (proc.stderr or proc.stdout or "unknown error"))
|
|
79
|
-
frames = sorted(out_dir.glob("frame_*.png"))
|
|
80
|
-
if not frames:
|
|
81
|
-
raise RuntimeError(f"no frames extracted into {out_dir}")
|
|
82
|
-
return frames
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
def _near_magenta_mask(rgb: np.ndarray, dist: float = 55.0) -> np.ndarray:
|
|
86
|
-
"""rgb: HxWx3 uint8 → bool mask of keyable magenta-ish pixels."""
|
|
87
|
-
f = rgb.astype(np.float32)
|
|
88
|
-
# Distance to pure magenta in RGB.
|
|
89
|
-
d = np.linalg.norm(f - MAGENTA, axis=2)
|
|
90
|
-
# Also catch bright pinks: high R+B, low G relative.
|
|
91
|
-
r, g, b = f[:, :, 0], f[:, :, 1], f[:, :, 2]
|
|
92
|
-
pinkish = (r > 160) & (b > 160) & (g < 140) & ((r + b) / 2 - g > 40)
|
|
93
|
-
return (d <= dist) | pinkish
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
def chroma_key_rgba(im: Image.Image, dist: float = 55.0) -> Image.Image:
|
|
97
|
-
"""Flood-fill magenta from corners, despill edges, return RGBA."""
|
|
98
|
-
rgba = im.convert("RGBA")
|
|
99
|
-
arr = np.array(rgba)
|
|
100
|
-
rgb = arr[:, :, :3]
|
|
101
|
-
h, w = rgb.shape[:2]
|
|
102
|
-
key = _near_magenta_mask(rgb, dist=dist)
|
|
103
|
-
|
|
104
|
-
visited = np.zeros((h, w), dtype=bool)
|
|
105
|
-
q: deque[tuple[int, int]] = deque()
|
|
106
|
-
for y, x in ((0, 0), (0, w - 1), (h - 1, 0), (h - 1, w - 1)):
|
|
107
|
-
if key[y, x]:
|
|
108
|
-
visited[y, x] = True
|
|
109
|
-
q.append((x, y))
|
|
110
|
-
# Also seed along edges where magenta is present.
|
|
111
|
-
for x in range(w):
|
|
112
|
-
for y in (0, h - 1):
|
|
113
|
-
if key[y, x] and not visited[y, x]:
|
|
114
|
-
visited[y, x] = True
|
|
115
|
-
q.append((x, y))
|
|
116
|
-
for y in range(h):
|
|
117
|
-
for x in (0, w - 1):
|
|
118
|
-
if key[y, x] and not visited[y, x]:
|
|
119
|
-
visited[y, x] = True
|
|
120
|
-
q.append((x, y))
|
|
121
|
-
|
|
122
|
-
while q:
|
|
123
|
-
x, y = q.popleft()
|
|
124
|
-
for nx, ny in ((x - 1, y), (x + 1, y), (x, y - 1), (x, y + 1)):
|
|
125
|
-
if 0 <= nx < w and 0 <= ny < h and not visited[ny, nx] and key[ny, nx]:
|
|
126
|
-
visited[ny, nx] = True
|
|
127
|
-
q.append((nx, ny))
|
|
128
|
-
|
|
129
|
-
out = arr.copy()
|
|
130
|
-
out[visited, 3] = 0
|
|
131
|
-
|
|
132
|
-
# Light despill on remaining near-magenta fringe (keep RGB, reduce alpha).
|
|
133
|
-
fringe = key & ~visited
|
|
134
|
-
if fringe.any():
|
|
135
|
-
# Pull toward less magenta and soften alpha.
|
|
136
|
-
fr = out[fringe].astype(np.float32)
|
|
137
|
-
r, g, b, a = fr[:, 0], fr[:, 1], fr[:, 2], fr[:, 3]
|
|
138
|
-
spill = np.maximum(0.0, (r + b) / 2.0 - g)
|
|
139
|
-
factor = np.clip(1.0 - spill / 180.0, 0.15, 1.0)
|
|
140
|
-
fr[:, 0] = np.clip(r - spill * 0.35, 0, 255)
|
|
141
|
-
fr[:, 2] = np.clip(b - spill * 0.35, 0, 255)
|
|
142
|
-
fr[:, 1] = np.clip(g + spill * 0.15, 0, 255)
|
|
143
|
-
fr[:, 3] = np.clip(a * factor, 0, 255)
|
|
144
|
-
out[fringe] = fr.astype(np.uint8)
|
|
145
|
-
|
|
146
|
-
# Fully transparent where alpha is 0.
|
|
147
|
-
out[out[:, :, 3] == 0, :3] = 0
|
|
148
|
-
return Image.fromarray(out, "RGBA")
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
def content_bbox(im: Image.Image, alpha_min: int = 32) -> tuple[int, int, int, int] | None:
|
|
152
|
-
arr = np.array(im.convert("RGBA"))
|
|
153
|
-
mask = arr[:, :, 3] > alpha_min
|
|
154
|
-
if not mask.any():
|
|
155
|
-
return None
|
|
156
|
-
ys, xs = np.where(mask)
|
|
157
|
-
return int(xs.min()), int(ys.min()), int(xs.max()) + 1, int(ys.max()) + 1
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
def normalize_sprite(
|
|
161
|
-
im: Image.Image,
|
|
162
|
-
cell: int = 128,
|
|
163
|
-
body_height: int = 100,
|
|
164
|
-
foot_y: int = 118,
|
|
165
|
-
anchor: str = "feet",
|
|
166
|
-
) -> Image.Image:
|
|
167
|
-
bb = content_bbox(im)
|
|
168
|
-
canvas = Image.new("RGBA", (cell, cell), (0, 0, 0, 0))
|
|
169
|
-
if not bb:
|
|
170
|
-
return canvas
|
|
171
|
-
crop = im.crop(bb)
|
|
172
|
-
cw, ch = crop.size
|
|
173
|
-
if ch <= 0 or cw <= 0:
|
|
174
|
-
return canvas
|
|
175
|
-
scale = body_height / float(ch)
|
|
176
|
-
nw = max(1, int(round(cw * scale)))
|
|
177
|
-
nh = max(1, int(round(ch * scale)))
|
|
178
|
-
if nw > cell - 4:
|
|
179
|
-
scale = (cell - 4) / float(cw)
|
|
180
|
-
nw = max(1, int(round(cw * scale)))
|
|
181
|
-
nh = max(1, int(round(ch * scale)))
|
|
182
|
-
if nh > cell - 4:
|
|
183
|
-
scale = (cell - 4) / float(ch)
|
|
184
|
-
nw = max(1, int(round(cw * scale)))
|
|
185
|
-
nh = max(1, int(round(ch * scale)))
|
|
186
|
-
resized = crop.resize((nw, nh), Image.Resampling.LANCZOS)
|
|
187
|
-
if anchor == "center":
|
|
188
|
-
x = (cell - nw) // 2
|
|
189
|
-
y = (cell - nh) // 2
|
|
190
|
-
else:
|
|
191
|
-
x = (cell - nw) // 2
|
|
192
|
-
y = foot_y - nh
|
|
193
|
-
if y < 0:
|
|
194
|
-
y = 0
|
|
195
|
-
if y + nh > cell:
|
|
196
|
-
y = max(0, cell - nh)
|
|
197
|
-
canvas.paste(resized, (x, y), resized)
|
|
198
|
-
return canvas
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
def clean_frames(
|
|
202
|
-
raw_dir: Path,
|
|
203
|
-
clean_dir: Path,
|
|
204
|
-
dist: float = 55.0,
|
|
205
|
-
) -> list[Path]:
|
|
206
|
-
_ensure_dir(clean_dir)
|
|
207
|
-
raws = sorted(raw_dir.glob("frame_*.png"))
|
|
208
|
-
if not raws:
|
|
209
|
-
raise RuntimeError(f"no raw frames in {raw_dir}")
|
|
210
|
-
outs: list[Path] = []
|
|
211
|
-
for i, path in enumerate(raws):
|
|
212
|
-
im = Image.open(path)
|
|
213
|
-
cleaned = chroma_key_rgba(im, dist=dist)
|
|
214
|
-
out = clean_dir / f"clean_{i:04d}.png"
|
|
215
|
-
cleaned.save(out)
|
|
216
|
-
outs.append(out)
|
|
217
|
-
if (i + 1) % 25 == 0 or i + 1 == len(raws):
|
|
218
|
-
print(f" cleaned {i + 1}/{len(raws)}")
|
|
219
|
-
return outs
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
def build_exports(
|
|
223
|
-
sprites: Sequence[Image.Image],
|
|
224
|
-
out_sprite_dir: Path,
|
|
225
|
-
tag: str,
|
|
226
|
-
n_frames: int,
|
|
227
|
-
gif_ms: int | None = None,
|
|
228
|
-
) -> dict:
|
|
229
|
-
_ensure_dir(out_sprite_dir)
|
|
230
|
-
sub = _ensure_dir(out_sprite_dir / tag) if tag else out_sprite_dir
|
|
231
|
-
paths = []
|
|
232
|
-
for i, sp in enumerate(sprites):
|
|
233
|
-
p = sub / f"sprite_{i + 1:02d}.png"
|
|
234
|
-
sp.save(p)
|
|
235
|
-
paths.append(str(p))
|
|
236
|
-
|
|
237
|
-
size = sprites[0].size[0]
|
|
238
|
-
strip = Image.new("RGBA", (size * len(sprites), size), (0, 0, 0, 0))
|
|
239
|
-
for i, sp in enumerate(sprites):
|
|
240
|
-
strip.paste(sp, (i * size, 0), sp)
|
|
241
|
-
strip_path = out_sprite_dir / f"run-strip-{n_frames}.png"
|
|
242
|
-
strip.save(strip_path)
|
|
243
|
-
|
|
244
|
-
cols = 8 if n_frames >= 16 else 4
|
|
245
|
-
rows = int(math.ceil(len(sprites) / cols))
|
|
246
|
-
grid = Image.new("RGBA", (size * cols, size * rows), (0, 0, 0, 0))
|
|
247
|
-
for i, sp in enumerate(sprites):
|
|
248
|
-
r, c = divmod(i, cols)
|
|
249
|
-
grid.paste(sp, (c * size, r * size), sp)
|
|
250
|
-
grid_path = out_sprite_dir / f"run-grid-{n_frames}.png"
|
|
251
|
-
grid.save(grid_path)
|
|
252
|
-
|
|
253
|
-
if gif_ms is None:
|
|
254
|
-
if n_frames >= 40:
|
|
255
|
-
gif_ms = 25
|
|
256
|
-
elif n_frames >= 20:
|
|
257
|
-
gif_ms = 40
|
|
258
|
-
elif n_frames >= 12:
|
|
259
|
-
gif_ms = 60
|
|
260
|
-
else:
|
|
261
|
-
gif_ms = 80
|
|
262
|
-
|
|
263
|
-
frames_gif = []
|
|
264
|
-
for sp in sprites:
|
|
265
|
-
bg = Image.new("RGBA", sp.size, (30, 30, 40, 255))
|
|
266
|
-
bg.paste(sp, (0, 0), sp)
|
|
267
|
-
frames_gif.append(bg.convert("P", palette=Image.ADAPTIVE, colors=255))
|
|
268
|
-
gif_path = out_sprite_dir / f"run-preview-{n_frames}.gif"
|
|
269
|
-
frames_gif[0].save(
|
|
270
|
-
gif_path,
|
|
271
|
-
save_all=True,
|
|
272
|
-
append_images=frames_gif[1:],
|
|
273
|
-
duration=gif_ms,
|
|
274
|
-
loop=0,
|
|
275
|
-
disposal=2,
|
|
276
|
-
)
|
|
277
|
-
|
|
278
|
-
# Legacy alias for 8-frame default
|
|
279
|
-
if n_frames == 8:
|
|
280
|
-
alias = out_sprite_dir / "run-preview.gif"
|
|
281
|
-
shutil.copy2(gif_path, alias)
|
|
282
|
-
|
|
283
|
-
return {
|
|
284
|
-
"count": n_frames,
|
|
285
|
-
"tag": tag,
|
|
286
|
-
"sprites": paths,
|
|
287
|
-
"strip": str(strip_path),
|
|
288
|
-
"grid": str(grid_path),
|
|
289
|
-
"gif": str(gif_path),
|
|
290
|
-
"gif_ms": gif_ms,
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
def sample_and_export(
|
|
295
|
-
clean_dir: Path,
|
|
296
|
-
out_dir: Path,
|
|
297
|
-
frame_counts: Sequence[int],
|
|
298
|
-
cell: int = 128,
|
|
299
|
-
body_height: int = 100,
|
|
300
|
-
foot_y: int = 118,
|
|
301
|
-
anchor: str = "feet",
|
|
302
|
-
) -> dict:
|
|
303
|
-
cleans = sorted(clean_dir.glob("clean_*.png"))
|
|
304
|
-
if not cleans:
|
|
305
|
-
raise RuntimeError(f"no cleaned frames in {clean_dir}")
|
|
306
|
-
sprite_dir = _ensure_dir(out_dir / "sprite")
|
|
307
|
-
n_total = len(cleans)
|
|
308
|
-
results = []
|
|
309
|
-
for n_want in frame_counts:
|
|
310
|
-
idxs = sample_indices(n_total, n_want)
|
|
311
|
-
sprites = []
|
|
312
|
-
for idx in idxs:
|
|
313
|
-
im = Image.open(cleans[idx]).convert("RGBA")
|
|
314
|
-
sprites.append(
|
|
315
|
-
normalize_sprite(
|
|
316
|
-
im,
|
|
317
|
-
cell=cell,
|
|
318
|
-
body_height=body_height,
|
|
319
|
-
foot_y=foot_y,
|
|
320
|
-
anchor=anchor,
|
|
321
|
-
)
|
|
322
|
-
)
|
|
323
|
-
tag = f"x{n_want}" if n_want != 8 else ""
|
|
324
|
-
# Always also write under xN for consistency when n!=8;
|
|
325
|
-
# for 8, write both root sprites and optional x8.
|
|
326
|
-
if n_want == 8:
|
|
327
|
-
# root-level sprite_01..08 for backwards compat
|
|
328
|
-
info = build_exports(sprites, sprite_dir, tag="", n_frames=n_want)
|
|
329
|
-
# also x8 folder
|
|
330
|
-
build_exports(sprites, sprite_dir, tag="x8", n_frames=n_want)
|
|
331
|
-
else:
|
|
332
|
-
info = build_exports(sprites, sprite_dir, tag=tag, n_frames=n_want)
|
|
333
|
-
info["indices"] = idxs
|
|
334
|
-
results.append(info)
|
|
335
|
-
print(f"exported {n_want} frames → {info['gif']}")
|
|
336
|
-
return {"total_clean": n_total, "sets": results}
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
def write_readme(out_dir: Path, meta: dict) -> None:
|
|
340
|
-
lines = [
|
|
341
|
-
"Video2dsprite output (Grok Build pipeline)",
|
|
342
|
-
"==========================================",
|
|
343
|
-
"base/ base still on #FF00FF",
|
|
344
|
-
"video/ imagine_image_to_video clip",
|
|
345
|
-
"frames-raw/ decoded frames",
|
|
346
|
-
"frames-clean/ chroma-keyed RGBA frames",
|
|
347
|
-
"sprite/ sampled normalized sprites + strips/grids/GIFs",
|
|
348
|
-
"pipeline-meta.json",
|
|
349
|
-
"",
|
|
350
|
-
"This folder was produced for Grok Build (imagine_text_to_image + imagine_image_to_video).",
|
|
351
|
-
"Codex/other agents cannot run the video step; they can still re-sample",
|
|
352
|
-
"existing frames with: python video2dsprite.py sample --clean-dir ...",
|
|
353
|
-
"",
|
|
354
|
-
json.dumps(meta, indent=2),
|
|
355
|
-
"",
|
|
356
|
-
]
|
|
357
|
-
(out_dir / "README.txt").write_text("\n".join(lines), encoding="utf-8")
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
def cmd_extract(args: argparse.Namespace) -> int:
|
|
361
|
-
frames = extract_frames(Path(args.video), Path(args.out_dir), fps=args.fps)
|
|
362
|
-
print(f"extracted {len(frames)} frames → {args.out_dir}")
|
|
363
|
-
return 0
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
def cmd_clean(args: argparse.Namespace) -> int:
|
|
367
|
-
outs = clean_frames(Path(args.raw_dir), Path(args.out_dir), dist=args.dist)
|
|
368
|
-
print(f"cleaned {len(outs)} frames → {args.out_dir}")
|
|
369
|
-
return 0
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
def cmd_sample(args: argparse.Namespace) -> int:
|
|
373
|
-
counts = _parse_counts(args.frame_counts)
|
|
374
|
-
meta = sample_and_export(
|
|
375
|
-
clean_dir=Path(args.clean_dir),
|
|
376
|
-
out_dir=Path(args.out_dir),
|
|
377
|
-
frame_counts=counts,
|
|
378
|
-
cell=args.cell_size,
|
|
379
|
-
body_height=args.body_height,
|
|
380
|
-
foot_y=args.foot_y,
|
|
381
|
-
anchor=args.anchor,
|
|
382
|
-
)
|
|
383
|
-
out = Path(args.out_dir)
|
|
384
|
-
full = {
|
|
385
|
-
"mode": "sample",
|
|
386
|
-
"clean_dir": str(Path(args.clean_dir).resolve()),
|
|
387
|
-
**meta,
|
|
388
|
-
}
|
|
389
|
-
(out / "pipeline-meta.json").write_text(json.dumps(full, indent=2), encoding="utf-8")
|
|
390
|
-
write_readme(out, full)
|
|
391
|
-
print("sample done")
|
|
392
|
-
return 0
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
def cmd_process(args: argparse.Namespace) -> int:
|
|
396
|
-
out = Path(args.out_dir)
|
|
397
|
-
raw_dir = out / "frames-raw"
|
|
398
|
-
clean_dir = out / "frames-clean"
|
|
399
|
-
video = Path(args.video)
|
|
400
|
-
if not video.is_file():
|
|
401
|
-
raise FileNotFoundError(video)
|
|
402
|
-
|
|
403
|
-
print(f"extract {video}")
|
|
404
|
-
frames = extract_frames(video, raw_dir, fps=args.fps)
|
|
405
|
-
print(f"clean {len(frames)} frames")
|
|
406
|
-
clean_frames(raw_dir, clean_dir, dist=args.dist)
|
|
407
|
-
counts = _parse_counts(args.frame_counts)
|
|
408
|
-
print(f"sample counts={counts}")
|
|
409
|
-
meta_sample = sample_and_export(
|
|
410
|
-
clean_dir=clean_dir,
|
|
411
|
-
out_dir=out,
|
|
412
|
-
frame_counts=counts,
|
|
413
|
-
cell=args.cell_size,
|
|
414
|
-
body_height=args.body_height,
|
|
415
|
-
foot_y=args.foot_y,
|
|
416
|
-
anchor=args.anchor,
|
|
417
|
-
)
|
|
418
|
-
meta = {
|
|
419
|
-
"skill": "video2dsprite",
|
|
420
|
-
"platform": "Grok Build (imagine_image_to_video required for generation step)",
|
|
421
|
-
"name": args.name,
|
|
422
|
-
"video": str(video.resolve()),
|
|
423
|
-
"out_dir": str(out.resolve()),
|
|
424
|
-
"raw_frames": len(frames),
|
|
425
|
-
"chroma_dist": args.dist,
|
|
426
|
-
"cell_size": args.cell_size,
|
|
427
|
-
"body_height": args.body_height,
|
|
428
|
-
"foot_y": args.foot_y,
|
|
429
|
-
"anchor": args.anchor,
|
|
430
|
-
**meta_sample,
|
|
431
|
-
}
|
|
432
|
-
(out / "pipeline-meta.json").write_text(json.dumps(meta, indent=2), encoding="utf-8")
|
|
433
|
-
write_readme(out, meta)
|
|
434
|
-
print("process done")
|
|
435
|
-
return 0
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
def build_parser() -> argparse.ArgumentParser:
|
|
439
|
-
p = argparse.ArgumentParser(description="Video → dense 2D sprite postprocessor")
|
|
440
|
-
sub = p.add_subparsers(dest="command", required=True)
|
|
441
|
-
|
|
442
|
-
def add_common_sample(sp: argparse.ArgumentParser) -> None:
|
|
443
|
-
sp.add_argument("--frame-counts", default="8,16,24,48")
|
|
444
|
-
sp.add_argument("--cell-size", type=int, default=128)
|
|
445
|
-
sp.add_argument("--body-height", type=int, default=100)
|
|
446
|
-
sp.add_argument("--foot-y", type=int, default=118)
|
|
447
|
-
sp.add_argument("--anchor", choices=("feet", "center"), default="feet")
|
|
448
|
-
|
|
449
|
-
pe = sub.add_parser("extract", help="ffmpeg extract frames")
|
|
450
|
-
pe.add_argument("--video", required=True)
|
|
451
|
-
pe.add_argument("--out-dir", required=True)
|
|
452
|
-
pe.add_argument("--fps", type=float, default=0.0, help="0 = all frames")
|
|
453
|
-
pe.set_defaults(func=cmd_extract)
|
|
454
|
-
|
|
455
|
-
pc = sub.add_parser("clean", help="chroma-key raw frames")
|
|
456
|
-
pc.add_argument("--raw-dir", required=True)
|
|
457
|
-
pc.add_argument("--out-dir", required=True)
|
|
458
|
-
pc.add_argument("--dist", type=float, default=55.0)
|
|
459
|
-
pc.set_defaults(func=cmd_clean)
|
|
460
|
-
|
|
461
|
-
ps = sub.add_parser("sample", help="sample cleaned frames into sprite sets")
|
|
462
|
-
ps.add_argument("--clean-dir", required=True)
|
|
463
|
-
ps.add_argument("--out-dir", required=True)
|
|
464
|
-
add_common_sample(ps)
|
|
465
|
-
ps.set_defaults(func=cmd_sample)
|
|
466
|
-
|
|
467
|
-
pp = sub.add_parser("process", help="extract + clean + sample")
|
|
468
|
-
pp.add_argument("--video", required=True)
|
|
469
|
-
pp.add_argument("--out-dir", required=True)
|
|
470
|
-
pp.add_argument("--name", default="clip")
|
|
471
|
-
pp.add_argument("--fps", type=float, default=0.0)
|
|
472
|
-
pp.add_argument("--dist", type=float, default=55.0)
|
|
473
|
-
add_common_sample(pp)
|
|
474
|
-
pp.set_defaults(func=cmd_process)
|
|
475
|
-
|
|
476
|
-
return p
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
def main(argv: Sequence[str] | None = None) -> int:
|
|
480
|
-
parser = build_parser()
|
|
481
|
-
args = parser.parse_args(argv)
|
|
482
|
-
try:
|
|
483
|
-
return int(args.func(args))
|
|
484
|
-
except Exception as exc: # noqa: BLE001 — CLI surface
|
|
485
|
-
print(f"error: {exc}", file=sys.stderr)
|
|
486
|
-
return 1
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
if __name__ == "__main__":
|
|
490
|
-
raise SystemExit(main())
|
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: xai-api
|
|
3
|
-
description: >
|
|
4
|
-
Call the xAI API (Grok) from this app's server code using the injected
|
|
5
|
-
XAI_API_KEY: chat/LLM features, image and video generation (Imagine), and
|
|
6
|
-
voice (text-to-speech). Use when the app needs any "AI" / "assistant" /
|
|
7
|
-
"chatbot" / "Grok" functionality, runtime image/video generation, or speech.
|
|
8
|
-
Triggers on "AI", "LLM", "chatbot", "assistant", "Grok", "xAI", "generate
|
|
9
|
-
text", "summarize", "generate image", "AI video", "voice", "text to speech",
|
|
10
|
-
"TTS", "OpenAI" (use xAI instead).
|
|
11
|
-
metadata:
|
|
12
|
-
short-description: "xAI API via the injected XAI_API_KEY: chat, Imagine (image/video), voice"
|
|
13
|
-
user-invocable: false
|
|
14
|
-
---
|
|
15
|
-
|
|
16
|
-
# xAI API (Grok)
|
|
17
|
-
|
|
18
|
-
When `XAI_API_KEY` is present in the environment, this app has **real xAI API
|
|
19
|
-
access** — use it for AI features instead of mocking responses or reaching for
|
|
20
|
-
another provider. The same variable is injected into the **deployed** app at
|
|
21
|
-
publish, so code built against it works identically in preview and production.
|
|
22
|
-
|
|
23
|
-
**The key is the app owner's personal key: every call spends their quota and
|
|
24
|
-
credits.** Be deliberate about usage — see [Spend responsibly](#spend-responsibly)
|
|
25
|
-
before wiring AI calls into anything that runs automatically or is open to
|
|
26
|
-
visitors.
|
|
27
|
-
|
|
28
|
-
The key unlocks the **full API surface**, not just chat:
|
|
29
|
-
|
|
30
|
-
- **Chat / LLM** — **latest model: `grok-4.5`**; default to it unless the
|
|
31
|
-
user asks otherwise.
|
|
32
|
-
- **Imagine (images & video)** — generate and edit images, generate video,
|
|
33
|
-
at runtime inside the app.
|
|
34
|
-
- **Voice** — text-to-speech with expressive voices (and transcription).
|
|
35
|
-
- **Official docs: [docs.x.ai](https://docs.x.ai)** — endpoints, models,
|
|
36
|
-
parameters, streaming, tool use. Don't guess API shapes; check the docs.
|
|
37
|
-
- The API is **OpenAI-compatible** (`https://api.x.ai/v1`), so any
|
|
38
|
-
OpenAI-style client works by switching the base URL and key.
|
|
39
|
-
|
|
40
|
-
## Env vars — do **not** create a `.env` file
|
|
41
|
-
|
|
42
|
-
| Var | Where | Purpose |
|
|
43
|
-
|---|---|---|
|
|
44
|
-
| `XAI_API_KEY` | server | Injected by the platform (preview and deploy). Never write, hardcode, or ask the user for it. |
|
|
45
|
-
|
|
46
|
-
The key is **server-only**: read it with `process.env.XAI_API_KEY` inside
|
|
47
|
-
`createServerFn` handlers / server code, never in client components, and never
|
|
48
|
-
expose it via a `VITE_`-prefixed variable or an API response.
|
|
49
|
-
|
|
50
|
-
It can be **absent** (rollout-gated). Degrade gracefully — check for it and
|
|
51
|
-
show a friendly "AI features are unavailable" state instead of crashing:
|
|
52
|
-
|
|
53
|
-
```ts
|
|
54
|
-
const apiKey = process.env.XAI_API_KEY;
|
|
55
|
-
if (!apiKey) throw new Error("AI is not available in this environment");
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
## Calling the API (server-only)
|
|
59
|
-
|
|
60
|
-
No SDK needed — plain `fetch` from a server function:
|
|
61
|
-
|
|
62
|
-
```ts
|
|
63
|
-
import { createServerFn } from "@tanstack/react-start";
|
|
64
|
-
|
|
65
|
-
export const askGrok = createServerFn({ method: "POST" })
|
|
66
|
-
.validator((input: { prompt: string }) => input)
|
|
67
|
-
.handler(async ({ data }) => {
|
|
68
|
-
const apiKey = process.env.XAI_API_KEY;
|
|
69
|
-
if (!apiKey) return { ok: false as const, error: "AI is not available" };
|
|
70
|
-
|
|
71
|
-
const res = await fetch("https://api.x.ai/v1/chat/completions", {
|
|
72
|
-
method: "POST",
|
|
73
|
-
headers: {
|
|
74
|
-
"Content-Type": "application/json",
|
|
75
|
-
Authorization: `Bearer ${apiKey}`,
|
|
76
|
-
},
|
|
77
|
-
body: JSON.stringify({
|
|
78
|
-
model: "grok-4.5",
|
|
79
|
-
messages: [{ role: "user", content: data.prompt }],
|
|
80
|
-
}),
|
|
81
|
-
});
|
|
82
|
-
if (!res.ok) {
|
|
83
|
-
return { ok: false as const, error: `xAI API error ${res.status}` };
|
|
84
|
-
}
|
|
85
|
-
const body = (await res.json()) as {
|
|
86
|
-
choices: { message: { content: string } }[];
|
|
87
|
-
};
|
|
88
|
-
return { ok: true as const, text: body.choices[0]?.message.content ?? "" };
|
|
89
|
-
});
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
For streaming, structured outputs, vision, or the full model list, follow
|
|
93
|
-
[docs.x.ai](https://docs.x.ai) — the shapes are OpenAI-compatible.
|
|
94
|
-
|
|
95
|
-
## Imagine — image & video generation (server-only)
|
|
96
|
-
|
|
97
|
-
The same key drives **runtime** image/video features in the app (user avatars,
|
|
98
|
-
scene art, generated content). Distinct from your build-time `imagine_text_to_image` / `imagine_image_to_image` / `imagine_image_to_video` tools (the
|
|
99
|
-
`imagine` skill): use the **API** when the *running app* generates media, the
|
|
100
|
-
tools when *you* create static assets while building.
|
|
101
|
-
|
|
102
|
-
```ts
|
|
103
|
-
// POST https://api.x.ai/v1/images/generations — same auth header as chat
|
|
104
|
-
body: JSON.stringify({
|
|
105
|
-
model: "grok-imagine-image-quality", // or "grok-imagine-image" (cheaper)
|
|
106
|
-
prompt: data.prompt,
|
|
107
|
-
// n (≤10), resolution ("1k"|"2k"), response_format ("url"|"b64_json")
|
|
108
|
-
})
|
|
109
|
-
// → body.data[0].url
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
- **Image editing**: `POST /v1/images/edits` — natural-language edits, up to 3
|
|
113
|
-
reference images.
|
|
114
|
-
- **Video**: `grok-imagine-video` via the async video endpoints (start, then
|
|
115
|
-
poll the returned request id; clips up to ~15s).
|
|
116
|
-
- Full parameters and examples: [docs.x.ai](https://docs.x.ai) → Imagine API.
|
|
117
|
-
|
|
118
|
-
## Voice — text-to-speech (server-only)
|
|
119
|
-
|
|
120
|
-
`POST https://api.x.ai/v1/tts` turns text into spoken audio — narration,
|
|
121
|
-
accessibility, character voices:
|
|
122
|
-
|
|
123
|
-
```ts
|
|
124
|
-
// Same Authorization header; returns audio bytes (e.g. MP3)
|
|
125
|
-
body: JSON.stringify({ text: data.text, voice_id: "eve" }) // eve = default voice
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
List voices at `GET /v1/tts/voices` (custom voices supported); transcription
|
|
129
|
-
(speech-to-text) is also available. Details: [docs.x.ai](https://docs.x.ai) →
|
|
130
|
-
Voice API. Serve the audio to the client from your server function — never
|
|
131
|
-
call the API from the browser (that would expose the key).
|
|
132
|
-
|
|
133
|
-
## Spend responsibly
|
|
134
|
-
|
|
135
|
-
The key belongs to the **app owner** (the user you're building for): every
|
|
136
|
-
call — including ones triggered by anonymous visitors of the deployed app —
|
|
137
|
-
**spends their personal quota and credits**. Burning it on wasteful calls
|
|
138
|
-
degrades or breaks every other use of their key. Be careful with usage:
|
|
139
|
-
|
|
140
|
-
- **Cap output** (`max_tokens`) and keep prompts small for visitor-facing
|
|
141
|
-
features. Image, and especially video, generation cost far more per call
|
|
142
|
-
than chat.
|
|
143
|
-
- **Never call the API in a loop, on every keystroke, or on page load** —
|
|
144
|
-
make calls user-initiated (button press, form submit) and debounce.
|
|
145
|
-
- **Cache or persist results** (see the `neon` skill) instead of regenerating
|
|
146
|
-
the same content per visitor or per render.
|
|
147
|
-
- **Gate expensive flows** — media generation in particular. On an app that
|
|
148
|
-
already has sign-in, put them behind `authMiddleware` (see the **`auth`
|
|
149
|
-
skill**). Sign-in is off by default, and adding the middleware without it
|
|
150
|
-
breaks the deployed app (AGENTS.md §0.5) — so on an app without accounts, cap
|
|
151
|
-
usage instead: user-initiated calls, small limits, cached results.
|
|
152
|
-
- Don't add retry storms: on an API error, surface it; retry at most once.
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"schema": "agentsam.skill.v1",
|
|
3
|
-
"id": "grok-xai-api",
|
|
4
|
-
"name": "Grok \u00b7 xai-api",
|
|
5
|
-
"description": "xAI API via the injected XAI_API_KEY: chat, Imagine (image/video), voice",
|
|
6
|
-
"icon": "skill",
|
|
7
|
-
"slash": {
|
|
8
|
-
"suggested": "/grok-xai-api"
|
|
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
|
-
}
|