Skip to content

feat(cli): Embed core template pack in CLI package for air-gapped deployment #1711

@mnriem

Description

@mnriem

Summary

Embed the core template pack (templates, commands, and scripts) inside the specify-cli Python package so that specify init --offline can scaffold projects entirely from local assets without calling the GitHub API. This eliminates the last hardcoded external network dependency and enables air-gapped deployment.

Problem Statement

After #1707 (multi-catalog) and #1708 (pluggable templates with scripts), every network touchpoint in Spec Kit becomes configurable — except one:

specify init always calls https://api.github.com/repos/github/spec-kit/releases/latest to fetch and download a release ZIP. This is hardcoded in download_template_from_github() (__init__.py L677) and cannot be redirected to an internal server.

On an air-gapped network where extension and template catalogs point at internal HTTPS servers (#1707, #1708), specify init still fails because it can't reach api.github.com.

Implemented Solution (PR #1803)

1. Bundle core assets inside the pip package

Ship templates, commands, scripts, and release scripts as package data within specify-cli via pyproject.toml force-include:

[tool.hatch.build.targets.wheel.force-include]
"templates"           = "specify_cli/core_pack/templates"
"templates/commands"  = "specify_cli/core_pack/commands"
"scripts/bash"        = "specify_cli/core_pack/scripts/bash"
"scripts/powershell"  = "specify_cli/core_pack/scripts/powershell"
".github/workflows/scripts/create-release-packages.sh" = "specify_cli/core_pack/release_scripts/create-release-packages.sh"
".github/workflows/scripts/create-release-packages.ps1" = "specify_cli/core_pack/release_scripts/create-release-packages.ps1"

2. --offline opt-in flag

By default, specify init continues to download from GitHub (unchanged behavior). The new --offline flag opts in to using bundled assets:

# Default — downloads from GitHub:
specify init my-project --ai claude

# Opt-in offline — uses bundled assets, no network access:
specify init my-project --ai claude --offline

If --offline is specified but bundled assets can't be found or scaffolding fails, the CLI errors out with a clear message rather than silently falling back to a network download.

3. Offline scaffolding via release script (scaffold_from_core_pack)

  • _locate_core_pack() — finds bundled core_pack directory (wheel install) or returns None
  • _locate_release_script() — finds the platform-appropriate release script; on Windows, probes pwsh then powershell via shutil.which()
  • scaffold_from_core_pack() — invokes the bundled create-release-packages.sh (or .ps1) in a temp directory to generate the exact same output as the GitHub release ZIPs, then copies the result to the project directory

This guarantees byte-for-byte parity with the GitHub release ZIPs because both paths use the exact same script.

4. Wheel published as release asset

  • release.yml: wheel build step runs after release packages are generated
  • create-github-release.sh: specify_cli-VERSION-py3-none-any.whl attached to every release

5. Shell script improvements

  • GENRELEASES_DIR overridable via environment variable for test isolation
  • validate_subset() hardened against glob metacharacter injection

Acceptance Criteria

  • specify init --offline scaffolds a complete project from embedded assets with no network calls
  • All supported agents produce correct command files (Markdown, TOML, agent.md) from embedded command templates
  • Default specify init (without --offline) retains current GitHub-download behavior
  • pip install specify-cli includes all core templates, commands, and scripts
  • Existing create-release-packages.sh continues to work (kept for release ZIP distribution)
  • Air-gapped deployment works: install wheel offline → specify init --offline succeeds without external network access
  • Byte-for-byte parity verified for all 21 agents between offline scaffold and release script ZIPs

Dependencies

Out of Scope

  • Removing release ZIPs entirely (kept as supplementary distribution for non-pip users)
  • Private PyPI mirror setup documentation (org-specific)
  • Catalog mirroring tooling

References

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions