Phase 1 of the Claude Code integration discussed in PR #24's comments. Phase 2 (parse jsonl semantics to determine waiting / done / error) and Phase 3 (macOS notifications) will be separate PRs.
What this PR delivers
A new internal subsystem that watches `~/.claude/projects/` for Claude Code session activity and exposes it through LISA's existing surfaces.
Privacy contract
We never read jsonl message contents. Only filename, mtime, and size are observed. The user's prompts and Claude's replies stay in their files. This is asserted in module-level comments and visible to anyone reading `watcher.ts`.
Pieces
| File |
Change |
| `src/integrations/claude-code/watcher.ts` (new) |
Recursive `fs.watch` on `~/.claude/projects/`; maintains in-memory session map; debounced 200ms; emits `update` events. No new dependencies (uses node built-ins). |
| `src/web/server.ts` |
Start watcher on `lisa serve --web`; broadcast updates as `{type:"claude_session_update", project, session, event, ts}` on the existing `/events` SSE; new endpoint `GET /api/claude/sessions` returns currently-active sessions. |
| `src/web/island.ts` |
New status indicator (orange) for active Claude sessions; new "CLAUDE CODE · N ACTIVE" section in the expand panel listing the most recent activity per project. |
| `docs/MAC_ISLAND_PLAN.md` |
Brief note that Claude Code integration shipped. |
Event semantics
The watcher emits one event per significant filesystem change:
- `new` — a fresh session jsonl appeared in a project directory
- `message` — an existing session's jsonl grew (i.e. new message arrived). Debounced 200ms so rapid streaming doesn't spam.
- (out of scope) `waiting` / `completed` / `error` — require jsonl parsing; Phase 2.
"Active" = mtime within the last 30 minutes.
Island UI
- Status dot gets an additional state `claude_active` (orange, gentle pulse).
- Priority: `thinking` > `dreaming` > `unread ★` > `claude_active` > `offline`. (LISA-state always wins over Claude monitor visualization.)
- Expand panel adds a section between the existing "currently wanting" and "while you were away":
```
CLAUDE CODE · 3 ACTIVE
Claude-Luddi · 12s ago
Adex · 4m ago
AgenticThinking · 9m ago
```
- Click a row → copy session id to clipboard (Phase 1 stub for "open in iTerm" planned Phase 2).
Reverse-decoding project names
`~/.claude/projects/-Users-oratis-Projects-Foo` → display "Foo". Algorithm: strip leading dash, replace remaining dashes with slashes, take basename. Best-effort — falls back to the encoded form if decoding looks weird.
Verification
```sh
lisa serve --web
In another terminal:
touch ~/.claude/projects/-Users-oratis-Projects-Adex/test-session.jsonl
echo '{}' >> ~/.claude/projects/-Users-oratis-Projects-Adex/test-session.jsonl
Watch the SSE fire:
curl -N http://localhost:5757/events | grep claude_session
```
Expected output:
```
data: {"type":"claude_session_update","project":"Adex","session":"test-session","event":"new","ts":"..."}
data: {"type":"claude_session_update","project":"Adex","session":"test-session","event":"message","ts":"..."}
```
Phase 1 of the Claude Code integration discussed in PR #24's comments. Phase 2 (parse jsonl semantics to determine waiting / done / error) and Phase 3 (macOS notifications) will be separate PRs.
What this PR delivers
A new internal subsystem that watches `~/.claude/projects/` for Claude Code session activity and exposes it through LISA's existing surfaces.
Privacy contract
We never read jsonl message contents. Only filename, mtime, and size are observed. The user's prompts and Claude's replies stay in their files. This is asserted in module-level comments and visible to anyone reading `watcher.ts`.
Pieces
Event semantics
The watcher emits one event per significant filesystem change:
"Active" = mtime within the last 30 minutes.
Island UI
```
CLAUDE CODE · 3 ACTIVE
Claude-Luddi · 12s ago
Adex · 4m ago
AgenticThinking · 9m ago
```
Reverse-decoding project names
`~/.claude/projects/-Users-oratis-Projects-Foo` → display "Foo". Algorithm: strip leading dash, replace remaining dashes with slashes, take basename. Best-effort — falls back to the encoded form if decoding looks weird.
Verification
```sh
lisa serve --web
In another terminal:
touch ~/.claude/projects/-Users-oratis-Projects-Adex/test-session.jsonl
echo '{}' >> ~/.claude/projects/-Users-oratis-Projects-Adex/test-session.jsonl
Watch the SSE fire:
curl -N http://localhost:5757/events | grep claude_session
```
Expected output:
```
data: {"type":"claude_session_update","project":"Adex","session":"test-session","event":"new","ts":"..."}
data: {"type":"claude_session_update","project":"Adex","session":"test-session","event":"message","ts":"..."}
```