ai-or-die 0.1.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.
Files changed (78) hide show
  1. package/.cursor/commands/commit-push.md +18 -0
  2. package/.github/agents/architect.md +26 -0
  3. package/.github/agents/engineer.md +29 -0
  4. package/.github/agents/qa-reviewer.md +31 -0
  5. package/.github/agents/researcher.md +30 -0
  6. package/.github/agents/troubleshooter.md +33 -0
  7. package/.github/copilot-instructions.md +55 -0
  8. package/.github/pull_request_template.md +21 -0
  9. package/.github/workflows/build-binaries.yml +76 -0
  10. package/.github/workflows/ci.yml +70 -0
  11. package/.github/workflows/release-on-main.yml +73 -0
  12. package/.prompts/log.md +9 -0
  13. package/AGENTS.md +84 -0
  14. package/CHANGELOG.md +25 -0
  15. package/CLAUDE.md +130 -0
  16. package/CONTRIBUTING.md +76 -0
  17. package/LICENSE +22 -0
  18. package/README.md +165 -0
  19. package/bin/ai-or-die.js +203 -0
  20. package/docs/.nojekyll +1 -0
  21. package/docs/README.md +37 -0
  22. package/docs/adrs/0000-template.md +35 -0
  23. package/docs/adrs/0001-bridge-base-class.md +53 -0
  24. package/docs/adrs/0002-devtunnels-over-ngrok.md +56 -0
  25. package/docs/adrs/0003-multi-tool-architecture.md +71 -0
  26. package/docs/adrs/0004-cross-platform-support.md +101 -0
  27. package/docs/adrs/0005-single-binary-distribution.md +58 -0
  28. package/docs/agent-instructions/00-philosophy.md +55 -0
  29. package/docs/agent-instructions/01-research-and-web.md +49 -0
  30. package/docs/agent-instructions/02-testing-and-validation.md +63 -0
  31. package/docs/agent-instructions/03-tooling-and-pipelines.md +59 -0
  32. package/docs/architecture/bridge-pattern.md +510 -0
  33. package/docs/architecture/overview.md +216 -0
  34. package/docs/architecture/websocket-protocol.md +609 -0
  35. package/docs/history/README.md +26 -0
  36. package/docs/specs/authentication.md +167 -0
  37. package/docs/specs/bridges.md +210 -0
  38. package/docs/specs/client-app.md +308 -0
  39. package/docs/specs/e2e-testing.md +311 -0
  40. package/docs/specs/server.md +334 -0
  41. package/docs/specs/session-store.md +170 -0
  42. package/docs/specs/usage-analytics.md +342 -0
  43. package/nul +0 -0
  44. package/package.json +54 -0
  45. package/scripts/build-sea.js +187 -0
  46. package/scripts/pty-sea-shim.js +21 -0
  47. package/scripts/publish-both.sh +21 -0
  48. package/scripts/release-pr.sh +73 -0
  49. package/scripts/smoke-test-binary.js +190 -0
  50. package/scripts/validate.ps1 +25 -0
  51. package/scripts/validate.sh +16 -0
  52. package/sea-bootstrap.js +54 -0
  53. package/site/ADVANCED_ANALYTICS.md +174 -0
  54. package/site/index.html +151 -0
  55. package/site/script.js +17 -0
  56. package/site/style.css +60 -0
  57. package/src/base-bridge.js +340 -0
  58. package/src/claude-bridge.js +48 -0
  59. package/src/codex-bridge.js +27 -0
  60. package/src/copilot-bridge.js +29 -0
  61. package/src/gemini-bridge.js +26 -0
  62. package/src/public/app.js +2123 -0
  63. package/src/public/auth.js +244 -0
  64. package/src/public/icon-generator.js +26 -0
  65. package/src/public/icons.js +36 -0
  66. package/src/public/index.html +397 -0
  67. package/src/public/manifest.json +45 -0
  68. package/src/public/plan-detector.js +186 -0
  69. package/src/public/service-worker.js +108 -0
  70. package/src/public/session-manager.js +1124 -0
  71. package/src/public/splits.js +574 -0
  72. package/src/public/style.css +2090 -0
  73. package/src/server.js +1269 -0
  74. package/src/terminal-bridge.js +49 -0
  75. package/src/usage-analytics.js +494 -0
  76. package/src/usage-reader.js +895 -0
  77. package/src/utils/auth.js +123 -0
  78. package/src/utils/session-store.js +181 -0
