Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,21 @@ Code Puppy is an AI-powered code generation agent, designed to understand progra
uvx code-puppy -i
````

## Lean bootstrap planning

If you need to decide installation policy before attaching optional capabilities,
use the lightweight bootstrap planner instead of treating `uvx code-puppy` as a
universal installer:

```bash
uvx --from code-puppy code-puppy-bootstrap plan --profile auto --json
```

That bootstrap path stays stdlib-only, inspects the environment, and emits a
profile-driven install/reattach plan so Android/Termux deployments can keep
optional browser/image/fuzzy/search/provider dependencies detached until the
real target environment is known.

## Installation

### UV (Recommended)
Expand All @@ -62,6 +77,56 @@ uvx code-puppy -i
curl -LsSf https://astral.sh/uv/install.sh | sh

uvx code-puppy
# or inspect a lean install plan first
uvx --from code-puppy code-puppy-bootstrap plan --profile auto --json
```

#### Native Android / Termux

On native Android / Termux, use the bootstrap planner first so you can inspect
a lean install plan before attaching optional extras:

```bash
# Install UV if you don't have it
curl -LsSf https://astral.sh/uv/install.sh | sh

# Inspect the current environment
uvx --from code-puppy code-puppy-bootstrap detect --json

# Build the recommended lean install plan for this device
uvx --from code-puppy code-puppy-bootstrap plan --profile auto

# Install suggested Termux-side system packages for the lean profile
pkg install ripgrep
pkg install proot

# Attach the lean base runtime
uv tool install --refresh code-puppy

# Run Code Puppy
code-puppy -i
```

Why this flow exists:

- `android-termux-lean` is the auto-selected profile on Termux
- it keeps the initial Python install minimal on constrained devices
- browser/image/fuzzy/search/provider extras stay detached until you decide to
reattach them

If you later want a heavier profile, inspect it first and then reattach using
that plan:

```bash
uvx --from code-puppy code-puppy-bootstrap plan --profile desktop-browser
```

You can also feed policy overrides through a manifest:

```bash
uvx --from code-puppy code-puppy-bootstrap plan \
--profile android-termux-lean \
--manifest-json '{"extras_add": ["durable"], "notes": ["Enable only after validating the device."]}'
```

#### Windows
Expand Down
121 changes: 121 additions & 0 deletions code_puppy/bootstrap.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
from __future__ import annotations

import argparse
import json
import sys
from typing import Any

from code_puppy.bootstrap_profiles import (
available_profiles,
build_install_plan,
detect_environment,
)


def _build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
description="Code Puppy bootstrap planner for lean environment-aware installs"
)
subparsers = parser.add_subparsers(dest="command")

detect_parser = subparsers.add_parser(
"detect",
help="Inspect the current environment without importing the runtime CLI",
)
detect_parser.add_argument("--json", action="store_true", help="Print JSON output")

plan_parser = subparsers.add_parser(
"plan",
help="Build a lean install/reattach plan from a builtin profile and optional manifest",
)
plan_parser.add_argument(
"--profile",
default="auto",
choices=["auto", *available_profiles()],
help="Builtin install profile to use",
)
plan_parser.add_argument(
"--manifest-json",
default="",
help="Inline JSON manifest with extras_add/extras_remove/notes overrides",
)
plan_parser.add_argument(
"--manifest-file",
default="",
help="Path to a JSON manifest file with plan overrides",
)
plan_parser.add_argument("--json", action="store_true", help="Print JSON output")

return parser


def _human_detect(environment: dict[str, Any]) -> str:
lines = [
"Code Puppy bootstrap environment",
f"- python: {environment['python_executable']}",
f"- version: {environment['python_version']}",
f"- platform: {environment['platform_system']} {environment['platform_release']} ({environment['platform_machine']})",
f"- termux: {'yes' if environment['is_termux'] else 'no'}",
f"- android: {'yes' if environment['is_android'] else 'no'}",
f"- uv: {'yes' if environment['has_uv'] else 'no'}",
f"- uvx: {'yes' if environment['has_uvx'] else 'no'}",
f"- proot: {'yes' if environment['has_proot'] else 'no'}",
f"- ripgrep: {'yes' if environment['has_ripgrep'] else 'no'}",
]
return "\n".join(lines)


def _human_plan(plan: dict[str, Any]) -> str:
lines = [
f"Profile: {plan['profile']}",
f"Package spec: {plan['package_spec']}",
f"Install: {plan['install_command']}",
f"Reattach: {plan['reattach_command']}",
f"Run: {plan['run_command']}",
]
if plan["extras"]:
lines.append(f"Extras: {', '.join(plan['extras'])}")
if plan["degraded_capabilities"]:
lines.append("Degraded until reattach:")
lines.extend(f"- {item}" for item in plan["degraded_capabilities"])
if plan["system_packages"]:
lines.append("Suggested system packages:")
lines.extend(f"- {item}" for item in plan["system_packages"])
if plan["notes"]:
lines.append("Notes:")
lines.extend(f"- {item}" for item in plan["notes"])
return "\n".join(lines)


def main(argv: list[str] | None = None) -> int:
parser = _build_parser()
args = parser.parse_args(argv)

command = args.command or "plan"

try:
if command == "detect":
environment = detect_environment()
if args.json:
print(json.dumps(environment, indent=2, sort_keys=True))
else:
print(_human_detect(environment))
return 0

plan = build_install_plan(
requested_profile=getattr(args, "profile", "auto"),
manifest_json=getattr(args, "manifest_json", ""),
manifest_file=getattr(args, "manifest_file", ""),
)
if getattr(args, "json", False):
print(json.dumps(plan, indent=2, sort_keys=True))
else:
print(_human_plan(plan))
return 0
except ValueError as exc:
parser.exit(status=2, message=f"error: {exc}\n")
return 2


if __name__ == "__main__":
raise SystemExit(main(sys.argv[1:]))
Loading