aibroker 0.2.6 → 0.5.1

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 (83) hide show
  1. package/README.md +164 -4
  2. package/dist/adapters/iterm/core.d.ts +2 -0
  3. package/dist/adapters/iterm/core.d.ts.map +1 -1
  4. package/dist/adapters/iterm/core.js +13 -5
  5. package/dist/adapters/iterm/core.js.map +1 -1
  6. package/dist/adapters/kokoro/media.d.ts +2 -1
  7. package/dist/adapters/kokoro/media.d.ts.map +1 -1
  8. package/dist/adapters/kokoro/media.js +51 -3
  9. package/dist/adapters/kokoro/media.js.map +1 -1
  10. package/dist/adapters/pailot/gateway.d.ts +49 -0
  11. package/dist/adapters/pailot/gateway.d.ts.map +1 -0
  12. package/dist/adapters/pailot/gateway.js +502 -0
  13. package/dist/adapters/pailot/gateway.js.map +1 -0
  14. package/dist/backend/api.d.ts +5 -1
  15. package/dist/backend/api.d.ts.map +1 -1
  16. package/dist/backend/api.js +74 -3
  17. package/dist/backend/api.js.map +1 -1
  18. package/dist/core/hybrid.d.ts +5 -0
  19. package/dist/core/hybrid.d.ts.map +1 -1
  20. package/dist/core/hybrid.js +25 -0
  21. package/dist/core/hybrid.js.map +1 -1
  22. package/dist/core/state.d.ts +3 -0
  23. package/dist/core/state.d.ts.map +1 -1
  24. package/dist/core/state.js +4 -0
  25. package/dist/core/state.js.map +1 -1
  26. package/dist/daemon/adapter-registry.d.ts +58 -0
  27. package/dist/daemon/adapter-registry.d.ts.map +1 -0
  28. package/dist/daemon/adapter-registry.js +179 -0
  29. package/dist/daemon/adapter-registry.js.map +1 -0
  30. package/dist/daemon/cli.d.ts +13 -0
  31. package/dist/daemon/cli.d.ts.map +1 -0
  32. package/dist/daemon/cli.js +58 -0
  33. package/dist/daemon/cli.js.map +1 -0
  34. package/dist/daemon/core-handlers.d.ts +24 -0
  35. package/dist/daemon/core-handlers.d.ts.map +1 -0
  36. package/dist/daemon/core-handlers.js +146 -0
  37. package/dist/daemon/core-handlers.js.map +1 -0
  38. package/dist/daemon/create-adapter.d.ts +22 -0
  39. package/dist/daemon/create-adapter.d.ts.map +1 -0
  40. package/dist/daemon/create-adapter.js +152 -0
  41. package/dist/daemon/create-adapter.js.map +1 -0
  42. package/dist/daemon/index.d.ts +12 -0
  43. package/dist/daemon/index.d.ts.map +1 -0
  44. package/dist/daemon/index.js +83 -0
  45. package/dist/daemon/index.js.map +1 -0
  46. package/dist/daemon/pai-projects.d.ts +68 -0
  47. package/dist/daemon/pai-projects.d.ts.map +1 -0
  48. package/dist/daemon/pai-projects.js +174 -0
  49. package/dist/daemon/pai-projects.js.map +1 -0
  50. package/dist/index.d.ts +11 -2
  51. package/dist/index.d.ts.map +1 -1
  52. package/dist/index.js +9 -1
  53. package/dist/index.js.map +1 -1
  54. package/dist/ipc/client.d.ts +4 -1
  55. package/dist/ipc/client.d.ts.map +1 -1
  56. package/dist/ipc/client.js +10 -1
  57. package/dist/ipc/client.js.map +1 -1
  58. package/dist/types/adapter.d.ts +41 -0
  59. package/dist/types/adapter.d.ts.map +1 -0
  60. package/dist/types/adapter.js +2 -0
  61. package/dist/types/adapter.js.map +1 -0
  62. package/dist/types/backend.d.ts +29 -1
  63. package/dist/types/backend.d.ts.map +1 -1
  64. package/dist/types/broker.d.ts +45 -0
  65. package/dist/types/broker.d.ts.map +1 -0
  66. package/dist/types/broker.js +21 -0
  67. package/dist/types/broker.js.map +1 -0
  68. package/dist/types/index.d.ts +2 -0
  69. package/dist/types/index.d.ts.map +1 -1
  70. package/dist/types/index.js +2 -0
  71. package/dist/types/index.js.map +1 -1
  72. package/package.json +9 -2
  73. package/templates/adapter/ONBOARDING_PROMPT.md +287 -0
  74. package/templates/adapter/README.md.tmpl +98 -0
  75. package/templates/adapter/package.json.tmpl +23 -0
  76. package/templates/adapter/src/watcher/cli.ts.tmpl +12 -0
  77. package/templates/adapter/src/watcher/commands.ts.tmpl +146 -0
  78. package/templates/adapter/src/watcher/connection.ts.tmpl +59 -0
  79. package/templates/adapter/src/watcher/index.ts.tmpl +177 -0
  80. package/templates/adapter/src/watcher/ipc-server.ts.tmpl +226 -0
  81. package/templates/adapter/src/watcher/send.ts.tmpl +62 -0
  82. package/templates/adapter/src/watcher/state.ts.tmpl +39 -0
  83. package/templates/adapter/tsconfig.json.tmpl +14 -0
