Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 40 additions & 6 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ npm run lint # ESLint (server + frontend)
npm run lint:fix # ESLint with auto-fix
npm run format # Prettier (write)
npm run format:check # Prettier (check only)
npm test # Vitest (209 tests, 22 files)
npm test # Vitest (525 tests, 48 files)
```

Pre-commit hooks (husky + lint-staged) run lint and format on staged files. Conventional commit messages enforced via commitlint. **Pre-commit hooks are not a substitute for CI** — they only check staged files of specific types. Always verify CI passes after pushing (see `.cursor/rules/ci-discipline.mdc`).
Expand All @@ -26,6 +26,7 @@ Web-based command center for Claude Code sessions via the Agent SDK. Two npm pro
**Backend** (`server/`) — Node.js + Express + TypeScript, run via `tsx`

- `index.ts` — Express app, mounts routes, HTTP server + WebSocket. Sends `client_id` on WS connect for session reattach. Runs stale worktree cleanup on startup.
- `app.ts` — Express app factory (extracted from index.ts for testability via supertest).
- `chat.ts` — Agent SDK integration: `startChat()` assembles prompts, creates streaming-input `AsyncQueue`, starts `query()`, wires message handler. `sendToChat()` and `interruptChat()` push follow-up messages. Session listing via `listSessions`/`getSessionMessages` SDK calls. `getMessages()` returns v2 `FinishedMessage[]` format for session restore.
- `query-loop.ts` — Core event translator: SDK events → v2 protocol. Maintains `openBlockCount` for deferred `message_end`. `forceFlushPendingMessage()` force-closes open blocks at turn boundaries and session end. Tracks snapshot state for iOS reattach recovery.
- `session-registry.ts` — `SessionRegistry` class: detach/reattach/rekey, TTL-based abort (10 min), `currentSnapshot` for reattach recovery, `findBySessionId` for reconnection.
Expand All @@ -35,6 +36,19 @@ Web-based command center for Claude Code sessions via the Agent SDK. Two npm pro
- `tool-summary.ts` — Tool input summarization. **SDK field names**: `file_path` (not `path`), `content` (not `contents`), `pattern`/`path` for Glob (not `glob_pattern`/`target_directory`).
- `content-blocks.ts` — SDK content block parsing (text, tool_use, tool_result). Used by stream and session restore API.
- `permissions.ts` — Permission request/response registry with tier metadata.
- `skills.ts` — Skill registry: scoped discovery (bundled → user → repo), lazy metadata/body loading, deterministic precedence, collision tracking.
- `slash-commands.ts` — Slash-command parsing, prompt expansion, skill resolution from user input.
- `skill-policy.ts` — Per-turn tool restriction: skills declare `allowed-tools` in frontmatter, enforced as a ceiling on `canUseTool`.
- `native-commands.ts` — Built-in native commands (`/skills`) — TypeScript product behavior, not prompt-based.
- `auto-rename.ts` — Automatic session renaming every N user prompts via LLM summarization.
- `event-store.ts` — Persistent event store for session message replay.
- `hook-bridge.ts` — Bridges project-level hooks (`.claude/settings.json`) to Agent SDK sessions.
- `api-schemas.ts` — Zod schemas for HTTP request/response validation.
- `ws-schemas.ts` — Zod schemas for WebSocket message validation.
- `internal-token.ts` — Internal token generation for inter-process auth.
- `repo-mcp-server.ts` — Repo-scoped MCP server configuration.
- `notification-helpers.ts` — Shared notification formatting utilities.
- `inbox.ts` — Inbox integration endpoint.
- `mcp-config.ts` — Loads MCP server configs from Cursor mcp.json.
- `worktree.ts` — Git worktree lifecycle.
- `repo-config.ts` — `.mitzo.json` reader for quick actions, venv paths, tier overrides.
Expand All @@ -48,10 +62,11 @@ Web-based command center for Claude Code sessions via the Agent SDK. Two npm pro
**Frontend** (`frontend/`) — React 19 + Vite + TypeScript

- `types/chat.ts` — v2 types: `StreamingBlock`, `StreamingMessage`, `FinishedBlock`, `FinishedMessage`, `BlockType`, `RawToolInput`, `PermissionRequest`, `ToolTier`, `Session`, `ImageAttachment`.
- `hooks/` — `useChatMessages` (useReducer for v2 protocol: MESSAGE_START/BLOCK_START/BLOCK_DELTA/BLOCK_END/TOOL_RESULT/MESSAGE_END/SESSION_END/MESSAGE_SNAPSHOT/RESTORE), `useChatSession`, `useChatConnection`, `usePermission`, `useFileNavigation`, `useFileEditor`.
- `lib/` — `ws-pool` (module-level WebSocket pool with 500-message buffer and auto-reconnect), `groupMessages` (tool block grouping with configurable threshold), `constants`, `formatTime`, `paste-images`, `model-preference`.
- Pages: `Login`, `SessionList`, `ChatView` (renders `current` inline + `messages[]` grouped), `FileViewer`.
- Components: `MessageBubble` (UserBubble/TextBubble), `ThinkingBlock`, `ToolPill`, `ToolGroup`, `PermissionBanner`, `ChatInput`, `ErrorBoundary`, `MitzoLogo`.
- `types/ws-messages.ts` — Typed WebSocket message unions (client → server, server → client).
- `hooks/` — `useChatMessages` (useReducer for v2 protocol: MESSAGE_START/BLOCK_START/BLOCK_DELTA/BLOCK_END/TOOL_RESULT/MESSAGE_END/SESSION_END/MESSAGE_SNAPSHOT/RESTORE), `useChatSession`, `useChatConnection`, `usePermission`, `useFileNavigation`, `useFileEditor`, `useLongPress`.
- `lib/` — `ws-pool` (module-level WebSocket pool with 500-message buffer and auto-reconnect), `groupMessages` (tool block grouping with configurable threshold), `constants`, `formatTime`, `paste-images`, `model-preference`, `rename-session`, `resizeImage`, `swipe-reveal`, `truncate`.
- Pages: `Login`, `SessionList`, `ChatView` (renders `current` inline + `messages[]` grouped), `FileViewer`, `InboxView`.
- Components: `MessageBubble` (UserBubble/TextBubble), `ThinkingBlock`, `ToolPill`, `ToolGroup`, `PermissionBanner`, `ChatInput`, `SlashPicker`, `ErrorBoundary`, `MitzoLogo`.
- Auth via `ProtectedRoute` wrapper. Vite dev server proxies `/api` and `/ws` to backend.

**v2 protocol — key reducer behaviors:**
Expand Down Expand Up @@ -108,6 +123,25 @@ Web-based command center for Claude Code sessions via the Agent SDK. Two npm pro
- Claude sessions get all configured MCP tools (Jira, GitLab, etc.) automatically.
- `GET /api/config` exposes server names (not credentials) to the frontend.

**Skills system:**

- Skills are reusable prompt packages invoked via `/slash-command` in chat input.
- Three scopes: **Native** (TypeScript commands like `/skills`), **Skills** (markdown with YAML frontmatter), **Quick actions** (launchers from `.mitzo.json`).
- Discovery: repo-local (`.mitzo/skills/`), user (`~/.mitzo/skills/`), bundled (`server/bundled-skills/`).
- Resolution order: Native → Repo → User → Bundled. Deterministic precedence with collision metadata.
- `SlashPicker` component shows available skills when user types `/` in chat input, with type badges and collision notes.
- Skills can declare `allowed-tools` in frontmatter — enforced as a ceiling (never expands permissions) via `skill-policy.ts`.
- Bundled skills: `/simplify` (code review), `/risk-scan` (security audit), `/pr-review` (PR review).
- `GET /api/skills` returns merged registry with collision info, scoped by `cwd` query param.

**Voice integration (landing with PR #108):**

- Client-direct architecture: frontend talks to [Yapper](~/projects/yapper/) for STT/TTS, server stays text-only.
- `lib/tts.ts` — Text chunking at sentence boundaries, WAV synthesis via Yapper API, singleton AudioContext playback.
- `hooks/useVoice.ts` — STT (push-to-talk) + TTS (auto-speak toggle, voice selection, sequential chunk playback).
- `components/VoiceSettings.tsx` — Speaker toggle with pulse indicator, voice picker dropdown grouped by language.
- Graceful degradation when Yapper is offline — voice features hide automatically.

**Key conventions:**

- All server imports use `.js` extensions (required for ESM + tsx)
Expand All @@ -119,7 +153,7 @@ Web-based command center for Claude Code sessions via the Agent SDK. Two npm pro

## What is Mitzo?

Mitzo is a web-based command center for Claude Code sessions, built on the Anthropic Agent SDK. It provides a mobile-first interface for managing AI-assisted workflows — chat sessions, file browsing/editing, worktree isolation, MCP tool integration, and quick actions.
Mitzo is a web-based command center for Claude Code sessions, built on the Anthropic Agent SDK. It provides a mobile-first interface for managing AI-assisted workflows — chat sessions with slash-command skills, file browsing/editing, worktree isolation, MCP tool integration, voice input/output via Yapper, and quick actions.

Mitzo lives at `~/tools/mitzo/` and is pointed at the `mgmt` workspace via the `REPO_PATH` env var. It is open source and designed to be portable — no hardcoded paths or machine-specific configuration.

Expand Down
20 changes: 17 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,13 @@ Claude Code on your phone. A self-hosted web UI built on the [Agent SDK](https:/

- **Streaming chat** with thinking blocks, tool pills, and markdown
- **Three modes** — Ask (read-only), Agent (file edits allowed), Auto (shell too). Switch mid-chat.
- **Slash-command skills** — `/simplify`, `/risk-scan`, `/pr-review`, plus repo-local and user skills. Type `/` to browse.
- **Voice** — push-to-talk input (STT) and auto-speak output (TTS) via [Yapper](https://github.com/dimakis/yapper). Graceful degradation when offline.
- **MCP tools** — reads `~/.cursor/mcp.json`, passes servers to every session
- **File browser** — view and edit repo files, switch between worktree roots
- **Worktree sandbox** — opt-in git worktree isolation per session
- **Session resilience** — phone sleeps, WS drops, session survives. Reattach on reconnect. Message snapshot recovery for iOS silent drops.
- **Auto-rename sessions** — sessions get meaningful names via LLM summarization after every few prompts
- **Quick actions** — one-tap commands via `.mitzo.json`
- **Push notifications** — ntfy + Pushover (Apple Watch) when Claude needs approval
- **Image attachments** — send photos/screenshots from your camera
Expand Down Expand Up @@ -47,7 +50,7 @@ Phone (Tailscale) ──┬── HTTP: REST API

The server translates raw SDK stream events into a v2 block lifecycle protocol (`block_start` → `block_delta` → `block_end`). Explicit turn boundaries (`message_start`/`message_end`), deferred finalization, and message snapshots for reconnect recovery. See [docs/design/message-protocol-v2.md](docs/design/message-protocol-v2.md).

### Backend (`server/`) — 20 modules
### Backend (`server/`) — 33 modules

| Core | Purpose |
| ----------------------- | ----------------------------------------------------------------------------------- |
Expand All @@ -57,12 +60,23 @@ The server translates raw SDK stream events into a v2 block lifecycle protocol (
| `permission-handler.ts` | `canUseTool` callback — auto-allow by tier, prompt via WS + push notifications |
| `async-queue.ts` | `AsyncIterable` queue for follow-up messages and interrupt |

| Skills | Purpose |
| -------------------- | --------------------------------------------------------- |
| `skills.ts` | Skill registry — scoped discovery, precedence, collisions |
| `slash-commands.ts` | Slash-command parsing and prompt expansion |
| `skill-policy.ts` | Per-turn tool restriction from skill frontmatter |
| `native-commands.ts` | Built-in native commands (`/skills`) |

| Supporting | Purpose |
| ------------------------------------------------------------------------------ | ------------------------------------------------- |
| `tool-tiers.ts` | Risk classification + mode/tier auto-allow matrix |
| `tool-summary.ts` | Summarizes tool inputs for pill display |
| `permissions.ts` | Request/response registry |
| `content-blocks.ts` | SDK content block parsing |
| `event-store.ts` | Persistent event store for session replay |
| `auto-rename.ts` | LLM-based session auto-renaming |
| `hook-bridge.ts` | Project hooks → Agent SDK bridge |
| `api-schemas.ts` / `ws-schemas.ts` | Zod validation schemas |
| `mcp-config.ts` | Loads Cursor MCP config |
| `worktree.ts` | Git worktree lifecycle |
| `repo-config.ts` | `.mitzo.json` reader |
Expand All @@ -72,7 +86,7 @@ The server translates raw SDK stream events into a v2 block lifecycle protocol (

### Frontend (`frontend/`) — React 19 + Vite

Four pages (`Login`, `SessionList`, `ChatView`, `FileViewer`), a `useReducer`-based message state machine (`useChatMessages`), module-level WebSocket pool with 500-message buffer, and components for thinking blocks, tool pills, tool groups, and permission banners.
Five pages (`Login`, `SessionList`, `ChatView`, `FileViewer`, `InboxView`), a `useReducer`-based message state machine (`useChatMessages`), module-level WebSocket pool with 500-message buffer, and components for thinking blocks, tool pills, tool groups, permission banners, and a slash-command picker.

## Environment

Expand Down Expand Up @@ -109,7 +123,7 @@ Drop this in your repo root for quick actions and Python venv support:

```bash
npm run dev # backend + frontend concurrently
npm test # vitest (209 tests)
npm test # vitest (525 tests, 48 files)
npm run lint # eslint
npm run format:check # prettier
```
Expand Down
6 changes: 6 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@ The `/api/files`, `/api/files/read`, and `/api/files/write` endpoints restrict a

`PUT /api/files/write` only overwrites existing files within allowed paths — it cannot create new files or write outside the repo boundary. All file endpoints are behind cookie auth middleware.

## Skills System

Skills (invoked via `/slash-command`) can declare `allowed-tools` in their YAML frontmatter. This is enforced by `skill-policy.ts` as a **ceiling** — skills can only restrict the tools available to a session, never expand them. A skill that declares `allowed-tools: [Read, Grep]` will block writes and shell access for that turn, regardless of the session's mode. Native commands (TypeScript) bypass this — they are product behavior, not prompt-based.

Skill files are read-only markdown loaded from disk (bundled, `~/.mitzo/skills/`, or `${cwd}/.mitzo/skills/`). They cannot execute code — they expand into system prompt content.

## Known Limitations

- **No HTTPS**: traffic is encrypted by Tailscale (WireGuard), but the HTTP layer itself is plaintext. If accessed outside Tailscale, cookies and passphrases would be transmitted in the clear.
Expand Down
24 changes: 17 additions & 7 deletions docs/enterprise-plan.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Mitzo Enterprise Engineering Plan

**Version:** 1.3
**Date:** 2026-04-03
**Version:** 1.4
**Date:** 2026-04-05
**Scope:** Documentation only — no code changes until explicitly requested

---
Expand All @@ -15,9 +15,19 @@ Mitzo is a functional, mobile-first Claude Code interface built on Node.js/Expre
- `chat.ts` reduced from 575 → 476 LOC (query-loop, permission-handler, async-queue extracted)
- `ChatView.tsx` reduced from 542 → 346 LOC (useChatMessages, useChatSession, useChatConnection, usePermission extracted)

Current state (2026-04-03):
**Post-v0.1.0 features shipped (2026-04-03 → 2026-04-05):**

1. **Testing** — 297 tests across 34 files. Route-layer tests, WS integration tests, component tests, hook tests all in place. No e2e tests yet.
- **Skills system** — full implementation across 6 PRs: skill registry with scoped discovery, slash-command parsing, per-turn tool restriction policy, skills API + slash picker UX, native command registry, and bundled skill pack (`/simplify`, `/risk-scan`, `/pr-review`).
- **Project hooks bridge** — `.claude/settings.json` hooks forwarded to Agent SDK sessions.
- **Event store** — persistent event store for session message replay.
- **Auto-rename sessions** — LLM-based session renaming every N user prompts.
- **Voice integration** — push-to-talk STT (phase 1) and TTS playback (phase 2, PR #108) via Yapper. Client-direct architecture — server stays text-only.
- **User message persistence** — user messages persisted at entry points, not query loop.
- Server module count grew from 20 → 33.

Current state (2026-04-05):

1. **Testing** — 525 tests across 48 files (~559 with voice TTS PR). Route-layer tests, WS integration tests, component tests, hook tests all in place. No e2e tests yet.
2. **Type safety** — Zod schemas validate all incoming WS messages and HTTP bodies. Production `any` count is 0. `ServerMessage` discriminated union covers all frontend WS types.
3. **Security** — Helmet CSP, login rate limiting, request size limits, graceful shutdown. CSRF and NTFY token signing remain.
4. **Session resilience** — AbortController on fetch races, session list auto-refresh, WS pool cleanup, registry leak guard, turn-complete push notifications.
Expand All @@ -30,7 +40,7 @@ The remaining work is Phase 5 completion (CSRF, token signing) and Phase 6 (e2e

## Current State Baseline

Measured against the codebase as of 2026-04-03 (v0.1.0, post-Phase 2 partial).
Measured against the codebase as of 2026-04-05 (post-v0.1.0, skills + voice shipped).

### Codebase Size

Expand Down Expand Up @@ -65,8 +75,8 @@ New since v1.2: `query-loop.ts`, `permission-handler.ts`, `async-queue.ts`, `use

