Skip to content
Merged
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
14 changes: 9 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# ftown

A remote CLI agent orchestrator that lets you manage and stream **Claude Code** and **Cursor Agent** sessions through a web dashboard. Terminal streaming climbs a transport ladder — local loopback, then WebRTC P2P, then Centrifugo — so output stays on your machine or network whenever possible, and automatically upgrades back to a direct connection once it can. Recurring agent work runs as scheduled **loops**: cron or interval triggers that spawn full sessions with guardrails, instead of a hand-rolled polling script.
A remote CLI agent orchestrator that lets you manage and stream **Claude Code, Cursor Agent, Codex, Grok, Pi, Kimi Code, opencode,** and shell sessions through a web dashboard. Terminal streaming climbs a transport ladder — local loopback, then WebRTC P2P, then Centrifugo — so output stays on your machine or network whenever possible, and automatically upgrades back to a direct connection once it can. Recurring agent work runs as scheduled **loops**: cron or interval triggers that spawn full sessions with guardrails, instead of a hand-rolled polling script.

## Demo

Expand Down Expand Up @@ -51,8 +51,12 @@ https://github.com/user-attachments/assets/e9c1ce70-70b0-4ba0-81d8-080d4eeef445

### Everything else

- **Claude Code** and **Cursor Agent** (`agent`) interactive sessions with resume support
- Hook forwarding to the dashboard (Claude `~/.claude/settings.json`, Cursor `~/.cursor/hooks.json`)
- Seven coding-agent CLIs plus raw shells as full interactive sessions
- Parent/child agent trees, durable cross-session mail, and session reparenting
- Native resume support for Claude, Cursor, Codex, and Pi; workdir-based continuation for Kimi Code
- Live per-session token/model usage for harnesses with structured native session logs
- Hook forwarding to the dashboard (Claude/Codex settings, Cursor hooks, and ftown's bundled Pi extension)
- Native Pi tools for session discovery/control, durable ftown mail, token usage, terminal inspection, archives, and loop management
- Multiple concurrent sessions with session management
- Multi-bridge support (connect multiple machines)
- Mobile-optimized responsive UI
Expand All @@ -75,7 +79,7 @@ ftown-sessions loop create \
```

- **Schedules** — interval (`--every 30s|5m|2h|1d`) or cron with timezone (`--cron "0 9 * * 1-5" --tz America/New_York`); create via the dashboard's loop modal or the CLI
- **Harness choice** — claude, cursor, codex, opencode, or plain shell, each with its own configurable workdir and model
- **Harness choice** — claude, cursor, codex, grok, pi, kimi-code, opencode, or plain shell, each with its own configurable workdir and model
- **Guardrails** — `--preflight <cmd>` (a non-zero exit skips the run; its stdout is injected into the prompt via `{{preflight}}`), `--postflight <cmd>` (receives run status, session id, and output), `--max-runtime` to force-stop a run
- **Overlap & retention** — overlapping runs are skipped by default (`--allow-overlap` to permit them); retention keeps only the newest N runs
- **Lifecycle** — pause/resume, fire a one-shot run manually, edit a loop live, and see run history with status dots (running/done/error/skipped/paused) plus next-due time in the dashboard
Expand All @@ -85,7 +89,7 @@ ftown-sessions loop create \
- Node.js 22+
- PostgreSQL database (e.g., [Neon](https://neon.tech))
- [Centrifugo](https://centrifugal.dev) v5 server
- On bridge machines: [Claude Code](https://docs.anthropic.com/en/docs/claude-code) and/or [Cursor CLI](https://cursor.com/docs/cli/overview) (`curl https://cursor.com/install -fsS | bash`, then `agent login`)
- On bridge machines: install and authenticate whichever agent CLIs you plan to run. For Pi: `npm install -g @mariozechner/pi-coding-agent`, then run `pi` and `/login` (or provide a supported provider API key).

## Quick Start

Expand Down
4 changes: 2 additions & 2 deletions bridge/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion bridge/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ftown-bridge",
"version": "0.19.7",
"version": "0.19.8",
"description": "CLI bridge for ftown — generic PTY-over-Centrifugo relay",
"type": "module",
"main": "dist/index.js",
Expand All @@ -16,6 +16,7 @@
"dist",
"bin",
"hooks",
"pi-extension",
"skills"
],
"scripts": {
Expand Down
64 changes: 64 additions & 0 deletions bridge/pi-extension/API.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Pi extension API

The bundled Pi extension exposes ftown's authenticated local bridge API as a small set of model-callable tools. It uses camel-case JSON, opaque string identifiers, plural resource names, and wrapped success objects. Failures are returned to Pi as a tool error with a sanitized `{ "error": string }` detail object.

## Resource model

| Resource | Identity | Relationships | Lifecycle |
| --- | --- | --- | --- |
| Session | Opaque UUID, or a unique exact name for lookup | Optional parent session; owns inbox, usage, screen, and log | Create, inspect, rename, reparent, stop, remove, revive |
| Mail message | Assigned by the session inbox | Sent from one session to another; optional thread | Send, peek/read, delivered |
| Loop | Opaque ID, or a unique exact name for lookup | Owns loop runs | Create, inspect, update, run, delete |

All tools talk only to the local bridge selected by `FTOWN_HOOK_PORT` or `~/.ftown/bridge.json`. The extension supplies that bridge's bearer token and never sends credentials to the model.

## Tool contract

| Tool | Operation | Required input | Result | Mutation |
| --- | --- | --- | --- | --- |
| `ftown_mail` | `send` | `target`, `body` | Stored inbox message | Yes |
| `ftown_mail` | `read` | None | `{ messages }` for the current session | No |
| `ftown_sessions` | `list` | None | `{ sessions }` | No |
| `ftown_sessions` | `archive` | None | `{ archived }` tombstones | No |
| `ftown_sessions` | `get` | `target` | `{ session }` | No |
| `ftown_sessions` | `running` | `target` | `{ sessionId, running }` | No |
| `ftown_sessions` | `usage` | `target` | Session token/model usage | No |
| `ftown_sessions` | `screen` | `target` | Paginated terminal screen | No |
| `ftown_sessions` | `grep` | `target`, `pattern` | Paginated terminal-log matches | No |
| `ftown_session_create` | create | `shell`, `prompt` | Created session | Yes |
| `ftown_session_manage` | `stop` | `target` | Stop acknowledgement | Yes |
| `ftown_session_manage` | `rename` | `target`, `name` | Updated session | Yes |
| `ftown_session_manage` | `reparent` | `target`, `parent` | Updated session | Yes |
| `ftown_session_manage` | `remove` | `target` | Removal acknowledgement | Yes |
| `ftown_session_manage` | `revive` | `target` | Recreated session and resume state | Yes |
| `ftown_loops` | `list` | None | `{ loops }` | No |
| `ftown_loops` | `get` | `target` | `{ loop }` | No |
| `ftown_loops` | `create` | `name`, `task`, `schedule` | `{ loop }` | Yes |
| `ftown_loops` | `update` | `target` and changed fields | `{ loop }` | Yes |
| `ftown_loops` | `delete` | `target` | Removal acknowledgement | Yes |
| `ftown_loops` | `runs` | `target` | Loop run history | No |
| `ftown_loops` | `run_now` | `target` | Requested loop run | Yes |

`target` accepts an opaque ID or a unique exact name. Mail additionally accepts `parent`. Reparenting with `parent: null` clears the parent. Session and loop creation intentionally accept structured fields only; arbitrary session commands, environment variables, and loop preflight/postflight shell commands are not part of the model-facing contract.

The extension also registers `/ftown-mail read [--peek]`, `/ftown-mail send <session> <message>`, and `/ftown-sessions` for interactive use.

## Authorization and safety

The local bridge bearer token authorizes access to the current ftown user's bridge resources. Session name resolution is performed against that same authenticated bridge. The model can inspect a terminal screen or search its captured log, but it cannot inject raw terminal keystrokes, resize terminals, clear terminal history, or call hook/conversation-resolution internals through these tools.

Mutation executions are deduplicated in the running extension by `(tool name, Pi tool-call ID)`, including concurrent retries. Failed attempts are not cached. This prevents a retried model tool call from duplicating mail, sessions, management actions, or loop runs; it is not a durable idempotency key across Pi process restarts.

## Pagination and errors

Inbox reads accept `limit` from 1 to 100. Screen and log operations accept zero-based `offset`; screen `limit` is 1 to 1,000, log `limit` is 1 to 1,000, and grep context is 0 to 10 lines. Session and loop collection endpoints currently return the bridge's complete local collection and inherit its unpaginated behavior.

Transport failures, missing/ambiguous names, unavailable parent context, validation failures, and bridge `{ "error": string }` responses become Pi tool errors. No response body, token, stack trace, or upstream URL is exposed to the model.

## Compatibility

The tool schemas ship with `ftown-bridge` and follow its semantic version. Adding an optional property or operation is additive. Renaming/removing a tool, operation, required field, or result field is breaking. The underlying local HTTP API is deliberately hidden behind these tool contracts so bridge route changes do not require prompt changes.

## Consumer walkthrough

An agent can call `ftown_sessions.list`, use the returned session ID with `ftown_sessions.get` or `usage`, and then send context with `ftown_mail.send`. To delegate, it can create a child with `ftown_session_create`, retain the returned ID, inspect its output, and later rename, reparent, stop, remove, or revive it. For scheduled work, it can create or update a structured loop, request `run_now`, then inspect `runs` without leaving Pi.
Loading
Loading