package/package.json CHANGED
@@ -1,18 +1,23 @@
1
1
  {
2
2
  "name": "aibroker",
3
- "version": "0.2.6",
3
+ "version": "0.5.1",
4
4
  "description": "Platform-agnostic AI message broker — routes between user channels and AI backends",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
8
8
  "files": [
9
9
  "dist",
10
+ "templates",
10
11
  "README.md",
11
12
  "LICENSE"
12
13
  ],
14
+ "bin": {
15
+ "aibroker": "dist/daemon/cli.js"
16
+ },
13
17
  "scripts": {
14
18
  "build": "tsc",
15
19
  "dev": "tsc --watch",
20
+ "start": "node dist/daemon/cli.js start",
16
21
  "prepublishOnly": "npm run build"
17
22
  },
18
23
  "repository": {
@@ -37,10 +42,12 @@
37
42
  },
38
43
  "dependencies": {
39
44
  "@anthropic-ai/claude-agent-sdk": "^0.1.0",
40
- "kokoro-js": "^1.2.1"
45
+ "kokoro-js": "^1.2.1",
46
+ "ws": "^8.19.0"
41
47
  },
42
48
  "devDependencies": {
43
49
  "@types/node": "^22.0.0",
50
+ "@types/ws": "^8.5.0",
44
51
  "typescript": "^5.0.0"
45
52
  }
46
53
  }