| Metric | Count |
| ----------------------------- | --------------------------------------------------------------------------------: |
| Test files | 34 |
| Test cases (`it()`) | 297 |
| Test files | 48 |
| Test cases (`it()`) | 525 |
| Frontend reducer test files | 1 (`chatMessagesReducer.test.ts` — 30+ cases) |
| Frontend hook test files | 4 (`useChatSession`, `useChatConnection`, `usePermission`, `useChatMessages`) |
| Frontend component test files | 5 (`ErrorBoundary`, `PermissionBanner`, `ThinkingBlock`, `ToolGroup`, `ToolPill`) |
Expand Down
5 changes: 5 additions & 0 deletions frontend/src/hooks/useChatMessages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,7 @@
onSessionAssigned: (id: string) => void,
onSessionExpired: (sessionId: string | undefined) => void,
onMessagesRestored?: () => void,
onSessionRenamed?: (name: string) => void,
) {
const [state, dispatch] = useReducer(chatMessagesReducer, INITIAL_STATE);
const pendingSend = useRef<Record<string, unknown> | null>(null);
Expand Down Expand Up @@ -494,6 +495,10 @@
onSessionAssigned(msg.sessionId as string);
break;

case 'session_renamed':
onSessionRenamed?.(msg.name as string);
break;

// v2 events
case 'message_start':
dispatch({ type: 'MESSAGE_START', messageId: msg.messageId as string });
Expand Down Expand Up @@ -634,7 +639,7 @@
}
}
},
[poolKey, onSessionAssigned, onSessionExpired, onMessagesRestored],

Check warning on line 642 in frontend/src/hooks/useChatMessages.ts

View workflow job for this annotation

GitHub Actions / ci

React Hook useCallback has a missing dependency: 'onSessionRenamed'. Either include it or remove the dependency array. If 'onSessionRenamed' changes too often, find the parent component that defines it and wrap that definition in useCallback
);

return {
Expand Down
Loading
Loading