Skip to content
Open
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
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Pi Pocket is often edited from inside itself, while it runs. [docs/map.md](docs/
- `web/**` changes reload every open browser right away. Keep the app loadable after each save: a syntax error blanks the screen for everyone, including you. Check with `node --check web/<file>.js` after editing.
- `src/server/extensions/*.ts` changes are reinstalled into the running server. A file that fails to load keeps its previous version and reports the error. Each module default-exports `(host, web) => Extension | Extension[] | void`, possibly async (`web` adds routes and a browser module to the app: [docs/extensions/app.md](docs/extensions/app.md)). Start each module with a `/** … */` comment: its first sentence is the description in the app's Extensions sheet, where the owner can turn modules off (a module that is off is not loaded, even when edited).
- Other `src/server/**` changes need a restart (menu → Restart server). Running work resumes after it, but a tool call cut off mid-run that is not replay-safe comes back as interrupted. Do not restart while your own tool call is running; finish the edit, run `npm run check` and `npm test`, then ask the user to restart.
- The owner's drop-in extensions in `~/.pi-pocket/extensions/` load the same way, after the built-in ones, and stay off until the owner turns them on. A `node_modules` link there lets them import Pi Pocket's packages.
- The owner's drop-in extensions in `~/.local/share/pi-pocket/extensions/` (`XDG_DATA_HOME` moves it) load the same way, after the built-in ones, and stay off until the owner turns them on. A `node_modules` link there lets them import Pi Pocket's packages.
- Document kinds in `src/server/docs.ts` are stored data. Do not rename them, or change their scope, history, or fork settings: Pi Durable checks those against what is stored.
- The server is plain TypeScript run by Node's type stripping: only erasable syntax (no enums, no parameter properties, no namespaces). Imports use `.ts` extensions.
- Code style is Prettier (`.prettierrc.json`: 4 spaces, 100 columns) plus ESLint's layout rules (`eslint.config.js`): braces on every `if` and loop, and a blank line after declarations, around blocks, and before `return` and `throw`. Run `npm run format` after editing. Prettier leaves the markup in `html` templates alone, so lay it out by hand the same way: 4 spaces per level, and a tag that runs past 100 columns gets one attribute per line and its children on their own lines. In htm a line break is not a space: break only where there is no space, or between tags, never inside running text.
Expand Down
20 changes: 15 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ While it runs: **q** quit · **r** restart server · **a** change access · **o*
- **Codemode.** On by default: Pi writes short scripts that call its tools, and every call still goes through the same checks.
- **Lancet Guard.** Off by default; turn it on in Menu → Extensions. With the [specpi-lancet-guard](https://github.com/TannerMidd/SpecPi) Pi package installed and on, risky bash, write, and edit calls wait for someone in the session to approve.
- **Roles, invites, and limits.** Steer or view-only access, to every session or just one, through one-time links and QR codes, with spend limits per person and per session.
- **Live-editable.** No build step: edit the web app or an extension and it reloads in place. Add your own extensions in `~/.pi-pocket/extensions/`: tools and checks for Pi, and pages, rail buttons, routes, and slash commands for the app.
- **Live-editable.** No build step: edit the web app or an extension and it reloads in place. Add your own extensions in `~/.local/share/pi-pocket/extensions/` (`XDG_DATA_HOME` moves it): tools and checks for Pi, and pages, rail buttons, routes, and slash commands for the app.

The full tour is in [docs/features.md](docs/features.md).

Expand All @@ -74,7 +74,7 @@ Choose a mode in the launcher, or pass `--access`:
| `cloudflare` | Anyone with the link | Free quick tunnel with https, no account needed (Cloudflare offers quick tunnels for testing and development). The address changes each time the launcher starts but stays the same across server restarts. |
| `tailscale` | Your tailnet | Tailscale address only, encrypted by the tailnet |

Cloudflare mode needs `cloudflared`. On Linux the launcher can download the official release into `~/.pi-pocket/bin`. To use another tunnel (ngrok, `tailscale serve`), choose `local` and point the tunnel at port 8787.
Cloudflare mode needs `cloudflared`. On Linux the launcher can download the official release into `~/.cache/pi-pocket/bin` (`XDG_CACHE_HOME` moves it). To use another tunnel (ngrok, `tailscale serve`), choose `local` and point the tunnel at port 8787.

> [!WARNING]
> Anyone with steering rights can make the agent run commands on this machine, as you. That reaches everything you can: your files, Pi Pocket's own settings and sign-in tokens, and its code. A single-session invite limits what someone sees in the app, not what Pi can reach, and Lancet Guard approvals can come from anyone who can steer, including the person who asked. People who can steer every session can also invite others. Invite only people you would trust at your keyboard. View-only users cannot make Pi act.
Expand Down Expand Up @@ -106,11 +106,21 @@ The flake builds Pi Pocket (`nix run github:TannerMidd/pi-pocket`) and has a Nix
| `-y` | | Reuse the last access choice |
| `--port` | `8787` | Port to listen on |
| `--cwd` | current folder | Default folder for new sessions |
| `--data` | `~/.pi-pocket` | Database, settings, uploads, and push keys |
| `--host` | | Listen on a specific address instead of choosing access |
| `--rotate-token` | | Issue a new owner link and sign out devices that used the old one |

Environment variables: `PI_POCKET_ACCESS`, `PI_POCKET_DIR`, `PI_POCKET_HOST`, `PI_POCKET_PORT`, `PI_POCKET_GUARD=off`, `PI_POCKET_BROWSER` (the browser to run, when Chromium or Chrome is not found by itself), and `PI_POCKET_BROWSER_ARGS` (extra flags for it, such as `--no-sandbox` where sandboxes are unavailable). Without a terminal (under systemd, for example), the launcher uses `--access` or the last choice.
Pi Pocket keeps its files in the [XDG base directories](https://specifications.freedesktop.org/basedir-spec/latest/), a `pi-pocket` folder in each:

| Kind | Default | Holds |
| ------ | -------------------------- | ---------------------------------------------------------------------------------------- |
| config | `~/.config/pi-pocket` | People, roles, tokens, and settings; the launcher's last choice |
| data | `~/.local/share/pi-pocket` | The database and its lock, push keys, uploads, worktrees, drop-in extensions, and themes |
| state | `~/.local/state/pi-pocket` | The Cloudflare tunnel's log |
| cache | `~/.cache/pi-pocket` | The downloaded cloudflared, and the built-in browser's profile |

`XDG_CONFIG_HOME`, `XDG_DATA_HOME`, `XDG_STATE_HOME`, and `XDG_CACHE_HOME` move them, and `PI_POCKET_CONFIG_DIR`, `PI_POCKET_DATA_DIR`, `PI_POCKET_STATE_DIR`, and `PI_POCKET_CACHE_DIR` name each one itself: the `--data` option and `PI_POCKET_DIR` that named one folder are gone. A `~/.pi-pocket` from an older Pi Pocket moves into them on the first start, and is left in place if anything else is in it.

Environment variables: `PI_POCKET_ACCESS`, `PI_POCKET_HOST`, `PI_POCKET_PORT`, `PI_POCKET_GUARD=off`, `PI_POCKET_BROWSER` (the browser to run, when Chromium or Chrome is not found by itself), and `PI_POCKET_BROWSER_ARGS` (extra flags for it, such as `--no-sandbox` where sandboxes are unavailable), and the four folder variables above. Without a terminal (under systemd, for example), the launcher uses `--access` or the last choice.

## Architecture

Expand Down Expand Up @@ -152,7 +162,7 @@ Pi knows where Pi Pocket's own documentation is: [docs/index.md](docs/index.md)
- Pi Durable is experimental, and its API can change between releases. Versions are pinned in `package.json`.
- Only one server process can use the database at a time.
- Forks are separate sessions; there is no branch tree to switch between inside one session.
- Pi's own extensions are not supported: Pi Pocket runs its own extensions on Pi Durable, and drop-ins from `~/.pi-pocket/extensions/`. Pi's prompt templates work as slash commands.
- Pi's own extensions are not supported: Pi Pocket runs its own extensions on Pi Durable, and drop-ins from `~/.local/share/pi-pocket/extensions/`. Pi's prompt templates work as slash commands.
- A git worktree keeps a session's files apart; it is not a sandbox. Pi can still reach everything you can.
- Artifacts run in an opaque-origin sandbox, so `localStorage` and cookies are unavailable inside them.
- The browser needs Chromium, Chrome, Brave, or Edge on the server. Its pages live in memory: a restart opens each one again at its last address, but not what was typed into it. Each session's cookies are its own and last until the page closes. A browser installed as a snap (Ubuntu's Chromium) is used only when there is no other; it cannot open files outside your home folder. The list of servers running on the machine is Linux-only. Tested on Linux and macOS; not yet on Windows.
Expand Down
2 changes: 1 addition & 1 deletion SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Pi Pocket runs a coding agent that executes commands on the machine it runs on,
- **Models** are a person's to choose. Pi starts subagents only on their session's model, or on models the owner allowed (Providers → Models for subagents); PiP passes a model only when its person named it; titles and summaries use the session's own model. Subagents on another model, and PiP, ask a model's sign-in afresh first and use none whose sign-in expired; a session's own run, a subagent on its model, titles, and summaries just try the model, and a run that fails on an expired sign-in marks it. This guards spending against Pi's choices, not against a person: anyone who can steer can pick a session's model, and Pi, which runs commands, can reach Pi's sign-ins and Pi Pocket's files like anything else on the machine.
- **Linked sessions** (Agent Teams, off until the owner turns it on) widen what prompt injection can reach. A session that read something hostile (a page, a file, an issue) can pass it on to the other sessions of its team in a message, and a message makes Pi work in the target for the person Pi worked for in the sender, with that person's access, in a session where other people may have written. Pi is told to take a linked session's message as a request, not as an order from a person, and to do nothing destructive or with credentials only because one asks; that is guidance to the model, not a boundary. A message is fenced, and a team stops after 20 messages without a person, at the starting session's spend limit, and at 10 sessions; a link gives no access to a session someone cannot already see.
- **Plan mode** keeps Pi to planning; it is not a boundary. Programs that people configured to run on their own, such as a git diff driver, run as they would for anyone.
- **Drop-in extensions** in `~/.pi-pocket/extensions/` run inside the server with the owner's rights. They stay off until the owner turns them on. One with a browser module also runs code in the app's own page, with the rights of whoever's tab loads it: the owner's alone unless the module opens it to others (never to someone invited to one session). The page's content security policy keeps that code from loading scripts and styles from another site or fetching from one, but code in the page could still send data out in other ways (navigating the page elsewhere, for one): turn on only browser modules you trust, as with the server part. What a module's route throws with `web.error` reaches everyone who may use that route as it is; anything else it throws, only the owner reads. Every file of a web type (`.js`, `.mjs`, `.css`, `.json`, images, and `.woff2` fonts) in a browser module's folder can be read by everyone the module is for. A module's conversation command is offered to everyone who can steer that session, people invited to only that session included, and its handler runs with the owner's rights: treat its arguments as untrusted, and check who runs it. A global command is the owner's alone.
- **Drop-in extensions** in `~/.local/share/pi-pocket/extensions/` (`XDG_DATA_HOME` moves it) run inside the server with the owner's rights. They stay off until the owner turns them on. One with a browser module also runs code in the app's own page, with the rights of whoever's tab loads it: the owner's alone unless the module opens it to others (never to someone invited to one session). The page's content security policy keeps that code from loading scripts and styles from another site or fetching from one, but code in the page could still send data out in other ways (navigating the page elsewhere, for one): turn on only browser modules you trust, as with the server part. What a module's route throws with `web.error` reaches everyone who may use that route as it is; anything else it throws, only the owner reads. Every file of a web type (`.js`, `.mjs`, `.css`, `.json`, images, and `.woff2` fonts) in a browser module's folder can be read by everyone the module is for. A module's conversation command is offered to everyone who can steer that session, people invited to only that session included, and its handler runs with the owner's rights: treat its arguments as untrusted, and check who runs it. A global command is the owner's alone.
- **The extension gallery** (Menu → Extensions → Find extensions) installs drop-ins other people wrote, so what it installs has the owner's rights once turned on. It reads its index strictly, downloads only over https from each entry's pinned commit, refuses a file whose SHA-256 differs from the entry's, keeps file names inside the drop-in folder, and installs everything off; updates are never automatic. The index's checks and its maintainers' review lower the risk, but do not remove it: read the code (or ask Pi to review it) before you turn one on. Only the owner can use the gallery. A theme from the gallery is CSS, fonts, and images that the owner chooses to install. Pi Pocket checks it as every theme folder before it is in place, so it cannot load anything from other servers, nor the app's own addresses, and it runs no code. It can still hide or relabel parts of the app, approvals too: install only themes you trust.
- **Replies are untrusted.** Anything Pi reads can steer what it writes, so the app page loads nothing from other sites, and replies cannot contain forms, scripts, or styling. Artifacts run in a sandbox with no access to the app. An HTML block in a reply is previewed in a frame of its own with its scripts off and a policy that loads nothing, not even from the app; tapping **Run** runs it as an artifact runs, with scripts and the network but no access to the app, so a page Pi was steered into writing could then send what it shows elsewhere. SVG blocks show as images, which run nothing.

Expand Down
2 changes: 1 addition & 1 deletion docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ How Pi Pocket's parts depend on each other, and the orderings the code relies on

## Durable truth and what memory keeps

Pi Durable owns the lasting state of conversations: conversations, entries, tasks, and Pi Pocket's documents (`docs.ts`), in `pocket.sqlite`. People and settings, push subscriptions, uploads, and worktrees are files beside it ([map.md](map.md#data-pi-pocket-or-pi_pocket_dir)). What `PocketApp` keeps in memory about conversations is read back from storage:
Pi Durable owns the lasting state of conversations: conversations, entries, tasks, and Pi Pocket's documents (`docs.ts`), in `pocket.sqlite`. People and settings, push subscriptions, uploads, and worktrees are files in Pi Pocket's folders ([map.md](map.md#data-the-xdg-folders-or-pi_pocket_dir)). What `PocketApp` keeps in memory about conversations is read back from storage:

- At startup, `#recover` reads each conversation's run, agent, chat, subagents, and who Pi works for in it.
- After that, every commit goes through `#committed`, which keeps the memory up to date.
Expand Down
4 changes: 2 additions & 2 deletions docs/design/theming.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ The goal, from issue #90, point 3: "A theme should be a folder. Copy the main th

## The decisions

1. **A theme is a folder:** `~/.pi-pocket/themes/<id>/`. The built-in **Pocket** theme is `web/themes/pocket/`, in the same format.
1. **A theme is a folder:** `~/.local/share/pi-pocket/themes/<id>/` (`XDG_DATA_HOME` moves it). The built-in **Pocket** theme is `web/themes/pocket/`, in the same format.
2. **Two choices: Theme and Colors.** The theme sets the look and the layout. Colors is a palette: the 22 Omarchy palettes, Desktop, or a folder's `colors.toml`. A layout must stay when the colors change, because Follow desktop and the launcher's previews change colors often. With one list, a layout would need a copy for each palette.
3. **A folder holds colors, CSS, or both.** `colors.toml` is Omarchy's format, unchanged, so any Omarchy theme folder dropped in shows under Colors. When you choose a theme that has its own colors, Colors changes to them too. You can change it after.
4. **Duplicate gives a starter, not a fork.** If Duplicate copied all of `style.css`, you would have a 14,700-line fork that falls behind at each release. So Pocket's folder holds a manifest and a `theme.css` that lists every token, commented out with its default, plus examples for each part.
Expand All @@ -46,7 +46,7 @@ The goal, from issue #90, point 3: "A theme should be a folder. Copy the main th
## The format by example

```
~/.pi-pocket/themes/paper/
~/.local/share/pi-pocket/themes/paper/
theme.json
theme.css
layout.css (theme.css imports it)
Expand Down
2 changes: 1 addition & 1 deletion docs/examples/clock.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/**
* A clock tool, and a /clock-zone command that sets the time zone it uses in a session. An example drop-in extension:
* copy it into `~/.pi-pocket/extensions/`, then turn it on in Menu → Extensions.
* copy it into `~/.local/share/pi-pocket/extensions/`, then turn it on in Menu → Extensions.
*/
import { Type } from "@earendil-works/pi-ai";
import { defineDoc, defineExtension, defineTool } from "@earendil-works/pi-durable";
Expand Down
2 changes: 1 addition & 1 deletion docs/examples/no-force-push.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/**
* No force-pushing: a bash call that force-pushes with git is blocked before it runs, and the system prompt says so.
* An example drop-in extension with a hook and a prompt section: copy it into `~/.pi-pocket/extensions/`, then turn it
* An example drop-in extension with a hook and a prompt section: copy it into `~/.local/share/pi-pocket/extensions/`, then turn it
* on in Menu → Extensions.
*/
import { defineExtension, hook, section, ToolTask } from "@earendil-works/pi-durable";
Expand Down
2 changes: 1 addition & 1 deletion docs/examples/server-status.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/**
* Server status: a page on the rail with this machine's load, memory, and uptime, which stays live while it shows.
* An example drop-in extension with a web part: copy it and `server-status.js` into `~/.pi-pocket/extensions/`, then
* An example drop-in extension with a web part: copy it and `server-status.js` into `~/.local/share/pi-pocket/extensions/`, then
* turn it on in Menu → Extensions.
*/
import { cpus, freemem, hostname, loadavg, totalmem, uptime } from "node:os";
Expand Down
2 changes: 1 addition & 1 deletion docs/extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ This page is the map. Read all of it, then only the pages that your task needs.

## Drop-in or built-in

**A drop-in** is a file in the drop-in folder, `~/.pi-pocket/extensions/` (the `extensions/` folder in the data folder). Use a drop-in for all that one person or one machine wants.
**A drop-in** is a file in the drop-in folder, `~/.local/share/pi-pocket/extensions/` (the `extensions/` folder in the data folder; `XDG_DATA_HOME` moves it). Use a drop-in for all that one person or one machine wants.

- It stays when Pi Pocket updates.
- It starts off. The owner turns it on in Menu → Extensions.
Expand Down
Loading