spritegen-cli 0.4.0__tar.gz → 0.5.0__tar.gz
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.
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/.github/workflows/ci.yml +29 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/.gitignore +9 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/PKG-INFO +4 -1
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/README.md +25 -5
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/docs/adr/0008-local-backends-are-the-default.md +2 -1
- spritegen_cli-0.5.0/docs/adr/0017-the-matte-goes-through-the-endpoint-by-default.md +49 -0
- spritegen_cli-0.5.0/docs/adr/0018-ship-the-editor-frontend-built.md +47 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/docs/glossary.md +1 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/docs/notes.md +5 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/docs/stack.md +20 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/docs/wiki/changelog.md +1 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/docs/wiki/index.md +1 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/docs/wiki/pages/local-instead-of-paid.md +9 -4
- spritegen_cli-0.5.0/docs/wiki/pages/the-editor.md +56 -0
- spritegen_cli-0.5.0/editor-ui/index.html +12 -0
- spritegen_cli-0.5.0/editor-ui/package-lock.json +1839 -0
- spritegen_cli-0.5.0/editor-ui/package.json +19 -0
- spritegen_cli-0.5.0/editor-ui/src/App.jsx +60 -0
- spritegen_cli-0.5.0/editor-ui/src/SheetEditor.jsx +188 -0
- spritegen_cli-0.5.0/editor-ui/src/Versions.jsx +50 -0
- spritegen_cli-0.5.0/editor-ui/src/api.js +54 -0
- spritegen_cli-0.5.0/editor-ui/src/main.jsx +6 -0
- spritegen_cli-0.5.0/editor-ui/src/style.css +100 -0
- spritegen_cli-0.5.0/editor-ui/vite.config.js +25 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/pyproject.toml +16 -2
- spritegen_cli-0.5.0/specs/editor/design.md +104 -0
- spritegen_cli-0.5.0/specs/editor/requirements.md +89 -0
- spritegen_cli-0.5.0/specs/editor/tasks.md +49 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/src/spritegen/cli.py +14 -3
- spritegen_cli-0.5.0/src/spritegen/editor/__init__.py +64 -0
- spritegen_cli-0.5.0/src/spritegen/editor/app.py +363 -0
- spritegen_cli-0.5.0/src/spritegen/editor/edits.py +81 -0
- spritegen_cli-0.5.0/src/spritegen/editor/static/assets/editor.js +49 -0
- spritegen_cli-0.5.0/src/spritegen/editor/static/assets/index.css +1 -0
- spritegen_cli-0.5.0/src/spritegen/editor/static/index.html +13 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/src/spritegen/settings.py +6 -4
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/src/spritegen/skill/__init__.py +59 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/src/spritegen/skill/files/SKILL.md +50 -19
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/src/spritegen/stages/catalog.py +3 -2
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/src/spritegen/stages/matte.py +12 -5
- spritegen_cli-0.5.0/tests/test_editor.py +654 -0
- spritegen_cli-0.5.0/tests/test_edits.py +129 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/tests/test_matting.py +18 -9
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/tests/test_settings.py +7 -3
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/tests/test_skill.py +74 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/tests/test_stages.py +18 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/uv.lock +71 -2
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/.claude/agents/code-review.md +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/.claude/agents/security-review.md +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/.claude/commands/scc-adr.md +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/.claude/commands/scc-codewiki.md +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/.claude/commands/scc-glossary.md +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/.claude/commands/scc-init.md +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/.claude/commands/scc-plan-run.md +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/.claude/commands/scc-prd.md +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/.claude/commands/scc-stack.md +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/.claude/commands/scc-wiki.md +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/.claude/rules/artifacts.md +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/.claude/rules/autonomy.md +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/.claude/rules/caveman.md +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/.claude/rules/code-search.md +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/.claude/rules/delivery.md +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/.claude/rules/knowledge-base.md +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/.claude/rules/methodology.md +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/.claude/rules/notes.md +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/.claude/rules/prior-art.md +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/.claude/rules/project.md +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/.claude/rules/routing.md +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/.claude/rules/specs.md +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/.claude/rules/tasks.md +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/.claude/rules/verification.md +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/.claude/scc-manifest.json +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/.claude/skills/adr/SKILL.md +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/.claude/skills/codewiki/SKILL.md +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/.claude/skills/glossary/SKILL.md +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/.claude/skills/init/SKILL.md +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/.claude/skills/plan-run/SKILL.md +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/.claude/skills/prd/SKILL.md +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/.claude/skills/stack/SKILL.md +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/.claude/skills/wiki/SKILL.md +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/.env.template +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/.gitattributes +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/.github/workflows/release.yml +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/.python-version +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/CLAUDE.md +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/docs/adr/0001-asset-directory-and-no-path-arguments.md +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/docs/adr/0002-transfer-movement-instead-of-generating-frames.md +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/docs/adr/0003-append-only-jsonl-ledger.md +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/docs/adr/0004-allow-list-every-downloaded-host.md +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/docs/adr/0005-a-directory-per-artifact-kind.md +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/docs/adr/0006-centralise-configuration-and-never-cache-it.md +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/docs/adr/0007-heavy-dependencies-are-optional-extras.md +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/docs/adr/0009-walk-the-redirect-chain-here.md +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/docs/adr/0010-ci-on-three-operating-systems.md +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/docs/adr/0011-keep-pixelfixer-out-of-the-distribution.md +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/docs/adr/0012-publish-with-one-secret.md +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/docs/adr/0013-require-python-3-13.md +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/docs/adr/0014-every-run-is-a-version-and-the-state-names-the-chosen-one.md +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/docs/adr/0015-record-the-call-before-the-files-it-writes.md +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/docs/adr/0016-a-workspace-is-marked-by-the-file-that-configures-it.md +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/docs/codewiki/spending-money.md +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/docs/codewiki/the-stage-registry.md +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/docs/codewiki/the-workspace.md +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/docs/wiki/pages/configuration.md +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/docs/wiki/pages/grid-and-palette-recovery.md +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/docs/wiki/pages/motion-transfer.md +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/docs/wiki/pages/paid-calls-and-the-ledger.md +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/docs/wiki/pages/the-asset-directory.md +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/docs/wiki/pages/the-generated-skill.md +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/docs/wiki/pages/the-pipeline.md +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/plans/code-health.md +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/plans/motion-optimisation.md +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/specs/artifact-versions/design.md +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/specs/artifact-versions/requirements.md +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/specs/artifact-versions/tasks.md +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/specs/workspace-config/design.md +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/specs/workspace-config/requirements.md +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/specs/workspace-config/tasks.md +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/src/spritegen/__init__.py +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/src/spritegen/atlas.py +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/src/spritegen/clip.py +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/src/spritegen/drive.py +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/src/spritegen/endpoints.py +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/src/spritegen/fal.py +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/src/spritegen/imaging.py +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/src/spritegen/ledger.py +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/src/spritegen/matting.py +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/src/spritegen/migrate.py +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/src/spritegen/prompts.py +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/src/spritegen/report.py +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/src/spritegen/rrdb.py +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/src/spritegen/sheet.py +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/src/spritegen/stages/__init__.py +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/src/spritegen/stages/_common.py +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/src/spritegen/stages/anchor.py +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/src/spritegen/stages/board.py +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/src/spritegen/stages/motion.py +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/src/spritegen/stages/pose.py +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/src/spritegen/stages/registry.py +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/src/spritegen/stages/video.py +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/src/spritegen/upscale.py +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/src/spritegen/workspace.py +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/tests/conftest.py +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/tests/helpers.py +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/tests/parity/expected/anchor_crop.pixelart.json +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/tests/parity/expected/anchor_crop.pixelart.png +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/tests/parity/expected/synthetic_chroma.cut.json +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/tests/parity/expected/synthetic_chroma.cut.png +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/tests/parity/expected/synthetic_matted.json +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/tests/parity/expected/synthetic_row.png +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/tests/parity/expected/synthetic_video_board.json +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/tests/parity/expected/synthetic_video_board.png +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/tests/parity/expected/walk_south_row.gif +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/tests/parity/expected/walk_south_row.png +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/tests/parity/input/anchor_crop.png +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/tests/parity/input/synthetic_board.png +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/tests/parity/input/synthetic_chroma.png +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/tests/parity/input/synthetic_matted.png +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/tests/parity/input/walk_south_board.png +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/tests/parity/manifest.json +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/tests/test_anchor.py +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/tests/test_atlas.py +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/tests/test_board_stage.py +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/tests/test_cli.py +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/tests/test_clip.py +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/tests/test_drive.py +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/tests/test_fal.py +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/tests/test_imaging.py +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/tests/test_ledger.py +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/tests/test_matte.py +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/tests/test_migrate.py +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/tests/test_motion.py +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/tests/test_parity.py +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/tests/test_pose.py +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/tests/test_prompts.py +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/tests/test_sheet.py +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/tests/test_show.py +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/tests/test_upscale.py +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/tests/test_versions.py +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/tests/test_video.py +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/tests/test_workspace.py +0 -0
- {spritegen_cli-0.4.0 → spritegen_cli-0.5.0}/tests/tests_fal_doubles.py +0 -0
|
@@ -117,3 +117,32 @@ jobs:
|
|
|
117
117
|
# is added to the command, and that is the assertion — a wheel that needs a
|
|
118
118
|
# hand-passed git URL to start is a wheel nobody can install.
|
|
119
119
|
run: uv run --isolated --no-project --with dist/*.whl spritegen --version
|
|
120
|
+
|
|
121
|
+
page:
|
|
122
|
+
name: page
|
|
123
|
+
runs-on: ubuntu-latest
|
|
124
|
+
steps:
|
|
125
|
+
- uses: actions/checkout@v7
|
|
126
|
+
with:
|
|
127
|
+
persist-credentials: false
|
|
128
|
+
|
|
129
|
+
- uses: actions/setup-node@v4
|
|
130
|
+
with:
|
|
131
|
+
node-version: "24"
|
|
132
|
+
cache: npm
|
|
133
|
+
cache-dependency-path: editor-ui/package-lock.json
|
|
134
|
+
|
|
135
|
+
- name: Install
|
|
136
|
+
working-directory: editor-ui
|
|
137
|
+
run: npm ci
|
|
138
|
+
|
|
139
|
+
- name: Build
|
|
140
|
+
working-directory: editor-ui
|
|
141
|
+
run: npm run build
|
|
142
|
+
|
|
143
|
+
- name: The committed page is the one this source builds
|
|
144
|
+
# `src/spritegen/editor/static` is committed because the wheel has to carry the
|
|
145
|
+
# page — a machine that installed the extra has no node. That makes it the one
|
|
146
|
+
# generated thing in this repository that can go stale silently, so the build
|
|
147
|
+
# runs here and the diff has to be empty.
|
|
148
|
+
run: git diff --exit-code -- src/spritegen/editor/static
|
|
@@ -226,3 +226,12 @@ assets/
|
|
|
226
226
|
|
|
227
227
|
# the spritegen workspace's own settings, and its key
|
|
228
228
|
.spritegen.json
|
|
229
|
+
|
|
230
|
+
# The frontend build inputs. `editor-ui/dist` never exists: vite writes into the
|
|
231
|
+
# package, at src/spritegen/editor/static, and that directory is committed on purpose.
|
|
232
|
+
editor-ui/node_modules/
|
|
233
|
+
|
|
234
|
+
# `assets/` above (Streamlit's block) matches every directory of that name, including
|
|
235
|
+
# the one vite builds into. The built page is committed on purpose — the wheel carries
|
|
236
|
+
# it, and `adr:0018` says why — so it is exempted here rather than renamed.
|
|
237
|
+
!src/spritegen/editor/static/assets/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: spritegen-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: Sprite generation CLI — a few AI-generated images into a game-ready character sheet
|
|
5
5
|
Requires-Python: >=3.13
|
|
6
6
|
Requires-Dist: av>=12.0
|
|
@@ -10,6 +10,9 @@ Requires-Dist: numpy>=1.26
|
|
|
10
10
|
Requires-Dist: opencv-python>=5.0.0.93
|
|
11
11
|
Requires-Dist: pillow>=10.0
|
|
12
12
|
Requires-Dist: pydantic-settings>=2.0
|
|
13
|
+
Provides-Extra: editor
|
|
14
|
+
Requires-Dist: fastapi>=0.115; extra == 'editor'
|
|
15
|
+
Requires-Dist: uvicorn>=0.30; extra == 'editor'
|
|
13
16
|
Provides-Extra: matte
|
|
14
17
|
Requires-Dist: rembg[cpu]>=2.0; extra == 'matte'
|
|
15
18
|
Provides-Extra: upscale
|
|
@@ -12,7 +12,7 @@ the rest:
|
|
|
12
12
|
|
|
13
13
|
```
|
|
14
14
|
pip install "spritegen-cli[upscale]" # Real-ESRGAN locally, so a paid call need not buy resolution
|
|
15
|
-
pip install "spritegen-cli[matte]" # BiRefNet locally
|
|
15
|
+
pip install "spritegen-cli[matte]" # BiRefNet locally; the matte goes through the endpoint without it
|
|
16
16
|
pip install "rembg[gpu]" # ...and on the GPU, if this machine has the CUDA for it
|
|
17
17
|
pip install "pixelfixer @ git+https://github.com/Retro-Diffusion/pixel-art-fixer.git#subdirectory=python"
|
|
18
18
|
# the pixel-art direction, which recovers a native grid
|
|
@@ -27,11 +27,12 @@ spritegen anchor <character> # the character, one image
|
|
|
27
27
|
spritegen pose <character> # where an animation starts, when the anchor is not it
|
|
28
28
|
spritegen motion <character> # movement from a driving clip
|
|
29
29
|
spritegen video <character> # or animate the anchor directly
|
|
30
|
-
spritegen matte <character> # cut the background
|
|
30
|
+
spritegen matte <character> # cut the background (paid; --backend local once the extra is in)
|
|
31
31
|
spritegen board <character> # close it into a sheet row, 166 px cells
|
|
32
32
|
spritegen upscale <character> # enlarge an artifact locally, for nothing
|
|
33
33
|
spritegen show <character> # every version it holds, and what comes next
|
|
34
34
|
spritegen choose <character> <artifact> <version> # which version everything else reads
|
|
35
|
+
spritegen edit <character> # a local page: pick a version, fix a row (needs the [editor] extra)
|
|
35
36
|
spritegen status
|
|
36
37
|
spritegen cost
|
|
37
38
|
spritegen init # write the Claude skill for all of this
|
|
@@ -45,6 +46,12 @@ Every run of a stage is kept: `anchor/v1`, `anchor/v2`, nothing overwritten. Gen
|
|
|
45
46
|
several, then `choose` the one the rest of the pipeline reads — which is how the anchor
|
|
46
47
|
`motion` deforms from gets picked.
|
|
47
48
|
|
|
49
|
+
`edit` is the same two jobs with the sprite in front of you: it opens a page on
|
|
50
|
+
`127.0.0.1`, draws every version side by side so choosing one is a click, and cuts a
|
|
51
|
+
board by its grid so a frame can be dropped, reordered or nudged and the row saved as a
|
|
52
|
+
new version. It spends nothing, and it closes the row through the same code `board`
|
|
53
|
+
does.
|
|
54
|
+
|
|
48
55
|
`motion` is the one that answers the problem the rest of this exists for. Generating a
|
|
49
56
|
walk frame by frame moves the character instead of animating it — measured on the purple
|
|
50
57
|
warrior sheet, the head-and-torso region shifts 0.25 between steps against the
|
|
@@ -73,9 +80,22 @@ written; `--dry-run` prints the payload and spends nothing.
|
|
|
73
80
|
|
|
74
81
|
## Running it
|
|
75
82
|
|
|
76
|
-
Needs [uv](https://docs.astral.sh/uv/) and
|
|
77
|
-
|
|
78
|
-
|
|
83
|
+
Needs [uv](https://docs.astral.sh/uv/) and a fal key.
|
|
84
|
+
|
|
85
|
+
`spritegen init` opens a workspace in the directory it is run in and writes
|
|
86
|
+
`.spritegen.json` there with `fal_key` empty; put the key in that file. It is added to a
|
|
87
|
+
`.gitignore` that exists, and `init` warns when git is already tracking it.
|
|
88
|
+
|
|
89
|
+
Passing the key on a command line — `FAL_KEY=... spritegen anchor` — works and is worth
|
|
90
|
+
avoiding: it lands in the shell's history file and stays there.
|
|
91
|
+
|
|
92
|
+
The file is plaintext at rest and is not safe merely for being a file: `init` narrows
|
|
93
|
+
its permissions where the platform has them, adds it to a `.gitignore` that exists, and
|
|
94
|
+
warns when the repository has none or when git already tracks it. What it buys over the
|
|
95
|
+
history is that it can be deleted, ignored, and given permissions at all.
|
|
96
|
+
|
|
97
|
+
An exported variable and a `.env` are still read, and both still win over the file,
|
|
98
|
+
which is what makes a one-off override and a CI runner work.
|
|
79
99
|
|
|
80
100
|
One dependency is not on PyPI: `pixelfixer`, from
|
|
81
101
|
[Retro-Diffusion/pixel-art-fixer](https://github.com/Retro-Diffusion/pixel-art-fixer),
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
---
|
|
2
|
+
status: accepted
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# The matte goes through the endpoint by default
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
|
|
9
|
+
`adr:0008-local-backends-are-the-default` made `matte --backend local` the default, on
|
|
10
|
+
the reasoning that `rembg` runs the same family of model the endpoint does, costs
|
|
11
|
+
nothing, and is no slower on a GPU. That reasoning still holds — for a machine that has
|
|
12
|
+
the extra installed and a GPU worth using.
|
|
13
|
+
|
|
14
|
+
What it does not describe is a machine that has just installed the tool. `rembg` is an
|
|
15
|
+
optional extra of `adr:0007-heavy-dependencies-are-optional-extras`, so the default
|
|
16
|
+
refused with an install line on every machine that had not run `uv sync --extra matte`,
|
|
17
|
+
and the first thing a new workspace met at this stage was a setup errand rather than a
|
|
18
|
+
cut frame. The extra also pulls `onnxruntime`, whose GPU build depends on the machine's
|
|
19
|
+
CUDA — a choice this project deliberately does not make for anybody
|
|
20
|
+
(`adr:0007`'s consequence, and why `rembg[cpu]` is what the extra names).
|
|
21
|
+
|
|
22
|
+
## Decision
|
|
23
|
+
|
|
24
|
+
`matte_backend` defaults to `fal`. `--backend local` and `"matte_backend": "local"` in
|
|
25
|
+
the workspace's `.spritegen.json` both select the local one, and a machine set up for it
|
|
26
|
+
should say so once in the file rather than passing the flag every time.
|
|
27
|
+
|
|
28
|
+
This supersedes `adr:0008-local-backends-are-the-default` **on the matte default only**.
|
|
29
|
+
`upscale` stays local, and is still local-only.
|
|
30
|
+
|
|
31
|
+
## Consequences
|
|
32
|
+
|
|
33
|
+
**This stage now spends without being asked to**, which nothing else here does. It is
|
|
34
|
+
declared paid, so `--dry-run` prints the payload and spends nothing, every call lands in
|
|
35
|
+
the asset's `ledger.jsonl`, and `cost` reports it — the guards the always-paid stages
|
|
36
|
+
have, applied to a stage that used to be free by default. That is the trade: the tool
|
|
37
|
+
works on a fresh machine, and the invoice is where somebody has to look to notice.
|
|
38
|
+
|
|
39
|
+
The paid backend joins frames into one image, so a board is one charge rather than six.
|
|
40
|
+
The default is therefore cheaper per run than it looks, and `--no-join` is six times the
|
|
41
|
+
price.
|
|
42
|
+
|
|
43
|
+
A machine with the extra and a GPU now pays for what it could do for free until somebody
|
|
44
|
+
sets `matte_backend`. The skill and the README say so where the backend is described,
|
|
45
|
+
which is the only place it can be said before the first charge.
|
|
46
|
+
|
|
47
|
+
The refusal naming the install line does not disappear — it moves to `--backend local`
|
|
48
|
+
on a machine without the extra, which is where it belongs: asked for by name, refused by
|
|
49
|
+
name.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
---
|
|
2
|
+
status: accepted
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Ship the editor's frontend built
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
|
|
9
|
+
`spritegen edit` serves a page, and a page written in React has to be built before a
|
|
10
|
+
browser can run it. The build needs node; the people who install this tool install a
|
|
11
|
+
Python wheel with `pip` or `uv`, and nothing in that path has node in it.
|
|
12
|
+
|
|
13
|
+
Three ways to bridge that, and each puts the cost somewhere different:
|
|
14
|
+
|
|
15
|
+
- **Load React from a CDN.** No build at all, and the editor stops working on a laptop
|
|
16
|
+
with no network — which is most of what this tool is for, since every local stage was
|
|
17
|
+
written so the machine could work for nothing.
|
|
18
|
+
- **Build at install time.** `pip install` would need node present and would run npm on
|
|
19
|
+
a machine that asked for a sprite tool. It also makes the install fail for a reason
|
|
20
|
+
that has nothing to do with what was installed.
|
|
21
|
+
- **Build here and commit the result.** The wheel carries the page, the install stays a
|
|
22
|
+
Python install, and node is a tool of this repository rather than of its users.
|
|
23
|
+
|
|
24
|
+
## Decision
|
|
25
|
+
|
|
26
|
+
`editor-ui/` holds the source and `npm run build` writes into
|
|
27
|
+
`src/spritegen/editor/static`, which is committed and shipped in the wheel. Node is a
|
|
28
|
+
development dependency of this repository and appears in no install path.
|
|
29
|
+
|
|
30
|
+
A CI job builds the page and fails when `git diff` over that directory is not empty, so
|
|
31
|
+
the committed page and the source that produced it cannot drift apart.
|
|
32
|
+
|
|
33
|
+
## Consequences
|
|
34
|
+
|
|
35
|
+
Built JavaScript is in the history, and a diff of it is unreadable. That is accepted:
|
|
36
|
+
the file is generated, the source beside it is what gets reviewed, and the CI job is
|
|
37
|
+
what says the two agree. Vite is configured to emit fixed filenames rather than hashed
|
|
38
|
+
ones for the same reason — a rebuild shows up as a diff, not as a rename plus a delete.
|
|
39
|
+
|
|
40
|
+
Anyone changing the page has to run the build before committing, and CI tells them so
|
|
41
|
+
in one line if they forget.
|
|
42
|
+
|
|
43
|
+
The alternative that stays open is dropping the build entirely — a page written in
|
|
44
|
+
plain JavaScript needs no node and no committed artifact. That trade was not taken
|
|
45
|
+
because the editor's state is a handful of interdependent values that change together,
|
|
46
|
+
which is what a component tree is good at, but it is the obvious escape hatch if the
|
|
47
|
+
build ever costs more than the page is worth.
|
|
@@ -36,4 +36,5 @@ use elsewhere, and listing it would report every correct use as a finding. Add a
|
|
|
36
36
|
- **version** — one run of a stage, kept whole in a directory of its own under the artifact: `anchor/v1`, `anchor/v2`. Numbered from 1, never overwritten, and the unit a candidate is kept as.
|
|
37
37
|
- **chosen version** — the version of an artifact that every later stage reads, recorded in `state.json` and moved by `choose`. The first one recorded is chosen; after that it never follows the newest.
|
|
38
38
|
- **candidate** — a version that was generated and not chosen. Kept, because it was paid for.
|
|
39
|
+
- **edit** — a change made in the editor to a board's grid, its frame order or a frame's offset inside its cell, saved as a new version of the row it produces and recorded with `stage: "edit"`. Not a stage: nothing in `stages.STAGES` is called this, and `stages` in the state file never mentions it.
|
|
39
40
|
- **layout** — the on-disk shape of an asset directory, numbered. Layout 1 was a directory per stage; layout 2 is a directory per artifact kind, and `migrate` is the only thing that moves between them.
|
|
@@ -45,3 +45,8 @@ over this file answers with the example above as well as with the notes. -->
|
|
|
45
45
|
- n-0005 2026-08-28 #gotcha @src/spritegen/upscale.py — the weight cache sidecar must stay a content hash — size and mtime are forgeable with the same write access a swap needs, so a stat-based fast path silently disables the check
|
|
46
46
|
- n-0006 2026-08-28 #gotcha @docs/codewiki/the-workspace.md — a codewiki citation that still resolves after a refactor is not still correct — the range shifts onto a neighbouring function and validate cannot tell, so check what each range opens on
|
|
47
47
|
- n-0007 2026-08-29 #gotcha @src/spritegen/__init__.py — the CLI version came from a hand-written __version__ in spritegen/__init__.py, a second copy of what pyproject.toml declares — it read 0.1.0 through three releases; it is importlib.metadata now and a test pins the two together
|
|
48
|
+
- n-0008 2026-08-29 #gotcha @src/spritegen/skill/files/SKILL.md — the generated skill told an agent to export FAL_KEY, and an exported key lands in the shell history file where it outlives the session - the skill sends it to .spritegen.json now, and a test refuses the export wording
|
|
49
|
+
- n-0009 2026-08-30 #gotcha @tests/conftest.py — the editor's route tests hand httpx.Client.send back to itself, because conftest's no_network closes the same call the ASGI test client goes through
|
|
50
|
+
- n-0010 2026-08-30 #gotcha @src/spritegen/editor/edits.py — edits.apply normalises every cell to the first one's size, because cut_cells leaves the division remainder in the last cell and a repack must not
|
|
51
|
+
- n-0011 2026-08-30 #gotcha @tests/test_edits.py — pasting an RGBA frame through itself as its own mask squares partial alpha; the identity-edit parity row is what caught it
|
|
52
|
+
- n-0012 2026-08-30 #gotcha @src/spritegen/editor/static — the repository-wide 'assets/' ignore also matched the editor's built page; it is exempted in .gitignore rather than renamed
|
|
@@ -39,6 +39,11 @@ inference from the import sites, it says so.
|
|
|
39
39
|
own runtime extras is chosen, and it refuses by calling `sys.exit()`. A GPU build is
|
|
40
40
|
`pip install "rembg[gpu]"` over the top, because which onnxruntime-gpu works depends
|
|
41
41
|
on the machine's CUDA.
|
|
42
|
+
- **fastapi** (`[editor]`) — the routes `spritegen edit` serves. It is an extra and not a
|
|
43
|
+
dependency because it is the only command that serves anything: `status`, `cost` and
|
|
44
|
+
every stage answer on a machine that never installed it. Small enough that the dev
|
|
45
|
+
group installs it too, so CI runs the editor's routes rather than skipping them.
|
|
46
|
+
- **uvicorn** (`[editor]`) — the ASGI server behind that app, bound to 127.0.0.1 only.
|
|
42
47
|
|
|
43
48
|
## Not on a registry
|
|
44
49
|
|
|
@@ -49,6 +54,21 @@ inference from the import sites, it says so.
|
|
|
49
54
|
everyone else. `imaging.require_pixelfixer` names the command when it is missing.
|
|
50
55
|
Pinned in `uv.lock` to commit `ef376e5` of `Retro-Diffusion/pixel-art-fixer`.
|
|
51
56
|
|
|
57
|
+
## The editor's page
|
|
58
|
+
|
|
59
|
+
Built by hand here and never on the machine that installs the tool: `npm run build` in
|
|
60
|
+
`editor-ui/` writes into `src/spritegen/editor/static`, that directory is committed, and
|
|
61
|
+
the wheel carries it. See `adr:0018-ship-the-editor-frontend-built`.
|
|
62
|
+
|
|
63
|
+
- **React** — the page `spritegen edit` serves. It is a handful of pieces of state that
|
|
64
|
+
all change together — the grid, the frames kept, the offsets, the preview — which is
|
|
65
|
+
the shape a component tree is for and the shape hand-written DOM updates get wrong.
|
|
66
|
+
- **Vite** — the build, and the dev server that proxies `/api` at the running editor.
|
|
67
|
+
It emits one script and one stylesheet under fixed names rather than hashed ones, so a
|
|
68
|
+
rebuild is a diff and not a rename.
|
|
69
|
+
- **Node** — a development tool of this repository and never an install-time one. CI
|
|
70
|
+
builds the page and fails when what is committed is behind the source.
|
|
71
|
+
|
|
52
72
|
## Development
|
|
53
73
|
|
|
54
74
|
- **uv** — the resolver, the lock and the runner. `uv sync --frozen` in every CI job,
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
What changed in the wiki, and when. Newest first, one line per change, naming the pages
|
|
4
4
|
it touched.
|
|
5
5
|
|
|
6
|
+
- 2026-08-30 — `spritegen edit` opens a local page to pick between versions and correct a sheet row, in [[the-editor]]
|
|
6
7
|
- 2026-08-28 — frames are read off the driving cycle's phase, the seed is recorded, clips and uploads are kept, and the duration and resolution defaults came down, in [[motion-transfer]]
|
|
7
8
|
- 2026-08-28 — layout 3: every run of a stage is a version and the state names the chosen one, in [[the-asset-directory]]
|
|
8
9
|
- 2026-08-28 — first pass, reconstructed from the code rather than remembered: added [[the-pipeline]], [[the-asset-directory]], [[paid-calls-and-the-ledger]], [[motion-transfer]], [[grid-and-palette-recovery]], [[local-instead-of-paid]], [[configuration]] and [[the-generated-skill]]
|
|
@@ -15,4 +15,5 @@ character sheet, one named stage at a time. Start with [[the-pipeline]].
|
|
|
15
15
|
- [[grid-and-palette-recovery]] — closing a board into a sheet row without making the animation flicker or stand still
|
|
16
16
|
- [[local-instead-of-paid]] — the three jobs done on this machine for nothing, and the import discipline that keeps them optional
|
|
17
17
|
- [[configuration]] — one settings class, and the upward search that has a ceiling for a reason
|
|
18
|
+
- [[the-editor]] — the one command that shows you the sprite: picking between candidates, correcting a row, and the two rules that keep it from being a second record
|
|
18
19
|
- [[the-generated-skill]] — what `init` writes, and why half of it is generated from the registry
|
|
@@ -19,9 +19,14 @@ resolution — the one thing a model like this puts back.
|
|
|
19
19
|
|
|
20
20
|
The local matte is not a lesser substitute either. `rembg` runs BiRefNet, the same family
|
|
21
21
|
of model the paid endpoint's `Matting` variant is; on a machine with CUDA the difference
|
|
22
|
-
is seconds and no invoice, and on one without it still runs on the CPU.
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
is seconds and no invoice, and on one without it still runs on the CPU.
|
|
23
|
+
|
|
24
|
+
**It is no longer the default** — `adr:0017-the-matte-goes-through-the-endpoint-by-default`
|
|
25
|
+
reversed that, because the extra it needs is optional and the default refused with an
|
|
26
|
+
install line on every machine that had not installed it. A machine set up for local says
|
|
27
|
+
so once, with `"matte_backend": "local"` in its `.spritegen.json`. Everything downstream
|
|
28
|
+
is identical either way: the same files land in the same directory with the same names,
|
|
29
|
+
and `board` cannot tell which backend ran.
|
|
25
30
|
|
|
26
31
|
## The joining trade, and why only the paid backend makes it
|
|
27
32
|
|
|
@@ -81,4 +86,4 @@ on its own.
|
|
|
81
86
|
|
|
82
87
|
## Records
|
|
83
88
|
|
|
84
|
-
`adr:0007-heavy-dependencies-are-optional-extras` · `adr:0008-local-backends-are-the-default` · `adr:0011-keep-pixelfixer-out-of-the-distribution`
|
|
89
|
+
`adr:0007-heavy-dependencies-are-optional-extras` · `adr:0008-local-backends-are-the-default` · `adr:0017-the-matte-goes-through-the-endpoint-by-default` · `adr:0011-keep-pixelfixer-out-of-the-distribution`
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# The editor
|
|
2
|
+
|
|
3
|
+
`spritegen edit <asset>` opens a page on the machine it is typed on. It is the only
|
|
4
|
+
command that shows you the sprite instead of describing it, and it is free: every paid
|
|
5
|
+
stage stays on the command line.
|
|
6
|
+
|
|
7
|
+
## What it is for
|
|
8
|
+
|
|
9
|
+
Two jobs the pipeline is bad at, and both are looking jobs.
|
|
10
|
+
|
|
11
|
+
**Picking between candidates.** Every run of a stage is kept, and choosing between them
|
|
12
|
+
from a terminal means reading `spritegen show`, opening files by hand, and typing
|
|
13
|
+
`spritegen choose anchor 3`. The page draws every version of every artifact side by
|
|
14
|
+
side, marks the chosen one, and moves the pointer on a click — see
|
|
15
|
+
[[the-asset-directory]] for what a version is and what choosing one means.
|
|
16
|
+
|
|
17
|
+
**Correcting a row.** A generated board comes back with a frame that is a smear, frames
|
|
18
|
+
in the wrong order, or a figure that sits three pixels off in one cell. Regenerating it
|
|
19
|
+
is a paid call for a problem that is three pixels wide. The page cuts the board by its
|
|
20
|
+
grid, drops, reorders and nudges frames, previews the row, and saves it — closing the
|
|
21
|
+
row through the same `sheet.board_to_row` the `board` stage uses, so an edited row and a
|
|
22
|
+
generated one come out of one function ([[grid-and-palette-recovery]]).
|
|
23
|
+
|
|
24
|
+
## What it never does
|
|
25
|
+
|
|
26
|
+
It holds no record of its own. Every read is `report.inspect` and every write is
|
|
27
|
+
`workspace.State` plus `save`, so the page and `spritegen show` cannot disagree about
|
|
28
|
+
what an asset holds.
|
|
29
|
+
|
|
30
|
+
It writes versions and never overwrites one: an edit lands as `sheet/sharp/v3`, recorded
|
|
31
|
+
under the artifact with `stage: "edit"` and the edit that produced it. It is not written
|
|
32
|
+
into `stages` — that map is the pipeline's completion record, and no stage called `edit`
|
|
33
|
+
exists.
|
|
34
|
+
|
|
35
|
+
It never chooses on your behalf when somebody already chose. A first version is chosen
|
|
36
|
+
because there was nothing to decide; after that the pointer stays where it was put.
|
|
37
|
+
|
|
38
|
+
It reaches no host and appends no line to the ledger ([[paid-calls-and-the-ledger]]).
|
|
39
|
+
|
|
40
|
+
## How it is put together
|
|
41
|
+
|
|
42
|
+
The server is fastapi and uvicorn, an optional `[editor]` extra resolved inside the
|
|
43
|
+
command that needs it — the same shape `torch` and `rembg` have
|
|
44
|
+
([[local-instead-of-paid]]). It binds `127.0.0.1` on a port the operating system picks.
|
|
45
|
+
|
|
46
|
+
Six routes: the asset, one file, choose, preview, save, and the page itself. Every path
|
|
47
|
+
the browser asks for goes through `workspace.inside`, which is the guard every path out
|
|
48
|
+
of `state.json` already goes through — the query string is the one input here that
|
|
49
|
+
something other than this tool writes.
|
|
50
|
+
|
|
51
|
+
Every write carries the token its page was drawn from, built from `state.json`'s
|
|
52
|
+
modification time and size. A stale one is a 409 telling the browser to read again. It
|
|
53
|
+
is not a lock: it turns a silent overwrite into a re-read, which is the whole ambition.
|
|
54
|
+
|
|
55
|
+
The page itself is React, built by vite into the Python package and committed there,
|
|
56
|
+
because the wheel has to carry it — `adr:0018-ship-the-editor-frontend-built`.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<title>spritegen</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="root"></div>
|
|
10
|
+
<script type="module" src="/src/main.jsx"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|