@@ -0,0 +1,287 @@
1
+ # AIBroker Adapter Onboarding — AI-Guided Creation
2
+
3
+ You are helping a developer create a new AIBroker messaging adapter. Your job is to ask up to 5 targeted questions, then generate a complete, working adapter from the scaffold templates.
4
+
5
+ Work through the following phases in order.
6
+
7
+ ---
8
+
9
+ ## Phase 1: Interview (5 questions max)
10
+
11
+ Ask only what you need. If the answer is obvious from context (e.g. the user says "Discord"), skip the question. Combine questions where sensible. Never ask more than 5.
12
+
13
+ **The 5 questions:**
14
+
15
+ 1. **Service name** — What service are you connecting? (e.g. Signal, Discord, Slack, IRC, Matrix)
16
+ 2. **Package name** — What npm package should be used? (If you don't know, say "search for me" — you will search npm.)
17
+ 3. **Auth method** — How does the SDK authenticate? (QR code scan, phone number + OTP, API key, OAuth, bot token, username/password)
18
+ 4. **Message model** — Does the service have the concept of channels/rooms/groups, or is it always a 1:1 direct chat? Who is the default recipient?
19
+ 5. **Voice support** — Does the service support sending voice/audio messages? If yes, what format does it accept? (Most accept OGG Opus or MP3.)
20
+
21
+ If the user already answered any of these in their initial message, skip those questions.
22
+
23
+ ---
24
+
25
+ ## Phase 2: npm Research
26
+
27
+ Before generating code, search npm for the best library for this service:
28
+
29
+ ```bash
30
+ # Search npm for available packages
31
+ npm search <service-name> --json | head -20
32
+
33
+ # Read the top candidate's README
34
+ npm show <package-name> readme
35
+ ```
36
+
37
+ Pick the most maintained package (high weekly downloads, recent publish date, active repo). If the user specified a package, verify it exists and read its docs anyway.
38
+
39
+ Read the package README for:
40
+ - Connection/auth pattern (how to establish a session)
41
+ - How to receive incoming messages (event name, callback signature)
42
+ - How to send a text message (method name, arguments)
43
+ - How to send a file/audio (if applicable)
44
+ - Session persistence (does it save credentials to disk?)
45
+
46
+ ---
47
+
48
+ ## Phase 3: Generate the Adapter
49
+
50
+ Generate all files listed below. Use the scaffold templates in `templates/adapter/` as the starting point and replace all `{{ADAPTER_NAME}}` and `{{DISPLAY_NAME}}` placeholders.
51
+
52
+ **Variable substitution:**
53
+ - `{{ADAPTER_NAME}}` — lowercase hyphenated package name (e.g. `signal-bridge`, `discord-adapter`)
54
+ - `{{DISPLAY_NAME}}` — human-readable service name (e.g. `Signal`, `Discord`)
55
+
56
+ ### Files to generate
57
+
58
+ **`package.json`** — from `templates/adapter/package.json.tmpl`
59
+ - Fill in name, description
60
+ - Add the chosen npm library to `dependencies`
61
+
62
+ **`tsconfig.json`** — from `templates/adapter/tsconfig.json.tmpl`
63
+ - No changes needed
64
+
65
+ **`src/watcher/state.ts`** — from `templates/adapter/src/watcher/state.ts.tmpl`
66
+ - Replace placeholders only
67
+ - The socket path must be `/tmp/{{ADAPTER_NAME}}-watcher.sock`
68
+
69
+ **`src/watcher/connection.ts`** — from `templates/adapter/src/watcher/connection.ts.tmpl`
70
+ - THIS IS THE CORE IMPLEMENTATION FILE — replace the stub with real SDK code
71
+ - Implement `connectWatcher()` using the library you researched
72
+ - The function must:
73
+ 1. Load credentials from `appDir` (use `getAppDir()` from aibroker if available, or `os.homedir()` + `.{{ADAPTER_NAME}}/auth/`)
74
+ 2. Establish connection to the service
75
+ 3. Subscribe to incoming messages, calling `onMessage(text, timestamp)` for each
76
+ 4. Return `cleanup()` to disconnect gracefully
77
+ 5. Return `triggerLogin()` to start a fresh auth flow (QR, OTP, etc.)
78
+ - Include all necessary imports from the chosen npm package
79
+ - Handle reconnection if the SDK supports it
80
+ - Log connection events with `log()` from aibroker
81
+
82
+ **`src/watcher/send.ts`** — from `templates/adapter/src/watcher/send.ts.tmpl`
83
+ - Replace stubs with real SDK delivery calls
84
+ - `sendText(text, recipient?)` — send plain text
85
+ - `sendVoice(audioPath, recipient?)` — send OGG Opus audio file (skip with a clear comment if service does not support voice)
86
+ - `sendFile(filePath, caption?, mimetype?, recipient?)` — send document attachment
87
+
88
+ **`src/watcher/commands.ts`** — from `templates/adapter/src/watcher/commands.ts.tmpl`
89
+ - Replace placeholders only
90
+ - Add any service-specific slash commands if the service warrants them (e.g. `/channel #general` to switch rooms)
91
+
92
+ **`src/watcher/ipc-server.ts`** — from `templates/adapter/src/watcher/ipc-server.ts.tmpl`
93
+ - Replace placeholders only
94
+ - The four required IPC handlers must be present: `deliver`, `health`, `connection_status`, `login`
95
+
96
+ **`src/watcher/index.ts`** — from `templates/adapter/src/watcher/index.ts.tmpl`
97
+ - Replace placeholders only
98
+ - The hub detection pattern must remain intact
99
+
100
+ **`src/watcher/cli.ts`** — from `templates/adapter/src/watcher/cli.ts.tmpl`
101
+ - Replace placeholders only
102
+
103
+ **`src/index.ts`** — MCP server entry point
104
+ - Model this on Whazaa's `src/index.ts` (the reference implementation)
105
+ - Expose MCP tools named `<service>_send`, `<service>_status`, `<service>_tts` (voice, if supported), `<service>_send_file`
106
+ - Wire each tool to the corresponding IPC handler via `WatcherClient`
107
+ - Socket path: `/tmp/{{ADAPTER_NAME}}-watcher.sock`
108
+
109
+ **`README.md`** — from `templates/adapter/README.md.tmpl`
110
+ - Fill in service name, auth instructions, and any service-specific setup steps
111
+
112
+ ---
113
+
114
+ ## Phase 4: Wire into AIBroker Config
115
+
116
+ After generating the adapter files, register the adapter with the AIBroker hub:
117
+
118
+ 1. **Check if `~/.aibroker/config.json` exists.** If yes, add an entry to the `adapters` array:
119
+ ```json
120
+ {
121
+ "name": "{{ADAPTER_NAME}}",
122
+ "socketPath": "/tmp/{{ADAPTER_NAME}}-watcher.sock",
123
+ "autoStart": false
124
+ }
125
+ ```
126
+
127
+ 2. **Register as an MCP server** in `~/.claude.json` under `mcpServers`:
128
+ ```json
129
+ "{{ADAPTER_NAME}}": {
130
+ "type": "stdio",
131
+ "command": "node",
132
+ "args": ["/path/to/{{ADAPTER_NAME}}/dist/index.js"],
133
+ "description": "{{DISPLAY_NAME}} MCP adapter"
134
+ }
135
+ ```
136
+
137
+ 3. **Add permission** in `~/.claude/settings.json` under `permissions.allow`:
138
+ ```json
139
+ "mcp__{{ADAPTER_NAME}}"
140
+ ```
141
+
142
+ ---
143
+
144
+ ## Phase 5: Test the Connection
145
+
146
+ After generating all files:
147
+
148
+ ```bash
149
+ # Build
150
+ cd /path/to/{{ADAPTER_NAME}}
151
+ npm install
152
+ npm run build
153
+
154
+ # Start the watcher (runs the upstream connection)
155
+ node dist/watcher/cli.js watch
156
+
157
+ # In a separate terminal, verify the IPC server is responding
158
+ node -e "
159
+ import { WatcherClient } from 'aibroker';
160
+ const c = new WatcherClient('/tmp/{{ADAPTER_NAME}}-watcher.sock');
161
+ c.call_raw('health', {}).then(r => console.log(JSON.stringify(r, null, 2)));
162
+ "
163
+ ```
164
+
165
+ Expected health response:
166
+ ```json
167
+ {
168
+ "ok": true,
169
+ "result": {
170
+ "status": "ok",
171
+ "connectionStatus": "connected",
172
+ "stats": { "messagesReceived": 0, "messagesSent": 0, "errors": 0 },
173
+ "lastMessageAgo": null
174
+ }
175
+ }
176
+ ```
177
+
178
+ If `status` is `"down"` or `connectionStatus` is not `"connected"`, check the watcher logs for auth/connection errors and run `triggerLogin()` via the `login` IPC handler.
179
+
180
+ ---
181
+
182
+ ## Interface Contracts (Reference)
183
+
184
+ ### MessengerAdapter interface
185
+
186
+ Every adapter fulfills this interface structurally via IPC handlers:
187
+
188
+ ```typescript
189
+ interface MessengerAdapter {
190
+ // Lifecycle
191
+ start(config: AdapterConfig): Promise<void>;
192
+ stop(): Promise<void>;
193
+ health(): Promise<AdapterHealth>; // IPC: "health"
194
+
195
+ // Auth
196
+ login(): Promise<string>; // IPC: "login"
197
+ connectionStatus(): Promise<AdapterConnectionStatus>; // IPC: "connection_status"
198
+
199
+ // Outbound
200
+ sendText(text, recipient?): Promise<void>; // IPC: "send"
201
+ sendVoice(audioPath, recipient?): Promise<void>; // IPC: "send_voice"
202
+ sendFile(filePath, caption?, mimetype?, recipient?): Promise<void>; // IPC: "send_file"
203
+ sendImage(imagePath, caption?, recipient?): Promise<void>; // IPC: "send_image"
204
+ }
205
+ ```
206
+
207
+ ### BrokerMessage (hub envelope)
208
+
209
+ ```typescript
210
+ interface BrokerMessage {
211
+ id: string; // UUID
212
+ timestamp: number; // epoch ms
213
+ source: string; // adapter name that sent it
214
+ target?: string; // target adapter name (omit for default routing)
215
+ type: "text" | "voice" | "file" | "command" | "status";
216
+ payload: {
217
+ text?: string;
218
+ filePath?: string;
219
+ audioPath?: string;
220
+ mimetype?: string;
221
+ caption?: string;
222
+ recipient?: string;
223
+ channel?: string;
224
+ metadata?: Record<string, unknown>;
225
+ };
226
+ }
227
+ ```
228
+
229
+ ### Required IPC handlers
230
+
231
+ | Handler | Called by | Must return |
232
+ |---------|-----------|-------------|
233
+ | `deliver` | Hub, when routing a BrokerMessage to this adapter | `{ ok: true, result: { delivered: true } }` or `{ ok: false, error: string }` |
234
+ | `health` | Hub health polling | `{ ok: true, result: AdapterHealth }` |
235
+ | `connection_status` | Hub, MCP tools | `{ ok: true, result: { status: AdapterConnectionStatus } }` |
236
+ | `login` | User, via MCP tool | `{ ok: true, result: { message: string } }` |
237
+ | `send` | MCP tool | `{ ok: true, result: { sent: true } }` |
238
+ | `send_voice` | MCP tool | `{ ok: true, result: { sent: true } }` |
239
+ | `send_file` | MCP tool | `{ ok: true, result: { sent: true } }` |
240
+ | `status` | MCP tool | Human-readable status object |
241
+
242
+ ### Hub detection pattern
243
+
244
+ The watcher probes the AIBroker daemon socket at startup:
245
+
246
+ ```typescript
247
+ async function detectHubMode(): Promise<boolean> {
248
+ const client = new WatcherClient(DAEMON_SOCKET_PATH);
249
+ try {
250
+ const result = await Promise.race([
251
+ client.call_raw("status", {}),
252
+ new Promise<null>((_, reject) => setTimeout(() => reject(new Error("timeout")), 2000)),
253
+ ]);
254
+ return result !== null;
255
+ } catch {
256
+ return false;
257
+ }
258
+ }
259
+ ```
260
+
261
+ If `detectHubMode()` returns `true`, the adapter:
262
+ - Forwards incoming messages to the hub via `route_message`
263
+ - Registers itself via `register_adapter` so the hub can push outbound messages back
264
+
265
+ If `false`, the adapter handles everything locally (embedded mode).
266
+
267
+ ---
268
+
269
+ ## Reference Implementation
270
+
271
+ **Whazaa** is the canonical reference adapter. When in doubt, model behaviour on Whazaa:
272
+
273
+ - Repo: `~/dev/ai/Whazaa/`
274
+ - Key file — connection: `src/watcher/baileys.ts` (WhatsApp-specific connection)
275
+ - Key file — send: `src/watcher/send.ts`
276
+ - Key file — IPC: `src/watcher/ipc-server.ts`
277
+ - Key file — MCP tools: `src/index.ts`
278
+ - Key pattern — Baileys saves auth state to `~/.whazaa/auth/` via `useMultiFileAuthState()`
279
+
280
+ The Whazaa pattern for `connectWatcher()` is:
281
+ 1. Call `useMultiFileAuthState(authDir)` (or equivalent for your SDK)
282
+ 2. Create the client instance with the auth state
283
+ 3. Set up message event listener calling `onMessage(text, timestamp)`
284
+ 4. Call `client.connect()` or equivalent
285
+ 5. Return `cleanup` (calls `client.logout()` or `client.disconnect()`) and `triggerLogin` (generates new QR/pairing)
286
+
287
+ Follow this pattern exactly. The template scaffold maps cleanly onto it.
@@ -0,0 +1,98 @@
1
+ # {{ADAPTER_NAME}}
2
+
3
+ AIBroker adapter for {{DISPLAY_NAME}}.
4
+
5
+ ## Overview
6
+
7
+ This adapter bridges {{DISPLAY_NAME}} and the [AIBroker](https://github.com/mnott/AIBroker) hub, enabling bidirectional message routing between {{DISPLAY_NAME}} and AI backends (Claude Code, Anthropic API, etc.).
8
+
9
+ ## Setup
10
+
11
+ ### 1. Install dependencies
12
+
13
+ ```bash
14
+ npm install
15
+ ```
16
+
17
+ ### 2. Implement the connection
18
+
19
+ Open `src/watcher/connection.ts` and implement `connectWatcher()` with your {{DISPLAY_NAME}} SDK.
20
+
21
+ The function must:
22
+ - Connect to {{DISPLAY_NAME}} using your credentials
23
+ - Call `onMessage(text, timestamp)` for each incoming message
24
+ - Return `cleanup()` (disconnect) and `triggerLogin()` (fresh auth flow)
25
+
26
+ ### 3. Implement outbound delivery
27
+
28
+ Open `src/watcher/send.ts` and implement `sendText()`, `sendVoice()`, and `sendFile()`.
29
+
30
+ ### 4. Build
31
+
32
+ ```bash
33
+ npm run build
34
+ ```
35
+
36
+ ### 5. Run
37
+
38
+ ```bash
39
+ # Standalone (embedded) mode — no hub required
40
+ npm run watch
41
+
42
+ # Or: with AIBroker hub running
43
+ aibroker start # in another terminal
44
+ npm run watch # adapter auto-detects and registers with hub
45
+ ```
46
+
47
+ ## Architecture
48
+
49
+ ```
50
+ {{DISPLAY_NAME}} <── connectWatcher() ──> watcher/index.ts
51
+
52
+ createMessageHandler() startIpcServer()
53
+ │ │
54
+ commands.ts ipc-server.ts
55
+
56
+ AIBroker hub / embedded
57
+ ```
58
+
59
+ ## Configuration
60
+
61
+ Set these environment variables before running:
62
+
63
+ | Variable | Default | Description |
64
+ |----------|---------|-------------|
65
+ | `AIBROKER_MODEL` | `sonnet` | AI model for API sessions |
66
+ | `AIBROKER_CWD` | `~` | Working directory for AI sessions |
67
+ | `AIBROKER_MAX_TURNS` | `30` | Max turns per API session |
68
+ | `AIBROKER_MAX_BUDGET` | `1.0` | Max USD budget per API session |
69
+ | `AIBROKER_PERMISSION_MODE` | `acceptEdits` | Claude permission mode |
70
+
71
+ ## Slash Commands
72
+
73
+ | Command | Description |
74
+ |---------|-------------|
75
+ | `/h`, `/help` | Show help |
76
+ | `/s`, `/sessions` | List all sessions |
77
+ | `/ss`, `/status` | Show active session status |
78
+ | `/n [name]` | Create a new API session |
79
+ | `/1`, `/2`, ... | Switch to session by index |
80
+
81
+ ## IPC Methods
82
+
83
+ The adapter exposes these methods on its Unix socket (`/tmp/{{ADAPTER_NAME}}-watcher.sock`):
84
+
85
+ | Method | Description |
86
+ |--------|-------------|
87
+ | `deliver` | Hub pushes a BrokerMessage for outbound delivery |
88
+ | `health` | Returns adapter health (status, stats, lastMessageAgo) |
89
+ | `connection_status` | Returns upstream connection status string |
90
+ | `send` | Send a text message |
91
+ | `send_voice` | Send a voice note (OGG Opus path) |
92
+ | `send_file` | Send a file attachment |
93
+ | `login` | Trigger a fresh login / QR pairing flow |
94
+ | `status` | Human-readable adapter status summary |
95
+
96
+ ## License
97
+
98
+ MIT
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "{{ADAPTER_NAME}}",
3
+ "version": "0.1.0",
4
+ "description": "AIBroker adapter for {{DISPLAY_NAME}}",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "{{ADAPTER_NAME}}": "dist/watcher/cli.js"
9
+ },
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "dev": "tsc --watch",
13
+ "watch": "node dist/watcher/cli.js watch",
14
+ "prepublishOnly": "npm run build"
15
+ },
16
+ "dependencies": {
17
+ "aibroker": "^0.5.0"
18
+ },
19
+ "devDependencies": {
20
+ "@types/node": "^22.0.0",
21
+ "typescript": "^5.0.0"
22
+ }
23
+ }
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+ import { watch } from "./index.js";
3
+
4
+ const command = process.argv[2];
5
+ if (command === "watch" || !command) {
6
+ watch().catch((err) => {
7
+ console.error("Fatal:", err);
8
+ process.exit(1);
9
+ });
10
+ } else {
11
+ console.log(`Usage: {{ADAPTER_NAME}} watch`);
12
+ }
@@ -0,0 +1,146 @@
1
+ /**
2
+ * commands.ts — Slash command handler for {{DISPLAY_NAME}}
3
+ *
4
+ * Parses incoming message text. If the text starts with "/" it is treated as
5
+ * a slash command; otherwise it is delivered as a plain message to the active
6
+ * AI session via the HybridSessionManager.
7
+ *
8
+ * Extend this file with adapter-specific slash commands as needed.
9
+ */
10
+
11
+ import {
12
+ hybridManager,
13
+ deliverViaApi,
14
+ log,
15
+ } from "aibroker";
16
+ import { sendText } from "./send.js";
17
+
18
+ // ── Command handler factory ───────────────────────────────────────────────────
19
+
20
+ /**
21
+ * Build and return the message handler closure used by the watcher.
22
+ *
23
+ * The closure captures mutable state via getter/setter callbacks so the
24
+ * watcher's `watch()` function can keep its own local variables authoritative
25
+ * while sharing them with the handler.
26
+ */
27
+ export function createMessageHandler(): (text: string, timestamp: number) => void {
28
+ return function handleMessage(text: string, _timestamp: number): void {
29
+ const trimmed = text.trim();
30
+
31
+ if (!trimmed.startsWith("/")) {
32
+ // Plain text — deliver to the active AI session
33
+ void deliverMessage(trimmed);
34
+ return;
35
+ }
36
+
37
+ // Slash command dispatch
38
+ void handleSlashCommand(trimmed);
39
+ };
40
+ }
41
+
42
+ // ── Slash command dispatcher ─────────────────────────────────────────────────
43
+
44
+ async function handleSlashCommand(cmd: string): Promise<void> {
45
+ const [base, ...args] = cmd.split(/\s+/);
46
+
47
+ switch (base) {
48
+ case "/h":
49
+ case "/help":
50
+ await sendText(buildHelp());
51
+ return;
52
+
53
+ case "/s":
54
+ case "/sessions": {
55
+ const mgr = hybridManager;
56
+ if (!mgr) { await sendText("No session manager active."); return; }
57
+ await sendText(mgr.formatSessionList());
58
+ return;
59
+ }
60
+
61
+ case "/ss":
62
+ case "/status": {
63
+ const mgr = hybridManager;
64
+ if (!mgr) { await sendText("No session manager active."); return; }
65
+ const status = mgr.formatActiveStatus();
66
+ await sendText(status ?? "Active session is a visual terminal — no text status available.");
67
+ return;
68
+ }
69
+
70
+ case "/n":
71
+ case "/new": {
72
+ const mgr = hybridManager;
73
+ if (!mgr) { await sendText("No session manager active."); return; }
74
+ const cwd = process.env.AIBROKER_CWD ?? process.env.HOME ?? "/";
75
+ const name = args[0] ?? `Session ${Date.now()}`;
76
+ const session = mgr.createApiSession(name, cwd);
77
+ await sendText(`Created new API session: "${session.name}" (${session.id})`);
78
+ return;
79
+ }
80
+
81
+ default:
82
+ // Numeric shortcut: /1, /2, /3 — switch to session by index
83
+ if (/^\/\d+$/.test(base)) {
84
+ const idx = parseInt(base.slice(1), 10);
85
+ const mgr = hybridManager;
86
+ if (!mgr) { await sendText("No session manager active."); return; }
87
+ const session = mgr.switchToIndex(idx);
88
+ if (session) {
89
+ await sendText(`Switched to: "${session.name}" (${session.kind})`);
90
+ } else {
91
+ await sendText(`No session at index ${idx}.`);
92
+ }
93
+ return;
94
+ }
95
+
96
+ log(`[{{ADAPTER_NAME}}] Unknown command: ${cmd}`);
97
+ await sendText(`Unknown command: ${cmd}\nType /h for help.`);
98
+ }
99
+ }
100
+
101
+ // ── Message delivery ─────────────────────────────────────────────────────────
102
+
103
+ async function deliverMessage(text: string): Promise<void> {
104
+ const mgr = hybridManager;
105
+ if (!mgr) {
106
+ log("[{{ADAPTER_NAME}}] No hybrid manager — dropping message");
107
+ return;
108
+ }
109
+
110
+ const active = mgr.activeSession;
111
+ if (!active) {
112
+ await sendText("No active session. Use /n to create one.");
113
+ return;
114
+ }
115
+
116
+ if (active.kind === "api") {
117
+ // Deliver via APIBackend — response comes back asynchronously via deliverViaApi
118
+ const response = await deliverViaApi(text, async (responseText: string) => {
119
+ await sendText(responseText);
120
+ });
121
+ if (response) {
122
+ // Synchronous response (some backends return inline)
123
+ await sendText(response);
124
+ }
125
+ } else {
126
+ // Visual (iTerm2) session — TODO: type into terminal if iTerm2 adapter is available
127
+ log(`[{{ADAPTER_NAME}}] Visual session delivery not yet implemented`);
128
+ await sendText("Visual session delivery not yet implemented for this adapter.");
129
+ }
130
+ }
131
+
132
+ // ── Help text ────────────────────────────────────────────────────────────────
133
+
134
+ function buildHelp(): string {
135
+ return [
136
+ "*{{DISPLAY_NAME}} Adapter — Commands*",
137
+ "",
138
+ "/h, /help — This help message",
139
+ "/s, /sessions — List all sessions",
140
+ "/ss, /status — Show active session status",
141
+ "/n [name] — Create a new API session",
142
+ "/1, /2, ... — Switch to session by index",
143
+ "",
144
+ "Any other text is delivered to the active AI session.",
145
+ ].join("\n");
146
+ }
@@ -0,0 +1,59 @@
1
+ /**
2
+ * connection.ts — Connect to {{DISPLAY_NAME}}
3
+ *
4
+ * TODO: Implement the upstream service connection here.
5
+ * This is the adapter-specific part — use the SDK/library for your service.
6
+ *
7
+ * Replace this stub with a real connection using the appropriate SDK, e.g.:
8
+ * - @whiskeysockets/baileys for WhatsApp
9
+ * - gramjs / telegram for Telegram
10
+ * - discord.js for Discord
11
+ * - signal-cli for Signal
12
+ *
13
+ * The onMessage callback receives every incoming message text so the watcher
14
+ * can route it through the command handler / hub forwarding.
15
+ */
16
+
17
+ export interface ConnectionResult {
18
+ /** Call to cleanly disconnect from the upstream service. */
19
+ cleanup: () => void;
20
+ /**
21
+ * Trigger a new login flow (QR code, pairing, etc.) and return a
22
+ * human-readable status string to send back to the user.
23
+ */
24
+ triggerLogin: () => Promise<string>;
25
+ }
26
+
27
+ /**
28
+ * Establish the upstream connection.
29
+ *
30
+ * @param onMessage - Called for each incoming message the adapter should process.
31
+ * Receives the message text and the message timestamp (epoch ms).
32
+ */
33
+ export async function connectWatcher(
34
+ onMessage: (text: string, timestamp: number) => void,
35
+ ): Promise<ConnectionResult> {
36
+ // TODO: Replace this stub with your actual connection logic.
37
+ //
38
+ // Typical pattern:
39
+ // 1. Load credentials from appDir (set by setAppDir() in index.ts)
40
+ // 2. Connect to the upstream service
41
+ // 3. Subscribe to incoming messages, calling onMessage() for each
42
+ // 4. Return cleanup() and triggerLogin() implementations
43
+ //
44
+ console.log("[{{ADAPTER_NAME}}] Connection stub — implement connectWatcher()");
45
+
46
+ // Suppress unused-parameter warning in stub
47
+ void onMessage;
48
+
49
+ return {
50
+ cleanup: () => {
51
+ // TODO: close WebSocket, stop polling, revoke auth, etc.
52
+ console.log("[{{ADAPTER_NAME}}] cleanup() called");
53
+ },
54
+ triggerLogin: async () => {
55
+ // TODO: start QR code / pairing flow, return status string
56
+ return "Login not yet implemented for {{DISPLAY_NAME}}";
57
+ },
58
+ };
59
+ }