An isometric IDE that visualizes a repository as a hex-tile city, where local tasks and agent runs become quests, contributors are guilds, and coding agents are the adventurers dispatched to fulfill them.
Citybase is a local-first desktop app: open one Git repository from disk, see it as a living isometric city, dispatch a Claude Code run from the UI, and watch the agent work the city in real time — then review the result without staring at raw diffs.
v3.0 — "Real-Time City." Dispatch is non-blocking and streaming: the UI never freezes, the run shows as running the instant it starts, and cancel actually terminates the agent. While it works, a live agent presence scans the city and the buildings it touches light up, resolving with a completion ripple. Run history is persisted across restarts. Built on v2.0's foundation — the isometric city where top-level folders are districts on raised slabs, files are extruded buildings, and Git status lights the skyline (staged green, unstaged amber), with a City / Work toggle.
Claude Code runs inside the IDE end-to-end; Codex CLI is wired through the same provider contract as a fallback adapter.
- Node 20+ and npm for the renderer / Electron build.
- Git ≥ 1.8.5 — the workspace service uses the
git -C <path>flag (released November 2013). - Claude Code CLI on
PATH, authenticated. Citybase shells out toclaude --print --output-format json …for non-interactive runs. Install per docs.claude.com and confirmclaude --versionworks in a terminal. - GitHub CLI (
gh) onPATH, authenticated, if you intend to use the PR-creation surface.gh auth statusmust show a logged-in account. - Codex CLI is optional in v1. If installed, it's available as an alternative adapter; if not, Citybase falls back to Claude.
npm install
npm run dev:desktop # Electron shell with HMR against the Vite dev serverFor a one-shot launch against the production build:
npm run build:desktop && npm run start:desktopThere is no standalone-browser path. The renderer always runs inside Electron — citybaseApi.js throws on import when window.citybase is missing instead of silently degrading to a stub. The browser-only dev and preview scripts were removed on 2026-05-10.
A normal session looks like this — every step is real activity, not seeded data:
- Launch. The main process restores the most recent workspace and runs
detectAgentBinaries(). Both results are pushed to the renderer over theBOOT_PAYLOAD_CHANNELbefore App.jsx mounts, so the UI lands populated with no extra clicks. - Open a workspace if one isn't already restored:
File → Open Workspace…. The Git service reads branch, status, file tree, and recent commits viagit status --porcelain=v2/git log/git ls-files. - See the city. The City view projects the tracked file tree into districts (folders) and buildings (files); dirty files glow green/amber by stage. Pan with drag, zoom with the wheel, hover for a path, click to inspect.
- Pick a branch in the top-bar selector. CHECKOUT runs when the workspace is clean; if it's dirty the selector shows a "commit first" pill instead.
- Dispatch an agent (switch to Work). The IPC handler resolves the right
AgentProvideradapter (autoprefers Claude when installed) and starts a realclaudeprocess inside the workspace cwd. While it runs, the City view auto-refreshes and lights up the buildings being changed. - Review the run detail: real CI checks, a real diff (parsed from
git diff --unified=3), and the live event stream. A Run History sidebar lists every run started this session. - Commit. With dirty files, the commit card opens. Type a message; the action runs
git add -A && git commit -m, thengit rev-parse HEADfor the new hash. - Open a PR by pushing the branch yourself, then calling
agents.openPRfrom the UI. The adapter shells out togh pr create --title --body --base --headfrom the run cwd and parses the URL out of stdout.
| Script | What it does |
|---|---|
npm run dev:desktop |
Electron shell with HMR against the Vite dev server |
npm run start:desktop |
Electron against the built renderer in dist/ |
npm run build / build:desktop |
Production renderer build to dist/ (alias of each other) |
npm run package:dir |
Unpacked dev build under dist-electron/ (no installer, no codesign) |
npm run package:mac |
macOS .app, ad-hoc / unsigned for local dev |
npm run lint |
ESLint over the project |
npm test |
Vitest in watch mode (npm test -- --run for one pass) |
npm run test:e2e |
Playwright desktop smoke test against the built dist/ (run npm run build first) |
package:dir and package:mac are dev-only — no DMG, no notarization, no signing. electron-builder config lives in the build field of package.json; output goes to gitignored dist-electron/.
Windows dev note. Running package:* from Windows requires Developer Mode (Settings → Privacy & Security → For Developers) or an admin shell — electron-builder's first run extracts a 7z cache containing symlinks, which Windows refuses to create otherwise.
electron/
main/
main.cjs BrowserWindow lifecycle + did-finish-load boot push
ipc.cjs / ipcHandlers.cjs typed IPC handlers (workspace, git, agents, checks)
bootPayload.cjs pure factory for the BOOT_PAYLOAD_CHANNEL message
menu.cjs / menuTemplate.cjs desktop menu + commands
windowConfig.cjs BrowserWindow defaults
services/
processService.cjs guarded execFile runner (argv arrays only, cwd pinned)
workspaceService.cjs workspace pick / restore / persist
gitService.cjs getSnapshot / getBranches / checkout / commit
workspaceChecks.cjs runs declared npm scripts as CheckResult[]
agents/
AgentAdapter.cjs the AgentProvider contract base class
CliAgentAdapter.cjs shared CLI-wrapping implementation
ClaudeAdapter.cjs real claude flags + JSON-result streamEvents
CodexAdapter.cjs same shape, codex CLI
agentManager.cjs registry + dispatcher + run history
detect.cjs PATH probe for claude / codex / gh
parseUnifiedDiff.cjs git diff parser
constants.cjs AGENT_EVENT_CHANNEL + BOOT_PAYLOAD_CHANNEL
preload/
preload.cjs isolated window.citybase bridge
src/
App.jsx top-level shell + view switching
app/
citybaseApi.js renderer facade — desktop bridge re-export (throws if missing)
useWorkspace.js workspace + Git snapshot state
useAgentDetect.js hook reading the boot payload, IPC fallback
useApprovalRequests.js pending-approval channel for write-capable runs
useRunHistory.js Run History data via agents.listRuns
data/
seed.js test fixtures only — not imported by production paths
game/
palette.js / hex.js non-component primitives (Fast Refresh-safe)
theme.jsx UI primitives
useTweaks.js runtime-toggles hook
map.jsx, kanban.jsx,
analysis.jsx (RunHistoryPanel,
CommitResultCard),
panels.jsx, modals.jsx,
command.jsx, branchSelector.jsx,
tweaks.jsx view + panel components
data.js slim re-export (SKILL_DEFS / hpFromContext / fmtTokens)
tests/
setup.js Vitest setup
*.test.{js,jsx} unit + smoke tests under jsdom
- No shell access from the renderer.
webPreferencessetscontextIsolation: true,nodeIntegration: false,sandbox: true. The renderer talks to the main process only through the typedwindow.citybaseobject exposed bypreload.cjs. - Allow-listed IPC channels. Every channel in
ipcHandlers.cjsvalidates inputs (workspace ids must be known, paths must resolve under workspace root) and returns plain serializable objects. The renderer can never pass arbitrary command strings. processService.runis the single execFile site. Argv arrays only — no shell strings, no string concatenation. cwd is required and pinned to a workspace path. Timeouts and max-buffer caps are enforced.- Mutating Git surfaces validate before they touch the world.
gitService.checkoutrefuses unknown branches (no-bauto-create);gitService.commitrejects empty messages; both return{ ok: false, error }instead of throwing into the main process. --permission-mode bypassPermissionsis used only for non-interactive Claude runs so the CLI doesn't hang waiting for an approval surface that doesn't exist yet. When the renderer's approval routing lands, this flips toautoordefaultand prompts pump throughuseApprovalRequests.
| Symptom | Likely cause | Fix |
|---|---|---|
| "claude CLI not found on PATH" | claude isn't installed, or Citybase was launched from Finder on macOS without the right PATH. |
Install per Anthropic's docs, then relaunch Citybase from a terminal so it inherits the right PATH. The processService augments PATH with /opt/homebrew/bin and /usr/local/bin on macOS but won't find a binary that lives somewhere else. |
| Claude run fails immediately with "not authenticated" | The CLI hasn't logged in yet. | Run claude login once in a terminal, confirm claude --print --output-format json -p "hi" works, then retry inside Citybase. |
openPR throws "no upstream" or "branch not pushed" |
The head branch hasn't been pushed to the remote. v1 deliberately doesn't auto-push — that side effect is deferred to v1.1. | git push -u origin <branch> from a terminal, then call openPR again. |
openPR throws "GraphQL: must have admin rights" or similar |
gh is authenticated as the wrong account, or doesn't have permission on the remote. |
gh auth status to inspect; gh auth login to re-auth. |
dev:desktop hangs at "waiting for http://localhost:5173" |
Vite couldn't bind 5173 — usually Docker. | Stop the conflicting process or change server.port in vite.config.js (and dev:desktop's wait-on URL). strictPort: true is on purpose so a port collision fails loud. |
| Commit hook rejects "subject does not match the project convention" | Non-conventional subject. | See CONTRIBUTING.md. Format is <type>(<scope>)?<!>?: <description>, ≤72 chars, lowercase after the colon. |
npm test -- --run runs the Vitest suite (jsdom; the per-file baseline lives in VERIFICATION.md Stage 2). The canonical release checklist is VERIFICATION.md — a six-stage V&V protocol covering static review, automated tests, the manual desktop walkthrough, adversarial checks, and the hard product constraints (renderer isolation, IPC allow-list, approval boundaries). Run it before any release or after any large refactor.
The full vision and phased plan live in ROADMAP.md. Phase 5 is complete and the v1 ship-gate items are addressed across PRs #36 / #37 / #38 / #40 / #41. Remaining v1.1 work and deferred items are listed there.
See CONTRIBUTING.md and AGENTS.md. Short version: AI agents propose changes via PRs; CI gates the merge.
Work is tracked in the project-cycle files: defects in bugs.md, scoped features in features.md, shipped/fixed history in CHANGELOG.md. File a BUG-NNN / FEAT-NNN entry before fixing or building; migrate it to the changelog when it lands. The full lifecycle is described in AGENTS.md.