* fix: honor current branch in new workspace flow * Use OpenCode global event stream (#916) * Use OpenCode global event stream * Use stable OpenCode model in initial prompt e2e * Clean up OpenCode verification notes * chore: changelog for 0.1.74 * chore(release): cut 0.1.74 * Fix custom Codex provider base URL routing (#915) * Wire Copilot Allow All mode to ACP permissions (#935) * Add trace logging and tighten daemon log defaults (#933) * Add trace logging and tighten daemon log defaults * Clean up daemon trace logging shape * Clean up daemon trace logging * Route provider turn-id checks through shared helper * Fix supervisor log config test paths on Windows * Expect resolved supervisor log path on Windows * nix: declarative config, typed relay options, desktop packaging (#923) * nix: expose npmDepsHash as a callPackage arg Downstream flakes that follow a different nixpkgs revision can hit a hash mismatch on the npm-deps FOD even though package-lock.json is unchanged, because fetchNpmDeps output is sensitive to nixpkgs version. The standard fix — `.overrideAttrs { npmDepsHash = ...; }` — does not work for buildNpmPackage: npmDepsHash is destructured from args, so the default `npmDeps = fetchNpmDeps { hash = npmDepsHash; }` is already bound by the time overrideAttrs runs. Promote npmDepsHash to a callPackage arg with the current value as the default. Consumers can now `.override { npmDepsHash = "sha256-..."; }` and have it propagate to the npmDeps fetcher. Upstream CI behavior is unchanged — update-nix.sh is adjusted to match the new `npmDepsHash ? "..."` pattern. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * nix: move npmDepsHash default to a sidecar file Read the default `npmDepsHash` from `nix/npm-deps.hash` via `lib.fileContents` instead of inlining it as a string literal in `nix/package.nix`. The CI auto-updater becomes a one-line file write instead of a regex against a .nix source — decoupling lockfile bumps from the formatting of the package definition. No behavior change: same hash, same default, same `.override` surface. Lockfile diffs become smaller and the update path stops being load- bearing on a sed pattern. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * nix: declarative config via services.paseo.settings Today only a handful of `config.json` fields are surfaced as module options (listen, port, hostnames, relay.enable). Anything richer — custom agent providers, MCP injection, log config, voice features — requires hand-editing `$PASEO_HOME/config.json`. Add `services.paseo.settings` as a freeform attrset rendered to JSON via `pkgs.formats.json` and installed at `$PASEO_HOME/config.json` on each service start. Standard NixOS idiom. `install` on `preStart` rather than a `tmpfiles` symlink because the daemon writes to `config.json` at runtime via `DaemonConfigStore.patch` (MCP / provider toggles). A read-only symlink would break those writes; a copy-on-start lets the daemon mutate freely within a session while the Nix-managed file remains the source of truth at boot. The full schema is `PersistedConfigSchema` in `packages/server/src/server/persisted-config.ts`. Documented in the option description that runtime mutations don't survive restarts when `settings` is non-empty. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * nix: typed services.paseo.relay options with auto-wired endpoint Addresses #224 (option surface only). Today `services.paseo.relay.enable` is a bool that just toggles `--no-relay`. Pointing the daemon at a self-hosted relay requires hand-setting `PASEO_RELAY_ENDPOINT` and `PASEO_RELAY_USE_TLS` via the freeform `environment` option. Add a typed relay subtree: - `relay.mode = "hosted" | "remote"` selects how the daemon reaches the relay when enabled. Default is `"hosted"` (current behavior). - `relay.{host,port,useTls}` configure the `"remote"` case. - The module auto-wires `PASEO_RELAY_ENDPOINT` and `PASEO_RELAY_USE_TLS` when `mode = "remote"`. - Assertion fires at eval time when `mode = "remote"` but `host` is empty. - `relay.enable` keeps its current semantics — bool answers "is it on?", the new options answer "how is it configured?". The `"local"` mode from #224 (running a relay on the same host as a systemd unit) is deliberately not added here: `packages/relay` ships only a Cloudflare Workers adapter, so there's no Node.js runtime to package as a binary. Adding a Node adapter is a TS-side feature change worth its own design discussion; tracked as a follow-up. No breaking changes — existing `relay.enable = true|false` configs evaluate unchanged with the new `mode = "hosted"` default. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * nix: package paseo desktop app for Linux NixOS users have no easy way to run the desktop app today — electron-builder's outputs (.deb, .rpm, .AppImage) don't fit Nix's model, so `nix run github:getpaseo/paseo#desktop` doesn't exist. Add `packages.<linux>.desktop` following the standard nixpkgs Electron pattern (see e.g. signal-desktop, vscode): skip electron-builder entirely, build the desktop main process with `tsc`, bundle the Expo web export and built daemon workspaces, and wrap `pkgs.electron` with `makeWrapper`. Output is a runnable derivation usable via `nix run` or `environment.systemPackages`. The install layout preserves the monorepo source tree (`packages/desktop/dist/main.js`, `packages/app/dist`, `node_modules` at the workspace root) so `main.ts`'s dev-mode path resolution (`__dirname/../../app/dist`, `__dirname/../assets/icon.png`) works without any source patches. When Electron is invoked unpackaged via `electron path/to/main.js`, `app.isPackaged` is false and these relative paths are used. `--no-sandbox` is set on the launcher: Chromium's setuid sandbox can't live in `/nix/store` (immutable, no setuid). A follow-up can wire `security.wrappers` from a NixOS module for users who want the renderer sandbox. No CI changes — `desktop-release.yml` continues to produce .deb/.AppImage/.rpm/macOS/Windows installers as today. This is purely additive for NixOS users. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * nix: copy full packages/ tree in desktop derivation The previous installPhase selectively copied built artifacts (dist/ under server, cli, relay, highlight, expo-two-way-audio), which left two workspace symlinks dangling and failed noBrokenSymlinks: - node_modules/@getpaseo/expo-two-way-audio → packages/expo-two-way-audio (the Expo native module ships source + native projects, no built dist/) - node_modules/.bin/paseo → @getpaseo/cli/bin/paseo (the CLI launcher script lives under bin/, not dist/) npm workspace symlinks expect every workspace package to exist at its source path. Copy the whole packages/ tree instead. The cleanSourceWith filter already excludes the heavy platform-specific paths (android/ios under packages/app, website, tests), and the remaining ~16MB of src is acceptable for an Electron app derivation. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * nix: route desktop renderer through paseo:// protocol handler When `paseo-desktop` is launched via `electron path/to/main.js` (our unpackaged Nix layout), `app.isPackaged` is false and main.ts loads `DEV_SERVER_URL` — which defaults to http://localhost:8081 (the Expo dev server). That URL has nothing listening in a Nix-installed run, so the renderer fails with ERR_CONNECTION_REFUSED. main.ts already supports overriding this via the `EXPO_DEV_URL` env var. Set it to `paseo://app/` so the request goes through the `paseo://` protocol handler that main.ts registers unconditionally. The handler resolves files via `getAppDistDir()`, which in the unpackaged branch returns `__dirname/../../app/dist` — exactly where our install layout places the Expo web export. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci: track nix/npm-deps.hash in nix-build commit step The commit step still referenced nix/package.nix in its diff check and git add. After moving the hash to nix/npm-deps.hash, the auto-updated hash would never be staged and the new value would sit unstaged in the working tree forever. * ci: push nix-build hash commits via paseo-ai[bot] App token The default GITHUB_TOKEN cannot bypass main's required status checks, so the auto-commit of stale Nix hash updates has been silently failing. Mint an installation token for the paseo-ai App (which is in the ruleset bypass list) and use it for checkout and push. --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com> * Normalize HEIC image attachments on the client (#934) * Normalize HEIC attachments before persistence * Preserve native JPEG and PNG picks * fix: update lockfile signatures and Nix hash * ci: skip App token on PRs in nix-build Fork PRs cannot read repo secrets, so the App-token step failed with 'Input required and not supplied: app-id'. Gate the App-token step on push-to-main and fall back to github.token for the checkout on PRs, since PRs only need to validate the build. * ci: skip CI on nix-build auto hash commits The hash-update commit only changes package-lock.json and nix/npm-deps.hash. The parent commit already passed all 13 required checks, and the file diff is content-only, so re-running the full suite on every hash bump is wasted CI minutes. * ci: drop unused contents: write override in nix-build GITHUB_TOKEN no longer pushes anything in this workflow. PR runs are read-only, and main pushes go through the paseo-ai App token. The workflow-level 'contents: read' is sufficient. Addresses #365. * ci: gate nix-build on same-repo PRs only scripts/update-nix.sh and scripts/fix-lockfile.mjs are executed from the PR's checked-out tree. A fork PR can modify those scripts to run arbitrary code on the runner. Same-repo PRs and push-to-main still run; fork PRs skip the job entirely. Closes #365. * ci: split nix workflow into build (anywhere) + update-hash (main only) build runs on push to main and every PR including forks. Same model as the test/lint/typecheck workflows: read-only token, no secrets injected, runs scripts/update-nix.sh + nix build to validate. No commit, no push. update-hash runs only on push to main. Mints the paseo-ai App token, re-runs the update + build, and commits the refreshed hash via the ruleset bypass. The default GITHUB_TOKEN no longer pushes anywhere. Rename file to nix.yml since it's no longer just a build job. * ci: split nix into two workflows nix.yml: PR-only build check. nix-update-hash.yml: push-to-main hash update via paseo-ai App token. * Fix iPad sidebar safe area background (#937) Co-authored-by: zbl <zbl@zbl-M4Pro.local> * Fix desktop daemon stale PID startup (#913) * Configure STT language from settings (#941) * Configure STT language from settings * Update websocket speech mock for language config * Handle Windows shell wrappers in Codex command summaries (#931) * fix(app): respect iPad safe area in settings sidebar (#922) Co-authored-by: zbl <zbl@zbl-M4Pro.local> * ci(nix): smoke test daemon boot (#939) * Fix scheduled agent cleanup (#945) * Fail Codex resume requests explicitly (#947) * fix(copilot): preserve legacy autopilot mode alias * docs(changelog): draft 0.1.75 entry * chore(release): cut 0.1.75 * fix: update lockfile signatures and Nix hash [skip ci] * docs(changelog): refine 0.1.75 entry * fix: preserve worktree action literal types --------- Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com> Co-authored-by: Matan Bendix Shenhav <matan@shenhav.fyi> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: paseo-ai[bot] <266920839+paseo-ai[bot]@users.noreply.github.com> Co-authored-by: Bolun Zhang <zhaggbl@mail.ustc.edu.cn> Co-authored-by: zbl <zbl@zbl-M4Pro.local> Co-authored-by: Biao Ma <sigemaict@gmail.com> Co-authored-by: ezra <ezra.ora.c@gmail.com>
Paseo
One interface for all your Claude Code, Codex and OpenCode agents.
Run agents in parallel on your own machines. Ship from your phone or your desk.
- Self-hosted: Agents run on your machine with your full dev environment. Use your tools, your configs, and your skills.
- Multi-provider: Claude Code, Codex, and OpenCode through the same interface. Pick the right model for each job.
- Voice control: Dictate tasks or talk through problems in voice mode. Hands-free when you need it.
- Cross-device: iOS, Android, desktop, web, and CLI. Start work at your desk, check in from your phone, script it from the terminal.
- Privacy-first: Paseo doesn't have any telemetry, tracking, or forced log-ins.
Getting Started
Paseo runs a local server called the daemon that manages your coding agents. Clients like the desktop app, mobile app, web app, and CLI connect to it.
Prerequisites
You need at least one agent CLI installed and configured with your credentials:
Desktop app (recommended)
Download it from paseo.sh/download or the GitHub releases page. Open the app and the daemon starts automatically. Nothing else to install.
To connect from your phone, scan the QR code shown in Settings.
CLI / headless
Install the CLI and start Paseo:
npm install -g @getpaseo/cli
paseo
This shows a QR code in the terminal. Connect from any client. This path is useful for servers and remote machines.
For full setup and configuration, see:
CLI
Everything you can do in the app, you can do from the terminal.
paseo run --provider claude/opus-4.6 "implement user authentication"
paseo run --provider codex/gpt-5.4 --worktree feature-x "implement feature X"
paseo ls # list running agents
paseo attach abc123 # stream live output
paseo send abc123 "also add tests" # follow-up task
# run on a remote daemon
paseo --host workstation.local:6767 run "run the full test suite"
See the full CLI reference for more.
Skills
Skills teach your agent to use Paseo to orchestrate other agents.
npx skills add getpaseo/paseo
Then use them in any agent conversation:
/paseo-handoff— hand off work between agents. I use this to plan with Claude and then handoff to Codex to implement./paseo-loop— loop an agent against clear acceptance criteria (aka Ralph loops), optionally with a verifier./paseo-advisor— spin up a single agent as an advisor for a second opinion, without delegating the work itself./paseo-committee— form a committee of two contrasting agents to step back, do root cause analysis, and produce a plan.
Development
Quick monorepo package map:
packages/server: Paseo daemon (agent process orchestration, WebSocket API, MCP server)packages/app: Expo client (iOS, Android, web)packages/cli:paseoCLI for daemon and agent workflowspackages/desktop: Electron desktop apppackages/relay: Relay package for remote connectivitypackages/website: Marketing site and documentation (paseo.sh)
Common commands:
# run all local dev services
npm run dev
# run individual surfaces
npm run dev:server
npm run dev:app
npm run dev:desktop
npm run dev:website
# build the daemon
npm run build:daemon
# repo-wide checks
npm run typecheck
Community
- paseo-relay — self-hosted relay in Go
Self-hosted relay TLS
Self-hosted relays use ws:// unless TLS is opted in. For a relay behind nginx on 443, start the daemon with:
PASEO_RELAY_ENDPOINT=127.0.0.1:8080 \
PASEO_RELAY_PUBLIC_ENDPOINT=relay.example.com:443 \
PASEO_RELAY_USE_TLS=true \
paseo daemon start
Equivalent config:
{
"daemon": {
"relay": {
"enabled": true,
"endpoint": "127.0.0.1:8080",
"publicEndpoint": "relay.example.com:443",
"useTls": true
}
}
}
Minimal nginx WebSocket proxy:
server {
listen 443 ssl;
server_name relay.example.com;
ssl_certificate /etc/letsencrypt/live/relay.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/relay.example.com/privkey.pem;
location /ws {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
}
}
License
AGPL-3.0

