Metadata-Version: 2.4
Name: painapple-code
Version: 1.0.0rc2
Summary: WebUI wrapper for Claude Code with auto journal - PWA frontend, REST + WebSocket API
Author-email: Michal Booth-Wrotkowski <michal@boothw.com>
License-Expression: AGPL-3.0-or-later
Project-URL: Homepage, https://github.com/wrotek/painapple-code
Project-URL: Source, https://github.com/wrotek/painapple-code
Project-URL: Issues, https://github.com/wrotek/painapple-code/issues
Keywords: claude,claude-code,websocket,bridge,pwa,ipad,remote,ai
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Web Environment
Classifier: Framework :: FastAPI
Classifier: Intended Audience :: Developers
Classifier: Operating System :: POSIX :: Linux
Classifier: Operating System :: MacOS
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
Requires-Python: >=3.12
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: fastapi>=0.104.0
Requires-Dist: uvicorn[standard]>=0.24.0
Requires-Dist: websockets>=12.0
Requires-Dist: duckdb>=1.0.0
Requires-Dist: pytz
Requires-Dist: pyyaml>=6.0
Requires-Dist: Pillow>=10.0
Requires-Dist: python-multipart>=0.0.6
Requires-Dist: cryptography>=41.0
Requires-Dist: httpx>=0.28.0
Requires-Dist: httpcore>=1.0
Requires-Dist: questionary>=2.1
Requires-Dist: claude-agent-sdk<0.3,>=0.2.111
Dynamic: license-file

# pAInapple Code

