aisnitch 0.2.3 → 0.2.4

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/README.md CHANGED
@@ -1,128 +1,129 @@
1
1
  # AISnitch
2
2
 
3
+ **See what your AI agents are doing. All of them. In real time.**
4
+
3
5
  [![CI](https://github.com/vava-nessa/AISnitch/actions/workflows/ci.yml/badge.svg)](https://github.com/vava-nessa/AISnitch/actions/workflows/ci.yml)
6
+ [![npm](https://img.shields.io/npm/v/aisnitch?logo=npm&label=aisnitch)](https://www.npmjs.com/package/aisnitch)
7
+ [![npm](https://img.shields.io/npm/v/@aisnitch/client?logo=npm&label=@aisnitch/client)](https://www.npmjs.com/package/@aisnitch/client)
4
8
  [![Node >=20](https://img.shields.io/badge/node-%3E%3D20-339933?logo=node.js&logoColor=white)](https://nodejs.org/)
5
9
  [![License: Apache-2.0](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](./LICENSE)
6
10
 
7
- **AISnitch** is a local event bridge for AI coding tools. It captures real-time activity from Claude Code, OpenCode, Gemini CLI, Codex, Goose, Aider, Copilot CLI, OpenClaw (and any CLI via PTY fallback), normalizes everything into one CloudEvents stream, and exposes it over a WebSocket on `ws://127.0.0.1:4820`.
8
-
9
- You connect your own frontend, dashboard, companion app, notification system, or sound engine to that WebSocket, and you get live sentences like:
11
+ AISnitch is a local daemon that captures activity from **every AI coding tool** running on your machine Claude Code, OpenCode, Gemini CLI, Codex, Goose, Aider, Copilot CLI, OpenClaw, and any CLI via PTY fallback normalizes everything into a single event stream, and broadcasts it over WebSocket.
10
12
 
11
- > *"Session #3Claude Code is thinking..."*
12
- > *"#2 ~/projects/myappCodex: Refactoring tables tool call: Edit file"*
13
- > *"#5 Claude Code edited 4 JS files"*
13
+ - **One stream, all tools** no more switching between terminals to see what each agent is doing
14
+ - **Zero storage**pure memory transit, nothing persists to disk, ever
15
+ - **Build anything on top** dashboards, sound engines, animated companions, Slack bots, menu bar widgets
14
16
 
15
- No data is stored. No cloud. No logs on disk. Pure live memory-only transit.
17
+ <!-- TODO: Add TUI demo GIF here -->
16
18
 
17
19
  ---
18
20
 
19
21
  ## Table of Contents
20
22
 
23
+ - [Why AISnitch?](#why-aisnitch)
21
24
  - [Quick Start](#quick-start)
22
25
  - [Install](#install)
23
- - [Supported Tools](#supported-tools)
26
+ - [Ecosystem](#ecosystem)
24
27
  - [How It Works](#how-it-works)
25
- - [Event Model Reference](#event-model-reference)
26
- - [Consumer Integration Guide](#consumer-integration-guide)
27
- - [Connect from Node.js / TypeScript](#connect-from-nodejs--typescript)
28
- - [Connect from a Browser (React, Vue, Vanilla JS)](#connect-from-a-browser-react-vue-vanilla-js)
29
- - [Build Human-Readable Status Lines](#build-human-readable-status-lines)
30
- - [Track Sessions](#track-sessions)
31
- - [Filter by Tool or Event Type](#filter-by-tool-or-event-type)
32
- - [Trigger Sounds or Notifications](#trigger-sounds-or-notifications)
33
- - [Build an Animated Mascot / Companion](#build-an-animated-mascot--companion)
34
- - [Health Check](#health-check)
28
+ - [Architecture](#architecture)
29
+ - [Supported Tools](#supported-tools)
30
+ - [Event Model](#event-model)
31
+ - [Build on Top of AISnitch](#build-on-top-of-aisnitch)
35
32
  - [CLI Reference](#cli-reference)
36
33
  - [TUI Keybinds](#tui-keybinds)
37
- - [Architecture](#architecture)
38
34
  - [Config Reference](#config-reference)
39
35
  - [Development](#development)
40
36
  - [License](#license)
41
37
 
42
38
  ---
43
39
 
44
- ## Quick Start
40
+ ## Why AISnitch?
41
+
42
+ You run Claude Code on your main project, Codex on the API, Aider reviewing a legacy repo. Three agents, three terminals, no shared visibility. You tab-switch constantly. You miss a permission prompt. You don't know which one is idle and which one is burning tokens.
43
+
44
+ **AISnitch solves this in one line:**
45
45
 
46
46
  ```bash
47
- pnpm install && pnpm build
47
+ aisnitch start
48
+ ```
48
49
 
49
- # Open the PM2-style dashboard
50
- node dist/cli/index.js start
50
+ Now every tool's activity flows into one dashboard. You see who's thinking, who's coding, who needs input, and who's hit a rate limit — all at once, in real time.
51
51
 
52
- # Launch with simulated events (no real AI tool needed)
53
- node dist/cli/index.js start --mock all
52
+ Want to build your own UI instead? The entire stream is available on `ws://127.0.0.1:4820` — connect with the [Client SDK](#ecosystem) and build dashboards, sound engines, animated companions, or anything else.
54
53
 
55
- # Exhaustive live logger, no TUI
56
- node dist/cli/index.js logger
57
- ```
54
+ ---
58
55
 
59
- `start` now always opens the TUI dashboard. If the daemon is offline you still land in the UI, see `Daemon not active`, and can start or stop it from inside the TUI with `d`.
56
+ ## Quick Start
60
57
 
61
- `start --mock all` boots the dashboard, ensures the daemon is active, then streams realistic fake events from Claude Code, OpenCode, and Gemini CLI so you can see the product immediately.
58
+ ```bash
59
+ # Install and run
60
+ npm i -g aisnitch
61
+ aisnitch start
62
+
63
+ # Try it without any AI tool — simulated events
64
+ aisnitch start --mock all
65
+ ```
62
66
 
63
- If you want the full live payload stream without Ink truncation, use `aisnitch logger`. It attaches to the running daemon and prints one structured field per line, including nested `data.raw.*` paths.
67
+ That's it. The TUI dashboard opens, and you see live activity from every configured AI tool.
64
68
 
65
- To consume the stream from another terminal:
69
+ To set up tools:
66
70
 
67
71
  ```bash
68
- # Quick test: print raw events
69
- node -e "
70
- const WebSocket = require('ws');
71
- const ws = new WebSocket('ws://127.0.0.1:4820');
72
- ws.on('message', m => {
73
- const e = JSON.parse(m.toString());
74
- if (e.type !== 'welcome') console.log(e.type, e['aisnitch.tool'], e.data?.project);
75
- });
76
- "
72
+ aisnitch setup claude-code # hooks into Claude Code
73
+ aisnitch setup opencode # hooks into OpenCode
74
+ aisnitch adapters # check what's enabled
77
75
  ```
78
76
 
79
77
  ---
80
78
 
81
79
  ## Install
82
80
 
83
- **From source (recommended for development):**
81
+ **npm (recommended):**
84
82
 
85
83
  ```bash
86
- git clone https://github.com/vava-nessa/AISnitch.git
87
- cd AISnitch
88
- pnpm install
89
- pnpm build
90
- node dist/cli/index.js --help
84
+ npm i -g aisnitch
91
85
  ```
92
86
 
93
- **Global npm install:**
87
+ **Homebrew:**
94
88
 
95
89
  ```bash
96
- npm i -g aisnitch
97
- aisnitch --help
90
+ brew install aisnitch
98
91
  ```
99
92
 
100
- Global installs now run a silent self-update check every time the dashboard opens. AISnitch auto-detects `npm`, `pnpm`, `bun`, or `brew` from the current install layout and upgrades itself in the background when a newer package version is available.
101
-
102
- **Homebrew:**
93
+ **From source:**
103
94
 
104
95
  ```bash
105
- # Formula ships at Formula/aisnitch.rb — copy into your tap
106
- brew install aisnitch
96
+ git clone https://github.com/vava-nessa/AISnitch.git
97
+ cd AISnitch
98
+ pnpm install && pnpm build
99
+ node dist/cli/index.js start
107
100
  ```
108
101
 
109
102
  ---
110
103
 
111
- ## Supported Tools
104
+ ## Ecosystem
112
105
 
113
- | Tool | Strategy | Setup |
114
- | --- | --- | --- |
115
- | **Claude Code** | HTTP hooks + JSONL transcript watching + process detection | `aisnitch setup claude-code` |
116
- | **OpenCode** | Local plugin + process detection | `aisnitch setup opencode` |
117
- | **Gemini CLI** | Command hooks + `logs.json` watching + process detection | `aisnitch setup gemini-cli` |
118
- | **Codex** | `codex-tui.log` parsing + process detection | `aisnitch setup codex` |
119
- | **Goose** | `goosed` API polling + SSE streams + SQLite fallback | `aisnitch setup goose` |
120
- | **Copilot CLI** | Repo hooks + session-state JSONL watching | `aisnitch setup copilot-cli` |
121
- | **Aider** | `.aider.chat.history.md` watching + notifications command | `aisnitch setup aider` |
122
- | **OpenClaw** | Managed hooks + command/memory/session watchers | `aisnitch setup openclaw` |
123
- | **Any other CLI** | PTY wrapper with output heuristics | `aisnitch wrap <command>` |
106
+ AISnitch ships as two packages with distinct audiences:
107
+
108
+ | Package | For | Install |
109
+ |---|---|---|
110
+ | [`aisnitch`](https://www.npmjs.com/package/aisnitch) | **Users** the daemon, CLI, TUI dashboard, adapters | `npm i -g aisnitch` |
111
+ | [`@aisnitch/client`](https://www.npmjs.com/package/@aisnitch/client) | **Developers** TypeScript SDK to consume the event stream | `pnpm add @aisnitch/client zod` |
112
+
113
+ **You're a user?** Install `aisnitch`, run `aisnitch start`, you're done.
114
+
115
+ **You're building something on top?** Install `@aisnitch/client` and connect in 3 lines:
116
+
117
+ ```typescript
118
+ import { createAISnitchClient, describeEvent } from '@aisnitch/client';
119
+ import WebSocket from 'ws';
120
+
121
+ const client = createAISnitchClient({ WebSocketClass: WebSocket as any });
122
+ client.on('event', (e) => console.log(describeEvent(e)));
123
+ // → "claude-code is editing code → src/index.ts [myproject]"
124
+ ```
124
125
 
125
- Adapters are disabled by default. Run `aisnitch setup <tool>` to arm them, then `aisnitch adapters` to verify.
126
+ Auto-reconnect, Zod-validated parsing, session tracking, filters, mascot state mapping all included. See the full **[Client SDK documentation](./packages/client/README.md)**.
126
127
 
127
128
  ---
128
129
 
@@ -155,611 +156,277 @@ Adapters are disabled by default. Run `aisnitch setup <tool>` to arm them, then
155
156
  (your consumers) (built-in)
156
157
  ```
157
158
 
158
- Each adapter captures tool activity using the best available strategy (hooks > file watching > process detection). Events are validated, normalized into CloudEvents, enriched with context (terminal, working directory, PID, multi-instance tracking), then pushed to the EventBus. The WebSocket server broadcasts to all connected clients with per-client backpressure handling.
159
+ Each adapter captures tool activity using the best available strategy hooks for tools that support them (Claude Code, OpenCode, Gemini CLI), file watching for log-based tools (Codex, Aider), process detection as universal fallback. Events are validated against Zod schemas, normalized into CloudEvents, enriched with context (terminal, working directory, PID, multi-instance tracking), then pushed through an in-memory EventBus. The WebSocket server broadcasts to all connected clients with per-client ring buffers (1,000 events, oldest-first drop).
159
160
 
160
- **Nothing is stored on disk.** Events exist in memory during transit, then they're gone.
161
+ **Nothing is stored on disk.** Events exist in memory during transit, then they're gone. Privacy-first by design.
161
162
 
162
163
  ---
163
164
 
164
- ## Event Model Reference
165
+ ## Architecture
166
+
167
+ ```mermaid
168
+ flowchart LR
169
+ subgraph Tools["External AI tools"]
170
+ CC["Claude Code"]
171
+ OC["OpenCode"]
172
+ GM["Gemini CLI"]
173
+ CX["Codex"]
174
+ GS["Goose"]
175
+ AD["Aider"]
176
+ OCL["OpenClaw"]
177
+ PTY["Generic PTY"]
178
+ end
179
+
180
+ subgraph AIS["AISnitch runtime"]
181
+ HTTP["HTTP hook receiver :4821"]
182
+ UDS["UDS ingest"]
183
+ REG["Adapter registry"]
184
+ BUS["Typed EventBus"]
185
+ WS["WebSocket server :4820"]
186
+ TUI["Ink TUI"]
187
+ end
188
+
189
+ subgraph SDK["Consumer ecosystem"]
190
+ CLIENT["@aisnitch/client SDK"]
191
+ DASH["Dashboards"]
192
+ SOUND["Sound engines"]
193
+ MASCOT["Companions"]
194
+ BOT["Bots"]
195
+ end
196
+
197
+ CC --> HTTP
198
+ OC --> HTTP
199
+ GM --> HTTP
200
+ OCL --> HTTP
201
+ CX --> REG
202
+ GS --> REG
203
+ AD --> REG
204
+ PTY --> UDS
205
+ HTTP --> BUS
206
+ UDS --> BUS
207
+ REG --> BUS
208
+ BUS --> WS
209
+ BUS --> TUI
210
+ WS --> CLIENT
211
+ CLIENT --> DASH
212
+ CLIENT --> SOUND
213
+ CLIENT --> MASCOT
214
+ CLIENT --> BOT
215
+ ```
216
+
217
+ ---
218
+
219
+ ## Supported Tools
220
+
221
+ | Tool | Strategy | Setup |
222
+ |---|---|---|
223
+ | **Claude Code** | HTTP hooks + JSONL transcript watching + process detection | `aisnitch setup claude-code` |
224
+ | **OpenCode** | Local plugin + process detection | `aisnitch setup opencode` |
225
+ | **Gemini CLI** | Command hooks + `logs.json` watching + process detection | `aisnitch setup gemini-cli` |
226
+ | **Codex** | `codex-tui.log` parsing + process detection | `aisnitch setup codex` |
227
+ | **Goose** | `goosed` API polling + SSE streams + SQLite fallback | `aisnitch setup goose` |
228
+ | **Copilot CLI** | Repo hooks + session-state JSONL watching | `aisnitch setup copilot-cli` |
229
+ | **Aider** | `.aider.chat.history.md` watching + notifications command | `aisnitch setup aider` |
230
+ | **OpenClaw** | Managed hooks + command/memory/session watchers | `aisnitch setup openclaw` |
231
+ | **Any other CLI** | PTY wrapper with output heuristics | `aisnitch wrap <command>` |
232
+
233
+ Run `aisnitch setup <tool>` to configure each tool, then `aisnitch adapters` to verify what's active.
234
+
235
+ ---
236
+
237
+ ## Event Model
165
238
 
166
239
  Every event is a [CloudEvents v1.0](https://cloudevents.io/) envelope with AISnitch extensions:
167
240
 
168
241
  ```jsonc
169
242
  {
170
- // CloudEvents core
171
243
  "specversion": "1.0",
172
244
  "id": "019713a4-beef-7000-8000-deadbeef0042", // UUIDv7
173
245
  "source": "aisnitch://claude-code/myproject",
174
- "type": "agent.coding", // one of 12 types
246
+ "type": "agent.coding", // one of 12 types below
175
247
  "time": "2026-03-28T14:30:00.000Z",
176
248
 
177
- // AISnitch extensions
178
- "aisnitch.tool": "claude-code", // which AI tool
179
- "aisnitch.sessionid": "claude-code:myproject:p12345", // session identity
180
- "aisnitch.seqnum": 42, // sequence number in this session
249
+ "aisnitch.tool": "claude-code",
250
+ "aisnitch.sessionid": "claude-code:myproject:p12345",
251
+ "aisnitch.seqnum": 42,
181
252
 
182
- // Normalized payload
183
253
  "data": {
184
- "state": "agent.coding", // mirrors type
185
- "project": "myproject", // project name
254
+ "state": "agent.coding",
255
+ "project": "myproject",
186
256
  "projectPath": "/home/user/myproject",
187
- "activeFile": "src/index.ts", // file being edited (if relevant)
188
- "toolName": "Edit", // tool being used (if relevant)
189
- "toolInput": { // tool arguments (if relevant)
190
- "filePath": "src/index.ts"
191
- },
192
- "model": "claude-sonnet-4-5-20250514", // model in use (if known)
193
- "tokensUsed": 1500, // token count (if known)
194
- "terminal": "iTerm2", // detected terminal
195
- "cwd": "/home/user/myproject", // working directory
196
- "pid": 12345, // process ID
197
- "instanceIndex": 1, // instance number (multi-instance)
198
- "instanceTotal": 3, // total instances running
199
-
200
- // Error fields (only on agent.error)
201
- "errorMessage": "Rate limit exceeded",
202
- "errorType": "rate_limit", // rate_limit | context_overflow | tool_failure | api_error
203
-
204
- // Raw source payload (adapter-specific, for advanced consumers)
205
- "raw": { /* original hook/log payload as-is */ }
257
+ "activeFile": "src/index.ts",
258
+ "toolName": "Edit",
259
+ "toolInput": { "filePath": "src/index.ts" },
260
+ "model": "claude-sonnet-4-5-20250514",
261
+ "tokensUsed": 1500,
262
+ "terminal": "iTerm2",
263
+ "cwd": "/home/user/myproject",
264
+ "pid": 12345,
265
+ "instanceIndex": 1,
266
+ "instanceTotal": 3,
267
+ "errorMessage": "Rate limit exceeded", // only on agent.error
268
+ "errorType": "rate_limit", // only on agent.error
269
+ "raw": { /* original adapter payload */ }
206
270
  }
207
271
  }
208
272
  ```
209
273
 
210
274
  ### The 12 Event Types
211
275
 
212
- | Type | Meaning | When it fires |
213
- | --- | --- | --- |
214
- | `session.start` | A tool session began | Tool launched, first hook received |
215
- | `session.end` | Session closed | Tool exited, process disappeared |
216
- | `task.start` | User submitted a prompt/task | New user message, task created |
217
- | `task.complete` | Task finished | Response complete, stop signal |
218
- | `agent.thinking` | Model is reasoning | Thinking block, internal reflection |
219
- | `agent.streaming` | Model is generating output | Text streaming, response in progress |
220
- | `agent.coding` | Model edited files | Write, Edit, MultiEdit tool calls |
221
- | `agent.tool_call` | Model used a non-edit tool | Search, Bash, Grep, web search, etc. |
222
- | `agent.asking_user` | Waiting for human input | Permission request, confirmation prompt |
223
- | `agent.idle` | No activity (timeout) | 120s of silence (configurable) |
224
- | `agent.error` | Something went wrong | Rate limit, API error, tool failure |
225
- | `agent.compact` | Context compaction | Memory cleanup, history pruning |
226
-
227
- ### Recognized Tool Names
228
-
229
- `claude-code`, `opencode`, `gemini-cli`, `codex`, `goose`, `copilot-cli`, `cursor`, `aider`, `amp`, `cline`, `continue`, `windsurf`, `qwen-code`, `openclaw`, `openhands`, `kilo`, `unknown`
276
+ | Type | What it means |
277
+ |---|---|
278
+ | `session.start` | A tool session began |
279
+ | `session.end` | Session closed |
280
+ | `task.start` | User submitted a prompt |
281
+ | `task.complete` | Task finished |
282
+ | `agent.thinking` | Model is reasoning |
283
+ | `agent.streaming` | Model is generating output |
284
+ | `agent.coding` | Model is editing files |
285
+ | `agent.tool_call` | Model is using a tool (Bash, Grep, etc.) |
286
+ | `agent.asking_user` | Waiting for human input |
287
+ | `agent.idle` | No activity (120s timeout, configurable) |
288
+ | `agent.error` | Something went wrong (rate limit, API error, tool failure) |
289
+ | `agent.compact` | Context compaction / memory cleanup |
230
290
 
231
291
  ---
232
292
 
233
- ## Consumer Integration Guide
293
+ ## Build on Top of AISnitch
234
294
 
235
- This is the main purpose of AISnitch: you connect to the WebSocket and exploit the event stream however you want. Below are complete examples for every common use case.
295
+ The whole point of AISnitch is to be a platform. Here are 5 things you can build with the [`@aisnitch/client`](./packages/client/README.md) SDK:
236
296
 
237
- ### Connect from Node.js / TypeScript
297
+ ### Live Dashboard
238
298
 
239
- ```ts
299
+ ```typescript
300
+ import { createAISnitchClient, describeEvent } from '@aisnitch/client';
240
301
  import WebSocket from 'ws';
241
302
 
242
- // 📖 Default port is 4820, configurable via ~/.aisnitch/config.json
243
- const ws = new WebSocket('ws://127.0.0.1:4820');
244
-
245
- ws.on('open', () => {
246
- console.log('Connected to AISnitch');
247
- });
248
-
249
- ws.on('message', (buffer) => {
250
- const event = JSON.parse(buffer.toString('utf8'));
303
+ const client = createAISnitchClient({ WebSocketClass: WebSocket as any });
251
304
 
252
- // 📖 First message is always a welcome payload — skip it
253
- if (event.type === 'welcome') {
254
- console.log('AISnitch version:', event.version);
255
- console.log('Active tools:', event.activeTools);
256
- return;
257
- }
258
-
259
- // 📖 Every other message is a normalized AISnitch event
260
- console.log({
261
- type: event.type, // "agent.coding"
262
- tool: event['aisnitch.tool'], // "claude-code"
263
- session: event['aisnitch.sessionid'],// "claude-code:myproject:p12345"
264
- seq: event['aisnitch.seqnum'], // 42
265
- project: event.data?.project, // "myproject"
266
- file: event.data?.activeFile, // "src/index.ts"
267
- model: event.data?.model, // "claude-sonnet-4-5-20250514"
268
- });
305
+ client.on('connected', (w) => {
306
+ console.log(`Connected to AISnitch v${w.version}`);
307
+ console.log(`Active tools: ${w.activeTools.join(', ')}`);
269
308
  });
270
309
 
271
- ws.on('close', () => {
272
- console.log('Disconnected AISnitch stopped or restarted');
273
- // 📖 Implement reconnect logic here if needed
310
+ client.on('event', (e) => {
311
+ const line = describeEvent(e);
312
+ console.log(`[${e['aisnitch.tool']}] ${line}`);
274
313
  });
275
314
 
276
- ws.on('error', (err) => {
277
- console.error('WebSocket error:', err.message);
278
- });
315
+ // Track all active sessions
316
+ setInterval(() => {
317
+ const sessions = client.sessions?.getAll() ?? [];
318
+ console.log(`\n--- ${sessions.length} active session(s) ---`);
319
+ for (const s of sessions) {
320
+ console.log(` ${s.tool} → ${s.lastActivity} (${s.eventCount} events)`);
321
+ }
322
+ }, 5000);
279
323
  ```
280
324
 
281
- ### Connect from a Browser (React, Vue, Vanilla JS)
282
-
283
- The WebSocket is plain `ws://` on localhost — browsers can connect directly with the native `WebSocket` API. No library needed.
325
+ ### Sound Notifications (PeonPing-style)
284
326
 
285
- **Vanilla JS:**
327
+ ```typescript
328
+ import { createAISnitchClient, filters } from '@aisnitch/client';
286
329
 
287
- ```js
288
- // 📖 Connect to AISnitch from any browser page on the same machine
289
- const ws = new WebSocket('ws://127.0.0.1:4820');
330
+ const client = createAISnitchClient({ WebSocketClass: WebSocket as any });
290
331
 
291
- ws.onmessage = (msg) => {
292
- const event = JSON.parse(msg.data);
293
- if (event.type === 'welcome') return;
294
-
295
- // Do anything: update DOM, play sound, trigger animation...
296
- document.getElementById('status').textContent =
297
- `${event['aisnitch.tool']} — ${event.type}`;
332
+ const SOUNDS: Record<string, string> = {
333
+ 'session.start': 'boot.mp3',
334
+ 'task.complete': 'success.mp3',
335
+ 'agent.asking_user': 'alert.mp3',
336
+ 'agent.error': 'error.mp3',
337
+ 'agent.coding': 'keyboard.mp3',
298
338
  };
299
- ```
300
339
 
301
- **React hook:**
302
-
303
- ```tsx
304
- import { useEffect, useState, useCallback, useRef } from 'react';
305
-
306
- interface AISnitchEvent {
307
- type: string;
308
- time: string;
309
- 'aisnitch.tool': string;
310
- 'aisnitch.sessionid': string;
311
- 'aisnitch.seqnum': number;
312
- data: {
313
- state: string;
314
- project?: string;
315
- activeFile?: string;
316
- toolName?: string;
317
- toolInput?: { filePath?: string; command?: string };
318
- model?: string;
319
- tokensUsed?: number;
320
- errorMessage?: string;
321
- errorType?: string;
322
- terminal?: string;
323
- cwd?: string;
324
- pid?: number;
325
- instanceIndex?: number;
326
- instanceTotal?: number;
327
- };
328
- }
329
-
330
- // 📖 Drop-in React hook — auto-reconnects every 3s if AISnitch restarts
331
- export function useAISnitch(url = 'ws://127.0.0.1:4820') {
332
- const [events, setEvents] = useState<AISnitchEvent[]>([]);
333
- const [connected, setConnected] = useState(false);
334
- const [latestEvent, setLatestEvent] = useState<AISnitchEvent | null>(null);
335
- const wsRef = useRef<WebSocket | null>(null);
336
-
337
- const connect = useCallback(() => {
338
- const ws = new WebSocket(url);
339
- wsRef.current = ws;
340
-
341
- ws.onopen = () => setConnected(true);
342
- ws.onclose = () => {
343
- setConnected(false);
344
- setTimeout(connect, 3000); // 📖 Reconnect after 3s
345
- };
346
-
347
- ws.onmessage = (msg) => {
348
- const event = JSON.parse(msg.data) as AISnitchEvent;
349
- if (event.type === 'welcome') return;
350
-
351
- setLatestEvent(event);
352
- setEvents((prev) => [...prev.slice(-499), event]); // 📖 Keep last 500
353
- };
354
-
355
- ws.onerror = () => ws.close();
356
- }, [url]);
357
-
358
- useEffect(() => {
359
- connect();
360
- return () => wsRef.current?.close();
361
- }, [connect]);
362
-
363
- const clear = useCallback(() => {
364
- setEvents([]);
365
- setLatestEvent(null);
366
- }, []);
367
-
368
- return { events, latestEvent, connected, clear };
369
- }
340
+ client.on('event', (e) => {
341
+ const sound = SOUNDS[e.type];
342
+ if (sound) playSound(`./sounds/${sound}`);
343
+ });
370
344
  ```
371
345
 
372
- **Usage in a component:**
373
-
374
- ```tsx
375
- function AIActivityPanel() {
376
- const { events, latestEvent, connected } = useAISnitch();
377
-
378
- return (
379
- <div>
380
- <span>{connected ? '🟢 Live' : '🔴 Disconnected'}</span>
381
-
382
- {latestEvent && (
383
- <p>
384
- {latestEvent['aisnitch.tool']} — {latestEvent.type}
385
- {latestEvent.data.project && ` on ${latestEvent.data.project}`}
386
- </p>
387
- )}
388
-
389
- <ul>
390
- {events.map((e, i) => (
391
- <li key={i}>
392
- [{e['aisnitch.tool']}] {e.type}
393
- {e.data.activeFile && ` → ${e.data.activeFile}`}
394
- </li>
395
- ))}
396
- </ul>
397
- </div>
398
- );
399
- }
400
- ```
346
+ ### Animated Mascot / Companion
401
347
 
402
- **Vue 3 composable:**
403
-
404
- ```ts
405
- import { ref, onMounted, onUnmounted } from 'vue';
406
-
407
- export function useAISnitch(url = 'ws://127.0.0.1:4820') {
408
- const events = ref<any[]>([]);
409
- const connected = ref(false);
410
- const latestEvent = ref<any>(null);
411
- let ws: WebSocket | null = null;
412
- let reconnectTimer: ReturnType<typeof setTimeout>;
413
-
414
- function connect() {
415
- ws = new WebSocket(url);
416
- ws.onopen = () => (connected.value = true);
417
- ws.onclose = () => {
418
- connected.value = false;
419
- reconnectTimer = setTimeout(connect, 3000);
420
- };
421
- ws.onmessage = (msg) => {
422
- const event = JSON.parse(msg.data);
423
- if (event.type === 'welcome') return;
424
- latestEvent.value = event;
425
- events.value = [...events.value.slice(-499), event];
426
- };
427
- ws.onerror = () => ws?.close();
428
- }
348
+ ```typescript
349
+ import { createAISnitchClient, eventToMascotState } from '@aisnitch/client';
429
350
 
430
- onMounted(connect);
431
- onUnmounted(() => {
432
- clearTimeout(reconnectTimer);
433
- ws?.close();
434
- });
351
+ const client = createAISnitchClient();
435
352
 
436
- return { events, latestEvent, connected };
437
- }
353
+ client.on('event', (e) => {
354
+ const state = eventToMascotState(e);
355
+ // state.mood → 'thinking' | 'working' | 'celebrating' | 'panicking' | ...
356
+ // state.animation → 'ponder' | 'type' | 'dance' | 'shake' | ...
357
+ // state.color → '#a855f7' (hex)
358
+ // state.label → 'Thinking...'
359
+ // state.detail → 'src/index.ts' (optional)
360
+ updateMySprite(state);
361
+ });
438
362
  ```
439
363
 
440
- ### Build Human-Readable Status Lines
441
-
442
- This is the core use case: transform raw events into sentences like *"Claude Code is editing src/index.ts"*.
443
-
444
- ```ts
445
- // 📖 Maps event type + data into a short human-readable description
446
- function describeEvent(event: AISnitchEvent): string {
447
- const tool = event['aisnitch.tool'];
448
- const d = event.data;
449
- const file = d.activeFile ? ` → ${d.activeFile}` : '';
450
- const project = d.project ? ` [${d.project}]` : '';
451
-
452
- switch (event.type) {
453
- case 'session.start':
454
- return `${tool} started a new session${project}`;
455
-
456
- case 'session.end':
457
- return `${tool} session ended${project}`;
458
-
459
- case 'task.start':
460
- return `${tool} received a new prompt${project}`;
461
-
462
- case 'task.complete':
463
- return `${tool} finished the task${project}` +
464
- (d.duration ? ` (${Math.round(d.duration / 1000)}s)` : '');
465
-
466
- case 'agent.thinking':
467
- return `${tool} is thinking...${project}`;
364
+ ### Slack / Discord Bot
468
365
 
469
- case 'agent.streaming':
470
- return `${tool} is generating a response${project}`;
471
-
472
- case 'agent.coding':
473
- return `${tool} is editing code${file}${project}`;
474
-
475
- case 'agent.tool_call':
476
- return `${tool} is using ${d.toolName ?? 'a tool'}` +
477
- (d.toolInput?.command ? `: ${d.toolInput.command}` : file) +
478
- project;
479
-
480
- case 'agent.asking_user':
481
- return `${tool} needs your input${project}`;
482
-
483
- case 'agent.idle':
484
- return `${tool} is idle${project}`;
485
-
486
- case 'agent.error':
487
- return `${tool} error: ${d.errorMessage ?? d.errorType ?? 'unknown'}${project}`;
366
+ ```typescript
367
+ import { createAISnitchClient, filters, formatStatusLine } from '@aisnitch/client';
368
+ import WebSocket from 'ws';
488
369
 
489
- case 'agent.compact':
490
- return `${tool} is compacting context${project}`;
370
+ const client = createAISnitchClient({ WebSocketClass: WebSocket as any });
491
371
 
492
- default:
493
- return `${tool}: ${event.type}`;
372
+ // Only notify on events that need attention
373
+ client.on('event', (e) => {
374
+ if (filters.needsAttention(e)) {
375
+ postToSlack(`⚠️ ${formatStatusLine(e)}`);
494
376
  }
495
- }
496
-
497
- // Example output:
498
- // "claude-code started a new session [myproject]"
499
- // "claude-code is editing code → src/index.ts [myproject]"
500
- // "codex is using Bash: npm test [api-server]"
501
- // "gemini-cli needs your input"
502
- // "claude-code error: Rate limit exceeded [myproject]"
503
- ```
504
-
505
- **Full status line with session number:**
506
-
507
- ```ts
508
- // 📖 Tracks session indices and builds numbered status lines
509
- const sessionIndex = new Map<string, number>();
510
- let sessionCounter = 0;
511
377
 
512
- function getSessionNumber(sessionId: string): number {
513
- if (!sessionIndex.has(sessionId)) {
514
- sessionIndex.set(sessionId, ++sessionCounter);
378
+ if (e.type === 'task.complete') {
379
+ postToSlack(`✅ ${formatStatusLine(e)}`);
515
380
  }
516
- return sessionIndex.get(sessionId)!;
517
- }
518
-
519
- function formatStatusLine(event: AISnitchEvent): string {
520
- const num = getSessionNumber(event['aisnitch.sessionid']);
521
- const desc = describeEvent(event);
522
- const cwd = event.data.cwd ?? '';
523
- return `#${num} ${cwd} — ${desc}`;
524
- }
525
-
526
- // Output:
527
- // "#1 /home/user/myproject — claude-code is thinking..."
528
- // "#2 /home/user/api — codex is editing code → src/db.ts"
529
- // "#1 /home/user/myproject — claude-code finished the task (12s)"
381
+ });
530
382
  ```
531
383
 
532
- ### Track Sessions
533
-
534
- Events carry `aisnitch.sessionid` for grouping. A session represents one tool instance working on one project.
535
-
536
- ```ts
537
- interface SessionState {
538
- tool: string;
539
- sessionId: string;
540
- project?: string;
541
- cwd?: string;
542
- lastEvent: AISnitchEvent;
543
- lastActivity: string; // human-readable
544
- eventCount: number;
545
- startedAt: string;
546
- }
384
+ ### Menu Bar Widget (Electron / Tauri)
547
385
 
548
- const sessions = new Map<string, SessionState>();
386
+ ```typescript
387
+ import { createAISnitchClient, formatStatusLine } from '@aisnitch/client';
549
388
 
550
- function updateSession(event: AISnitchEvent): void {
551
- const sid = event['aisnitch.sessionid'];
389
+ const client = createAISnitchClient();
390
+ let sessionCounter = 0;
391
+ const sessionMap = new Map<string, number>();
552
392
 
553
- if (event.type === 'session.end') {
554
- sessions.delete(sid);
555
- return;
393
+ client.on('event', (e) => {
394
+ if (!sessionMap.has(e['aisnitch.sessionid'])) {
395
+ sessionMap.set(e['aisnitch.sessionid'], ++sessionCounter);
556
396
  }
397
+ const num = sessionMap.get(e['aisnitch.sessionid'])!;
557
398
 
558
- const existing = sessions.get(sid);
559
- sessions.set(sid, {
560
- tool: event['aisnitch.tool'],
561
- sessionId: sid,
562
- project: event.data.project ?? existing?.project,
563
- cwd: event.data.cwd ?? existing?.cwd,
564
- lastEvent: event,
565
- lastActivity: describeEvent(event),
566
- eventCount: (existing?.eventCount ?? 0) + 1,
567
- startedAt: existing?.startedAt ?? event.time,
568
- });
569
- }
570
-
571
- // 📖 Call updateSession() on every event, then read sessions Map for current state
572
- // sessions.values() gives you all active sessions across all tools
573
- ```
574
-
575
- ### Filter by Tool or Event Type
576
-
577
- Filtering is client-side. The server broadcasts everything — you pick what you need.
578
-
579
- ```ts
580
- // 📖 Filter examples — apply to your ws.onmessage handler
581
-
582
- // Only Claude Code events
583
- const isClaudeCode = (e: AISnitchEvent) => e['aisnitch.tool'] === 'claude-code';
584
-
585
- // Only coding activity (edits, tool calls)
586
- const isCodingActivity = (e: AISnitchEvent) =>
587
- e.type === 'agent.coding' || e.type === 'agent.tool_call';
588
-
589
- // Only errors
590
- const isError = (e: AISnitchEvent) => e.type === 'agent.error';
591
-
592
- // Only rate limits specifically
593
- const isRateLimit = (e: AISnitchEvent) =>
594
- e.type === 'agent.error' && e.data.errorType === 'rate_limit';
595
-
596
- // Only events for a specific project
597
- const isMyProject = (e: AISnitchEvent) => e.data.project === 'myproject';
598
-
599
- // Events that need user attention
600
- const needsAttention = (e: AISnitchEvent) =>
601
- e.type === 'agent.asking_user' || e.type === 'agent.error';
602
-
603
- // Combine filters
604
- ws.onmessage = (msg) => {
605
- const event = JSON.parse(msg.data);
606
- if (event.type === 'welcome') return;
607
-
608
- if (isClaudeCode(event) && isCodingActivity(event)) {
609
- // Only Claude Code writing code
610
- updateUI(event);
611
- }
612
- };
399
+ // Update your menu bar / tray icon
400
+ tray.setTitle(formatStatusLine(e, num));
401
+ tray.setToolTip(`${client.sessions?.count ?? 0} active sessions`);
402
+ });
613
403
  ```
614
404
 
615
- ### Trigger Sounds or Notifications
616
-
617
- ```ts
618
- // 📖 Map event types to sounds — perfect for a companion/pet app
619
- const SOUND_MAP: Record<string, string> = {
620
- 'session.start': '/sounds/boot.mp3',
621
- 'session.end': '/sounds/shutdown.mp3',
622
- 'task.start': '/sounds/ping.mp3',
623
- 'task.complete': '/sounds/success.mp3',
624
- 'agent.thinking': '/sounds/thinking-loop.mp3', // loop this one
625
- 'agent.coding': '/sounds/keyboard.mp3',
626
- 'agent.tool_call': '/sounds/tool.mp3',
627
- 'agent.asking_user':'/sounds/alert.mp3',
628
- 'agent.error': '/sounds/error.mp3',
629
- 'agent.idle': '/sounds/idle.mp3',
630
- };
631
-
632
- function playEventSound(event: AISnitchEvent): void {
633
- const soundFile = SOUND_MAP[event.type];
634
- if (!soundFile) return;
635
-
636
- // Browser: use Web Audio API
637
- const audio = new Audio(soundFile);
638
- audio.play();
639
- }
405
+ For complete API docs, React/Vue hooks, filters, TypeScript integration, and more examples, see the **[Client SDK README](./packages/client/README.md)**.
640
406
 
641
- // 📖 Desktop notification for important events
642
- function notifyIfNeeded(event: AISnitchEvent): void {
643
- if (event.type === 'agent.asking_user') {
644
- new Notification(`${event['aisnitch.tool']} needs input`, {
645
- body: event.data.project
646
- ? `Project: ${event.data.project}`
647
- : 'Waiting for your response...',
648
- });
649
- }
407
+ <details>
408
+ <summary>Raw WebSocket (without SDK)</summary>
650
409
 
651
- if (event.type === 'agent.error') {
652
- new Notification(`${event['aisnitch.tool']} error`, {
653
- body: event.data.errorMessage ?? 'Something went wrong',
654
- });
655
- }
410
+ If you don't want the SDK, you can connect directly:
656
411
 
657
- if (event.type === 'task.complete') {
658
- new Notification(`${event['aisnitch.tool']} done!`, {
659
- body: event.data.project
660
- ? `Task completed on ${event.data.project}`
661
- : 'Task completed',
662
- });
663
- }
664
- }
412
+ ```bash
413
+ # One-liner to see raw events
414
+ node -e "
415
+ const WebSocket = require('ws');
416
+ const ws = new WebSocket('ws://127.0.0.1:4820');
417
+ ws.on('message', m => {
418
+ const e = JSON.parse(m.toString());
419
+ if (e.type !== 'welcome') console.log(e.type, e['aisnitch.tool'], e.data?.project);
420
+ });
421
+ "
665
422
  ```
666
423
 
667
- ### Build an Animated Mascot / Companion
424
+ The first message is always a `welcome` payload with version, active tools, and uptime. Every subsequent message is a CloudEvents event as described above.
668
425
 
669
- ```ts
670
- // 📖 Map event states to mascot moods — for animated desktop pets,
671
- // menu bar companions, or overlay widgets
672
-
673
- interface MascotState {
674
- mood: 'idle' | 'thinking' | 'working' | 'waiting' | 'celebrating' | 'panicking';
675
- animation: string;
676
- color: string;
677
- label: string;
678
- detail?: string;
679
- }
680
-
681
- function eventToMascotState(event: AISnitchEvent): MascotState {
682
- const d = event.data;
683
-
684
- switch (event.type) {
685
- case 'agent.thinking':
686
- return {
687
- mood: 'thinking',
688
- animation: 'orbit',
689
- color: '#f59e0b',
690
- label: 'Thinking...',
691
- detail: d.model,
692
- };
693
-
694
- case 'agent.coding':
695
- return {
696
- mood: 'working',
697
- animation: 'typing',
698
- color: '#3b82f6',
699
- label: 'Coding',
700
- detail: d.activeFile,
701
- };
702
-
703
- case 'agent.tool_call':
704
- return {
705
- mood: 'working',
706
- animation: 'inspect',
707
- color: '#14b8a6',
708
- label: `Using ${d.toolName ?? 'tool'}`,
709
- detail: d.toolInput?.command ?? d.toolInput?.filePath,
710
- };
711
-
712
- case 'agent.streaming':
713
- return {
714
- mood: 'working',
715
- animation: 'pulse',
716
- color: '#8b5cf6',
717
- label: 'Generating...',
718
- };
719
-
720
- case 'agent.asking_user':
721
- return {
722
- mood: 'waiting',
723
- animation: 'wave',
724
- color: '#ec4899',
725
- label: 'Needs you!',
726
- };
727
-
728
- case 'agent.error':
729
- return {
730
- mood: 'panicking',
731
- animation: 'shake',
732
- color: '#ef4444',
733
- label: d.errorType === 'rate_limit' ? 'Rate limited!' : 'Error!',
734
- detail: d.errorMessage,
735
- };
736
-
737
- case 'task.complete':
738
- return {
739
- mood: 'celebrating',
740
- animation: 'celebrate',
741
- color: '#22c55e',
742
- label: 'Done!',
743
- };
744
-
745
- default:
746
- return {
747
- mood: 'idle',
748
- animation: 'breathe',
749
- color: '#94a3b8',
750
- label: 'Idle',
751
- };
752
- }
753
- }
754
-
755
- // 📖 Plug this into your rendering loop:
756
- // ws.onmessage → eventToMascotState(event) → update sprite/CSS/canvas
757
- ```
426
+ </details>
758
427
 
759
428
  ### Health Check
760
429
 
761
- AISnitch exposes a health endpoint on the HTTP port:
762
-
763
430
  ```bash
764
431
  curl http://127.0.0.1:4821/health
765
432
  ```
@@ -774,8 +441,6 @@ curl http://127.0.0.1:4821/health
774
441
  }
775
442
  ```
776
443
 
777
- Useful for monitoring if the bridge is alive before connecting your consumer.
778
-
779
444
  ---
780
445
 
781
446
  ## CLI Reference
@@ -790,9 +455,12 @@ aisnitch start --view full-data # expanded JSON inspector
790
455
  # Background daemon
791
456
  aisnitch start --daemon
792
457
  aisnitch status # check if daemon is running
793
- aisnitch attach # open the same dashboard and attach if active
458
+ aisnitch attach # open TUI attached to running daemon
794
459
  aisnitch stop # kill daemon
795
460
 
461
+ # Raw event logger (no TUI, full payload)
462
+ aisnitch logger
463
+
796
464
  # Tool setup (run once per tool)
797
465
  aisnitch setup claude-code
798
466
  aisnitch setup opencode
@@ -807,11 +475,11 @@ aisnitch setup claude-code --revert # undo setup
807
475
  # Check enabled adapters
808
476
  aisnitch adapters
809
477
 
810
- # Demo mode
478
+ # Demo mode (simulated events)
811
479
  aisnitch mock claude-code --speed 2 --duration 20
812
- aisnitch start --mock all --mock-duration 20
480
+ aisnitch start --mock all
813
481
 
814
- # PTY wrapper fallback (any unsupported CLI)
482
+ # PTY wrapper (any unsupported CLI)
815
483
  aisnitch wrap aider --model sonnet
816
484
  aisnitch wrap goose session
817
485
  ```
@@ -821,9 +489,9 @@ aisnitch wrap goose session
821
489
  ## TUI Keybinds
822
490
 
823
491
  | Key | Action |
824
- | --- | --- |
492
+ |---|---|
825
493
  | `q` / `Ctrl+C` | Quit |
826
- | `d` | Start / stop the daemon from the dashboard |
494
+ | `d` | Start / stop the daemon |
827
495
  | `r` | Refresh daemon status |
828
496
  | `v` | Toggle full-data JSON inspector |
829
497
  | `f` | Tool filter picker |
@@ -834,70 +502,25 @@ aisnitch wrap goose session
834
502
  | `c` | Clear event buffer |
835
503
  | `?` | Help overlay |
836
504
  | `Tab` | Switch panel focus |
837
- | `↑↓` / `jk` | Navigate rows / inspector |
505
+ | `↑↓` / `jk` | Navigate rows |
838
506
  | `[` `]` | Page inspector up / down |
839
507
 
840
508
  ---
841
509
 
842
- ## Architecture
843
-
844
- ```mermaid
845
- flowchart LR
846
- subgraph Tools["External AI tools"]
847
- CC["Claude Code"]
848
- OC["OpenCode"]
849
- GM["Gemini CLI"]
850
- CX["Codex"]
851
- GS["Goose"]
852
- AD["Aider"]
853
- OCL["OpenClaw"]
854
- PTY["Generic PTY"]
855
- end
856
-
857
- subgraph AIS["AISnitch runtime"]
858
- HTTP["HTTP hook receiver :4821"]
859
- UDS["UDS ingest"]
860
- REG["Adapter registry"]
861
- BUS["Typed EventBus"]
862
- WS["WebSocket server :4820"]
863
- TUI["Ink TUI"]
864
- end
865
-
866
- CC --> HTTP
867
- OC --> HTTP
868
- GM --> HTTP
869
- OCL --> HTTP
870
- CX --> REG
871
- GS --> REG
872
- AD --> REG
873
- PTY --> UDS
874
- HTTP --> BUS
875
- UDS --> BUS
876
- REG --> BUS
877
- BUS --> WS
878
- BUS --> TUI
879
- ```
880
-
881
- ---
882
-
883
510
  ## Config Reference
884
511
 
885
- AISnitch state lives under `~/.aisnitch/` by default (override with `AISNITCH_HOME` env var).
512
+ AISnitch state lives under `~/.aisnitch/` (override with `AISNITCH_HOME`).
886
513
 
887
514
  | Path | Purpose |
888
- | --- | --- |
889
- | `~/.aisnitch/config.json` | User configuration |
890
- | `~/.aisnitch/aisnitch.pid` | Daemon PID file |
891
- | `~/.aisnitch/daemon-state.json` | Daemon connection info |
892
- | `~/.aisnitch/daemon.log` | Daemon output log (5MB max) |
893
- | `~/.aisnitch/aisnitch.sock` | Unix domain socket (daemon IPC) |
894
- | `~/.aisnitch/auto-update.json` | Silent self-update state |
895
- | `~/.aisnitch/auto-update.log` | Last silent self-update worker log |
896
-
897
- The dashboard surfaces the active WebSocket URL directly in the header so it is easy to copy into another consumer.
515
+ |---|---|
516
+ | `config.json` | User configuration |
517
+ | `aisnitch.pid` | Daemon PID file |
518
+ | `daemon-state.json` | Daemon connection info |
519
+ | `daemon.log` | Daemon output log (5 MB max) |
520
+ | `aisnitch.sock` | Unix domain socket (IPC) |
898
521
 
899
522
  | Port | Purpose |
900
- | --- | --- |
523
+ |---|---|
901
524
  | `4820` | WebSocket stream (consumers connect here) |
902
525
  | `4821` | HTTP hook receiver + `/health` endpoint |
903
526
 
@@ -907,20 +530,40 @@ The dashboard surfaces the active WebSocket URL directly in the header so it is
907
530
 
908
531
  ```bash
909
532
  pnpm install
910
- pnpm build # ESM + CJS + .d.ts
911
- pnpm lint # ESLint
912
- pnpm typecheck # tsc --noEmit
913
- pnpm test # Vitest (142 tests)
533
+ pnpm build # ESM + CJS + .d.ts (main + client SDK)
534
+ pnpm lint # ESLint
535
+ pnpm typecheck # tsc --noEmit
536
+ pnpm test # Vitest (156 tests)
914
537
  pnpm test:coverage
915
- pnpm test:e2e # requires opencode installed
538
+ pnpm test:e2e # requires opencode installed
539
+
540
+ # Client SDK only
541
+ pnpm --filter @aisnitch/client build
542
+ pnpm --filter @aisnitch/client test # 48 tests
543
+ ```
544
+
545
+ Project structure:
546
+
547
+ ```
548
+ aisnitch/ # main package — daemon, CLI, TUI, adapters
549
+ ├── src/
550
+ │ ├── adapters/ # 13 adapter implementations
551
+ │ ├── cli/ # commander commands
552
+ │ ├── core/ # events, pipeline, config
553
+ │ └── tui/ # Ink dashboard
554
+ ├── packages/
555
+ │ └── client/ # @aisnitch/client SDK
556
+ │ └── src/ # types, client, sessions, filters, helpers
557
+ ├── docs/ # technical documentation
558
+ └── tasks/ # kanban task board
916
559
  ```
917
560
 
918
- Detailed docs: [`docs/index.md`](./docs/index.md) | [`tasks/tasks.md`](./tasks/tasks.md)
561
+ Docs: [`docs/index.md`](./docs/index.md) | Tasks: [`tasks/tasks.md`](./tasks/tasks.md)
919
562
 
920
- Contributing: [`CONTRIBUTING.md`](./CONTRIBUTING.md) | [`CODE_OF_CONDUCT.md`](./CODE_OF_CONDUCT.md) | [`AGENTS.md`](./AGENTS.md)
563
+ Contributing: [`CONTRIBUTING.md`](./CONTRIBUTING.md) | [`CODE_OF_CONDUCT.md`](./CODE_OF_CONDUCT.md)
921
564
 
922
565
  ---
923
566
 
924
567
  ## License
925
568
 
926
- Apache-2.0, © Vanessa Depraute / vava-nessa.
569
+ Apache-2.0, © [Vanessa Depraute / vava-nessa](https://github.com/vava-nessa).