@@ -0,0 +1,609 @@
1
+ # WebSocket Protocol
2
+
3
+ This document describes the full WebSocket protocol used for real-time communication between the browser client and the ai-or-die server.
4
+
5
+ ## Connection
6
+
7
+ Clients connect to the WebSocket server at:
8
+
9
+ ```
10
+ ws://localhost:{port}?token={authToken}
11
+ ```
12
+
13
+ Or with HTTPS:
14
+
15
+ ```
16
+ wss://localhost:{port}?token={authToken}
17
+ ```
18
+
19
+ If a `sessionId` query parameter is provided, the server will automatically join the client to that session after the connection handshake:
20
+
21
+ ```
22
+ ws://localhost:{port}?token={authToken}&sessionId={sessionId}
23
+ ```
24
+
25
+ All messages are JSON-encoded strings. Every message has a `type` field that identifies the message kind.
26
+
27
+ ---
28
+
29
+ ## Client-to-Server Messages
30
+
31
+ ### `create_session`
32
+
33
+ Create a new session and automatically join it.
34
+
35
+ ```json
36
+ {
37
+ "type": "create_session",
38
+ "name": "My Session",
39
+ "workingDir": "/home/user/project"
40
+ }
41
+ ```
42
+
43
+ | Field | Type | Required | Description |
44
+ |-------|------|----------|-------------|
45
+ | `name` | string | No | Human-readable session name. Defaults to `"Session {timestamp}"`. |
46
+ | `workingDir` | string | No | Working directory for the session. Must be within the server's base directory. Defaults to the selected working directory or the server's base folder. |
47
+
48
+ ---
49
+
50
+ ### `join_session`
51
+
52
+ Join an existing session. Receives the output buffer for replay.
53
+
54
+ ```json
55
+ {
56
+ "type": "join_session",
57
+ "sessionId": "550e8400-e29b-41d4-a716-446655440000"
58
+ }
59
+ ```
60
+
61
+ | Field | Type | Required | Description |
62
+ |-------|------|----------|-------------|
63
+ | `sessionId` | string (UUID) | Yes | The ID of the session to join. |
64
+
65
+ ---
66
+
67
+ ### `leave_session`
68
+
69
+ Disconnect from the current session without stopping the CLI process.
70
+
71
+ ```json
72
+ {
73
+ "type": "leave_session"
74
+ }
75
+ ```
76
+
77
+ No additional fields. The server removes the WebSocket connection from the session's connection set but leaves the CLI process running.
78
+
79
+ ---
80
+
81
+ ### `start_claude`
82
+
83
+ Start the Claude CLI in the current session.
84
+
85
+ ```json
86
+ {
87
+ "type": "start_claude",
88
+ "options": {
89
+ "dangerouslySkipPermissions": false,
90
+ "cols": 120,
91
+ "rows": 40
92
+ }
93
+ }
94
+ ```
95
+
96
+ | Field | Type | Required | Description |
97
+ |-------|------|----------|-------------|
98
+ | `options.dangerouslySkipPermissions` | boolean | No | Pass `--dangerously-skip-permissions` to the Claude CLI. Default `false`. |
99
+ | `options.cols` | number | No | Terminal width in columns. Default `80`. |
100
+ | `options.rows` | number | No | Terminal height in rows. Default `24`. |
101
+
102
+ ---
103
+
104
+ ### `start_codex`
105
+
106
+ Start the Codex CLI in the current session.
107
+
108
+ ```json
109
+ {
110
+ "type": "start_codex",
111
+ "options": {
112
+ "dangerouslySkipPermissions": false,
113
+ "cols": 120,
114
+ "rows": 40
115
+ }
116
+ }
117
+ ```
118
+
119
+ | Field | Type | Required | Description |
120
+ |-------|------|----------|-------------|
121
+ | `options.dangerouslySkipPermissions` | boolean | No | Pass `--dangerously-bypass-approvals-and-sandbox` to the Codex CLI. Default `false`. |
122
+ | `options.cols` | number | No | Terminal width in columns. Default `80`. |
123
+ | `options.rows` | number | No | Terminal height in rows. Default `24`. |
124
+
125
+ ---
126
+
127
+ ### `start_agent`
128
+
129
+ Start the Cursor Agent CLI in the current session.
130
+
131
+ ```json
132
+ {
133
+ "type": "start_agent",
134
+ "options": {
135
+ "cols": 120,
136
+ "rows": 40
137
+ }
138
+ }
139
+ ```
140
+
141
+ | Field | Type | Required | Description |
142
+ |-------|------|----------|-------------|
143
+ | `options.cols` | number | No | Terminal width in columns. Default `80`. |
144
+ | `options.rows` | number | No | Terminal height in rows. Default `24`. |
145
+
146
+ ---
147
+
148
+ ### `input`
149
+
150
+ Send user input (keystrokes) to the running CLI process.
151
+
152
+ ```json
153
+ {
154
+ "type": "input",
155
+ "data": "hello world\r"
156
+ }
157
+ ```
158
+
159
+ | Field | Type | Required | Description |
160
+ |-------|------|----------|-------------|
161
+ | `data` | string | Yes | Raw terminal input data. Use `\r` for Enter. |
162
+
163
+ The server validates that the sending WebSocket connection belongs to the target session and that a CLI process is actively running before forwarding the input. If no agent is running, the server responds with an `info` or `error` message.
164
+
165
+ ---
166
+
167
+ ### `resize`
168
+
169
+ Update the terminal dimensions for the running CLI process.
170
+
171
+ ```json
172
+ {
173
+ "type": "resize",
174
+ "cols": 120,
175
+ "rows": 40
176
+ }
177
+ ```
178
+
179
+ | Field | Type | Required | Description |
180
+ |-------|------|----------|-------------|
181
+ | `cols` | number | Yes | New terminal width in columns. |
182
+ | `rows` | number | Yes | New terminal height in rows. |
183
+
184
+ Resize is silently ignored if no agent is currently running.
185
+
186
+ ---
187
+
188
+ ### `stop`
189
+
190
+ Terminate the running CLI process in the current session.
191
+
192
+ ```json
193
+ {
194
+ "type": "stop"
195
+ }
196
+ ```
197
+
198
+ No additional fields. The server sends `SIGTERM` to the process, then `SIGKILL` after a 5-second timeout if the process has not exited. The appropriate `*_stopped` message is broadcast to all connected clients.
199
+
200
+ ---
201
+
202
+ ### `ping`
203
+
204
+ Keepalive ping. The server responds with `pong`.
205
+
206
+ ```json
207
+ {
208
+ "type": "ping"
209
+ }
210
+ ```
211
+
212
+ ---
213
+
214
+ ### `get_usage`
215
+
216
+ Request current usage statistics (token counts, costs, burn rate, session timer).
217
+
218
+ ```json
219
+ {
220
+ "type": "get_usage"
221
+ }
222
+ ```
223
+
224
+ No additional fields. The server reads Claude's JSONL usage logs, calculates analytics, and responds with a `usage_update` message.
225
+
226
+ ---
227
+
228
+ ## Server-to-Client Messages
229
+
230
+ ### `connected`
231
+
232
+ Sent immediately after the WebSocket connection is established.
233
+
234
+ ```json
235
+ {
236
+ "type": "connected",
237
+ "connectionId": "550e8400-e29b-41d4-a716-446655440000"
238
+ }
239
+ ```
240
+
241
+ | Field | Type | Description |
242
+ |-------|------|-------------|
243
+ | `connectionId` | string (UUID) | Unique identifier for this WebSocket connection. |
244
+
245
+ ---
246
+
247
+ ### `session_created`
248
+
249
+ Confirmation that a new session was created and the client has joined it.
250
+
251
+ ```json
252
+ {
253
+ "type": "session_created",
254
+ "sessionId": "550e8400-e29b-41d4-a716-446655440000",
255
+ "sessionName": "My Session",
256
+ "workingDir": "/home/user/project"
257
+ }
258
+ ```
259
+
260
+ | Field | Type | Description |
261
+ |-------|------|-------------|
262
+ | `sessionId` | string (UUID) | The new session's unique identifier. |
263
+ | `sessionName` | string | The human-readable session name. |
264
+ | `workingDir` | string | The resolved working directory. |
265
+
266
+ ---
267
+
268
+ ### `session_joined`
269
+
270
+ Confirmation that the client has joined an existing session. Includes the output buffer for replaying recent terminal output.
271
+
272
+ ```json
273
+ {
274
+ "type": "session_joined",
275
+ "sessionId": "550e8400-e29b-41d4-a716-446655440000",
276
+ "sessionName": "My Session",
277
+ "workingDir": "/home/user/project",
278
+ "active": true,
279
+ "outputBuffer": ["line1\r\n", "line2\r\n"]
280
+ }
281
+ ```
282
+
283
+ | Field | Type | Description |
284
+ |-------|------|-------------|
285
+ | `sessionId` | string (UUID) | The session's unique identifier. |
286
+ | `sessionName` | string | The human-readable session name. |
287
+ | `workingDir` | string | The session's working directory. |
288
+ | `active` | boolean | Whether a CLI process is currently running. |
289
+ | `outputBuffer` | string[] | The last 200 lines of terminal output for replay. |
290
+
291
+ ---
292
+
293
+ ### `session_left`
294
+
295
+ Confirmation that the client has left the current session.
296
+
297
+ ```json
298
+ {
299
+ "type": "session_left"
300
+ }
301
+ ```
302
+
303
+ ---
304
+
305
+ ### `claude_started`
306
+
307
+ Broadcast to all clients in the session when the Claude CLI process starts.
308
+
309
+ ```json
310
+ {
311
+ "type": "claude_started",
312
+ "sessionId": "550e8400-e29b-41d4-a716-446655440000"
313
+ }
314
+ ```
315
+
316
+ ---
317
+
318
+ ### `codex_started`
319
+
320
+ Broadcast to all clients in the session when the Codex CLI process starts.
321
+
322
+ ```json
323
+ {
324
+ "type": "codex_started",
325
+ "sessionId": "550e8400-e29b-41d4-a716-446655440000"
326
+ }
327
+ ```
328
+
329
+ ---
330
+
331
+ ### `agent_started`
332
+
333
+ Broadcast to all clients in the session when the Cursor Agent CLI process starts.
334
+
335
+ ```json
336
+ {
337
+ "type": "agent_started",
338
+ "sessionId": "550e8400-e29b-41d4-a716-446655440000"
339
+ }
340
+ ```
341
+
342
+ ---
343
+
344
+ ### `output`
345
+
346
+ Terminal output from the running CLI process. Broadcast to all clients connected to the session.
347
+
348
+ ```json
349
+ {
350
+ "type": "output",
351
+ "data": "\u001b[32mHello World\u001b[0m\r\n"
352
+ }
353
+ ```
354
+
355
+ | Field | Type | Description |
356
+ |-------|------|-------------|
357
+ | `data` | string | Raw terminal output including ANSI escape sequences. |
358
+
359
+ This is the highest-frequency message type. Output is streamed in real time as the CLI process writes to its PTY.
360
+
361
+ ---
362
+
363
+ ### `exit`
364
+
365
+ The CLI process has exited. Broadcast to all clients in the session.
366
+
367
+ ```json
368
+ {
369
+ "type": "exit",
370
+ "code": 0,
371
+ "signal": null
372
+ }
373
+ ```
374
+
375
+ | Field | Type | Description |
376
+ |-------|------|-------------|
377
+ | `code` | number | The process exit code. `0` indicates success. |
378
+ | `signal` | string \| null | The signal that terminated the process, if any (e.g., `"SIGTERM"`, `"SIGKILL"`). |
379
+
380
+ ---
381
+
382
+ ### `error`
383
+
384
+ An error occurred. Sent to the originating client or broadcast to the session depending on context.
385
+
386
+ ```json
387
+ {
388
+ "type": "error",
389
+ "message": "Failed to start Claude Code: command not found"
390
+ }
391
+ ```
392
+
393
+ | Field | Type | Description |
394
+ |-------|------|-------------|
395
+ | `message` | string | Human-readable error description. |
396
+
397
+ Common error scenarios:
398
+ - No session joined when trying to start a CLI or send input
399
+ - Agent already running in the session
400
+ - Session not found when joining
401
+ - CLI command not found on the system
402
+ - Path validation failure
403
+
404
+ ---
405
+
406
+ ### `claude_stopped` / `codex_stopped` / `agent_stopped`
407
+
408
+ Broadcast to all clients in the session when the corresponding CLI process is explicitly stopped via the `stop` message.
409
+
410
+ ```json
411
+ {
412
+ "type": "claude_stopped"
413
+ }
414
+ ```
415
+
416
+ These messages indicate the process was stopped by user action, as opposed to `exit` which indicates the process terminated on its own.
417
+
418
+ ---
419
+
420
+ ### `usage_update`
421
+
422
+ Response to `get_usage`. Contains comprehensive usage analytics.
423
+
424
+ ```json
425
+ {
426
+ "type": "usage_update",
427
+ "sessionStats": {
428
+ "requests": 42,
429
+ "inputTokens": 150000,
430
+ "outputTokens": 85000,
431
+ "cacheCreationTokens": 12000,
432
+ "cacheReadTokens": 45000,
433
+ "cacheTokens": 57000,
434
+ "totalTokens": 235000,
435
+ "totalCost": 4.52,
436
+ "models": {
437
+ "sonnet": { "requests": 40, "inputTokens": 145000, "outputTokens": 82000, "cost": 4.10 },
438
+ "opus": { "requests": 2, "inputTokens": 5000, "outputTokens": 3000, "cost": 0.42 }
439
+ },
440
+ "sessionStartTime": "2025-01-15T10:00:00.000Z",
441
+ "lastUpdate": "2025-01-15T12:30:00.000Z",
442
+ "sessionNumber": 1,
443
+ "isExpired": false
444
+ },
445
+ "dailyStats": {
446
+ "requests": 120,
447
+ "totalTokens": 680000,
448
+ "totalCost": 12.80,
449
+ "periodHours": 24,
450
+ "hourlyRate": 5.0,
451
+ "tokensPerHour": 28333,
452
+ "costPerHour": 0.53
453
+ },
454
+ "sessionTimer": {
455
+ "startTime": "2025-01-15T10:00:00.000Z",
456
+ "elapsed": 9000000,
457
+ "remaining": 9000000,
458
+ "formatted": "02:30:00",
459
+ "remainingFormatted": "02:30",
460
+ "hours": 2,
461
+ "minutes": 30,
462
+ "seconds": 0,
463
+ "remainingMs": 9000000,
464
+ "sessionDurationHours": 5,
465
+ "sessionNumber": 1,
466
+ "isExpired": false,
467
+ "burnRate": 450.5,
468
+ "burnRateConfidence": 0.82,
469
+ "depletionTime": "2025-01-15T14:15:00.000Z",
470
+ "depletionConfidence": 0.75
471
+ },
472
+ "analytics": {
473
+ "currentSession": { "id": "session_1", "startTime": "...", "endTime": "...", "tokens": 235000, "remaining": -15000, "percentUsed": 106.8 },
474
+ "burnRate": { "current": 450.5, "trend": "stable", "history": [] },
475
+ "predictions": { "depletionTime": "...", "confidence": 0.75, "minutesRemaining": 105 },
476
+ "plan": { "type": "max20", "limits": { "tokens": 220000, "cost": 140.00, "messages": 2000 } }
477
+ },
478
+ "burnRate": { "rate": 450.5, "confidence": 0.82, "dataPoints": 42 },
479
+ "overlappingSessions": 0,
480
+ "plan": "max20",
481
+ "limits": { "tokens": 220000, "cost": 140.00, "messages": 2000, "algorithm": "fixed" }
482
+ }
483
+ ```
484
+
485
+ ---
486
+
487
+ ### `session_deleted`
488
+
489
+ Sent to all clients connected to a session that has been deleted via the REST API.
490
+
491
+ ```json
492
+ {
493
+ "type": "session_deleted",
494
+ "message": "Session has been deleted"
495
+ }
496
+ ```
497
+
498
+ After receiving this message, the client's WebSocket connection is closed by the server.
499
+
500
+ ---
501
+
502
+ ### `pong`
503
+
504
+ Response to a `ping` message.
505
+
506
+ ```json
507
+ {
508
+ "type": "pong"
509
+ }
510
+ ```
511
+
512
+ ---
513
+
514
+ ### `info`
515
+
516
+ Informational message, typically sent when the user attempts to send input without a running agent.
517
+
518
+ ```json
519
+ {
520
+ "type": "info",
521
+ "message": "No agent is running. Choose an option to start."
522
+ }
523
+ ```
524
+
525
+ ---
526
+
527
+ ## Message Flow Diagrams
528
+
529
+ ### Full Session Lifecycle
530
+
531
+ ```mermaid
532
+ sequenceDiagram
533
+ participant C as Client
534
+ participant S as Server
535
+
536
+ C->>S: WebSocket connect (?token=xxx)
537
+ S->>C: connected {connectionId}
538
+
539
+ C->>S: create_session {name, workingDir}
540
+ S->>C: session_created {sessionId, sessionName, workingDir}
541
+
542
+ C->>S: start_claude {options}
543
+ S->>C: claude_started {sessionId}
544
+
545
+ loop Terminal I/O
546
+ C->>S: input {data}
547
+ S->>C: output {data}
548
+ end
549
+
550
+ C->>S: resize {cols, rows}
551
+
552
+ C->>S: stop
553
+ S->>C: claude_stopped
554
+
555
+ Note over C,S: Or process exits naturally:
556
+ S->>C: exit {code, signal}
557
+ ```
558
+
559
+ ### Multi-Client Session Sharing
560
+
561
+ ```mermaid
562
+ sequenceDiagram
563
+ participant A as Client A
564
+ participant S as Server
565
+ participant B as Client B
566
+
567
+ A->>S: create_session
568
+ S->>A: session_created {sessionId: "abc"}
569
+
570
+ A->>S: start_claude
571
+ S->>A: claude_started
572
+
573
+ B->>S: join_session {sessionId: "abc"}
574
+ S->>B: session_joined {outputBuffer: [...]}
575
+
576
+ Note over A,B: Both clients receive output
577
+ S->>A: output {data}
578
+ S->>B: output {data}
579
+
580
+ Note over A,B: Either client can send input
581
+ B->>S: input {data}
582
+ S->>A: output {response}
583
+ S->>B: output {response}
584
+
585
+ A->>S: leave_session
586
+ S->>A: session_left
587
+ Note over S,B: CLI keeps running for Client B
588
+ ```
589
+
590
+ ## REST API Endpoints
591
+
592
+ The server also exposes REST endpoints for operations that do not require real-time streaming:
593
+
594
+ | Method | Endpoint | Description |
595
+ |--------|----------|-------------|
596
+ | `GET` | `/auth-status` | Check if authentication is required (no auth needed) |
597
+ | `POST` | `/auth-verify` | Validate an authentication token |
598
+ | `GET` | `/api/health` | Server health check with session/connection counts |
599
+ | `GET` | `/api/config` | Server configuration (folder mode, aliases, base folder) |
600
+ | `GET` | `/api/sessions/list` | List all sessions with metadata |
601
+ | `GET` | `/api/sessions/persistence` | Session storage metadata |
602
+ | `POST` | `/api/sessions/create` | Create a new session (REST alternative to WS) |
603
+ | `GET` | `/api/sessions/:id` | Get session details |
604
+ | `DELETE` | `/api/sessions/:id` | Delete a session and stop its CLI process |
605
+ | `GET` | `/api/folders` | Browse directories (with path validation) |
606
+ | `POST` | `/api/folders/select` | Select a working directory |
607
+ | `POST` | `/api/set-working-dir` | Set the server's default working directory |
608
+ | `POST` | `/api/create-folder` | Create a new directory |
609
+ | `POST` | `/api/close-session` | Clear the selected working directory |
@@ -0,0 +1,26 @@
1
+ # ai-or-die Project History
2
+
3
+ This document records major architectural decisions, intended as a handoff reference for new contributors.
4
+
5
+ ---
6
+
7
+ ## ai-or-die v0.1.0
8
+
9
+ Cortex is a universal AI coding terminal providing browser-based access to multiple CLI tools through a unified interface.
10
+
11
+ ### Architecture
12
+
13
+ - **BaseBridge pattern**: All CLI tools extend a shared `BaseBridge` base class with cross-platform command discovery, session lifecycle, and process management. See [ADR-0001](../adrs/0001-bridge-base-class.md).
14
+
15
+ - **Multi-tool support**: Claude, Codex, Copilot, Gemini, and Terminal are supported via the bridge pattern. New tools can be added with a bridge subclass — no client code changes required. See [ADR-0003](../adrs/0003-multi-tool-architecture.md).
16
+
17
+ - **Dev Tunnels**: Remote access uses Microsoft Dev Tunnels (`devtunnel` CLI) instead of third-party tunnel providers. See [ADR-0002](../adrs/0002-devtunnels-over-ngrok.md).
18
+
19
+ - **Cross-platform**: Platform-specific code is centralized in `BaseBridge` using `os.homedir()`, `where`/`which` detection, and ConPTY on Windows. See [ADR-0004](../adrs/0004-cross-platform-support.md).
20
+
21
+ ### Key Design Decisions
22
+
23
+ - Session persistence to `~/.ai-or-die/sessions.json` with atomic writes
24
+ - Dynamic UI card generation from server-reported tool availability
25
+ - Token-based auth enabled by default (auto-generated on startup)
26
+ - Express + WebSocket on a single port (default 7777)