A WebUI wrapper for [Claude Code](https://github.com/anthropics/claude-code). A Python bridge that runs the CLI as a subprocess and serves a vanilla-JS PWA on top, over a WebSocket chat stream and a REST API — use Claude Code from any browser, with an auto-journal of every turn.

The desktop browser is the primary target, but the UI is mobile-friendly enough to be installable as a PWA on iOS/Android devices. Roughly a third of this app was developed on iOS and the rest on iPad with hardware keyboard.

## Cautions - read first


### Permissions
This is an **MVP**, but by default it now drives Claude through the **Claude Agent SDK**, so approval prompts surface as **interactive approve/deny cards** rather than being auto-denied. The default mode on the SDK engine is **Ask** — every tool call pauses for your decision (see [Interactive permissions](#interactive-permissions-agent-sdk-provider) below).

If you switch to the classic line-protocol provider (`--default-provider claude`), the bridge runs Claude in headless mode (`-p`) where nothing can answer an approval prompt, so any ask is **auto-denied**. On that provider the best experience is 'Auto' or 'Bypass Permissions' (YOLO) modes.

**Permission modes available** (configurable per session in the settings popup):
- **Plan** — read-only
- **Ask** *(SDK default)* — every tool call pauses on an approve/deny card *(Agent SDK provider only)*
- **Don't Ask** — auto-deny unless pre-approved
- **Accept-Edits** — auto-allow writes + some basic bash commands like `cp` or `mv`.
- **Auto** — Claude's AI classifier gates each tool call
- **YOLO** — `bypassPermissions`, full access

Mode and model changes apply **live** to the running session on the Agent SDK provider — no restart needed.

For more info, check Claude Code docs https://code.claude.com/docs/en/permission-modes#available-modes

### Terminal
The terminal widget is a real PTY running as the user that started the server. That means any potential prompt injection and other attacks (including infected npm packages) can run arbitrary commands as user.
This issue isn't unique for this project, but it always worth to remind potential consequences of running not isolated AI agents that can fetch and run random code from the internet (including npm, pip or even this project).

### Isolation
Thats why you should always run claude environment (especially YOLO oriented) in isolation (Docker, VM, LXC, BSD jails and other). 
The Docker setup below with `painapple-docker.sh` wrapper is the easiest path for simple isolation. 
**If those concepts are unfamiliar, this MVP is probably not for you yet.**

By default the server binds `127.0.0.1` over plain HTTP. Bind it to a non-loopback host and it auto-enables TLS with a self-signed cert (browsers show a one-time cert warning — see `--tls`). Either way, the built-in auth is a single-password gate, not a substitute for proper network controls: for real public exposure put it behind your own reverse proxy.

## What it is, and what it isn't

**It is** a thin wrapper around Claude Code — every prompt is streamed to the CLI via `--input-format stream-json --output-format stream-json`. Sessions you create here can be resumed in the regular CLI with `claude --resume <id>`.

**It is not** an AI agent of its own. It does not modify your prompts, inject planning steps, parallelize work, or change Claude's behavior. The one exception is the optional `shadow-git-helper` agent, which knows how to query the Shadow Git history.

**It is not easy installable development from anywhere on mobile.** Yet. It is possible to work from your mobile as PWA app, but you need to wire-up it via network. By default server listens on 127.0.0.1, you can set it with arg `--host x.x.x.x` or using `painapple-code docker setup` (or `painapple-docker.sh setup`) for docker instances. 
With default auto tls mode, it will use self signed cert for non-localhost interfaces which may be the issue for mobile browsers. 
My advice is to keep listening on 127.0.0.1 and use reverse proxy (Caddy for example) for SSL on trusted network interface (like home or personal VPN).

## Requirements

- Python 3.12+
- [Claude Code CLI](https://github.com/anthropics/claude-code) installed and authenticated
- Network access between client and server (any modern browser works as a client)

## Quick start (Docker / Podman — recommended)

The image bundles Python 3.13, Node 20, `@anthropic-ai/claude-code` and some basic dev tools. 
State persists in named volumes; your project and an isolated Claude CLI home are bind-mounted from the host.

### Option A — built-in `docker` CLI (pipx, no clone needed)

The pip package ships a `painapple-code docker` command group with an interactive TUI setup — arrow-key menus, a browsable directory picker (type to filter, `←`/`→` to climb/enter folders), back navigation on every step, and a final review screen that jumps back into any section.

```bash
pipx install painapple-code
painapple-code docker           # first run opens the setup wizard
painapple-code docker pull      # fetch wrotek/painapple-code:latest (pull v1.0.0 to pin)
painapple-code docker up -d     # start the bridge detached
```

In a hurry? `painapple-code docker quick` skips setup entirely and starts the bridge against the current directory with sane defaults.

Other subcommands: `stop`, `down`, `restart`, `logs`, `shell`, `claude-login`, `password` (reveal the login URL), `extract`, `status`, `config` (non-interactive `--flag value` overrides). Run `painapple-code docker help` for the full list. The setup wizard configures an isolated `.claude` home by default (the container doesn't share state with your host CLI) and offers to seed it from your host login once.

Docker and Podman are auto-detected; override with `RUNTIME=docker` or `RUNTIME=podman` if both are installed. This CLI is pull-only — building the image from source needs a repo checkout (Option C below; both tools share the same config, so mixing them works).

### Option B — raw `docker run` (one-liner, no clone needed)

```bash
docker run -d --name painapple-code \
    -p 127.0.0.1:8765:8765 \
    -v "$PWD:/workspace" \
    -v "$HOME/.painapple-code/.claude:/home/app/.claude" \
    -v painapple-data:/data \
    wrotek/painapple-code:latest
docker logs painapple-code 2>&1 | grep -E 'http(s)?://' | head -1   # bootstrap URL
```

Tags: `:latest` (newest stable), `:vX.Y.Z` (pinned to a specific release), `:edge` (manual builds off `main`).

### Option C — build from source with `./painapple-docker.sh` (clone the repo)

The Bash wrapper does everything Option A does and adds local image builds (including personalized builds layered from your `devcontainer.json` or Dockerfile).

```bash
./painapple-docker.sh setup     # interactive: workspace, claude credentials, port, accent
./painapple-docker.sh build     # build the image locally (~2-3 min, once)
./painapple-docker.sh up -d     # start the bridge detached
```

Open `http://localhost:8765/` in a browser. The first run logs a bootstrap URL with the auth password embedded as `?tkn=…` — open that link once and the cookie will keep you logged in.

### Manual Docker Compose (no wrapper)

```bash
docker compose build --build-arg USER_UID=$(id -u) --build-arg USER_GID=$(id -g)
WORKSPACE=/absolute/path/to/your/project docker compose up
```

By default the compose file mounts `~/.painapple-code/claude-home/` as the container's `.claude`. To seed it with your existing login:

```bash
mkdir -p ~/.painapple-code/claude-home
cp ~/.claude/.credentials.json ~/.painapple-code/claude-home/
```

### Manual Podman (no wrapper)

```bash
podman build --build-arg USER_UID=$(id -u) -t painapple-code:latest .
podman run --rm -it --userns=keep-id \
    -p 8765:8765 \
    -v painapple-data:/data \
    -v "$HOME/.painapple-code/claude-home:/home/app/.claude:Z" \
    -v "/absolute/path/to/your/project:/workspace:Z" \
    painapple-code:latest
```

## Quick start (GitHub Codespaces / Dev Containers)

Drop one line into a project's `.devcontainer/devcontainer.json` and every Codespace you open on that project boots with pAInapple Code installed, started, and port-forwarded — ready to connect from an iPad or any browser:

```jsonc
{
    "image": "mcr.microsoft.com/devcontainers/base:ubuntu",
    "features": {
        "ghcr.io/wrotek/painapple-code/painapple-code:1": {}
    },
    "forwardPorts": [8765]
}
```

Set `ANTHROPIC_API_KEY` as a Codespaces secret (or `claude login` in the codespace shell once). See [`examples/devcontainer/`](examples/devcontainer/) for a complete config and [`features/src/painapple-code/`](features/src/painapple-code/) for Feature options.

Want to drop pAInapple Code into an already-running Codespace without
touching `devcontainer.json` (e.g. to share with a tester)? Run the
Feature's `install.sh` directly:

```bash
curl -fsSL https://raw.githubusercontent.com/wrotek/painapple-code/main/features/src/painapple-code/install.sh \
    | sudo bash
```

Note: this won't survive a "Rebuild Container". For persistent installs use the `features` block above.

## Quick start (run directly)

No container — the bridge runs bare on the host (needs the Claude Code CLI installed and authenticated):

```bash
pipx install painapple-code
painapple-code --workspace /path/to/your/project
```

Or from a clone:

```bash
git clone <repo-url> && cd painapple-code
python3 -m venv venv
venv/bin/pip install -e .
venv/bin/python -m painapple_code --workspace /path/to/your/project
```

(`source venv/bin/activate` is bash-only. On fish/csh use `venv/bin/python` directly, or your shell's matching activate script.)

Open `http://localhost:8765/` and follow the bootstrap URL printed on first run.

## Authentication

The server requires a password for every HTTP and WebSocket request. On first start it generates a token and stores it in the config dir (`~/.config/painapple-code/config.yaml` — inside the container image that's under `/home/app/`; mode 0600 either way), then logs a bootstrap URL with the token embedded as `?tkn=…`.

```bash
# Reveal the password
awk '/^password:/ {print $2}' ~/.config/painapple-code/config.yaml
# …or when running in a container
docker exec painapple-code awk '/^password:/ {print $2}' \
    /home/app/.config/painapple-code/config.yaml

# Rotate (delete → restart, new password generated on next start)
rm ~/.config/painapple-code/config.yaml   # then restart the server
# …or when running in a container
docker exec painapple-code rm /home/app/.config/painapple-code/config.yaml
docker restart painapple-code
docker logs painapple-code 2>&1 | grep -E 'http(s)?://' | head -1
```

Three auth paths work for HTTP requests:

- Cookie `bridge_auth=<HMAC-derived-token>` (set automatically after first auth)
- Query string `?tkn=<password>` (HTTP middleware also issues a `Set-Cookie`)
- Header `Authorization: Bearer <password>` (for `curl` / scripts)

## Server options

These flags apply to the default server invocation (`painapple-code [flags]`, or the explicit `painapple-code serve [flags]`). Container lifecycle lives under the `painapple-code docker …` subcommand group — see Quick start above.

| Flag | Default | Description |
|------|---------|-------------|
| `--host` | `127.0.0.1` | Bind address |
| `--port` | `8765` | Listen port |
| `--workspace` | `.` | Workspace directory — the dir Claude operates in. (`--cwd` is an alias.) |
| `--workspace-root` | `--workspace` | Directory scanned for sibling project folders surfaced on the welcome screen |
| `--instance-name` | — | Label for PWA icon and header (e.g., `DEV`, `STABLE`) |
| `--accent` | — | Accent color: preset name or hex (`#f87171`) |
| `--tls` | `auto` | TLS mode: `auto` (on for non-loopback binds), `on`, `off`. Self-signed cert auto-generated; fingerprint shown at startup |
| `--tls-cert` | auto-generated | TLS cert path (default `<config-dir>/cert.pem`) |
| `--tls-key` | auto-generated | TLS key path (default `<config-dir>/key.pem`) |
| `--tls-fp-url` | off | Append the cert fingerprint to login URLs as `&fp=…` (for cert-pinning clients; browsers ignore it) |
| `--shadow-db` | `~/.painapple-code/shadow.duckdb` | DuckDB path for the turn store |
| `--log-dir` | `~/.painapple-code/logs/` | Log directory |
| `--auth-config-file` | `~/.config/painapple-code/config.yaml` | Override auth config path |
| `--default-provider` | `claude-sdk` | Engine for **new** sessions (existing sessions keep their recorded provider): `claude-sdk` (Agent SDK driver — interactive permission cards, live mode/model switching) or `claude` (line protocol). Overrides the `default_provider` config key. |
| `--enable-eruda` | off | Enable the Eruda mobile-devtools quick action (loads from CDN) |
| `-v`, `--version` | — | Print version and exit |

### Environment variables

| Variable | Purpose |
|----------|---------|
| `PAINAPPLE_CODE_HOME` | Override the data directory (default `~/.painapple-code`). Set to `/data` in Docker. |
| `PAINAPPLE_CODE_CONFIG` | Override the config directory (default `~/.config/painapple-code`) — where `config.yaml` and the auto-generated TLS cert/key live. |
| `BRIDGE_ALLOWED_ORIGINS` | Comma-separated CORS allow-list. Defaults to localhost on ports 8765/8800/8880. Set this when serving from a public hostname. |

### Color presets

| Preset | Hex |
|--------|-----|
| `blue` | `#58a6ff` |
| `green` | `#22c55e` |
| `red` | `#f87171` |
| `orange` | `#fb923c` |
| `purple` | `#c084fc` |
| `cyan` | `#06b6d4` |
| `gray` | `#9ca3af` |
| `yellow` | `#facc15` |
| `pink` | `#f472b6` |
| `teal` | `#14b8a6` |
| `indigo` | `#818cf8` |
| `lime` | `#a3e635` |

Any hex color also works: `--accent '#e11d48'`.

## Highlighted features

### Interactive permissions (Agent SDK provider)

By default the bridge drives Claude Code through the official **Claude Agent SDK** rather than a bare `claude -p` pipe (the raw `stream-json` is still teed verbatim, so sessions stay resumable in the plain CLI). This opens up the SDK's control plane, which unlocks a genuinely interactive approval flow.

- **"Ask" mode** — every tool call pauses on an inline **approve / deny card**. Cards render a human-readable preview of what's about to run (Write shows the file and content, Edit/MultiEdit an old→new diff, Bash the command) instead of a raw JSON blob, plus a live elapsed-wait timer and a deny box where your typed guidance is fed back to the model.
- **"Always allow" shortcuts** — cards surface the engine's own rule suggestions (allow this tool, trust this directory, or switch mode) so you can widen permissions in one tap.
- **Live mode & model switching** — changing the permission mode (YOLO, Accept Edits, Auto, Don't Ask, Plan, Ask) or the model now applies to the *running* process over the control plane, effective immediately even mid-turn — no more killing and restarting the session just to change levels. The **Stop** button interrupts gracefully and keeps the process warm, so the next message skips the `--resume` cost.
- **Paused-turn UX** — a background tab waiting on an approval gets an attention badge and a click-to-go toast, and the activity strip shows an amber "Waiting for permission…" lock with a frozen timer.

The classic line-protocol `claude` provider is still available (`--default-provider claude`) and keeps its static permission modes for line-only environments.

### Shadow Git with auto-journal

After each turn the bridge runs a Haiku-summarization fork in the background and commits all file changes to a per-project shadow git repository (respecting `.gitignore`). The summary is parsed into structured fields (work done, decisions, learnings, problems solved, files changed, …) and stored in a local DuckDB so future sessions can query it.

<img src="docs-ai/readme/assets/shadow-journal.png" alt="Journal widget showing per-turn Haiku summaries, files changed and cost, grouped by session" width="500">

The optional **`shadow-git-helper`** agent knows how to explore that history. After installing it (see [Optional helpers](#optional-helpers)) you can write prompts like *"Consult with shadow-git-helper about X"* and Claude spawns a sub-agent that pulls the historical context — keeping your main thread focused on the current task instead of bloating it with research.

This is **not a backup.** The shadow commit happens after the Haiku fork finishes, so a crash in between leaves a gap. Use it for "what did I change last time?" not "restore from disaster".

### Comments stash

Click the bubble icon next to any paragraph/list item/heading and the message you type gets attached, with the selected quote, to your next prompt.

![Selecting a paragraph, adding a note, and the stash attaching itself to the next prompt](docs-ai/readme/assets/comments-stash.gif)

### Per-turn summary bar

After each turn an inline bar shows context usage (filled %, this-turn gain, token count), token delta, files changed (with +/- diff stats and git status), tools used count, duration, cost, and which model ran. Click to expand the full breakdown. File pills are clickable on their own — tap for diff/preview, long-press for compare presets.

![Collapsed per-turn summary bar with file pills, tool counts, cost and duration](docs-ai/readme/assets/turn-summary-bar.png)

![Expanded summary bar showing the context-window breakdown and per-file session changes](docs-ai/readme/assets/turn-summary-bar-expanded.png)

### Embedded terminal

Real PTY via xterm.js, toggle with ` Ctrl+\ `. On mobile there's a keyboard extension bar with Ctrl/Alt/arrows above the iOS keyboard, long-press for shortcut popups, and a virtual d-pad joystick on touch-and-hold.

![Embedded terminal running ls, git status and the project test suite](docs-ai/readme/assets/terminal.gif)

### Markdown quick edit

Open any markdown file in the file preview, press `e`, and click any block — paragraph, heading, list item, blockquote — to edit the raw markdown in place. Enter saves to disk, Shift+Enter adds a newline, Esc cancels. Task-list checkboxes (`- [ ]` / `- [x]`) toggle through the same plumbing.

![Editing a list item in place in the rendered preview, then toggling a task checkbox](docs-ai/readme/assets/markdown-quick-edit.gif)

### Prompt history + favorites

Heart any prompt to favorite it. The history explorer (`Alt+P` or `Ctrl+R`) searches every prompt across all sessions and projects with phrase, exclusion, date, and content filters. From results you can reuse, fork into a new session, or jump back to the original message.

<img src="docs-ai/readme/assets/prompt-history.png" alt="Prompt history explorer with search filters, a favorited prompt, and reuse actions" width="700">

### Snippets and agents picker (`#`)

Type `#` in the chat input to insert a snippet or trigger an agent (built-in, global, or project-local). Sibling triggers on the same toolbar: `/` slash commands, `@` files, `$` skills.

<img src="docs-ai/readme/assets/snippets-picker.png" alt="The # picker popup listing agents and user snippets above the chat input" width="906">

### Browser widget

A floating panel that renders local HTML files or loads external URLs through a same-origin proxy that strips frame-blocking headers and rewrites sub-resources. Pages run in a sandboxed iframe with no access to bridge cookies or auth. Per-session URL and back/forward history, like the terminal.

<img src="docs-ai/readme/assets/browser-widget.png" alt="Browser widget rendering a local HTML test report in file mode" width="700">

### And more

Multi-session tabs (up to 5), git widget, cost analytics (`Alt+4`), file explorer (`Alt+F`), inline file/skill/agent suggester, JSON / CSV / Markdown rendered preview, multi-OAuth tokens configurable per session, paste-to-annotate image editor (draw on a pasted screenshot before upload), and live density modes (compact / normal / spacious) switchable from the status bar.

![File explorer and git widget open over a session, showing the working tree and uncommitted changes](docs-ai/readme/assets/overview.png)

<img src="docs-ai/readme/assets/cost-analytics.png" alt="Cost analytics widget with per-model and per-thread cost breakdown" width="700">

## Optional helpers

A small `shadow-git` CLI, a `shadow-query` DuckDB-query wrapper, and the `shadow-git-helper` Claude agent template ship with the repo. The Docker image installs all three at build time, so containerised deploys get them for free. On a host install, run the script yourself:

```bash
src/painapple_code/tools/install-helpers.sh             # install (skip if already present)
src/painapple_code/tools/install-helpers.sh --update    # refresh from repo
src/painapple_code/tools/install-helpers.sh --uninstall # remove
src/painapple_code/tools/install-helpers.sh --dry-run   # preview
```

This copies:

| Source | Target |
|---|---|
| `src/painapple_code/tools/shadow-git` | `~/.local/bin/shadow-git` |
| `src/painapple_code/tools/shadow-query` | `~/.local/bin/shadow-query` |
| `src/painapple_code/tools/agents/shadow-git-helper.md` | `~/.claude/agents/shadow-git-helper.md` |

No `sudo`, no `$PATH` edit. The agent invokes the CLI via the absolute path `~/.local/bin/shadow-git`, so it works the same in Docker, VMs, Codespaces, and WSL.

## Vendored frontend assets

CodeMirror 6 and xterm.js are served same-origin from `static/vendor/`, not loaded from a CDN. The bundles are checked in so a fresh clone or Docker build runs without any frontend build step.

To upgrade those libraries, bump versions in `tools/package.json` and run:

```bash
cd tools && npm install
```

`npm install` triggers a `postinstall` hook that regenerates `static/vendor/{codemirror.js,xterm.js,...}`. Commit the regenerated files alongside the lockfile change. See [`static/vendor/README.md`](static/vendor/README.md).

## Data storage

All bridge data lives under `~/.painapple-code/` (or `$PAINAPPLE_CODE_HOME` if set):

```
~/.painapple-code/
├── config.json              # Global settings
├── shadow.duckdb            # Turn metadata database
├── logs/                    # Server logs (auto-rotating)
└── projects/{hash}/         # Per-project data
    ├── sessions/{id}/       # Session messages, metadata, uploads
    └── shadow-git/          # Per-project shadow git repository
```

Auth config lives separately at `~/.config/painapple-code/config.yaml` (mode 0600) so it survives wiping the data directory.

## Known weaknesses

This is an MVP — there are tradeoffs.

1. **Windowing system** — works, but doesn't support multiple instances of the same widget and could use a rethink.
2. **Code editor** — currently a notepad with syntax highlighting. The plan is a review-driven workflow rather than a VSCode-grade editor; the markdown inline editor is the exception and works well for plan/doc tweaks.
3. **GUI for OS-level features** (git widget, file explorer) — exists but the maintainer prefers the embedded terminal for `grep`/`sed`/`find`/`du`, so these widgets have not been a priority.

## License

AGPL 3.0 — see [`LICENSE`](LICENSE).
