{"id":51558495,"url":"https://github.com/oscarlehuu/nuncio","last_synced_at":"2026-07-10T08:01:44.650Z","repository":{"id":367655477,"uuid":"1281262059","full_name":"oscarlehuu/nuncio","owner":"oscarlehuu","description":"Self-hosted, mobile-first web app for delegating tasks to AI coding agents (Pi, Cursor) — like Devin but provider-neutral.","archived":false,"fork":false,"pushed_at":"2026-07-04T02:25:11.000Z","size":6784,"stargazers_count":1,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-07-04T03:10:53.740Z","etag":null,"topics":["ai-agents","ai-coding-agent","bun","coding-agent","mobile-first","nestjs","pwa","react","self-hosted","shadcn-ui","sqlite","sse","tailscale","tailwindcss","typescript"],"latest_commit_sha":null,"homepage":"https://github.com/oscarlehuu/nuncio","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/oscarlehuu.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-06-26T11:31:12.000Z","updated_at":"2026-07-04T02:24:40.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/oscarlehuu/nuncio","commit_stats":null,"previous_names":["oscarlehuu/nuncio"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/oscarlehuu/nuncio","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oscarlehuu%2Fnuncio","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oscarlehuu%2Fnuncio/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oscarlehuu%2Fnuncio/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oscarlehuu%2Fnuncio/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/oscarlehuu","download_url":"https://codeload.github.com/oscarlehuu/nuncio/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oscarlehuu%2Fnuncio/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35325188,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-10T02:00:06.465Z","response_time":60,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai-agents","ai-coding-agent","bun","coding-agent","mobile-first","nestjs","pwa","react","self-hosted","shadcn-ui","sqlite","sse","tailscale","tailwindcss","typescript"],"created_at":"2026-07-10T08:01:42.784Z","updated_at":"2026-07-10T08:01:44.638Z","avatar_url":"https://github.com/oscarlehuu.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Nuncio\n\n[![CI](https://github.com/oscarlehuu/nuncio/actions/workflows/ci.yml/badge.svg)](https://github.com/oscarlehuu/nuncio/actions/workflows/ci.yml)\n[![Release](https://github.com/oscarlehuu/nuncio/actions/workflows/release.yml/badge.svg)](https://github.com/oscarlehuu/nuncio/actions/workflows/release.yml)\n[![license: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)\n[![runtime: Bun](https://img.shields.io/badge/runtime-Bun%20%E2%89%A5%201.3-f9f1e1?logo=bun)](https://bun.sh)\n[![PRs welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](CONTRIBUTING.md)\n\nA self-hosted, mobile-first web app for delegating tasks to AI coding agents. Run it on your own machine, point it at your own Pi / Codex / Cursor credentials, and assign work from your phone — agents keep going while you're away, and you can steer them mid-task.\n\nThink Devin, but self-hosted and provider-neutral: the agent layer is a single interface, so Pi, Codex, Cursor, and future agent SDKs plug in uniformly.\n\n## Features\n\n- **Delegate tasks** — create a session with a prompt; the agent runs in-process and streams output as events\n- **Per-session provider + model** — choose the agent provider (`pi` / `codex` / `cursor`) and the exact model (e.g. `codex:gpt-5.5`, `cursor:composer-2`, `anthropic:claude-sonnet-4`) per session; both are stored on the session and wired through to the provider runtime\n- **Steer mid-task** — send follow-up messages that continue the same agent conversation when the provider supports it\n- **Pause / archive / restore / delete** — suspend a running session, retire it to the Archived tab, restore it back to IDLE, or permanently delete it; a session FSM enforces valid transitions and a confirm dialog guards deletes\n- **Real-time + replay** — WebSocket relay (subscribe/steer on one duplex channel, gap-free resume via the event-log cursor — see [docs/ws-relay-contract.md](docs/ws-relay-contract.md)) plus the SSE stream and cursor replay endpoints for API consumers\n- **Mobile-first PWA** — installable on iPhone via Tailscale HTTPS; standalone dark UI, safe-area aware\n- **Interactive browser dock** — desktop uses a real embedded Electron browser view with a persistent Nuncio profile; the web/PWA surface does not expose a browser dock\n- **Self-hosted** — your machine, your SQLite, your credentials; nothing leaves your tailnet\n- **Provider-neutral agent layer** — `AgentProvider` interface + `AgentRegistry`; Pi, Codex, Cursor, and Mock today, extensible\n- **Settings store** — runtime-configurable env vars (API keys, paths, flags) stored in SQLite and editable via the frontend; secrets encrypted at rest (AES-256-GCM), env vars still honoured as fallback\n- **Codex approvals** — switch Codex between full-access and approval-required mode from the composer, then approve or deny pending provider actions in the transcript\n- **Folder picker** — browse the host machine's directories to pick a project (server-side, works on iPhone PWA), or paste a custom path\n- **Workspace control** — choose a project, run in the local checkout, or create a new `nuncio/\u003csessionId\u003e-\u003cslug\u003e` worktree forked from the selected branch\n- **Continue on mobile** — import an in-progress Cursor IDE/CLI chat or Pi CLI session from your Mac and steer it from the phone PWA (Cursor uses CLI `--resume`; Pi resumes in-process through the SDK)\n- **Session grid** — a desktop-first multi-session workbench at `/grid`: lay out sessions in a 1x1/2x1/2x2/3x2 preset, watch each tile stream its transcript tail with a status-coded border, steer the focused tile inline, and maximize any tile into the full session view; keyboard-first (`Cmd/Ctrl+1..9` focus a slot, `Cmd/Ctrl+Enter` maximize, `Esc` restore), with preset and slot bindings persisted locally\n- **Cross-machine grid (hub mode)** — grid slots can target any tailnet machine reachable through the hub: pick a machine in the slot composer to browse its projects, use its model catalog, and start or attach sessions there; remote tiles stream and steer live against that machine, show a reconnect state while it is down, and maximize into the session on the machine's own page\n- **Inspector dock** — the session side panel (source control + pull request, files, terminal, browser on desktop) remembers whether it was open and its last tab across visits; the source-control tab now includes opening a PR and watching its checks\n\n## Screenshots\n\n\u003e Screenshots live in [`assets/`](assets/) (added as needed). Until then, the in-app UI matches [`mockup.html`](mockup.html) (the UI blueprint used during development).\n\n## Status\n\nPhase 0–3 complete (vertical slice · PWA/mobile · steer + model picker) with agent-provider abstraction and Pi/Codex/Cursor providers. Phase 4 workspace support is partially shipped (project picker + Work locally/New worktree mode picker); PR flow/cleanup and Phase 5 (web push / webhooks) remain planned — see [Roadmap](#roadmap).\n\n## Changelog\n\nReleases are versioned and documented with [Changesets](https://github.com/changesets/changesets) — each pull request ships a hand-written summary fragment, so the changelog reads like curated release notes rather than a commit log.\n\n- **In app:** open the sidebar → ✨ **What's new** (bottom-left) to browse every release, grouped by version and category, with links back to the pull requests that shipped each change.\n- **On GitHub:** each release is published as a [GitHub Release](https://github.com/oscarlehuu/nuncio/releases) with the matching changelog section as the body, tagged `v\u003cversion\u003e`.\n- **In the repo:** [`CHANGELOG.md`](CHANGELOG.md) is the single source of truth.\n\nTo add a changelog entry for your PR:\n\n```bash\nbun run changeset        # select \"nuncio\", pick minor/patch, write a release-note-style summary\n```\n\nMerging PRs triggers a `chore: release version` PR that bumps the version and updates `CHANGELOG.md`; merging that PR cuts the release (git tag + GitHub Release). See [`.changeset/README.md`](.changeset/README.md) and [AGENTS.md → Releases \u0026 changelog](AGENTS.md) for the full workflow.\n\n## SDK lane branches\n\nProvider SDK work lands through integration branches before `main`:\n\n```bash\ncursor/\u003cfeature\u003e  →  cursor-sdk  →  main\npi/\u003cfeature\u003e      →  pi-sdk      →  main\ncodex/\u003cfeature\u003e   →  codex-sdk   →  main\n```\n\nUse `codex/\u003cslug\u003e` branches for Codex provider work and open PRs against `codex-sdk`. Verify locally with:\n\n```bash\nBASE_REF=codex-sdk HEAD_REF=codex/my-feature bun run check-branch-flow\n```\n\n## Quick start\n\nRequires [Bun](https://bun.sh) ≥ 1.3 (the server uses `bun:sqlite`, a Bun builtin — Node won't work).\n\n```bash\nbun install\ncp .env.example .env   # optional but recommended — shared SQLite across worktrees\nbun run dev\n```\n\n- **API:** http://localhost:3000/api/health\n- **Web:** http://localhost:5173 (proxies `/api` → 3000)\n\n### Local data (sessions \u0026 settings)\n\nSQLite lives under `NUNCIO_DATA_DIR` (default: `./data` relative to the **server process cwd**, which is usually `apps/server/data/` when you run `bun run dev` from the repo root). Each git checkout or worktree without a shared path gets its **own empty database** — that is why a feature worktree can show an empty sidebar while your main clone has sessions.\n\n**Recommended:** point every checkout at one directory:\n\n```bash\ncp .env.example .env\n# edit if needed — default is $HOME/.nuncio/data\n```\n\nMigrate existing data once (example if your sessions were under `apps/server/data/`):\n\n```bash\nmkdir -p ~/.nuncio/data\ncp -a apps/server/data/. ~/.nuncio/data/\n```\n\nRestart `bun run dev` on port **3000** only — see [CONTRIBUTING.md → Dev server ports](CONTRIBUTING.md#dev-server-ports--dont-squat-new-ports).\n\n```bash\nbun run build   # build server + web\n```\n\n### Pi credentials\n\nNuncio drives the [Pi SDK](https://github.com/earendil-works/pi) in-process. Log in with the `pi` CLI first so `~/.pi/agent/auth.json` exists — it holds your API key **or** OAuth/subscription tokens (OpenAI, Anthropic). Override the agent directory with `PI_CODING_AGENT_DIR`. When no Pi credentials are configured, Nuncio falls back to a built-in **Mock** provider so the UI still works end-to-end.\n\n### Cursor credentials\n\nSet `CURSOR_API_KEY` (from [Cursor dashboard](https://cursor.com/dashboard/cloud-agents)) to enable the **Cursor** provider (`provider: \"cursor\"`). Uses `@cursor/sdk` local runtime under Bun with `useHttp1ForAgent` + `JsonlLocalAgentStore` escape hatches. Default cwd: `NUNCIO_CURSOR_CWD` or `process.cwd()`. Per-session `workspace` field (Phase 4 UI) overrides cwd when set. The key can also be set via the **Settings** UI (gear icon in the sidebar) — it's stored encrypted at rest and overrides the env var without a restart.\n\n### Codex credentials\n\nNuncio launches the local Codex CLI's **app server** (`codex app-server`) for the **Codex** provider (`provider: \"codex\"`). Log in once with the CLI first:\n\n```bash\ncodex login\ncodex login status\n```\n\nThe default binary is `codex` on `PATH`. Override it with `NUNCIO_CODEX_BIN`; override Codex's home with `NUNCIO_CODEX_HOME`; override the default cwd with `NUNCIO_CODEX_CWD`. `NUNCIO_CODEX_RUNTIME_MODE=full-access` is the default for local self-hosted use. `approval-required` starts Codex in read-only/untrusted mode and surfaces pending provider approval requests in the session transcript. Pending request state is stored in SQLite; if the server restarts while Codex is waiting, Nuncio marks that stale request denied because the original app-server callback is gone.\n\n### Desktop browser profile\n\nThe browser dock is a desktop-only native Electron `BrowserView` embedded in the\nsession view. It uses Electron's persistent `persist:nuncio-browser` partition,\nso cookies, cache, localStorage, and session storage survive app restarts without\ntouching your personal Chrome profile.\n\nThe web/PWA surface does not expose a browser dock. Normal browsers cannot embed\narbitrary sites like Google as a real child browser, and Nuncio intentionally\navoids presenting a streamed remote-browser viewport there.\n\n## Testing\n\n```bash\nbun run test                                       # server unit tests (simulated providers)\nbun run --filter @nuncio/server test:e2e           # HTTP e2e (simulated provider)\nbun run --filter @nuncio/server test:integration   # real Pi auth — skips when ~/.pi/agent absent\nbun run --filter @nuncio/web test                  # web component tests (vitest)\n```\n\nAll server tests run on `bun test` (no jest). The integration suite is gated on `~/.pi/agent/auth.json` and self-skips when absent, so it is CI-safe.\n\n## Production deploy (Tailscale)\n\nBuild and run the production stack on your machine, then expose it over Tailscale for HTTPS access from your phone or other devices on your tailnet.\n\n```bash\nbun run build\nbun run --filter @nuncio/server start:prod   # API + built UI on :3000\ntailscale serve --bg 3000\n```\n\nOpen `https://\u003cyour-machine\u003e.\u003ctailnet\u003e.ts.net` — Tailscale terminates TLS so iPhone PWA install works.\n\n**Single-port serving:** In dev, Vite still runs on :5173 and proxies `/api` to the NestJS server on :3000. After `bun run build`, the Nest/Bun daemon serves `apps/web/dist` itself: `/api/*` remains JSON API traffic and every other app route falls back to the built SPA shell.\n\n## Remote access \u0026 authentication\n\nThe server trusts loopback connections unconditionally — local use (desktop app, dev) needs zero\nsetup. Any non-loopback client (LAN, Tailscale, another machine) must present the server's access\ntoken once.\n\n- **Tailscale auto-trust:** if Tailscale runs on the server, devices signed into the **same\n  Tailscale account** connect with no token at all — identity is verified per connection via\n  `tailscale whois`. Manage it (and see your devices) in **Settings → Remote access**; other\n  tailnet members' devices still need the token.\n- The token is auto-generated on first boot, printed in the server log, shown in\n  **Settings → Remote access**, and stored at `\u003cdataDir\u003e/auth-token` (see\n  [Local data](#local-data-sessions--settings)). Override with `NUNCIO_AUTH_TOKEN`.\n- Opening the web UI from another machine shows a one-time token prompt; on success the token is\n  exchanged for an HttpOnly cookie (1 year), so each browser/device asks only once. The cookie\n  also authenticates the SSE stream and the in-browser terminal.\n- API clients can send `Authorization: Bearer \u003ctoken\u003e` per request instead.\n- Forge webhooks (`/api/webhooks/forge/:provider`) are exempt — they verify their own HMAC signatures.\n\nTo work on a project that lives on another machine, run the server there and connect from here:\n\n```bash\n# one-shot: deploy the current working tree to a tailnet machine and register\n# it as a user service (launchd on macOS, systemd --user on Linux)\nscripts/remote/deploy.sh user@machine.tailnet.ts.net\n\n# or manually on the project machine\nbun run build \u0026\u0026 bun run --filter @nuncio/server start:prod\ncat apps/server/data/auth-token   # or copy it from the boot log\n```\n\n`scripts/remote/deploy.sh` rsyncs the repo (never the target's `data/`) and runs\n`scripts/remote/bootstrap.sh` there — idempotent, re-run it to update. On the same\nTailscale account you then connect with no token at all; otherwise open\n`http://\u003cmachine\u003e:3000` and paste the printed token once.\n\n## PWA install (iPhone)\n\nNuncio ships as an installable PWA (`vite-plugin-pwa`: manifest, service worker, standalone display). **Add to Home Screen on iPhone requires HTTPS** — Safari will not offer a full install from plain `http://` localhost.\n\n1. Deploy with Tailscale HTTPS (see [Production deploy](#production-deploy-tailscale) above).\n2. On your iPhone, open the Tailscale URL in **Safari** (not an in-app browser).\n3. Tap **Share** → **Add to Home Screen**.\n4. Launch Nuncio from the home-screen icon — it runs in standalone mode with the dark theme and app icon.\n\nThe service worker precaches the UI shell; `/api/*` uses network-first so session data stays fresh.\n\n## Architecture\n\n- **Agent providers:** Pi SDK, Codex app-server, Cursor SDK, and Mock behind a common `AgentProvider` interface; `AgentRegistry` selects per session. Pi auth via the SDK's `AuthStorage` at `~/.pi/agent`; Codex auth via the local `codex` CLI login; Cursor auth via `CURSOR_API_KEY`. See [docs/system-architecture.md](docs/system-architecture.md).\n- **Backend:** NestJS (`apps/server`) on port 3000; after `bun run build`, it also serves `apps/web/dist` at `/` while keeping `/api/*` for JSON routes\n- **Frontend:** Vite + React + Tailwind + shadcn/ui (`apps/web`) on port 5173 in dev/preview (`NUNCIO_WEB_PORT` overrides dev/preview; `NUNCIO_API_ORIGIN` overrides the `/api` proxy target)\n- **Browser dock:** desktop-only Electron `BrowserView` over the React viewport with a persistent app profile; web/PWA does not expose a browser dock\n- **Persistence:** SQLite (`bun:sqlite`) in `data/nuncio.db` — sessions (with `provider`, `model`, and provider runtime state), append-only event log, and a `settings` table for runtime-configurable env overrides (secrets encrypted at rest)\n- **Auth:** Tailscale (network) + static app token (planned)\n- **Distribution:** Open source — friends/colleagues self-host on their own Linux/macOS machines\n\n## API\n\n| Method | Path | Description |\n|--------|------|-------------|\n| GET | `/api/health` | Health check |\n| GET | `/api/sessions` | List sessions (`?includeArchived=1`) |\n| POST | `/api/sessions` | Create session `{ \"prompt\": \"...\", \"provider?\": \"pi\\|codex\\|cursor\", \"model?\": \"...\", \"attachments?\": [{ \"kind\": \"image\", \"mimeType\": \"image/png\", \"data\": \"base64\" }], \"projectPath?\": \"/abs/repo\", \"useWorktree?\": true, \"baseBranch?\": \"main\" }`; `projectPath` without `useWorktree` runs in the selected repo and records `baseBranch` as the selected branch, while `useWorktree: true` creates a generated `nuncio/\u003cid\u003e-\u003cslug\u003e` worktree from `baseBranch` |\n| POST | `/api/sessions/handoff` | Import a Cursor IDE/CLI chat `{ \"cursorChatId\": \"...\", \"workspace\": \"/abs/path\", \"title?\": \"...\" }` or Pi CLI session `{ \"piSessionPath\": \"/abs/session.jsonl\", \"workspace\": \"/abs/path\", \"title?\": \"...\" }` → `IDLE` session with transcript hydrated |\n| GET | `/api/cursor/local-sessions?workspace=` | List in-progress Cursor chats on this Mac for the handoff picker |\n| GET | `/api/pi/local-sessions?workspace=` | List local Pi CLI sessions for a workspace using the Pi SDK session store |\n| GET | `/api/sessions/:id` | Session detail (incl. `provider`, `model`, `supportsInteraction`, `cursorBackend`, `cursorChatId`) |\n| GET | `/api/sessions/:id/events?since=` | Event log (cursor) |\n| GET | `/api/sessions/:id/active-run` | `{ \"active\": boolean }` — whether Cursor IDE/CLI is likely still running this handoff chat on the host (transcript/store mtime \u003c 60s) |\n| POST | `/api/sessions/:id/refresh-transcript` | Append new turns from the on-disk Cursor/Pi transcript; emits `transcript_refreshed` via SSE when rows land |\n| GET | `/api/sessions/:id/stream?since=` | SSE stream; for handoff sessions, the server watches the external transcript file and streams new rows live |\n| POST | `/api/sessions/:id/steer` | Steer agent `{ \"message\": \"...\", \"forceResume?\": true, \"attachments?\": [...] }` — `forceResume` skips the active-run guard for CLI handoff sessions |\n| POST | `/api/sessions/:id/interrupt` | Interrupt a live run when the provider advertises `capabilities.interrupt` (Pi supports this without disposing the session) |\n| PATCH | `/api/sessions/:id/model` | Persist a session model/options update `{ \"model\": \"provider:model\", \"options?\": { ... } }`; providers with in-session switching (Pi) apply it live |\n| POST | `/api/sessions/:id/interactions/:requestId/respond` | Submit answers for a live interactive tool prompt `{ \"answers\": [...], \"resolvedBy\": \"user\\|skip\" }` |\n| POST | `/api/sessions/:id/provider-requests/:requestId/respond` | Approve or deny a pending provider request `{ \"decision\": \"approve\" \\| \"deny\" }` |\n| POST | `/api/sessions/:id/pause` | Pause session |\n| POST | `/api/sessions/:id/archive` | Archive session (recoverable via `restore`) |\n| POST | `/api/sessions/:id/restore` | Restore an archived session → IDLE |\n| DELETE | `/api/sessions/:id` | Permanently delete an archived session + its event log |\n| GET | `/api/models` | Model catalog (aggregated from available providers, including per-provider `capabilities`) |\n| GET | `/api/settings` | List all settings (secrets masked, never raw) |\n| PUT | `/api/settings/:key` | Update a setting `{ \"value\": \"...\" }` (encrypts secrets, busts provider caches) |\n| DELETE | `/api/settings/:key` | Clear a setting (falls back to env/default) |\n| GET | `/api/fs/dirs?path=` | Server-side directory browser (defaults to `$HOME`); used by the folder picker |\n| POST | `/api/push/register` | Register a device Expo push token `{ \"token\": \"...\", \"platform?\": \"ios\\|android\", \"deviceName?\": \"...\" }`; the server pushes on session finish / needs-input / error |\n| POST | `/api/push/unregister` | Remove a device push token `{ \"token\": \"...\" }` |\n\n### Session FSM\n\n`CREATED` → `RUNNING` → `IDLE` | `ERROR` | `PAUSED`\n`IDLE`/`PAUSED` → `RUNNING` (steer) · `IDLE`/`PAUSED`/`ERROR` → `ARCHIVED` · `ARCHIVED` → `IDLE` (restore)\n\nArchived sessions are browsable in the sidebar's **Archived** tab (search by title/prompt, restore to IDLE, or delete permanently with a confirm dialog).\n\n### Continue on mobile (handoff)\n\nPick **one** in-progress Cursor chat or Pi CLI session on your Mac and continue it from the Nuncio phone PWA without losing transcript context.\n\n**Setup**\n\n- Install the Cursor CLI (`agent` binary). Default path: `~/.local/bin/agent`.\n- Override via env `NUNCIO_CURSOR_AGENT_BIN` or **Settings → NUNCIO_CURSOR_AGENT_BIN** (gear icon in sidebar).\n- Set `NUNCIO_PROJECT_ROOTS` (or use **Browse folders…** in the picker) so Nuncio can find chats for your repo.\n\n**Flow**\n\n1. Start or continue a chat in **Cursor IDE** on your Mac (same project folder you pick in Nuncio).\n2. On your phone (Tailscale HTTPS PWA), tap **Continue on mobile** (home composer or session header for SDK Cursor sessions).\n3. Pick the project folder → select the chat → **Import** (or **Open** if already imported).\n4. Steer from the phone; Nuncio runs `agent -p --resume \u003cchatId\u003e` and streams tokens into the session transcript.\n\n**Troubleshooting**\n\n| Symptom | Fix |\n|---------|-----|\n| Chat not listed | Open the chat in Cursor first; confirm the project folder matches the repo path Cursor uses. Tap **Refresh** in the picker. |\n| \"Cursor is still running this chat…\" (409) | Pause or finish the run in Cursor IDE on your Mac, then retry. |\n| \"Cursor CLI not found\" (503) | Install the CLI or set `NUNCIO_CURSOR_AGENT_BIN` in Settings. |\n| \"Chat no longer exists\" (404) | The transcript folder was removed; start a new chat in Cursor. |\n| Steer hangs / no output | Run the server with `bun run --filter @nuncio/server start` (not `dev`) when testing Cursor — `--watch` reloads on DB writes and kills in-flight CLI runs. |\n\nImported Cursor sessions use `cursor_backend=cli` and resume via the CLI subprocess. Imported Pi sessions use `provider='pi'`, store the Pi session JSONL path in `providerThreadId`, and resume the same file in-process through `SessionManager.open(path)`. While a handoff session is open in Nuncio, server-side transcript file watchers stream new external CLI writes into the transcript without a manual refresh. Sessions you **create** in Nuncio still use the provider's normal in-process path (`cursor_backend=sdk` for Cursor).\n\n## Project layout\n\n```\napps/\n  server/\n    src/\n      agents/        AgentProvider interface + BaseAgentProvider + AgentRegistry + providers/ (pi, codex, cursor)\n      sessions/      api/ · domain/ (types, fsm) · persistence/ (repositories) + service + module\n      models/        model catalog aggregation from providers\n      health/ · db/\n    test/\n      unit/          *.spec.ts (bun test)\n      e2e/           HTTP e2e (simulated cursor provider)\n      integration/   real Pi auth (skips when ~/.pi/agent absent; opt-in)\n  web/      Vite + React + Tailwind v4 + shadcn/ui (installable PWA)\nmockup.html UI blueprint (reference)\ndata/       SQLite (gitignored)\ndocs/       system-architecture.md\nassets/     Screenshots for the README (un-ignored only here — see .gitignore)\n```\n\n## Design principles\n\n- **3-layer state decoupling:** Conversation (durable) / Agent loop (replaceable) / Machine state (FSM)\n- **Provider-neutral agent layer:** every agent SDK implements `AgentProvider`; `AgentRegistry` resolves per session so Pi/Codex/Cursor/any future SDK plug in uniformly\n- **Per-session provider + model selection** — `provider` + `model` stored on the session, wired through to the SDK\n- **Long-running, resumable sessions** — FSM + event log persist in SQLite; Pi conversation history is in-memory pending session revival (planned)\n\n## Roadmap\n\nPhase plans and milestones: [plans/260626-nuncio-roadmap/](plans/260626-nuncio-roadmap/)\n\n| Phase | Focus | Status |\n|-------|-------|--------|\n| 0–1 | Vertical slice (sessions, events, SSE, Pi harness) | Done |\n| 2 | PWA + mobile + Tailscale prod | Done |\n| 3 | Steer, pause, model picker | Done |\n| — | Agent-provider abstraction + Pi/Codex/Cursor providers | Done |\n| 4 | Git workspace, branch, PR | Workspace support partially shipped; PR/cleanup planned |\n| 5 | Web Push + webhooks | Planned |\n\n## Contributing\n\nContributions are welcome! Before opening a PR:\n\n- Read [CONTRIBUTING.md](CONTRIBUTING.md) — it covers the TDD-first workflow, the Changeset-based release process, branch/worktree naming, and the PR checklist.\n- For context on architecture and conventions, see [AGENTS.md](AGENTS.md) and [docs/system-architecture.md](docs/system-architecture.md).\n- Found a security issue? See [SECURITY.md](SECURITY.md) — **do not** open a public issue for vulnerabilities.\n- Everyone is expected to follow the [Code of Conduct](CODE_OF_CONDUCT.md).\n\n## License\n\n[MIT](LICENSE) — © oscarlehuu. See the [LICENSE](LICENSE) file for the full text.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foscarlehuu%2Fnuncio","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Foscarlehuu%2Fnuncio","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foscarlehuu%2Fnuncio/lists"}