AGENTS.md · diff
git:20261010.ccaf3bb to git:20261010.7dc4715
9 added, 0 removed. Audit A to A.
# The Cockpit That Drives AI
## Development
- **Dev Server Port**: 3456 (run `npm run dev`)
- **Tech Stack**: Next.js 16, React, TypeScript, TailwindCSS
- **Isolating an instance**: `COCKPIT_HOME=~/.cockpit-dev npm run dev` points
the whole data dir (config, projects, history) somewhere else — use it rather
than testing against a live `~/.cockpit`. Note that only ONE dev server can
run per repo checkout (Next holds a lock on `.next/`), so `PORT` alone does
not buy a second instance; a running prod `cockpit` also refuses a second
boot unless isolated this way.
## UI Layout
- **Three-panel swipe mode**: Uses `SwipeableViewContainer` (translateX) to place three panels side by side, with left/right swipe to switch:
- Panel 1 **Agent** (Chat)
- Panel 2 **Explorer** (File browser)
- Panel 3 **Console** (Terminal + browser bubbles)
- **All three panels are always rendered simultaneously**; switching panels is just a CSS transform translation — components are never unmounted/remounted
- **UI component considerations**: When building menus, modals, and floating popovers, be mindful of the three-panel layout:
- Positioning calculations must account for the boundaries of the current panel
- z-index levels must be managed consistently
- Prevent components from overflowing into adjacent panels
## React Performance Conventions
All three panels + every open chat tab stay mounted, so one state change (session
switch, WS push, terminal tick) re-renders large sibling subtrees. Props to a
`memo`'d heavy renderer (chat `MessageBubble`, Explorer previews, Console bubbles)
must be **referentially stable** — one unstable prop silently defeats the `memo`:
- Callbacks → `useCallback`; if a dep churns every render (`fileTree`, live
`sessionId`), use a ref indirection so the passed-down identity never changes.
- Objects/arrays (e.g. `extra={{…}}`) → `useMemo`. Inside `.map()` extract a
`memo`'d row (see `ConsoleBubbleRow`) fed stable callbacks + per-item booleans.
- Expensive per-render work → `useMemo([content])`; never parse/format an
O(document) blob inline in JSX.
## Effect Conventions
All IO / side-effects / dependencies / errors go through the
`Effect<A, E, R>` paradigm. See **`EFFECT.md`** for the full contract:
- Tagged Error types (DBError / WSError / FSError / AgentError / ...)
- Service Tag + Layer.scoped templates for connection pools / subprocesses
- API route / WebSocket handler templates
- React bridging (`useEffectQuery` / `BrowserRuntime.runPromise`)
- `Effect.withSpan` / `CockpitConfig` / Logger conventions
- Server vs Browser bundle boundary rules
When adding any new IO operation (HTTP route, DB query, WS handler,
client-side fetch), **first match a template in EFFECT.md §3-§7**; do not
write raw `fetch` / `try-catch` / `setInterval` in business code.
## Project Structure
Business code lives in `packages/`; `src/` is intentionally minimal
framework boot. See `MODULES.md` for the dependency rules.
- `/src/app/` - Next.js routing only (page.tsx + layout.tsx + one-line
`route.ts` shims that re-export from feature packages)
- `/src/lib/` - Server bootstrap: `wsServer.ts` (WS server),
`fileWatcher.ts` (fs watcher)
- `/packages/feature/` - Self-contained domain features:
- `agent/` - Chat domain (Claude/Ollama/Codex/Kimi/DeepSeek/GLM), scheduled
tasks, slash commands, sidebar panels, tool-call snapshots (shadow git
per project under `~/.cockpit/snapshots/`, per-call diff viewer)
- `comments/` - Code annotation API + hooks
- `console/` - Terminal + browser bubbles + DB bubbles (Postgres / MySQL
/ Redis / Neo4j / MongoDB / Bash)
- `explorer/` - File browser + code rendering (DiffView, CodeViewer,
InteractiveMarkdownPreview, PreviewModal) + git + LSP
- `skills/` - SKILL.md parser + slash autocomplete + cross-frame bus
- `workspace/` - Application integrator (Workspace, TabManager,
Providers, SettingsModal, NoteModal, SessionBrowser)
- `/packages/shared/` - Cross-feature infrastructure:
- `i18n/` - Translation dictionary + i18next singleton
- `ui/` - UI primitives (Toast, MarkdownRenderer, Tooltip,
codeHighlighter, Swipeable*, useViMode, useWebSocket, …)
- `utils/` - Pure utilities (paths, ollamaEnv, platform, shortId)
- `/apps/` - Built-in HTML apps (`file-viewer/`). Deliberately NOT under
`public/` — they are served by a route so the bash SDK can be injected
(see **HTML Apps Runtime**)
- `/public/html-lib/` - Same-origin hosted frontend libs for HTML apps
(React, ReactDOM, Babel, `theme.css`, and the standalone
markdown / json / pdf widget bundles). Regenerated by
`scripts/build-html-lib.mjs` on every `predev` / `prebuild`
- `/chrome-extension/` - Chrome extension (Manifest V3, independent
sub-project)
- `/bin/` - CLI entry points (`cockpit.mjs`, `postinstall.mjs`)
## HTML Apps Runtime
Two address spaces, one route
(`packages/feature/explorer/src/server/api/apps.ts`):
- `/apps/builtin/<name>/index.html` — apps shipped in `/apps/`
- `/apps/local/<abs path>` — any file on this machine; this is also the
plain local-file server (relative sub-resources, images, css)
**SDK injection has no marker**: every `.html` served by this route gets
`window.cockpit`. Nothing to opt into, nothing to forget. `cockpit-name` is
purely a registry concern (panel card + `/name`) and has no bearing on what
runs.
**Do not add a marker back.** Two earlier designs gated injection on one (a URL
flag, then a meta tag) and both failed silently — the header comment in
`apps.ts` records how, so the mistake isn't repeated.
Making it unconditional is free because **injection was never a security
boundary**: any page served here is same-origin and can open its own WebSocket
to `/ws/bash` regardless. **The real gate is the same-origin check on that
upgrade** (`src/lib/wsServer.ts`), which means the security decision is *what
you choose to preview*, not what we inject.
Say the consequence out loud when working here: **previewing any local `.html`
runs it**, with full shell access. The iframe carries no `sandbox` attribute
(matching the console browser bubble), so a previewed page can also navigate
the whole Cockpit window away. Surfaces that preview unreviewed content — e.g.
a `.html` straight out of a git diff — are making that call for the user.
When working in this area:
- **Built-in apps must only use capabilities user apps have** —
`window.cockpit.bash`, `/html-lib/*`, and the `cockpit-*` meta tags. No
privileged endpoints, no internal postMessage protocols. They double as the
reference implementations for the `/html` skill, so a shortcut taken here
becomes advice users cannot follow.
- **Root-relative paths 404** except `/html-lib/*` and `/apps/*`, which the
server hosts. Same-level relative refs (`./app.jsx`) and CDN URLs resolve
normally.
- **Markdown images**: rewrite relative srcs to `/apps/local/<abs>`, never to
`data:` URLs — react-markdown's `defaultUrlTransform` drops every protocol
outside http/https/mailto/xmpp, silently leaving the `<img>` with no `src`
at all, in both the `` and raw-HTML forms.
- **Never hand-assemble these URLs.** The builders and their reverse
(`toLocalAppUrl` / `toFileViewerUrl` / `fromLocalAppUrl`, plus the per-segment
encoding Windows paths depend on) live in
`packages/shared/utils/src/htmlBashSdk.ts`.
- Directory URLs hit Next's trailingSlash redirect, after which the shell's
relative `./app.jsx` fetch resolves one level too high and 404s — always
spell out `index.html`.
## Key Features
- File browser with virtual scrolling and syntax highlighting (Shiki)
- Git status and history integration
- Per-tool-call project snapshots (shadow git per cwd, one commit per
mutating tool call, 7-day retention; message FileDiff icon opens the
per-call diff viewer)
- Git blame view
- Code search with Cmd+F (case sensitive / whole word matching)
- ESC key exits blame view first, then closes modal (3s debounce)
## Commands
```bash
npm run dev # Start dev server on port 3456
npm run build # Build for production
npm run setup # Build + npm link
npm run lint # Run ESLint
cockpit # Start production server on port 3457 (prefer this — primary entry)
cockpit-dev # Start dev server on port 3456 (dev only; no short alias)
cockpit -v # Show version
```
## npm Publish
Publishing is done via GitHub Actions — **never publish locally with `npm publish`**.
```bash
npm version patch # Bump version (patch/minor/major)
git push origin main # Push commit
git push origin v<version> # Push tag → triggers CI publish
```
The `v*` tag push triggers `.github/workflows/publish.yml` which:
1. Builds the project
2. Publishes to npm (`@surething/cockpit`) with provenance
3. Creates a GitHub Release with auto-generated notes
## Project Characteristics
- **Purely local application**: All API request latency is under 10ms
- **No API caching needed**: Local requests are fast enough that caching provides negligible performance gains while introducing data consistency issues
## Claude Code Usage Guidelines
- **Browser testing**: Use `evaluate_script` for DOM manipulation; avoid `take_screenshot` (consumes excessive tokens)
- **Minimize screenshots**: Only take screenshots when visual confirmation is truly needed
- **MCP tools**: Do not use MCP tools unless the user explicitly requests it (e.g., "use xxx")
- **Git commits**: Do not auto-commit code; only commit when the user explicitly says "commit"
- **English everywhere in code**: Write commit messages (subject and body), code comments, and CLI/console log output entirely in English; Chinese may appear only in quoted literals (e.g. UI copy, example prompts). Conversation replies stay in Chinese
+ <!-- cdd:begin -->
+ ## cdd (Constraint-Driven Development)
+
+ - Constraints live in `constraints/`. Changing one is a constraint change and needs human review; never weaken a constraint to make a check pass.
+ - cdd config lives in `.cdd/` (hidden directory): `config.json` and `baseline.json` decide how changes are judged. Editing them is a constraint change too — do not edit them to get a pass.
+ - Before committing, run `cdd change` to classify the change as A/B/C/D. A type-D change (incident fix) must add a new constraint that fails before the fix.
+ - Where checks run: local self-check (default) · commit hook · CI — same command in each. Which ones this repo actually wires up is defined by its own hook / CI config.
+ <!-- cdd:end -->
+