Ctrl K
ComunidadGuías…

claude-code-integration-research.md

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# Claude Code Integration Research

Research notes from studying jc (a Rust/gpui app that wraps Claude Code) to inform
building similar functionality in iTerm2.

## How jc Communicates with Claude Code

The communication is asymmetric:

- **jc -> Claude Code**: PTY stdin (keystrokes and bracketed-paste text)
- **Claude Code -> jc**: HTTP hooks (Claude Code POSTs JSON to a localhost server)

There is no bidirectional API. jc is essentially a terminal emulator (using
alacritty's VTE) with a hook server bolted on.

### Sending Input

Claude Code runs as a PTY subprocess via the `portable_pty` crate. All input
goes through the PTY writer:

- **Keystrokes**: Converted to terminal escape sequences and written directly.
- **Text paste**: Wrapped in bracketed paste mode (`\x1b[200~...\x1b[201~`),
  with ESC bytes stripped to prevent escape injection. Enter (`\r`) is sent
  after a 200ms delay on a background thread to give Claude Code time to
  process the paste.
- **Commands**: `/copy`, prompt text, etc. are pasted the same way.

### Receiving State

jc installs hooks into `.claude/settings.local.json` pointing at a localhost
HTTP server (using `tiny_http`, ~190 lines). Claude Code fires POST requests
when its state changes. The hook server parses the JSON body and sends events
through a flume channel to the main app.

Hook event types jc uses:

| Hook | Meaning |
|------|---------|
| `UserPromptSubmit` | User submitted a prompt -- Claude is about to work |
| `Stop` | Claude finished normally |
| `StopFailure` | API error during execution |
| `Notification` (idle_prompt) | Claude finished, waiting for new input |
| `Notification` (permission_prompt) | Permission dialog shown |
| `PermissionRequest` | Tool permission dialog appeared |
| `SessionEnd` | Session ended (used to detect `/clear`) |
| `SessionStart` | Session started (paired with SessionEnd for `/clear`) |

Not all hook types support `"type": "http"`. `SessionEnd` and `SessionStart`
only support `"type": "command"`, so jc works around this by using command hooks
that pipe stdin through `curl` back to its own HTTP server.

## State Tracking

Core state in jc's `SessionState`:

- `busy: bool` -- true while Claude is actively working
- `has_ever_been_busy: bool` -- true once Claude has worked at least once
- `pending_events: HashSet<PendingEvent>` -- tracks permission prompts, API errors, terminal bells

Transitions:
- `PromptSubmit` -> `busy = true`
- `Stop` / `IdlePrompt` / `PermissionPrompt` / `StopFailure` -> `busy = false`

## Diff Discovery

jc does NOT rely on hooks to learn which files changed. It runs
`git2::Repository::diff_tree_to_workdir_with_index()` to compare HEAD against
the working tree. Diffs are refreshed:

- Every 2 seconds when the window is active (working-tree diffs are always
  considered stale since `.git/index` mtime doesn't reflect unstaged edits)
- Immediately when a hook event fires (Stop, StopFailure, etc.)

Each file diff gets a checksum. A `reviewed` hashmap tracks which files the user
has marked as reviewed. If the diff content changes, the reviewed state is
invalidated.

## Tricky Edge Cases jc Solved

1. **`/clear` two-event correlation**: `/clear` fires `SessionEnd` then
   `SessionStart` as separate hooks. jc stashes the `SessionEnd` with a
   10-second expiry and pairs it with the subsequent `SessionStart` by
   project path.

2. **UUID bootstrapping**: Fresh `claude` sessions start with `uuid = None`.
   The UUID is discovered on the first hook event. jc does a two-pass match:
   first by UUID across all projects, then falls back to assigning to a
   pending `uuid=None` session in the matching project.

3. **200ms paste delay**: Enter is sent on a background thread after 200ms to
   let the PTY buffer process pasted content. There's no "paste complete"
   acknowledgment.

4. **Escape sanitization**: `write_text()` strips `\x1b` from pasted content
   to prevent premature termination of bracketed paste mode.

5. **L2 problem suppression**: "Unsent WAIT" notifications are suppressed when
   Claude is busy or higher-priority problems exist, to avoid noise.

6. **L0 cross-session "home"**: Permission prompts can require jumping to a
   different session. The system saves a "home" session on the first jump and
   returns after all L0 problems are resolved.

7. **Multi-instance hook collision**: Hook uninstall removes ALL `/jc-hook/`
   URLs, which would break a second instance's hooks.

8. **File watcher self-write suppression**: An `AtomicBool` flag suppresses
   filesystem watcher events for 200ms after jc itself writes a file.

9. **`usize::MAX` sentinel**: The refresh channel uses `usize::MAX` as a
   "wake up and re-evaluate" signal distinct from project-index-targeted
   refreshes.

10. **Clipboard polling for `/copy`**: After sending `/copy` to Claude Code's
    PTY, jc polls the system clipboard every 200ms for up to 3 seconds waiting
    for it to change. No API exists to extract Claude's last response directly.

## jc UI Structure

jc is a multi-pane IDE built on gpui (Zed's GPU-accelerated UI framework).
Default layout is three resizable side-by-side panes, each of which can be:

- **Claude Terminal** -- Claude Code's TUI in a full terminal emulator
- **General Terminal** -- a regular shell
- **Code Viewer** -- syntax-highlighted editor (tree-sitter) for files and `/copy` replies
- **Git Diff** -- diff view with reviewed/unreviewed tracking
- **TODO Editor** -- per-project TODO.md with session management
- **Global TODO** -- cross-project todo list

Claude Code's output is shown directly in the terminal pane. The `/copy`
feature extracts the last reply into the code viewer as a convenience.

## Implications for iTerm2

### Hook transport

Command hooks work for all event types; HTTP hooks don't (SessionEnd/SessionStart
are command-only). For iTerm2, command hooks are the natural choice since they
can invoke `it2` to talk to iTerm2 over its existing API endpoint (raw protobuf).
This avoids running an HTTP server entirely.

The hook command would be something like:
```
it2 claude-hook --session-id $SESSION_ID
```
with JSON on stdin, and `it2` forwards it to iTerm2 via the API socket.

### What iTerm2 needs from hooks

For parity with jc's functionality:

- **UserPromptSubmit**: Mark session as busy (spinner, status indicator)
- **Stop / Notification(idle_prompt)**: Mark session as idle
- **StopFailure**: Show error indicator
- **Notification(permission_prompt) / PermissionRequest**: Show attention badge
- **SessionEnd / SessionStart**: Track `/clear` for session identity

Other events (PreToolUse, PostToolUse, SubagentStart, etc.) could power
richer UI like tool activity indicators or progress tracking.

### Existing iTerm2 mechanisms that could help

- **Triggers**: Regex on terminal output, useful for detecting patterns Claude
  Code renders (progress bars, prompts). Complement hooks for things not
  covered by the hook protocol.
- **Badges/marks**: Visual indicators for session state.
- **Python API / `it2`**: IPC channel for hook commands to report events.

### Swift types

`ClaudeCodeHookEvent.swift` was written to `it2cli/Sources/it2/` with Codable
structs covering all 25 hook event types from the official spec at
https://code.claude.com/docs/en/hooks.