Metadata-Version: 2.4
Name: woosh
Version: 0.1.0
Summary: Agentic auto-deployer: scan a project + VM, propose a deployment plan, confirm, then ship it behind a reverse proxy — one command.
Author-email: Navaneeth <you@example.com>
License-Expression: MIT
Classifier: Environment :: Console
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Software Development :: Build Tools
Requires-Python: >=3.10
Requires-Dist: jinja2>=3.1
Requires-Dist: openai>=1.40
Requires-Dist: psutil>=5.9
Requires-Dist: pyyaml>=6.0
Requires-Dist: questionary>=2.0
Requires-Dist: rich>=13.7
Requires-Dist: typer>=0.12
Description-Content-Type: text/markdown

# woosh

One command — `woosh install` — that scans a project and the host it's running
on, has an LLM agent plan the deployment, shows you the plan, and only then
deploys behind a shared reverse proxy (Caddy or Nginx).

## Install

```bash
pip install -e .
```

First run triggers onboarding automatically; or run it explicitly:

```bash
woosh init
```

This asks for your OpenAI API key and which model to use, then stores them at
`~/.woosh/config.json` (owner-only file permissions, `chmod 600`). You can
also skip onboarding by exporting `OPENAI_API_KEY` yourself.

## Use

```bash
cd your-project
woosh install
```

Flow:

1. **Scan** (deterministic, no LLM involved) — project files
   (`docker-compose.yml`, `Dockerfile`, `.env*`), host's listening TCP ports,
   running containers, existing docker networks.
2. **Plan** — the agent (OpenAI tool-calling loop) reasons over the scan and
   calls `propose_plan`. It can call `ask_user` mid-reasoning if it's missing
   something (domain name, TLS on/off, how to resolve a port clash). It has
   **read-only** tools in this phase — it cannot write or run anything yet.
3. **Confirm** — the plan is printed (strategy, steps, flagged `[risky]`
   steps, port conflicts, a diff of any `docker-compose.yml` changes). You
   approve or abort. Nothing is touched until you say yes.
4. **Execute** — only now does the agent get `write_file` / `run_shell` /
   `health_check`. `run_shell` is whitelisted at the argv level to
   `docker`/`docker-compose` only. Any destructive action *not* already in
   the approved plan (stopping an unrelated container, pruning, deleting a
   volume) still requires a fresh `ask_user` confirmation.

## Why a shared reverse proxy

On a VM running multiple deployments, the agent is instructed to prefer
attaching new services to one shared Caddy (or Nginx) instance on an external
docker network (`woosh_proxy`) rather than spinning up a proxy per project —
that's what avoids the "everything wants port 80/443" problem permanently.
It only proposes installing a fresh shared proxy if neither is already
running on the host.

## Project layout

```
woosh/
  cli.py                 # `woosh install` / `woosh init`
  config.py              # onboarding + local key storage
  compose_editor.py       # structured docker-compose.yml read/edit/write
  scanners/
    filesystem.py         # detect deploy strategy from project files
    ports.py               # what's listening on the host
    containers.py           # docker ps / docker network ls
  proxy/
    caddy.py, nginx.py     # render reverse-proxy config from routes
    templates/              # Jinja2 templates for both
  agent/
    prompts.py             # plan-phase / execute-phase system prompts
    tools.py                 # OpenAI tool schemas + implementations
    loop.py                   # the tool-calling loop itself
  utils/
    shell.py               # argv-level command whitelist
    display.py              # plan rendering, confirm gate, ask_user UI
```

## Notes / next steps

- The OpenAI key is stored in plaintext JSON with `chmod 600`. For a more
  hardened setup, swap `config.py` to use the `keyring` package (OS
  keychain) instead — the rest of the code doesn't care where the key comes
  from.
- `scan_listening_ports()` uses `psutil`; process-name resolution can be
  incomplete without root on some systems, but port numbers are always
  accurate.
- TLS: Caddy defaults to automatic HTTPS for real domains; pass
  `tls: "internal"` in a route for self-signed/local certs.
