feat: add quick install script and packaging (#197)
Give bot-bottle a real distribution path so new users can install
without cloning the repo:
- pyproject.toml: full project metadata, a `bot-bottle` console-script
entry point (bot_bottle.cli:main), and package-data for the runtime
assets (Dockerfiles, egress entrypoint, netpool defaults, macos init).
Still zero runtime pip dependencies.
- install.sh: POSIX, sudo-free, idempotent bootstrapper — checks Python
>= 3.11, creates ~/.bot-bottle/{agents,bottles,contrib}, installs via
pipx (pip --user fallback), then runs `bot-bottle doctor`.
- `bot-bottle doctor`: new store-free subcommand reporting Python
version, backend availability (reuses is_backend_available rather than
hardcoding Docker), and config-dir presence. Exits non-zero when a hard
prerequisite is unmet.
- PRD prd-new-install-script and unit tests for doctor, the packaging
contract, and the install script.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
"""Unit: pyproject.toml packaging contract.
|
||||
|
||||
Guards the install/distribution surface: the console-script entry point,
|
||||
the stdlib-only (empty) dependency list, and that every package-data glob
|
||||
still points at a file that exists (so an installed wheel isn't missing a
|
||||
Dockerfile or entrypoint the runtime reads).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import tomllib
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[2]
|
||||
PYPROJECT = REPO_ROOT / "pyproject.toml"
|
||||
|
||||
|
||||
class TestPyproject(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
with PYPROJECT.open("rb") as fh:
|
||||
cls.data = tomllib.load(fh)
|
||||
|
||||
def test_entry_point_targets_cli_main(self):
|
||||
scripts = self.data["project"]["scripts"]
|
||||
self.assertEqual("bot_bottle.cli:main", scripts["bot-bottle"])
|
||||
|
||||
def test_no_runtime_dependencies(self):
|
||||
# AGENTS.md: the package has no runtime pip dependencies.
|
||||
self.assertEqual([], self.data["project"]["dependencies"])
|
||||
|
||||
def test_requires_python_311(self):
|
||||
self.assertEqual(">=3.11", self.data["project"]["requires-python"])
|
||||
|
||||
def test_package_data_files_exist(self):
|
||||
pkg_data = self.data["tool"]["setuptools"]["package-data"]["bot_bottle"]
|
||||
self.assertTrue(pkg_data, "expected package-data entries")
|
||||
for rel in pkg_data:
|
||||
path = REPO_ROOT / "bot_bottle" / rel
|
||||
self.assertTrue(path.is_file(), f"package-data missing: {path}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user