This release is a pre-release and may not be stable for production use.
pAInapple Code
A self-hosted web client for Claude Code, installable as a PWA. The server runs on your own machine and drives Claude Code through the official Agent SDK, using your own Claude subscription. OpenAI Codex CLI support is experimental. Inspired by code-server.
Thanks to the Auto Journal, you can easily pull up the full history of any topic or file you've already worked on. After each turn finishes, the session is forked in the background to a fast model (Haiku by default) that summarizes the turn — and the summary is stored in a local DuckDB and in the project's shadow git, as the commit message over everything that changed during the turn. It's not just for you: the optional shadow-git-helper agent gives Claude the same access, digging through past turns to brief the session with full historical context.
Right now it's a PWA, but desktop and mobile apps are in development.
Documentation · Install · Features · Security
Before you start
This is an MVP, and heavily "vibe-coded". All of the code was written by AI. I try to keep the security hygiene tight, but I can't promise there isn't an RCE hiding somewhere — one more reason to take the isolation advice in Security model seriously. A rewrite to a more rigorous standard is planned; for now there are plenty of ideas I want to implement and test first.
Security model
Whoever can authenticate to pAInapple Code gets the shell and filesystem authority of the OS user that runs it. pAInapple Code exists to run a coding agent on your behalf — /api/exec, the embedded PTY, and every approved tool call execute as that user. Treat the password like an SSH key.
The embedded terminal is a real PTY, and anything Claude is approved to run executes as the user who started the server — prompt injection and poisoned packages are real risks for any coding agent.
The server binds 127.0.0.1 over plain HTTP by default; non-loopback binds auto-enable TLS with a self-signed cert. Auth is a single-password gate — adequate on a home network or behind a personal VPN. I strongly discourage exposing it on a public interface.
It is single-user, not multi-tenant. Don't share one instance between people who shouldn't have each other's shell access; run separate instances as separate OS users instead. → Security notes
Found a vulnerability? Please report it privately — see SECURITY.md.
Simple isolation with the built-in Docker/Podman instance manager
If you have Docker or Podman installed, add the --in-docker flag: it automatically creates and runs a container from an image that already has the basic development tools, the Claude Code CLI, and pAInapple Code itself.
pipx install painapple-code
painapple --in-docker # sandbox the current directory, foreground
painapple --in-docker --workspace ~/dev/ # sandbox ~/dev/ instead, foreground
For a sandbox you come back to, create a named instance. The wizard asks for the workspace, port, and container settings; after that the usual verbs manage it:
painapple setup myapp # wizard — pick "Docker" as the run mode
painapple start myapp # detached, comes back up with the runtime
painapple list # every instance, host or container, running or not
painapple password myapp # print its login URL
painapple stop myapp
Full reference: Docker & container mode.
What it is, and what it isn't
It is a thin wrapper around Claude Code — every prompt streams through the official CLI/Agent SDK, and any session started here can be resumed in the plain CLI with claude --resume <id>. It never modifies Claude's system prompt, tool policy, or behavior — no injected planning steps, no hidden instructions. What it does add to a prompt is the context you attached: the output of !bang commands you ran, paths of files you uploaded, and snippets from the comments stash are prepended as plain text. The same wrapper can drive the OpenAI Codex CLI, selected per session, resumable with codex exec resume <id> — the Codex path is newer and has had less testing than the Claude path.
It is not a hosted service. You run it, on your hardware, with your own Claude account.
It is not zero-config "code from anywhere". The client works as a PWA on a phone or iPad, but the networking between them is yours to wire up. The practical path: keep the default 127.0.0.1 bind and add a reverse proxy (Caddy, nginx) or a VPN for remote access — mobile browsers handle self-signed certificates poorly.
Requirements
- Server: Linux, macOS, or Windows 10/11 — natively, no WSL required. (Windows notes: install the Claude CLI with
irm https://claude.ai/install.ps1 | iex, not npm.) - Direct install: Python 3.12+ and the Claude Code CLI, installed and authenticated. (The Docker image ships Python and Node, and installs the agent CLIs itself on first start.)
- Git on
PATH— the auto-journal records every turn as a shadow-git commit, and the Git panel shells out to it. Without git the server still runs, but journals nothing. (Windows: Git for Windows, which also provides the bash the optional helpers run under.) - Optional: Docker or Podman, if you want the sandboxed
--in-dockerrun mode and the named container instances. - Optional: the OpenAI Codex CLI, if you want the Codex engine.
- Client: any modern browser with network access to the server.
Quick start
pipx (recommended)
No pipx yet? It's two lines (or brew install pipx on macOS):
python3 -m pip install --user pipx
python3 -m pipx ensurepath
Then:
pipx install painapple-code
# Recommended: sandbox each instance in a container (see above).
cd ~/code/my-project
painapple --in-docker
# …or run it straight on the host — it serves the current directory:
painapple
# …or point it at a workspace holding several projects:
painapple --workspace /path/to/your/projects
Installing on the host doesn't mean running on the host. One pipx install manages both: bare is the simplest setup, --in-docker gives each instance its own sandbox without a separate install.
The console prints the app URL with a generated password embedded — open it once and a cookie keeps you logged in.
Plain pip install painapple-code into a venv works too — see the pip/pipx guide.
In container mode the image is pulled automatically on the first run; painapple pull re-fetches it to update or pin a release. State persists in named volumes and your project is bind-mounted. Raw docker run / compose / Podman recipes and source builds: Docker install guide.
Desktop & mobile apps (in development)
Desktop and mobile apps are in development — stay tuned.
More ways to run it
- GitHub Codespaces / Dev Containers — one line in
devcontainer.jsonboots every Codespace with pAInapple Code installed, started, and port-forwarded → Dev Container Feature - From source —
git clone,venv/bin/pip install -e ., run withvenv/bin/painapple→ source install
Authentication
Every HTTP and WebSocket request needs a password. The server generates one on first start, stores it in ~/.config/painapple-code/config.yaml (inside the container that's under /home/app/; owner-only either way — mode 0600 on Unix, an owner-only NTFS ACL on Windows), and logs a bootstrap URL with the token embedded as ?tkn=… — open it once, the cookie does the rest.
# Reveal the password — prints ready-to-open login URLs
painapple password # add a profile name for named deployments
# …or read the config file directly
awk '/^password:/ {print $2}' ~/.config/painapple-code/config.yaml
# …or in a container not managed by the CLI
docker exec painapple-code awk '/^password:/ {print $2}' \
/home/app/.config/painapple-code/config.yaml
# Rotate: delete the config, restart, and a new password is generated
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
Three auth paths: the bridge_auth cookie (set automatically after first login), ?tkn=<password> in any URL, or Authorization: Bearer <password> for curl and scripts.
Server options
The most common flags:
| Flag | Default | Description |
|---|---|---|
--host / --port |
127.0.0.1 / 8765 |
Bind address and port |
--workspace |
. |
Workspace root — the directory holding your projects. You pick the project in-app from the welcome screen (--cwd is an alias) |
--instance-name, --accent |
— | Label + accent color to distinguish multiple instances |
--tls |
auto |
Self-signed TLS, auto-enabled on non-loopback binds |
--default-provider |
claude-sdk |
Default AI engine for new sessions — Claude Code (SDK or classic line protocol) or OpenAI Codex |
--profile |
— | Run a named profile — several independent deployments (host or docker mode) under one user |
--in-docker |
off | Run the same invocation in a container instead (prebuilt image, cwd mounted) |
painapple setup is an interactive wizard that saves global defaults (network/TLS + the container runtime for --in-docker); painapple setup NAME creates a named deployment — host or docker mode — that painapple start NAME runs in the background. painapple list shows every instance on the machine, and status/logs/password NAME inspect any of them.
painapple --help prints the full list; every flag, environment variable, and accent-color preset is in the server CLI reference.
Highlighted features
A selection — the full list is in the feature docs.
Auto Journal (shadow git)
After each turn, the session forks itself in the background to a fast summarizer model (Haiku by default) that reads the whole turn, not just the diff. Its structured write-up — work done, decisions, learnings, problems solved — becomes the commit message for a per-project shadow git repo holding that turn's file changes (respecting .gitignore), and the same fields land in a local DuckDB, so the project's own history is queryable: from the Journal widget, over a SQL endpoint, or by future sessions.
That last case is what the optional shadow-git-helper agent is for — write "consult shadow-git-helper about X" and a sub-agent digs through past turns without loading them into your main context. It's not a backup mechanism; it's a searchable record of what was done and why.
Per-turn summary bar
Context usage, token delta, files changed with diff stats, tool counts, duration, cost, and which model ran — inline after every turn. File pills open diffs and previews directly.
Comments stash
Click the bubble next to any paragraph, add a note, and it attaches — quote included — to your next prompt.
Screenshots ride the same mechanism. Paste an image and the annotation editor opens (pen, arrow, box, text) — drop a numbered marker anywhere on it, type a note, and that note lands in the same stash as "Marker 2 on screenshot.png". The badge pins the spot on the picture, the comment travels as prompt text, and the annotated image is attached to the same message, so the model can connect the two.
Embedded terminal
A real PTY via xterm.js (Ctrl+`). On mobile there's a key bar with Ctrl/Alt/arrows above the keyboard, and a virtual d-pad joystick on touch-and-hold.
Prompt history + favorites
Search every prompt you've ever sent, across all sessions and projects, with phrase, exclusion, date, and content filters (Alt+P or Ctrl+R). Mark favorites; reuse any result or fork it into a new session.
And more
Optional helpers
A shadow-git CLI, a shadow-query DuckDB wrapper, and the shadow-git-helper Claude agent ship with the package — the Docker image installs all three at build time. On a host install:
src/painapple_code/tools/install-helpers.sh # --update / --uninstall / --dry-run
No sudo, no $PATH edits — targets ~/.local/bin and ~/.claude/agents/. Details: optional helpers reference.
Data storage
Everything lives under ~/.painapple-code/ (or $PAINAPPLE_CODE_HOME; /data in Docker): per-project sessions, shadow git repos, the DuckDB turn store, and logs. Auth config sits apart in ~/.config/painapple-code/config.yaml (owner-only: mode 0600 on Unix, an NTFS ACL on Windows), so wiping the data directory doesn't rotate your password. Full layout: data & storage reference.
What it touches on your machine
pAInapple Code runs a coding agent, so it is not a light-touch program. Everything it does to your system, in one place:
It does not write into your project. No dotfiles, no metadata directory, no worktrees. Changes to your code come only from things you asked for — an approved tool call, the editor, the terminal, a shadow-git restore.
Shadow git copies your project into the data home — on by default. After each turn it commits the whole working tree, including untracked files, into a private repo under ~/.painapple-code/. That's what powers the timeline, undo, and file history. It skips .git, node_modules, virtualenvs, build output, logs, .env files, and anything over 50 MB — but a secret in a file that doesn't match those exclusions (say credentials.json) gets copied there and kept in that repo's history. Worth knowing before you point it at a directory full of production keys; disable it per project in the Auto-journal settings.
Auto-journal spends tokens in the background — on by default. After each turn a second, short model call (Haiku by default) summarizes what happened to produce the journal entries and commit messages. It's cheap, but it is real API usage you didn't explicitly trigger. Same settings panel turns it off.
Outside the data home it touches very little. Auth config and TLS cert live in ~/.config/painapple-code/, restricted to your account (mode 0600 on Unix; an owner-only ACL applied with icacls on Windows, since POSIX mode bits do nothing on NTFS). The optional helpers — only if you install them — add two scripts to ~/.local/bin/ and one agent file to ~/.claude/agents/; no sudo, no $PATH or shell-rc edits. (On Windows the two scripts are bash, so each also gets a small generated .cmd wrapper beside it that invokes Git for Windows' bash — that's what lets you call them by name from the PowerShell terminal. Without Git for Windows they're skipped and only the agent file installs.) It reads ~/.claude/ and $CODEX_HOME to list your existing skills, agents, commands, and models.
Nothing phones home. No telemetry, no update checks, no analytics. The only outbound request the server itself makes is the browser widget fetching a URL you asked it to open (guarded against internal-network addresses). The Claude and Codex CLIs it launches talk to their own vendors, as they would anyway.
In Docker, the Claude home is isolated by default. Containers get their own ~/.painapple-code/shared/.claude rather than a mount of your host ~/.claude, so one claude login serves every sandbox without any of them being able to touch your host config. The setup wizard can seed that isolated home from your host login once, and you can point claude_home at the host copy explicitly if you'd rather share it.
Uninstalling the package leaves your data. ~/.painapple-code/ (sessions, shadow repos, the DuckDB store) and ~/.config/painapple-code/ (password, cert) stay until you delete them; the helpers have their own uninstall flag. Full inventory: data & storage reference.
Known weaknesses
This is an MVP — there are tradeoffs.
- Windows support is new — the server now runs natively on Windows 10/11, no WSL: the terminal is ConPTY-backed, file locking and process control have Windows implementations, and CI smoke-tests install/import/boot on every push. But it is by far the youngest and least-exercised platform — Linux is where this is developed and used daily. Expect rougher edges than on Linux or macOS. Two deliberate differences: the terminal tab runs PowerShell (so
!bangcommands are PowerShell-flavored, notsh), and the optionalshadow-git/shadow-queryhelpers are shell scripts that need Git for Windows to provide a bash. - Windowing system — works, but doesn't support multiple instances of the same widget and could use a rethink.
- 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.
- GUI for OS-level features (git widget, file explorer) — exists, but I prefer the embedded terminal for
grep/sed/find/du, so these widgets have not been a priority. - Codex engine — functional, but much newer than the Claude path and not yet as thoroughly tested.
License
Copyright (C) 2026 Michał Booth-Wrotkowski
AGPL 3.0 or later — see LICENSE. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. It is distributed WITHOUT ANY WARRANTY; see the license for details.
Because §13 covers network use, the running app offers its own source: the About panel links to the repository.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file painapple_code-1.0.0rc33.tar.gz.
File metadata
- Download URL: painapple_code-1.0.0rc33.tar.gz
- Upload date:
- Size: 10.2 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
74bfabda941ecebb28438dce2f841e7269b49be03e593db9f7d23d0a45f5614a
|
|
| MD5 |
3b651c42e7d29781ff1f5a5a085b3dd9
|
|
| BLAKE2b-256 |
7993a559765569152e8552c6213c50286a4d96c834eb9f02c57bec41f667e6a0
|
Provenance
The following attestation bundles were made for painapple_code-1.0.0rc33.tar.gz:
Publisher:
pypi-publish.yml on wrotek/painapple-code
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
painapple_code-1.0.0rc33.tar.gz -
Subject digest:
74bfabda941ecebb28438dce2f841e7269b49be03e593db9f7d23d0a45f5614a - Sigstore transparency entry: 2414959554
- Sigstore integration time:
-
Permalink:
wrotek/painapple-code@34ed95d2c3b6fdead97b98fdfcfea5f150fd704f -
Branch / Tag:
refs/tags/v1.0.0-rc33 - Owner: https://github.com/wrotek
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
pypi-publish.yml@34ed95d2c3b6fdead97b98fdfcfea5f150fd704f -
Trigger Event:
push
-
Statement type:
File details
Details for the file painapple_code-1.0.0rc33-py3-none-any.whl.
File metadata
- Download URL: painapple_code-1.0.0rc33-py3-none-any.whl
- Upload date:
- Size: 2.9 MB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
12dfc707d2bb2308fc19a8162429d08f9919cbca12f4e7f704c49bfd55fa0246
|
|
| MD5 |
d37f66018e7250203f82d3e851e09140
|
|
| BLAKE2b-256 |
8bdcd7015cb31e56b62d7bffd616e19281b9713164af693e2c75911261ee571f
|
Provenance
The following attestation bundles were made for painapple_code-1.0.0rc33-py3-none-any.whl:
Publisher:
pypi-publish.yml on wrotek/painapple-code
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
painapple_code-1.0.0rc33-py3-none-any.whl -
Subject digest:
12dfc707d2bb2308fc19a8162429d08f9919cbca12f4e7f704c49bfd55fa0246 - Sigstore transparency entry: 2414959557
- Sigstore integration time:
-
Permalink:
wrotek/painapple-code@34ed95d2c3b6fdead97b98fdfcfea5f150fd704f -
Branch / Tag:
refs/tags/v1.0.0-rc33 - Owner: https://github.com/wrotek
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
pypi-publish.yml@34ed95d2c3b6fdead97b98fdfcfea5f150fd704f -
Trigger Event:
push
-
Statement type: