Skip to content

Commit 9abf4dd

Browse files
authored
Merge pull request #5 from illegalstudio/feature/api
feat: add HTTP API server with SSE streaming and interactive playground
2 parents dc25b43 + 26cc2f7 commit 9abf4dd

7 files changed

Lines changed: 1115 additions & 53 deletions

File tree

README.md

Lines changed: 57 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
![License: MIT](https://img.shields.io/badge/license-MIT-blue)
55
[![Product Hunt](https://img.shields.io/badge/Product%20Hunt-Launch-ff6154?logo=producthunt&logoColor=white)](https://www.producthunt.com/products/lazy-agent)
66

7-
A terminal UI and macOS menu bar app for monitoring all running [Claude Code](https://claude.ai/code) instances on your machine — inspired by [lazygit](https://github.com/jesseduffield/lazygit), [lazyworktree](https://github.com/chmouel/lazyworktree) and [pixel-agents](https://github.com/pablodelucca/pixel-agents).
7+
A terminal UI, macOS menu bar app, and HTTP API for monitoring all running [Claude Code](https://claude.ai/code) instances on your machine — inspired by [lazygit](https://github.com/jesseduffield/lazygit), [lazyworktree](https://github.com/chmouel/lazyworktree) and [pixel-agents](https://github.com/pablodelucca/pixel-agents).
88

99
### Terminal UI
1010
![lazyagent TUI](assets/screenshot.png)
@@ -42,19 +42,19 @@ It also surfaces:
4242
| Last 20 tools used | JSONL |
4343
| Last activity timestamp | JSONL |
4444

45-
## Two interfaces, one binary
45+
## Three interfaces, one binary
4646

47-
lazyagent ships as a single binary with two interfaces:
47+
lazyagent ships as a single binary with three interfaces:
4848

49-
| | TUI | macOS Menu Bar |
50-
|---|---|---|
51-
| Interface | Terminal (bubbletea) | Native menu bar panel (Wails v3 + Svelte 5) |
52-
| Launch | `lazyagent` | `lazyagent --tray` |
53-
| Dock icon | N/A | Hidden (accessory) |
54-
| Sparkline | Unicode braille characters | SVG area chart |
55-
| Theme | Terminal colors | Catppuccin Mocha (Tailwind 4) |
49+
| | TUI | macOS Menu Bar | HTTP API |
50+
|---|---|---|---|
51+
| Interface | Terminal (bubbletea) | Native menu bar panel (Wails v3 + Svelte 5) | REST + SSE |
52+
| Launch | `lazyagent` | `lazyagent --tray` | `lazyagent --api` |
53+
| Dock icon | N/A | Hidden (accessory) | N/A |
54+
| Sparkline | Unicode braille characters | SVG area chart | JSON data |
55+
| Theme | Terminal colors | Catppuccin Mocha (Tailwind 4) | N/A |
5656

57-
Both share `internal/core/` — session discovery, file watcher, activity state machine, cost estimation, and config. You can run both simultaneously with `lazyagent --tui --tray`.
57+
All three share `internal/core/` — session discovery, file watcher, activity state machine, cost estimation, and config. You can combine them freely: `lazyagent --tui --tray --api`.
5858

5959
## Install
6060

@@ -92,11 +92,14 @@ On first launch, macOS may block the binary. Go to **System Settings → Privacy
9292
## Usage
9393

9494
```
95-
lazyagent Launch the terminal UI
96-
lazyagent --tui Launch the terminal UI (explicit)
97-
lazyagent --tray Launch as macOS menu bar app (detaches automatically)
98-
lazyagent --tui --tray Launch both TUI and tray app
99-
lazyagent --help Show help
95+
lazyagent Launch the terminal UI (default)
96+
lazyagent --api Start the HTTP API (http://127.0.0.1:7421)
97+
lazyagent --api --host :8080 Start the HTTP API on a custom address
98+
lazyagent --tui --api Launch TUI + API server
99+
lazyagent --tray Launch as macOS menu bar app (detaches)
100+
lazyagent --tray --api Launch tray + API server (foreground)
101+
lazyagent --tui --tray --api Launch everything
102+
lazyagent --help Show help
100103
```
101104

102105
### TUI
@@ -141,6 +144,31 @@ The tray process detaches automatically — your terminal returns immediately. T
141144
- **Refresh Now** — force reload all sessions
142145
- **Quit** — exit the app
143146

147+
### HTTP API
148+
149+
```
150+
lazyagent --api
151+
```
152+
153+
Starts a read-only HTTP API server on `http://127.0.0.1:7421` (default port, with automatic fallback if busy).
154+
155+
| Endpoint | Description |
156+
|----------|-------------|
157+
| `GET /api` | Interactive playground (open in browser) |
158+
| `GET /api/sessions` | List visible sessions (`?search=`, `?filter=`) |
159+
| `GET /api/sessions/{id}` | Full session detail |
160+
| `GET /api/stats` | Summary stats (total, active, window) |
161+
| `GET /api/config` | Current configuration |
162+
| `GET /api/events` | SSE stream for real-time updates |
163+
164+
To expose on the network (e.g. for a mobile app):
165+
166+
```bash
167+
lazyagent --api --host 0.0.0.0:7421
168+
```
169+
170+
Full API documentation: [docs/API.md](docs/API.md)
171+
144172
### Editor support
145173

146174
Pressing `o` (TUI) or the **Open** button (app) opens the selected session's working directory in your editor.
@@ -186,10 +214,11 @@ lazyagent reads `~/.config/lazyagent/config.json` (created automatically with de
186214

187215
```
188216
lazyagent/
189-
├── main.go # Entry point: dispatches --tui / --tray / both
217+
├── main.go # Entry point: dispatches --tui / --tray / --api
190218
├── internal/
191219
│ ├── core/ # Shared: watcher, activity, session, config, helpers
192220
│ ├── claude/ # JSONL parsing, types, session discovery
221+
│ ├── api/ # HTTP API server (REST + SSE)
193222
│ ├── ui/ # TUI rendering (bubbletea + lipgloss)
194223
│ ├── tray/ # macOS menu bar app (Wails v3, build-tagged)
195224
│ └── assets/ # Embedded frontend dist (go:embed)
@@ -199,6 +228,8 @@ lazyagent/
199228
│ │ ├── lib/ # SessionList, SessionDetail, Sparkline, ActivityBadge
200229
│ │ └── bindings/ # Auto-generated Wails TypeScript bindings
201230
│ └── app.css # Tailwind 4 @theme (Catppuccin Mocha)
231+
├── docs/ # Documentation
232+
│ └── API.md # Full HTTP API reference
202233
└── Makefile
203234
```
204235

@@ -267,8 +298,16 @@ make clean
267298
- [ ] DMG distribution
268299
- [ ] Homebrew cask
269300

301+
### v0.4 — HTTP API
302+
- [x] REST API server (`--api` flag)
303+
- [x] Session list, detail, stats, config endpoints
304+
- [x] Server-Sent Events (SSE) for real-time push updates
305+
- [x] Interactive API playground (`/api` in browser)
306+
- [x] Default port with automatic fallback (7421–7431)
307+
- [x] Custom bind address (`--host`)
308+
- [x] Combinable with TUI and tray (`--tui --tray --api`)
309+
270310
### Future ideas
271-
- [ ] HTTP API with SSE streaming
272311
- [ ] Outbound webhooks on status changes
273312
- [ ] Multi-machine support via shared config / remote API
274313
- [ ] TUI actions: kill session, attach terminal

docs/API.md

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
# lazyagent API
2+
3+
lazyagent exposes an HTTP API for monitoring Claude Code sessions. The API is read-only and designed for building external clients (mobile apps, dashboards, integrations).
4+
5+
## Starting the API server
6+
7+
```bash
8+
# Default: http://127.0.0.1:7421
9+
lazyagent --api
10+
11+
# Custom address
12+
lazyagent --api --host :8080
13+
lazyagent --api --host 0.0.0.0:7421
14+
15+
# Combined with TUI or tray
16+
lazyagent --tui --api
17+
lazyagent --tray --api
18+
lazyagent --tui --tray --api
19+
```
20+
21+
The default port is **7421**. If it's busy, the server tries up to 10 sequential ports (7421–7431) and binds to the first available one. The actual address is printed to stderr on startup.
22+
23+
When `--host` is specified, it binds to that exact address with no fallback.
24+
25+
## Interactive playground
26+
27+
Open **http://127.0.0.1:7421/api** in a browser to access the interactive API playground. It lets you test all endpoints and connect to the SSE stream with a single click.
28+
29+
## Endpoints
30+
31+
### GET /api/sessions
32+
33+
List all visible sessions within the configured time window.
34+
35+
**Query parameters** (optional):
36+
37+
| Parameter | Type | Description |
38+
|-----------|--------|-------------|
39+
| `search` | string | Filter by project path (case-insensitive substring match) |
40+
| `filter` | string | Filter by activity kind (e.g. `thinking`, `writing`, `idle`) |
41+
42+
**Response:** `200 OK`
43+
44+
```json
45+
[
46+
{
47+
"session_id": "abc123",
48+
"cwd": "/Users/me/projects/myapp",
49+
"short_name": "…/projects/myapp",
50+
"activity": "thinking",
51+
"is_active": true,
52+
"model": "claude-sonnet-4-20250514",
53+
"git_branch": "main",
54+
"cost_usd": 0.42,
55+
"last_activity": "2026-03-08T15:30:00Z",
56+
"total_messages": 24
57+
}
58+
]
59+
```
60+
61+
**Activity values:** `idle`, `waiting`, `thinking`, `compacting`, `reading`, `writing`, `running`, `searching`, `browsing`, `spawning`
62+
63+
---
64+
65+
### GET /api/sessions/{id}
66+
67+
Get full details for a specific session.
68+
69+
**Response:** `200 OK`
70+
71+
```json
72+
{
73+
"session_id": "abc123",
74+
"cwd": "/Users/me/projects/myapp",
75+
"short_name": "…/projects/myapp",
76+
"activity": "writing",
77+
"is_active": true,
78+
"model": "claude-sonnet-4-20250514",
79+
"git_branch": "feature/api",
80+
"cost_usd": 1.23,
81+
"last_activity": "2026-03-08T15:30:00Z",
82+
"total_messages": 48,
83+
"version": "1.0.33",
84+
"is_worktree": false,
85+
"main_repo": "",
86+
"input_tokens": 125000,
87+
"output_tokens": 42000,
88+
"cache_creation_tokens": 50000,
89+
"cache_read_tokens": 80000,
90+
"user_messages": 20,
91+
"assistant_messages": 28,
92+
"current_tool": "Edit",
93+
"last_file_write": "internal/api/server.go",
94+
"last_file_write_at": "2026-03-08T15:29:50Z",
95+
"recent_tools": [
96+
{"name": "Read", "timestamp": "2026-03-08T15:29:30Z"},
97+
{"name": "Edit", "timestamp": "2026-03-08T15:29:50Z"}
98+
],
99+
"recent_messages": [
100+
{"role": "user", "text": "Add the API endpoint", "timestamp": "2026-03-08T15:28:00Z"},
101+
{"role": "assistant", "text": "I'll create the endpoint...", "timestamp": "2026-03-08T15:28:05Z"}
102+
]
103+
}
104+
```
105+
106+
**Response:** `404 Not Found` if session doesn't exist.
107+
108+
---
109+
110+
### GET /api/stats
111+
112+
Summary statistics.
113+
114+
**Response:** `200 OK`
115+
116+
```json
117+
{
118+
"total_sessions": 5,
119+
"active_sessions": 2,
120+
"window_minutes": 30
121+
}
122+
```
123+
124+
---
125+
126+
### GET /api/config
127+
128+
Current lazyagent configuration.
129+
130+
**Response:** `200 OK`
131+
132+
```json
133+
{
134+
"window_minutes": 30,
135+
"default_filter": "",
136+
"editor": "",
137+
"launch_at_login": false,
138+
"notifications": false,
139+
"notify_after_sec": 30
140+
}
141+
```
142+
143+
---
144+
145+
### GET /api/events
146+
147+
**Server-Sent Events (SSE)** stream for real-time updates. The server pushes an `update` event whenever session data changes (file watcher triggers, activity state changes, or periodic reload).
148+
149+
An initial snapshot is sent immediately upon connection.
150+
151+
**Event format:**
152+
153+
```
154+
event: update
155+
data: {"sessions":[...],"stats":{"total_sessions":5,"active_sessions":2,"window_minutes":30}}
156+
```
157+
158+
The `data` field contains a JSON object with:
159+
160+
| Field | Type | Description |
161+
|------------|-----------------|-------------|
162+
| `sessions` | `SessionItem[]` | Same format as `GET /api/sessions` |
163+
| `stats` | `StatsResponse` | Same format as `GET /api/stats` |
164+
165+
**JavaScript example:**
166+
167+
```javascript
168+
const evtSource = new EventSource('http://127.0.0.1:7421/api/events');
169+
170+
evtSource.addEventListener('update', (e) => {
171+
const { sessions, stats } = JSON.parse(e.data);
172+
console.log(`${stats.active_sessions} active sessions`);
173+
sessions.forEach(s => {
174+
console.log(`${s.short_name}: ${s.activity}`);
175+
});
176+
});
177+
```
178+
179+
**React Native example:**
180+
181+
```typescript
182+
import EventSource from 'react-native-sse';
183+
184+
const es = new EventSource('http://YOUR_HOST:7421/api/events');
185+
186+
es.addEventListener('update', (event) => {
187+
const { sessions, stats } = JSON.parse(event.data);
188+
setSessions(sessions);
189+
setStats(stats);
190+
});
191+
192+
// Cleanup
193+
es.close();
194+
```
195+
196+
**Notes:**
197+
- The connection auto-reconnects (standard SSE behavior)
198+
- Events are sent when data changes (file watcher, activity state transitions) and on the 30-second safety reload tick (even if nothing changed)
199+
200+
## Data freshness
201+
202+
The API server uses three mechanisms to keep data current:
203+
204+
1. **File system watcher** — detects JSONL file changes in `~/.claude/projects/` with 200ms debounce
205+
2. **Activity ticker** (1s) — re-evaluates activity states (idle/thinking/waiting transitions)
206+
3. **Safety reload** (30s) — full rescan as fallback
207+
208+
SSE clients receive push notifications from all three sources. REST clients see the latest state on each request.
209+
210+
## Network access
211+
212+
By default the server binds to `127.0.0.1` (localhost only). To expose it on the network (e.g. for a mobile app on the same WiFi):
213+
214+
```bash
215+
lazyagent --api --host 0.0.0.0:7421
216+
```
217+
218+
> **Warning:** There is no authentication. Only expose the API on trusted networks.

0 commit comments

Comments
 (0)