@levistudio/redline 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md ADDED
@@ -0,0 +1,227 @@
1
+ # Redline — agent onboarding
2
+
3
+ This file orients an AI agent contributing to the Redline codebase. It captures the architecture, the load-bearing implementation decisions, and the non-obvious gotchas that have bitten previous changes. For product framing (what Redline is, who it's for, how to use it), see [README.md](README.md).
4
+
5
+ ## What you're working in
6
+
7
+ Redline is a single-player, local Markdown review tool. The user points it at a `.md` file; it opens a browser-based reader; the user leaves inline comments; an agent process replies in real time; the user resolves and accepts; the document is revised. The two pieces are:
8
+
9
+ 1. **The Review Reader** — local Bun + Hono server, renders Markdown, serves a small client-side JS app, handles select-to-comment, persists comments to a sidecar.
10
+ 2. **The conversational agent** — a child process spawned alongside the server that listens to comment events and replies via the selected local agent provider (`claude` or `codex`).
11
+
12
+ The product is real-time conversation, not turn-based submit/respond. Reviews can span multiple rounds (comment → reply → resolve → revise → next round) until the user signs off.
13
+
14
+ ## Architecture
15
+
16
+ ### Sidecar — `.review/<filename>.json`
17
+
18
+ The sidecar is the source of truth for a review session. Schema lives in [src/sidecar.ts](src/sidecar.ts):
19
+
20
+ ```ts
21
+ Sidecar { file, context?, rounds: Round[] }
22
+ Round { round, started_at, submitted_at, agent_replied_at, resolved_at, comments: Comment[] }
23
+ Comment { id, quote, context_before, context_after, thread: ThreadEntry[], resolved }
24
+ ThreadEntry { role: human|agent, name?, message, at, requires_revision?, revision_reason? }
25
+ ```
26
+
27
+ - **Per-file mutex.** `withSidecar(filePath, fn)` in [src/sidecar.ts](src/sidecar.ts) holds a per-file lock across `load → mutate → save` so concurrent POSTs can't interleave and silently drop a writer's mutation. Use it for every endpoint that touches the sidecar.
28
+ - **History.** Every `resolve` snapshots the pre-revision file to `.review/history/<file>.<iso>.md`. Past rounds are viewable read-only at `/round/:n`.
29
+
30
+ ### CLI shape
31
+
32
+ ```
33
+ redline <file> # opens the review reader; URL is printed and written to .review/<file>.startup.json
34
+ redline <file> --context "..." # opens with a reviewer-supplied focus statement (see "Context-aware prompts" below)
35
+ redline <file> --agent codex # use Codex instead of the auto-detected/default provider
36
+ redline <file> --no-agent # manual annotation mode — no agent spawn, no provider CLI required on PATH
37
+ redline resolve <file> # one-shot: read the sidecar, run the revision pass, write back
38
+ ```
39
+
40
+ The bare-arg path also spawns a dedicated agent subprocess alongside the server — see "The dedicated agent process" below.
41
+
42
+ ### Context-aware prompts
43
+
44
+ When the user passes `--context "..."` (or the sidecar already has `context` set from a prior run), the string is rendered above the doc as a banner *and* prepended to both prompt builders:
45
+
46
+ - [src/agent.ts](src/agent.ts) calls `loadSidecar` per reply and prepends a `Reviewer's stated focus` block to the user message before the document/comment sections.
47
+ - [src/resolve.ts](src/resolve.ts) prepends the same block to the revision user message.
48
+
49
+ The shared helper is [src/contextBlock.ts](src/contextBlock.ts) — a one-liner that returns either the formatted block or empty string. It wraps the context through the same UUID-anchored envelope as the document and comment text, so adversarial context content can't escape its envelope to pose as system instructions.
50
+
51
+ ### Frontend
52
+
53
+ Client-side JS lives in [src/client/main.ts](src/client/main.ts). It is bundled at server startup via `Bun.build` and served from memory at `/client.js`. Pure helpers (`escapeHtml`, `latestVerdict`, `nearestCell`, `clampRangeToCell`, `captureSelection`, `highlightText`, `computeNavState`, `preserveScroll`) live in [src/client/lib.ts](src/client/lib.ts) with happy-dom test coverage in [src/client/lib.test.ts](src/client/lib.test.ts). New pure logic should land in `lib.ts` with tests.
54
+
55
+ Server-side state is bootstrapped into the page as `window.__REDLINE__` ahead of the bundle.
56
+
57
+ ### Inline diff toggle
58
+
59
+ After a revision pass, the new round opens with the document rendered **as the diff** (block-level insert/delete bands, word-level `<ins>`/`<del>` marks for modified paragraphs). A header toggle (`#btn-toggle-diff`, "Show changes" / "Hide changes") flips between diff view and clean view. The toggle is only rendered on rounds 2+ in the live view — round 1 has nothing to compare against, and the read-only `/round/:n` views can't use `/api/diff` honestly because that endpoint always compares the current file to the most recent snapshot.
60
+
61
+ - Pure swap helpers (`applyDiffSwap`, `revertDiffSwap`, `updateToggleButton`, `diffStateKey`) live in [src/client/diffToggle.ts](src/client/diffToggle.ts) with tests in [src/client/diffToggle.test.ts](src/client/diffToggle.test.ts). DOM + network glue is in [src/client/diff.ts](src/client/diff.ts).
62
+ - Diff HTML is fetched lazily from `/api/diff` and cached in-module; the original clean innerHTML is captured on the first swap so toggling back is a pure restore (no second fetch, no server round trip).
63
+ - Per-file state is persisted in `sessionStorage` under `rl-diff-on-<title>` so soft refreshes don't reset the view. Auto-enabled on round open when `just-revised` is set.
64
+ - After every swap, both `applyHighlights()` and `renderComments()` re-run because the prose DOM was rebuilt and comment marks/cards need to re-anchor.
65
+ - Commenting stays enabled in diff mode. Quotes captured against diff text may include `<ins>`/`<del>` boundaries; flipping back to clean view can fail to highlight those comments. Accepted tradeoff.
66
+
67
+ ## Design decisions worth knowing
68
+
69
+ - **Free-text comments only.** Typed actions (`[expand]`, `[challenge]`, `[cut]`) are a future direction; don't add structure prematurely.
70
+ - **Sidecar lives next to the file, in `.review/`.** Gitignore-able as a pattern, obvious where to look. Don't move it.
71
+ - **No anchoring-under-edits problem.** The agent doesn't edit the file while the user is reviewing — write happens between rounds. Quote + 32 chars before/after is enough to relocate a comment when reopening a review.
72
+ - **Comments are instructions, not just notes.** The sidecar is structured so an agent can read and act on it in one pass.
73
+ - **Shell out to local agent CLIs, don't use SDKs.** Both `src/agent.ts` (replies) and `src/resolve.ts` (revisions) invoke the selected provider through [src/agentProvider.ts](src/agentProvider.ts), so auth comes from the user's existing Claude Code or Codex session. Do not switch Redline to SDK/API-key auth without a deliberate product decision.
74
+
75
+ ## The dedicated agent process
76
+
77
+ `redline <file>` spawns [src/agent.ts](src/agent.ts) as a child process alongside the server. The agent opens its own SSE connection to `/api/events` and reacts to comment events directly, with no harness-level task-notification overhead.
78
+
79
+ - Reply latency is bounded by the selected provider's inference time instead of by task scheduling.
80
+ - The agent's read loop **does not await** event handlers — it fires them and continues reading. This lets multiple comments process in parallel and prevents a slow response from blocking the SSE stream.
81
+ - An `inProgress` Set deduplicates so the same comment isn't replied to twice if `comment-reply` fires multiply.
82
+ - `agent-replied` is fired only when `inProgress` drains to zero, so the UI sees one "agent done" event per batch rather than per reply.
83
+ - The CLI installs `exit`/`SIGINT`/`SIGTERM` handlers to kill the agent when the server dies, and auto-restarts the agent on unexpected exit (capped to 5 restarts per 60s window — see [src/cli.ts](src/cli.ts)).
84
+
85
+ ### `--no-agent` (manual mode)
86
+
87
+ Skips provider preflight and the agent spawn. The server still serves the page, accepts comments, and resolves them; there's just no agent to reply or revise. The page bootstraps `noAgent: true` into `window.__REDLINE__` and shows a "Manual mode" pill in the header. The client uses the flag to:
88
+
89
+ - Force the round-level button into **finish** mode (`/api/finish`) regardless of comment count, since `/api/accept` would dead-end on the watchdog with no agent to land `/api/reload`.
90
+ - Suppress the "revise the document anyway" secondary link for the same reason.
91
+
92
+ Per-comment verdict logic degrades naturally: with no agent there are no `requires_revision` fields, and the existing fallback ("field absent → treated as accept") makes "Accept as-is" the natural finish path.
93
+
94
+ ## Model picking
95
+
96
+ Both `agent.ts` (replies) and `resolve.ts` (revisions) pick a model tier based on the human's message text:
97
+
98
+ - Default to `fast` for short confirmations and simple substitutions.
99
+ - Promote to `smart` when the message is long (>120 chars for replies, >150 for revisions), contains a question mark, or matches an "involved" keyword (`suggest`, `alternative`, `rewrite`, `restructure`, `tone`, etc.).
100
+ - The provider maps tiers to concrete model ids. `redline resolve --model <id>` overrides the picker for a one-off run.
101
+
102
+ The chosen model is logged so you can see which path each event took. See [src/pickModel.ts](src/pickModel.ts).
103
+
104
+ ## Workflow model
105
+
106
+ The conversation is real-time. There is no "submit for review" step. The flow:
107
+
108
+ 1. Human leaves a comment → server broadcasts `comment-added` SSE event → agent replies immediately.
109
+ 2. Human can reply in the thread → broadcasts `comment-reply` → agent replies again.
110
+ 3. Human resolves comments one by one. When all are resolved, the round-level button enables — labeled by the **majority verdict** the agent attached to its replies (see "Verdict-aware resolve" below). Default is "Revise document"; if every comment was answered without implying an edit, the default flips to "Accept as-is" (calls `/api/finish`, no revision pass). The non-default action is always one click away as a small secondary link below.
111
+ 4. Human clicks Revise → broadcasts `accepted` → agent runs the resolve flow → `/api/reload` triggers a full browser reload to the next round.
112
+ 5. When a round opens with no comments to act on, the same button reads "Done" and calls `/api/finish`. That broadcasts `finished`, the browser shows a "review complete" splash, and the server prints a summary and exits — handing control back to whichever terminal launched it.
113
+
114
+ The Revise button is intentionally **not** styled green. It triggers a non-trivial revision pass, not a final confirmation; neutral styling reflects that.
115
+
116
+ ## SSE event vocabulary
117
+
118
+ | Event | Fired when | Client behavior |
119
+ |---|---|---|
120
+ | `comment-added` | Human posts a new comment | Soft refresh + `applyHighlights()` (new highlight needed) |
121
+ | `comment-reply` | Human or agent posts to existing thread | Soft refresh; remove that comment from `thinkingCommentIds` |
122
+ | `comment-resolved` | Human resolves a comment | Soft refresh + `applyHighlights()` (color change) |
123
+ | `comment-thinking` | Agent POSTs `/api/comment/:id/thinking` | Add to `thinkingCommentIds`, show dots in that thread (multiple threads can be active simultaneously) |
124
+ | `agent-replied` | Agent POSTs `/api/agent-replied` after all in-flight replies finish | Clear `thinkingCommentIds`, soft refresh |
125
+ | `accepted` | Human clicks "Revise document" | Agent's cue to run the resolve flow |
126
+ | `finished` | Human clicks "Done" on a round with no comments, or "Looks good — close session" in the diff overlay | Replace body with a "Review complete — close this tab" splash |
127
+ | `reload` | The resolve flow finishes writing the revised document | Full `window.location.reload()` |
128
+ | `agent-unavailable` | CLI hits the agent restart cap (5 in 60s) | Show persistent "Agent offline" pill in header; sticky until page reload |
129
+
130
+ Soft refresh = `GET /api/comments` then re-render the sidebar. Full reload = `window.location.reload()`. **Use soft refresh for everything except `reload`.** Full reloads scroll to top and feel jarring.
131
+
132
+ The server sends `: ping\n\n` comment frames every 15 seconds to keep the SSE connection alive through long revisions. Without this the browser would silently disconnect during a long revision pass and miss the `reload` event when it finally fired.
133
+
134
+ ## Frontend gotchas (paid in blood)
135
+
136
+ - **Never call `window.location.reload()` for routine UI updates.** It loses scroll position and feels like a page navigation. Soft-refresh via `softRefresh()` for comment events.
137
+ - **DOM rebuilds inside scrollable content can scroll the page to 0.** Two distinct causes:
138
+ 1. `prose.normalize()` + re-wrapping text in `<mark>` shifts layout briefly.
139
+ 2. Destroying a focused `<textarea>` (e.g. when `renderComments()` rebuilds the sidebar after a reply submit) makes the browser scroll the focus target into view, which can land at the document top.
140
+ - The fix is the `preserveScroll(fn)` wrapper in [src/client/lib.ts](src/client/lib.ts). It saves `scrollY`, calls `.blur()` on the active element first, runs the mutation, then sets `scrollTop` synchronously **and** across two `requestAnimationFrame` callbacks. The triple restore is not paranoia — focus-related scroll lands a frame later.
141
+ - Wrap any function that mutates significant DOM with `preserveScroll`. Right now that's `applyHighlights` and `renderComments`.
142
+ - `applyHighlights` is expensive (full prose rewrite). Only call it when highlights actually change: new comment, resolved/unresolved. **Do not call it on reply.** This is why `softRefresh` takes `{ rehighlight: true }`.
143
+
144
+ ## Server lifecycle
145
+
146
+ There is no auto-restart. The server binds to an OS-assigned port (`port: 0`, hostname `127.0.0.1`) so you can't kill it by port number — kill the CLI process. After editing `src/server.ts` or `src/agent.ts`:
147
+
148
+ ```bash
149
+ pkill -f "src/cli.ts" 2>/dev/null
150
+ pkill -f "src/agent.ts" 2>/dev/null
151
+ bun run src/cli.ts <file.md> &
152
+ ```
153
+
154
+ (The CLI also kills the agent on its own SIGINT/SIGTERM, but if you only edited `agent.ts` you can restart just the agent: `pkill -f "src/agent.ts" && REDLINE_PORT=<port> REDLINE_TOKEN=<token> bun src/agent.ts <absolute-path-to-file.md> &` — port and token come from the printed startup URL and `.review/<file>.startup.json`.)
155
+
156
+ The browser SSE EventSource auto-reconnects (3s backoff), so the open tab survives a restart — but you still need to hard-reload (Cmd+Shift+R) to pick up new client-side JS.
157
+
158
+ On startup the server checks the sidecar and auto-creates an open round if all rounds are resolved. This prevents a stuck "Revising the document" spinner state when reopening a previously-finished review.
159
+
160
+ ## Agent etiquette inside the conversation flow
161
+
162
+ When you're acting as the agent on the other end of the SSE stream:
163
+
164
+ 1. POST `/api/comment/:id/thinking` *before* you start composing. This is the user's only signal that you saw their message.
165
+ 2. POST `/api/comment/:id/reply` with `{ role: "agent", name: "<provider display name>", message, requires_revision, revision_reason }`. The verdict fields are required of every agent reply — see "Verdict-aware resolve" below.
166
+ 3. POST `/api/agent-replied` to clear the thinking indicator and trigger a soft refresh.
167
+
168
+ Skipping step 1 leaves the user staring at silence wondering if anything is happening.
169
+
170
+ ## Verdict-aware resolve
171
+
172
+ Every agent reply ships with a verdict on whether the comment, once resolved, implies an edit to the document. The fields live on the `ThreadEntry` for that agent reply:
173
+
174
+ - `requires_revision: true` — the comment implies a doc edit (typo fix, rewording, restructure, content change). The per-comment Resolve button reads "Resolve → queue edit" and tints warm. The resolved card carries an "✎ Edit queued" badge.
175
+ - `requires_revision: false` — the conversation answered it (clarifying question, approval, agent explanation that doesn't imply an edit). The Resolve button stays plain, the card carries a "✓ Answered" badge.
176
+ - field absent — the comment was resolved before the agent ever replied. Treated as `accept` (the human resolved unilaterally, so they're saying "doesn't matter").
177
+
178
+ The round-level button picks its default by the latest verdict on each comment:
179
+
180
+ - All verdicts are `accept` → primary = **Accept as-is** (`/api/finish`, no revision pass).
181
+ - Any verdict is `revise` → primary = **Revise document** (`/api/accept`).
182
+ - The non-default action is always available as a secondary text link under the banner. Choosing "accept anyway" when comments imply edits triggers a `confirm()` warning.
183
+
184
+ The agent provider ([src/agentProvider.ts](src/agentProvider.ts), called by [src/agent.ts](src/agent.ts)) gets the verdict by asking the selected local agent to reply in a delimiter envelope rather than JSON:
185
+
186
+ ```
187
+ REQUIRES_REVISION: <true|false>
188
+ ESCALATE: <true|false>
189
+ REASON: <one short sentence>
190
+ ---MESSAGE---
191
+ <free-form reply prose>
192
+ ---END---
193
+ ```
194
+
195
+ JSON was tried first and abandoned: agent replies frequently contain quotes, code fences, and Markdown that the model fails to escape inside a JSON string, which then makes `JSON.parse` blow up on the whole envelope. The delimiter form sidesteps escaping entirely. [src/parseReply.ts](src/parseReply.ts) prefers the delimiter form, accepts the legacy JSON shape as a fallback, and on any parse failure defaults to `{ requires_revision: true }` (safer to run an unnecessary revision than silently skip an implied edit).
196
+
197
+ The verdict is **agent-owned**. The human cannot flip it directly. Disagreement flows through a follow-up reply, which gives the agent a chance to re-classify rather than be silently overridden.
198
+
199
+ ## Escalation to the launching agent
200
+
201
+ The inline review agent and the agent that *launched* `redline` share no live channel — the sidecar is the only persisted artifact, and the launching agent only regains control when the session exits. So two mechanisms carry feedback back to it:
202
+
203
+ - **`ESCALATE` verdict.** A second, orthogonal flag in the reply envelope. The agent sets `ESCALATE: true` when a comment can't be acted on from inside the review — it needs the launching agent's project context, tools, or authority (an external style guide, a spec to check against, a wider-project decision). It's stored as `escalate?: boolean` on the agent's `ThreadEntry` and rendered as an "↑ Escalated" badge on the comment and an "↑ Routed to the launching agent" note in the thread. Escalation is independent of `requires_revision` — the agent judges each on its own.
204
+ - **Closeout transcript.** On `finished`, the CLI loads the sidecar and prints every comment thread verbatim via [src/reviewSummary.ts](src/reviewSummary.ts) (`formatReviewSummary`), with a dedicated callout listing escalated comments (`collectEscalations`). The escalation count is also written to `.review/<file>.result` and appended to the `REDLINE_RESULT:` line. This is the launching agent's read point — it sees the transcript when control hands back.
205
+
206
+ ## Security & resilience as built
207
+
208
+ These are post-publish hardenings (M5/M8). They're load-bearing — don't regress them when refactoring:
209
+
210
+ - **Loopback bind.** Server is `Bun.serve({ port: 0, hostname: "127.0.0.1" })` in [src/cli.ts](src/cli.ts). No external interface.
211
+ - **Markdown sanitization.** `marked` v9 stopped sanitizing in-band; we run [`sanitize-html`](src/render.ts) over its output. Tests in [src/render.test.ts](src/render.test.ts) cover the dangerous-payload cases — keep them passing.
212
+ - **CSRF token.** The CLI mints `REDLINE_TOKEN` (a UUID) and threads it to the server (mints from), the agent subprocess (env), and the bundled client (via `window.__REDLINE__`). Mutating endpoints require `X-Redline-Token`. Don't add a mutating endpoint without checking the token.
213
+ - **Realpath on static asset reads.** [src/server.ts](src/server.ts) calls `realpath` before serving anything off disk so a symlink in `.review/history/` can't traverse out.
214
+ - **Cross-process sidecar lock.** [src/sidecar.ts](src/sidecar.ts) layers an in-memory mutex over `proper-lockfile` on `<sidecar>.lock`. Both layers are needed: in-process is the fast path, lockfile catches concurrent processes (e.g. `redline resolve` running while the server is up).
215
+ - **Prompt-input envelopes.** Both `agent.ts` and `resolve.ts` wrap user-controlled text (doc body, comment text) in `---DOCUMENT---` / `---COMMENTS---` style markers before sending to the selected agent provider, so a comment that contains "Ignore previous instructions" is recognizable as data, not prompt.
216
+ - **Revision watchdog.** [src/server.ts](src/server.ts) starts a 3-min timer on `/api/accept`. If `/api/reload` doesn't land in time, the round un-resolves and the server broadcasts `revision-stalled`. The revision token in `currentRevision` makes this race-safe — see the comment block at line ~129.
217
+ - **Revision integrity check + retry.** `validateRevision` in [src/resolve.ts](src/resolve.ts) is a pure check on the model's output: it strips fences/wrapper-tags/meta-sections, then rejects output that has no headings (when the input had them) or that drops a heading whose section no settled comment touched — the signal that the model mangled the doc instead of editing it. A failed validation retries the revision once with the same model before surfacing `revision-error`; a non-zero CLI exit is not retried (environment failure, not a model stumble).
218
+ - **Zombie SSE recovery.** [src/client/sse.ts](src/client/sse.ts) listens for `visibilitychange`/`focus` and runs a 30s heartbeat watchdog while the `.revising` banner is up, so a tab backgrounded across the whole revision recovers when refocused.
219
+ - **Capped agent restart.** [src/cli.ts](src/cli.ts) auto-restarts the agent on unexpected exit but caps at 5 in 60s. If you see "gave up" in the log, something structural is wrong — fix root cause, don't raise the cap.
220
+
221
+ ## Tests
222
+
223
+ `bun test` runs the suite. Server, sidecar, parsing, model-picking, rendering, diff, SSE, integration, and happy-dom client tests all live in `tests/` and `src/`.
224
+
225
+ ## Closeout
226
+
227
+ When the user asks to "close out" a fix/patch/feature after its PR is open, additional dev-process steps may apply. They're not documented in this public file — check local/private project notes if present, or fall back to project memory.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,25 @@ All notable changes to Redline are documented here. The format follows [Keep a C
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.4.0] - 2026-05-15
8
+
9
+ ### Added
10
+ - Claude and Codex agent providers are now supported through a provider-neutral runtime. Use `--agent claude|codex` or `REDLINE_AGENT` to choose explicitly; otherwise Redline auto-detects an available local provider.
11
+ - `redline install-skill --agent claude|codex|both` installs the bundled `redline-review` skill into Claude Code and/or Codex so agents can reach for Redline automatically when a Markdown doc needs human sign-off.
12
+
13
+ ### Changed
14
+ - `AGENTS.md` is now the canonical contributor onboarding doc. `CLAUDE.md` remains as a Claude Code compatibility pointer to the same provider-neutral guidance.
15
+
16
+ ## [0.3.0] - 2026-05-15
17
+
18
+ ### Added
19
+ - **Escalation handoff** ([#86](https://github.com/alevi/redline/pull/86)). When a comment needs something the inline review agent can't provide — an external style guide, a spec it can't see, a decision that needs the wider project — the agent flags its reply with an escalate verdict. The comment shows an "Escalated" badge, the round banner notes the count, and the session's closeout summary and `.review/<file>.result` carry an `escalations` array so the agent that launched the review picks the feedback up.
20
+
21
+ ### Fixed
22
+ - **Revision integrity check and retry** ([#86](https://github.com/alevi/redline/pull/86)). The revision pass now validates its output on every run and rejects a revision that silently drops document sections no comment touched, instead of writing a mangled document to disk. A failed validation retries once before surfacing an error.
23
+ - **Cross-block and image-spanning selections** ([#85](https://github.com/alevi/redline/pull/85)) now anchor correctly. Selecting across a block boundary, or across an image, no longer fails with an anchoring error.
24
+ - **Sessions are no longer abandoned on a transient SSE drop** ([#84](https://github.com/alevi/redline/pull/84)). A backgrounded tab, laptop sleep, or a brief network blip no longer causes the server to exit; only an explicit tab close ends the session. A server that does go away surfaces a clear "session ended" banner instead of a raw fetch error.
25
+
7
26
  ## [0.2.0] - 2026-05-11
8
27
 
9
28
  ### Added
@@ -41,6 +60,8 @@ Initial public release on npm as `@levistudio/redline`.
41
60
  - Auto-installs missing dependencies on first CLI run.
42
61
  - Initial test suite: server, sidecar, parsing, model-picking, rendering, diff, SSE, integration, happy-dom client.
43
62
 
44
- [Unreleased]: https://github.com/alevi/redline/compare/v0.2.0...HEAD
63
+ [Unreleased]: https://github.com/alevi/redline/compare/v0.4.0...HEAD
64
+ [0.4.0]: https://github.com/alevi/redline/compare/v0.3.0...v0.4.0
65
+ [0.3.0]: https://github.com/alevi/redline/releases/tag/v0.3.0
45
66
  [0.2.0]: https://github.com/alevi/redline/releases/tag/v0.2.0
46
67
  [0.1.0]: https://github.com/alevi/redline/releases/tag/v0.1.0
package/CLAUDE.md ADDED
@@ -0,0 +1,9 @@
1
+ # Redline — Claude Code compatibility
2
+
3
+ The canonical agent onboarding doc is [AGENTS.md](AGENTS.md). Read that file first; it is intentionally provider-neutral and applies to Claude Code, Codex, and any other agent working in this repository.
4
+
5
+ Claude-specific notes:
6
+
7
+ - Redline supports Claude Code through the `claude` provider in [src/agentProvider.ts](src/agentProvider.ts).
8
+ - Keep using the local `claude -p` CLI path for Claude support. Redline inherits the user's Claude Code auth session and does not require `ANTHROPIC_API_KEY`.
9
+ - Do not replace the local CLI provider with the Anthropic SDK unless the product direction explicitly changes. The local-agent auth model is load-bearing.
package/README.md CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  **Inline comments on Markdown files, designed for human-in-the-loop AI doc review.**
4
4
 
5
- Open a Markdown file, highlight text, leave inline comments, discuss changes with Claude, and apply accepted revisions back to the document.
5
+ Open a Markdown file, highlight text, leave inline comments, discuss changes with a local agent, and apply accepted revisions back to the document.
6
6
 
7
- ![Redline demo — highlight text, leave a comment, Claude replies, resolve to apply the edit.](docs/assets/demo.gif)
7
+ ![Redline demo — highlight text, leave a comment, an agent replies, resolve to apply the edit.](docs/assets/demo.gif)
8
8
 
9
9
  ## Try it in 30 seconds
10
10
 
@@ -14,15 +14,32 @@ bunx @levistudio/redline ./spec.md
14
14
 
15
15
  (or `npx @levistudio/redline ./spec.md` — Bun is required either way.)
16
16
 
17
- The terminal prints a `localhost` URL. Cmd-click it. Select any text in the document, type a comment, hit reply. Claude responds in the thread within ~2s. Resolve each comment, then click **Revise document** to have Claude apply the agreed edits back to the file on disk.
17
+ The terminal prints a `localhost` URL. Cmd-click it. Select any text in the document, type a comment, hit reply. Your selected local agent responds in the thread within a few seconds. Resolve each comment, then click **Revise document** to have the agent apply the agreed edits back to the file on disk.
18
18
 
19
- Requires [Bun](https://bun.sh) 1.0 and an authenticated [Claude Code](https://claude.com/claude-code) session. The agent inherits your existing Claude Code OAuth — no `ANTHROPIC_API_KEY` needed.
19
+ Requires [Bun](https://bun.sh) >= 1.0 and an authenticated local agent CLI. Redline currently supports [Claude Code](https://claude.com/claude-code) and Codex. The agent inherits your existing CLI auth session — no provider API key needed.
20
+
21
+ ## Install
22
+
23
+ For one-off reviews, `bunx` / `npx` is enough:
24
+
25
+ ```sh
26
+ bunx @levistudio/redline ./spec.md
27
+ ```
28
+
29
+ To make Redline available as a normal command and install its agent skill:
30
+
31
+ ```sh
32
+ bun add -g @levistudio/redline
33
+ redline install-skill --agent codex # or: claude, both
34
+ ```
35
+
36
+ That installs the bundled `redline-review` skill into your agent environment (`~/.codex/skills/` and/or `~/.claude/skills/`). After that, ask your agent to redline a Markdown file when it needs your sign-off; it should launch Redline, surface the local URL, wait for you to finish, then continue from the approved file.
20
37
 
21
38
  ## Why Redline exists
22
39
 
23
- A lot of recent docs start as a Claude draft. Reviewing those drafts in a chat window is awkward. Comments scroll out from under the text. "Fix paragraph 3" loses its anchor as soon as the doc is rewritten. Google-Docs-style tools don't speak the file on disk.
40
+ A lot of recent docs start as an AI-agent draft. Reviewing those drafts in a chat window is awkward. Comments scroll out from under the text. "Fix paragraph 3" loses its anchor as soon as the doc is rewritten. Google-Docs-style tools don't speak the file on disk.
24
41
 
25
- Redline puts an inline-comment layer on top of a local Markdown file, with a Claude agent participating in the review thread. Comments are anchored to the text they're about. The conversation is real-time. Accepted changes are applied back to the file you started with, no copy-paste.
42
+ Redline puts an inline-comment layer on top of a local Markdown file, with a local agent participating in the review thread. Comments are anchored to the text they're about. The conversation is real-time. Accepted changes are applied back to the file you started with, no copy-paste.
26
43
 
27
44
  ## Who it's for
28
45
 
@@ -38,25 +55,25 @@ People shipping docs with the help of AI agents:
38
55
  Two long-lived processes:
39
56
 
40
57
  - **Server** ([src/server.ts](src/server.ts)) — renders Markdown, serves the review UI, accepts comment / reply / resolve POSTs, broadcasts SSE events.
41
- - **Agent** ([src/agent.ts](src/agent.ts)) — child process listening to the SSE stream. Calls `claude -p` to compose replies and post them back. When you accept a round, the agent runs the document revision pass and writes the result to disk.
58
+ - **Agent** ([src/agent.ts](src/agent.ts)) — child process listening to the SSE stream. Calls the selected local provider (`claude` or `codex`) to compose replies and post them back. When you accept a round, the agent runs the document revision pass and writes the result to disk.
42
59
 
43
60
  Review state lives in a sidecar JSON file at `.review/<filename>.json` next to the doc. History snapshots of every revision land in `.review/history/<filename>.<iso>.md` *before* the revision is written, so you can roll back from disk if a revision goes sideways. Both should be gitignored unless you want them in the repo.
44
61
 
45
62
  After each revision, the new round opens with the document rendered inline as a diff against the previous round — insert and delete bands at the block level, word-level marks within modified paragraphs — so you can read the agent's changes in place. Use the **Show changes** / **Hide changes** toggle in the header to flip back to the clean view at any time. From there you either open another round of comments or click **Looks good** to finish.
46
63
 
47
- [CLAUDE.md](CLAUDE.md) has the full architecture tour: sidecar schema, SSE event vocabulary, model picking, frontend gotchas.
64
+ [AGENTS.md](AGENTS.md) has the full architecture tour: sidecar schema, SSE event vocabulary, model picking, frontend gotchas. `CLAUDE.md` remains as a Claude Code compatibility pointer.
48
65
 
49
66
  ## Use cases
50
67
 
51
- - **Reviewing AI-generated PRDs.** Hand Claude Code a one-line brief, let it draft the PRD, then redline it line by line before passing it on.
68
+ - **Reviewing AI-generated PRDs.** Hand your coding agent a one-line brief, let it draft the PRD, then redline it line by line before passing it on.
52
69
  - **Reviewing architecture specs.** Mark assumptions you want challenged, ask the agent to expand sections, ship the revised spec.
53
- - **Reviewing README drafts.** Claude wrote your README — read through, leave inline comments where the framing is off, accept the revision in one click.
54
- - **Approving Claude Code output before merge.** Use the bundled [redline-review skill](skills/redline-review/SKILL.md) so your outer agent automatically hands you the doc to sign off on.
70
+ - **Reviewing README drafts.** Your agent wrote your README — read through, leave inline comments where the framing is off, accept the revision in one click.
71
+ - **Approving agent output before merge.** Use the bundled [redline-review skill](skills/redline-review/SKILL.md) so your outer agent automatically hands you the doc to sign off on.
55
72
  - **Human approval loops for agent-written docs.** Anywhere an agent needs your sign-off on prose before continuing, run it through Redline.
56
73
 
57
74
  ## Reviewing for a specific lens
58
75
 
59
- Pass `--context` at startup to tell Claude what you're focused on for the review. The context is shown in a banner above the doc and threaded into both the reply and revision prompts, so the agent can weight its responses accordingly.
76
+ Pass `--context` at startup to tell the agent what you're focused on for the review. The context is shown in a banner above the doc and threaded into both the reply and revision prompts, so the agent can weight its responses accordingly.
60
77
 
61
78
  ```sh
62
79
  bunx @levistudio/redline ./spec.md --context "Reviewing for technical accuracy, not prose style."
@@ -67,16 +84,16 @@ The context is persisted in the sidecar on first run, so it carries across round
67
84
 
68
85
  ## Manual annotation (no agent)
69
86
 
70
- If you just want inline comments on a Markdown file without a Claude conversation, pass `--no-agent`:
87
+ If you just want inline comments on a Markdown file without an agent conversation, pass `--no-agent`:
71
88
 
72
89
  ```sh
73
90
  bunx @levistudio/redline ./spec.md --no-agent
74
91
  ```
75
92
 
76
- The server still runs, you can still leave comments and resolve them, but no agent process is spawned and no `claude` binary is required on your PATH. The header shows a "Manual mode" pill so the absence of replies is intentional, not broken. Useful for:
93
+ The server still runs, you can still leave comments and resolve them, but no agent process is spawned and no provider CLI is required on your PATH. The header shows a "Manual mode" pill so the absence of replies is intentional, not broken. Useful for:
77
94
 
78
- - Annotating a doc you don't want sent to Claude
79
- - Trying Redline before configuring Claude Code
95
+ - Annotating a doc you don't want sent to an agent
96
+ - Trying Redline before configuring Claude Code or Codex
80
97
  - Quick inline-comment passes where you don't want a revision step
81
98
 
82
99
  ## One-shot revision
@@ -86,24 +103,25 @@ If you already have a sidecar with resolved comments and just want to apply the
86
103
  ```sh
87
104
  bunx @levistudio/redline resolve ./spec.md
88
105
  bunx @levistudio/redline resolve ./spec.md --model claude-sonnet-4-6
106
+ bunx @levistudio/redline resolve ./spec.md --agent codex
89
107
  ```
90
108
 
91
109
  ## Outer-agent handoff (optional)
92
110
 
93
- Want your AI coding agent (Claude Code etc.) to invoke Redline automatically whenever it produces a Markdown doc you need to sign off on? Install the bundled skill:
111
+ Want your coding agent to invoke Redline automatically whenever it produces a Markdown doc you need to sign off on? Install the bundled skill:
94
112
 
95
113
  ```sh
96
- git clone https://github.com/alevi/redline.git && cd redline
97
- ./scripts/install-skill.sh
114
+ bun add -g @levistudio/redline
115
+ redline install-skill --agent codex # or: claude, both
98
116
  ```
99
117
 
100
- This copies `skills/redline-review/` into `~/.claude/skills/` with absolute paths baked in. After installation, your outer agent will reach for `redline-review` whenever it has Markdown that needs human review before it can continue.
118
+ This copies `skills/redline-review/` into your agent skills directory with a durable launcher baked in. After installation, your agent can reach for `redline-review` whenever it has Markdown that needs human review before it can continue.
101
119
 
102
120
  ## Local-first / security
103
121
 
104
122
  - Single-player. Server binds to `127.0.0.1`. No auth, no audit log, no cloud.
105
123
  - Rendered Markdown is sanitized at the render boundary; raw HTML, inline event handlers, and `javascript:` URLs are stripped before the document reaches the browser.
106
- - The agent inherits your existing Claude Code OAuth session; no `ANTHROPIC_API_KEY` required.
124
+ - The agent inherits your existing Claude Code or Codex auth session; no provider API key required.
107
125
  - **Prompt injection in document content is not defended against.** Run Redline on docs you trust.
108
126
 
109
127
  Full threat model: [SECURITY.md](SECURITY.md).
package/SECURITY.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Security
2
2
 
3
- Redline is a **single-player, localhost-only** review tool that runs on the operator's machine. Reviewed Markdown is rendered in a browser and fed to a local Claude subprocess. This document describes what is defended against, what is out of scope, and how to report issues.
3
+ Redline is a **single-player, localhost-only** review tool that runs on the operator's machine. Reviewed Markdown is rendered in a browser and fed to a selected local agent subprocess. This document describes what is defended against, what is out of scope, and how to report issues.
4
4
 
5
5
  ## Threat model
6
6
 
@@ -11,9 +11,9 @@ Redline is a **single-player, localhost-only** review tool that runs on the oper
11
11
 
12
12
  **Out of scope.** Redline is a developer tool, not a sandbox. It is appropriate to run on documents you authored, generated, or trust. The following are **not** defended against:
13
13
 
14
- - **Adversarial Markdown trying to manipulate the agent via prompt injection.** The agent reads the document text and comment thread as input to Claude; carefully crafted content can attempt to redirect the model. Treat agent output the same way you'd treat any other AI output you didn't fully verify.
14
+ - **Adversarial Markdown trying to manipulate the agent via prompt injection.** The agent reads the document text and comment thread as input to the selected local provider; carefully crafted content can attempt to redirect the model. Treat agent output the same way you'd treat any other AI output you didn't fully verify.
15
15
  - **Multiple `redline` processes operating on the same file.** The sidecar lock is in-process; concurrent runs on the same `.md` can corrupt review state.
16
- - **Malicious CLI flags or environment.** `redline` runs with the operator's full file-system permissions and shells out to `claude -p` with the operator's auth.
16
+ - **Malicious CLI flags or environment.** `redline` runs with the operator's full file-system permissions and shells out to the selected local provider CLI with the operator's auth.
17
17
  - **Sharing reviews across operators.** Redline is single-player — it has no auth, no access control, and no audit log.
18
18
 
19
19
  ## What "single-player, localhost" means in practice
package/bin/redline.cjs CHANGED
@@ -47,7 +47,10 @@ if (!bun) {
47
47
  process.exit(1);
48
48
  }
49
49
 
50
- const child = spawn(bun, ["run", CLI, ...process.argv.slice(2)], { stdio: "inherit" });
50
+ const child = spawn(bun, ["run", CLI, ...process.argv.slice(2)], {
51
+ stdio: "inherit",
52
+ env: { ...process.env, REDLINE_BIN_ABS: __filename },
53
+ });
51
54
  child.on("exit", (code, signal) => {
52
55
  if (signal) {
53
56
  try { process.kill(process.pid, signal); } catch { process.exit(1); }
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@levistudio/redline",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "Inline comments on Markdown files, for human-in-the-loop AI doc review.",
5
5
  "keywords": [
6
6
  "markdown",
7
7
  "code-review",
8
8
  "ai",
9
9
  "claude",
10
+ "codex",
10
11
  "bun",
11
12
  "documentation"
12
13
  ],
@@ -33,6 +34,8 @@
33
34
  "SECURITY.md",
34
35
  "ROADMAP.md",
35
36
  "CHANGELOG.md",
37
+ "AGENTS.md",
38
+ "CLAUDE.md",
36
39
  "LICENSE"
37
40
  ],
38
41
  "engines": {