npm test
npm run lint
npm run test:coverage
npm run test:coverage -- --line-min=45 --branch-min=65 --func-min=50server.js # Entry point, WebSocket handlers
lib/
routes/ # REST API endpoints
index.js # Route setup
conversations.js # CRUD, export, fork, compress, search
git.js # Git operations
files.js # File browser + cwd picker (browse + recursive search)
memory.js # Memory system
capabilities.js # Model/CLI capabilities
preview.js # Live web preview server controls
duckdb.js # DuckDB data analysis routes
bigquery.js # BigQuery ADC + query routes
workflow.js # Write lock + patch queue routes
helpers.js # Shared utilities (withConversation, etc.)
providers/ # LLM provider system
base.js # Base provider interface
claude.js # Claude CLI provider
codex.js # OpenAI Codex CLI provider
ollama.js # Ollama HTTP API provider
index.js # Provider registry
memory-prompt.txt # Memory injection template
claude.js # Backwards compat wrapper
data.js # Storage, atomic writes
bigquery.js # BigQuery ADC/token/query helpers
embeddings.js # Semantic search with local embeddings
constants.js # Shared constants
public/js/
app.js # Entry point
state.js # Shared state
utils.js # Helpers (toast, dialog, formatTime)
websocket.js # WebSocket connection
render.js # Message rendering, TTS
conversations.js # Conversation list UI
ui.js # Event handlers
markdown.js # Markdown parser
branches.js # Fork tree visualization
explorer/ # Shared file viewer + git controllers
files-standalone.js # Standalone cwd-scoped files/git view
file-panel/ # Conversation-scoped shell + live preview tab
index.js # Main file panel module
file-browser.js # File tree shell bindings
git-branches.js # Branch shell bindings
git-changes.js # Changes shell bindings
git-commits.js # Commit history shell bindings
gestures.js # Touch interactions
data.js # Data tab (DuckDB + BigQuery SQL + exports)
preview.js # Live preview server controls
ui/ # Modular UI features
capabilities.js # Provider/model capabilities
context-bar.js # Context usage indicator
directory-browser.js # CWD picker
file-browser.js # Standalone file browser
memory.js # Memory management
stats.js # Usage dashboard
theme.js # Theme switcher
voice.js # Speech input/output
constants.js # Frontend constants
file-utils.js # File handling utilities
public/css/
base.css # Variables, resets
layout.css # Page structure
components.css # UI components
messages.css # Chat messages
list.css # Conversation list
file-panel.css # File panel
branches.css # Branch tree
themes/ # 8 color themes
{
id: string, // UUID
name: string,
cwd: string, // Working directory
claudeSessionId: string, // For --resume (Claude)
codexSessionId: string, // For `codex exec resume` (Codex)
messages: Message[], // null when not loaded
status: 'idle' | 'thinking',
archived: boolean,
pinned: boolean,
autopilot: boolean, // Skip permissions (when unsandboxed)
sandboxed: boolean, // Default true - use sandbox settings
useMemory: boolean, // Default true - inject memories
provider: string, // 'claude' | 'codex' | 'ollama' (default: 'claude')
model: string, // Provider-specific model ID
createdAt: number,
messageCount: number,
parentId: string, // Fork parent
forkIndex: number, // Fork point
forkSourceCwd: string, // Original cwd when forked into dedicated worktree
lastMessage: { role, text, timestamp, cost, duration }
}{
role: 'user',
text: string,
timestamp: number,
attachments: [{ path, filename, url }] // optional
}{
role: 'assistant',
text: string,
timestamp: number,
cost: number, // USD
duration: number, // ms
sessionId: string,
inputTokens: number, // Provider-reported input token count (raw when available)
outputTokens: number,
netInputTokens?: number, // rawInputTokens - cachedInputTokens
typedInputTokens?: number, // rough estimate of user-typed prompt tokens
rawInputTokens?: number, // full provider-reported prompt/context tokens
cachedInputTokens?: number, // provider cache hit tokens (if available)
reasoningTokens?: number // provider-reported reasoning/thinking tokens
}{
id: string,
text: string,
scope: string, // 'global' or cwd path
category: string, // optional
enabled: boolean, // default true
source: string, // where memory came from
createdAt: number
}{
id: string, // 'claude' | 'codex' | 'ollama'
name: string, // Display name
}{
id: string, // e.g., 'claude-sonnet-4.5'
name: string, // Display name
context: number, // Context window size
inputPrice: number, // Per million tokens (optional)
outputPrice: number, // Per million tokens (optional)
}{
sandbox: {
enabled: boolean,
autoAllowBashIfSandboxed: boolean,
allowUnsandboxedCommands: boolean,
network: {
allowedDomains: string[]
}
},
permissions: {
allow: string[], // Glob patterns like "Edit(/path/**)"
deny: string[] // Glob patterns like "Read(**/.env)"
}
}| Function | Location | Purpose |
|---|---|---|
atomicWrite(path, data) |
data.js | Safe JSON write (tmp + rename) |
loadFromDisk() |
data.js | Load index.json at startup |
saveIndex() |
data.js | Persist metadata |
saveConversation(id) |
data.js | Persist messages |
ensureMessages(id) |
data.js | Lazy-load messages |
convMeta(conv) |
data.js | Extract metadata |
spawnClaude(ws, convId, conv, ...) |
claude.js | Spawn CLI with streaming (backwards compat) |
processStreamEvent(line) |
claude.js | Parse CLI JSON output |
cancelProcess(convId) |
claude.js | Kill active process |
initProviders() |
providers/index.js | Initialize all providers at startup |
getProvider(id) |
providers/index.js | Get provider instance by ID |
getAllProviders() |
providers/index.js | List all registered providers |
provider.chat(ws, convId, ...) |
providers/base.js | Send message (provider-specific) |
provider.cancel(convId) |
providers/base.js | Cancel active generation |
provider.generateSummary(msgs, model, cwd) |
providers/base.js | Summarize for compression |
embedConversation(conv) |
embeddings.js | Generate embedding for conversation |
semanticSearch(query, topK) |
embeddings.js | Search by meaning |
backfillEmbeddings(convs, loadMsgs) |
embeddings.js | Generate missing embeddings |
deleteEmbedding(convId) |
embeddings.js | Remove embedding on delete |
| Function | Location | Purpose |
|---|---|---|
showToast(msg, opts) |
utils.js | Toast with optional undo action |
showDialog(opts) |
utils.js | Alert/confirm/prompt |
connectWS() |
websocket.js | Establish WebSocket |
renderMessages(msgs) |
render.js | Full message render |
appendDelta(text) |
render.js | Buffer streaming chunk |
finalizeMessage(data) |
render.js | Complete streaming |
loadConversations() |
conversations.js | Fetch + render list |
openConversation(id) |
conversations.js | Load + display conversation |
forkConversation(idx) |
conversations.js | Fork from message |
sendMessage(text) |
ui.js | Send with local uploads or pre-attached server files |
renderMarkdown(text) |
markdown.js | Markdown → HTML |
- Add route in
lib/routes/{file}.js - Access data via
conversationsMap from data.js - Call
ensureMessages(id)before accessing.messages - Call
saveIndex()orsaveConversation(id)after changes
- Server → Client: Add to handler in
server.js, send viaws.send(JSON.stringify({...})) - Client handler: Add case in
handleWSMessage()in websocket.js
- Add to
POST /api/conversationsin routes/conversations.js - Add to
PATCHhandler - Add to
convMeta()in data.js - Add UI control in index.html modal
- Add state in state.js if needed
- Restore in
openConversation()in conversations.js
- Create
public/css/themes/{name}.css(copy existing) - Define
:root(dark) andhtml[data-theme="light"]variants - Add to
STATIC_ASSETSin sw.js - Add option to theme dropdown in index.html
- Increment service worker cache version
- Create class extending LLMProvider in
lib/providers/ - Implement required methods:
getModels(),chat(),cancel(),isActive(),generateSummary() - Set static
idandnameproperties - Register in
lib/providers/index.jsinitProviders() - Add UI option in new conversation modal
- Test with different conversation settings (autopilot, sandbox)
- Sandbox is enabled by default (
conv.sandboxed !== false) - Use --settings JSON flag to pass sandbox config to Claude CLI
- Permission patterns use glob syntax (e.g.,
Edit(/path/**)) - Always deny sensitive paths (.env, .ssh, credentials) in deny list
- Unsandboxed + autopilot mode uses --dangerously-skip-permissions
- Unsandboxed without autopilot prompts for each permission
- Extend structured content handling in
lib/routes/files.js(sendFileContentResponse) - Add/adjust parsing or normalization logic for the new format
- Add rendering support in
public/js/explorer/file-viewer-content.js - If interactive, add helper module(s) in
public/js/explorer/(for examplegeo-preview.js) - Reuse shared viewer wiring from
public/js/explorer/context.jsso both convo and standalone shells inherit it - Update docs and supported-type UI hints in the viewer header
| Class | Usage |
|---|---|
.message.user |
User bubble (right, gradient) |
.message.assistant |
Assistant bubble (left) |
.conv-card |
Conversation list item |
.conv-card.selected |
Selected in bulk mode |
.slide-in / .slide-out |
View transitions |
.recording |
Mic button recording state |
.speaking |
TTS button playing state |
.glass-bg |
Glass-morphism effect |
.scope-group |
Conversation group by cwd |
.selection-mode |
Bulk select active |
.file-panel |
File browser side panel |
.preview-container |
File preview display |
.branch-tree |
Fork visualization |
.memory-item |
Memory list item |
.sandbox-badge |
Indicates sandboxed conversation |
claude -p "{text}" \
--output-format stream-json \
--verbose \
--model {model_id} \
--include-partial-messages \
[--settings {sandbox_json}] # if sandboxed (default)
[--dangerously-skip-permissions] # if unsandboxed + autopilot
[--resume {sessionId}] # continuing conversation
[--add-dir {cwd}] # working directory
[--append-system-prompt {memories}] # inject memoriesSession IDs: Stored after first result, passed via --resume on subsequent messages. Reset to null when editing or regenerating.
Sandbox Settings: When conv.sandboxed !== false, the --settings flag passes a JSON config with permission rules:
allowpatterns grant specific permissions (e.g.,Edit(/Users/me/project/**))denypatterns block access (e.g.,Read(**/.env))- Network domains whitelist (e.g.,
github.com,*.npmjs.org)
Autopilot: When conv.sandboxed === false AND conv.autopilot !== false, uses --dangerously-skip-permissions instead.