@wolpertingerlabs/drawlatch 1.0.0-alpha.1 → 1.0.0-alpha.12.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 (93) hide show
  1. package/README.md +309 -486
  2. package/bin/drawlatch.js +1336 -0
  3. package/dist/cli/generate-keys.d.ts +4 -4
  4. package/dist/cli/generate-keys.js +30 -25
  5. package/dist/connections/{anthropic.json → ai/anthropic.json} +19 -1
  6. package/dist/connections/{devin.json → ai/devin.json} +7 -1
  7. package/dist/connections/{google-ai.json → ai/google-ai.json} +7 -1
  8. package/dist/connections/{openai.json → ai/openai.json} +7 -1
  9. package/dist/connections/{openrouter.json → ai/openrouter.json} +7 -1
  10. package/dist/connections/developer-tools/github-events-poll.json +78 -0
  11. package/dist/connections/developer-tools/github.json +129 -0
  12. package/dist/connections/{hex.json → developer-tools/hex.json} +7 -1
  13. package/dist/connections/developer-tools/linear.json +75 -0
  14. package/dist/connections/{lichess.json → gaming/lichess.json} +7 -1
  15. package/dist/connections/messaging/discord-bot.json +114 -0
  16. package/dist/connections/{discord-oauth.json → messaging/discord-oauth.json} +7 -1
  17. package/dist/connections/messaging/slack.json +75 -0
  18. package/dist/connections/messaging/telegram.json +66 -0
  19. package/dist/connections/{google.json → productivity/google.json} +7 -1
  20. package/dist/connections/productivity/notion.json +75 -0
  21. package/dist/connections/productivity/stripe.json +74 -0
  22. package/dist/connections/productivity/trello.json +113 -0
  23. package/dist/connections/{bluesky.json → social-media/bluesky.json} +41 -0
  24. package/dist/connections/social-media/mastodon.json +65 -0
  25. package/dist/connections/social-media/reddit.json +80 -0
  26. package/dist/connections/{twitch.json → social-media/twitch.json} +40 -0
  27. package/dist/connections/social-media/x.json +67 -0
  28. package/dist/mcp/server.js +544 -31
  29. package/dist/remote/ingestors/base-ingestor.d.ts +19 -3
  30. package/dist/remote/ingestors/base-ingestor.js +27 -6
  31. package/dist/remote/ingestors/discord/discord-gateway.d.ts +2 -2
  32. package/dist/remote/ingestors/discord/discord-gateway.js +29 -6
  33. package/dist/remote/ingestors/e2e/setup.d.ts +69 -0
  34. package/dist/remote/ingestors/e2e/setup.js +147 -0
  35. package/dist/remote/ingestors/manager.d.ts +91 -10
  36. package/dist/remote/ingestors/manager.js +395 -42
  37. package/dist/remote/ingestors/poll/poll-ingestor.d.ts +4 -2
  38. package/dist/remote/ingestors/poll/poll-ingestor.js +26 -5
  39. package/dist/remote/ingestors/registry.d.ts +2 -2
  40. package/dist/remote/ingestors/registry.js +2 -2
  41. package/dist/remote/ingestors/slack/socket-mode.d.ts +2 -2
  42. package/dist/remote/ingestors/slack/socket-mode.js +28 -6
  43. package/dist/remote/ingestors/types.d.ts +25 -0
  44. package/dist/remote/ingestors/webhook/base-webhook-ingestor.d.ts +46 -7
  45. package/dist/remote/ingestors/webhook/base-webhook-ingestor.js +115 -10
  46. package/dist/remote/ingestors/webhook/github-webhook-ingestor.d.ts +19 -0
  47. package/dist/remote/ingestors/webhook/github-webhook-ingestor.js +34 -2
  48. package/dist/remote/ingestors/webhook/lifecycle-types.d.ts +63 -0
  49. package/dist/remote/ingestors/webhook/lifecycle-types.js +12 -0
  50. package/dist/remote/ingestors/webhook/stripe-webhook-ingestor.js +2 -2
  51. package/dist/remote/ingestors/webhook/trello-webhook-ingestor.d.ts +17 -5
  52. package/dist/remote/ingestors/webhook/trello-webhook-ingestor.js +32 -26
  53. package/dist/remote/ingestors/webhook/webhook-lifecycle-manager.d.ts +46 -0
  54. package/dist/remote/ingestors/webhook/webhook-lifecycle-manager.js +261 -0
  55. package/dist/remote/server.d.ts +21 -1
  56. package/dist/remote/server.js +1132 -51
  57. package/dist/remote/triggers/rule-engine.d.ts +40 -0
  58. package/dist/remote/triggers/rule-engine.js +228 -0
  59. package/dist/remote/triggers/types.d.ts +69 -0
  60. package/dist/remote/triggers/types.js +10 -0
  61. package/dist/remote/tunnel.d.ts +40 -0
  62. package/dist/remote/tunnel.js +116 -0
  63. package/dist/shared/config.d.ts +92 -25
  64. package/dist/shared/config.js +50 -34
  65. package/dist/shared/connections.d.ts +21 -5
  66. package/dist/shared/connections.js +56 -14
  67. package/dist/shared/crypto/index.d.ts +1 -0
  68. package/dist/shared/crypto/index.js +1 -0
  69. package/dist/shared/crypto/key-manager.d.ts +67 -0
  70. package/dist/shared/crypto/key-manager.js +152 -0
  71. package/dist/shared/env-utils.d.ts +42 -0
  72. package/dist/shared/env-utils.js +150 -0
  73. package/dist/shared/listener-config.d.ts +157 -0
  74. package/dist/shared/listener-config.js +10 -0
  75. package/dist/shared/protocol/handshake.js +14 -1
  76. package/dist/shared/protocol/index.d.ts +2 -0
  77. package/dist/shared/protocol/index.js +2 -0
  78. package/dist/shared/protocol/sync-client.d.ts +52 -0
  79. package/dist/shared/protocol/sync-client.js +99 -0
  80. package/dist/shared/protocol/sync.d.ts +71 -0
  81. package/dist/shared/protocol/sync.js +176 -0
  82. package/package.json +23 -6
  83. package/dist/connections/discord-bot.json +0 -24
  84. package/dist/connections/github.json +0 -25
  85. package/dist/connections/linear.json +0 -29
  86. package/dist/connections/mastodon.json +0 -25
  87. package/dist/connections/notion.json +0 -33
  88. package/dist/connections/reddit.json +0 -28
  89. package/dist/connections/slack.json +0 -23
  90. package/dist/connections/stripe.json +0 -25
  91. package/dist/connections/telegram.json +0 -26
  92. package/dist/connections/trello.json +0 -25
  93. package/dist/connections/x.json +0 -27
package/README.md CHANGED
@@ -1,685 +1,508 @@
1
- # drawlatch
1
+ # Drawlatch
2
2
 
3
- A config-driven MCP (Model Context Protocol) proxy that lets Claude Code make authenticated HTTP requests to external APIs. Supports 22 pre-built API connections with endpoint allowlisting, per-caller access control, and real-time event ingestion — all configured through a single JSON file.
3
+ > **Alpha Software:** Expect breaking changes between updates.
4
4
 
5
- Drawlatch can run in two modes:
5
+ Drawlatch is a config-driven proxy that gives AI agents authenticated access to external APIs. Define your connections and secrets in a single config file — agents get structured, allowlisted access to 22 pre-built APIs without ever seeing your credentials.
6
6
 
7
- - **Remote mode** local proxy + remote server, with end-to-end encryption. Secrets never leave the remote server.
8
- - **Local mode** — imported as a library and called in-process (no server, no encryption). Secrets are on the same machine, but you get the same config-driven route resolution, endpoint allowlisting, and ingestor support.
7
+ **Using [Callboard](https://github.com/WolpertingerLabs/callboard)?** Drawlatch is built in Callboard manages connections, secrets, and agent identities through its UI. You don't need to set up drawlatch separately.
8
+
9
+ ## Key Features
10
+
11
+ - **22 pre-built connections** — GitHub, Slack, Discord, Stripe, Notion, Linear, OpenAI, and [more](CONNECTIONS.md)
12
+ - **Endpoint allowlisting** — agents can only reach explicitly configured URL patterns
13
+ - **Per-caller access control** — each agent identity sees only its assigned connections
14
+ - **Real-time event ingestion** — WebSocket, webhook, and polling listeners for incoming events ([details](INGESTORS.md))
15
+ - **Two operating modes** — remote (secrets on a separate server with E2EE) or local (in-process library)
9
16
 
10
17
  ## How It Works
11
18
 
12
- ### Remote Mode (Two-Component)
19
+ Drawlatch runs in two modes depending on your trust model:
13
20
 
14
- In remote mode, the system has two components:
21
+ ### Remote Mode Secrets Never Leave the Server
15
22
 
16
- 1. **Local MCP Proxy** — runs on your machine as a Claude Code MCP server (stdio transport). It holds **no secrets**. It encrypts requests and forwards them to the remote server.
17
- 2. **Remote Secure Server** — holds all secrets (API keys, tokens, etc.) and only communicates through encrypted channels after mutual authentication. It injects secrets into outgoing HTTP requests on the proxy's behalf.
23
+ The local MCP proxy holds no secrets. It encrypts requests and forwards them to a remote server that injects credentials and makes the actual API calls.
18
24
 
19
25
  ```
20
- ┌──────────────┐ Encrypted Channel ┌──────────────────┐ Authenticated ┌──────────────┐
21
- │ Claude Code │ ◄──── stdio ────► MCP │◄── HTTP + E2EE ──►│ Remote Server │──── HTTPS ───►│ External API │
22
- │ │ Proxy │ │ (holds secrets) │ │
23
- └──────────────┘ └──────────────────┘ └──────────────┘
24
- No secrets here Injects API keys,
25
- tokens, headers
26
+ ┌──────────────┐ ┌──────────────────┐ ┌──────────────┐
27
+ │ Claude Code │◄── stdio ──► MCP Proxy │◄── HTTP + E2EE ──► Remote Server │── HTTPS ────►│ External API │
28
+ │ │ (no secrets) │ │ (holds secrets) │ │
29
+ └──────────────┘ └──────────────────┘ └──────────────┘
26
30
  ```
27
31
 
28
- The crypto layer uses **Ed25519** signatures for authentication and **X25519 ECDH** for key exchange, deriving **AES-256-GCM** session keys — all built on Node.js native `crypto` with zero external crypto dependencies.
32
+ The crypto layer uses Ed25519 signatures for mutual authentication and X25519 ECDH to derive AES-256-GCM session keys — all built on Node.js native `crypto` with zero external dependencies.
29
33
 
30
- ### Local Mode (In-Process Library)
34
+ ### Local Mode In-Process Library
31
35
 
32
- In local mode, there is no separate server, no network port, and no encryption. Your application imports drawlatch's core functions directly and calls them in-process:
36
+ No server, no encryption. Your application imports drawlatch directly and calls the same `executeProxyRequest()` function the remote server uses. Secrets come from `process.env` on the same machine.
33
37
 
34
38
  ```
35
- ┌──────────────────────────────────────────┐ Authenticated ┌──────────────┐
36
- │ Your Application │──── HTTPS ───────────►│ External API │
37
- │ ┌──────────┐ in-process ┌────────┐ │ │ │
38
- │ │ Agent │◄── call ────►│ drawl. │ │ Reads secrets from └──────────────┘
39
- │ │ │ │ routes │ │ local env / config
39
+ ┌──────────────────────────────────────────┐ ┌──────────────┐
40
+ │ Your Application │── HTTPS ──────────►│ External API │
41
+ │ ┌──────────┐ in-process ┌────────┐ │ │ │
42
+ │ │ Agent │◄── call ──────►│ drawl. │ │ └──────────────┘
40
43
  │ └──────────┘ └────────┘ │
41
44
  └──────────────────────────────────────────┘
42
- Secrets are on the same machine
43
45
  ```
44
46
 
45
- **What you get in local mode:** The same config-driven route resolution, endpoint allowlisting, per-caller access control, connection templates, ingestor support (WebSocket, webhook, polling), and the exact same `executeProxyRequest()` function the remote server uses — no behavioral drift.
46
-
47
- **What you don't get:** Secret isolation from the agent. When running locally, secrets live in `process.env` on the same machine. The value proposition shifts from cryptographic secret hiding to **convenience and structured access** — a single config file managing many API connections with consistent patterns.
47
+ You still get config-driven route resolution, endpoint allowlisting, per-caller access control, and ingestor support just without cryptographic secret isolation.
48
48
 
49
- > **When to use which mode:** Use remote mode when you need to hide secrets from the machine running the agent (e.g., shared CI servers, untrusted environments). Use local mode when running on your own machine and you want the convenience of config-driven API management without the overhead of running a separate server.
49
+ > **When to use which:** Remote mode when secrets must be hidden from the agent's machine (shared servers, CI, untrusted environments). Local mode when running on your own machine and you want convenience without a separate server.
50
50
 
51
51
  ## Quick Start
52
52
 
53
- ### Option 1: Install as a Claude Code Plugin (Recommended)
54
-
55
- This repo is structured as a **Claude Code plugin** with a marketplace. Install it directly:
56
-
57
- ```shell
58
- # Add the marketplace (from a local clone)
59
- /plugin marketplace add ./path/to/drawlatch
60
-
61
- # Install the plugin
62
- /plugin install drawlatch@drawlatch
63
- ```
64
-
65
- Or load it directly during development:
66
-
67
- ```shell
68
- claude --plugin-dir ./path/to/drawlatch
69
- ```
70
-
71
- Before using, set the `MCP_CONFIG_DIR` environment variable so the proxy can find its config and keys:
53
+ Get from zero to working in three commands:
72
54
 
73
55
  ```bash
74
- export MCP_CONFIG_DIR=~/.drawlatch
75
- ```
56
+ # Install globally
57
+ npm install -g @wolpertingerlabs/drawlatch
76
58
 
77
- The plugin's MCP server starts automatically when enabled. The `secure_request` and `list_routes` tools become available immediately.
59
+ # Set up keys, config, and .env in one step
60
+ drawlatch init --connections github
78
61
 
79
- ### Option 2: Auto-Discovery (opening this repo directly)
62
+ # Set your API token (edit the file or run this)
63
+ echo "GITHUB_TOKEN=ghp_your_token_here" >> ~/.drawlatch/.env
80
64
 
81
- This repo includes a `.mcp.json` file at the root, so Claude Code **automatically discovers** the MCP proxy server when you open the project. On first launch, Claude Code will prompt you to approve the server — accept, and the `secure_request` and `list_routes` tools become available immediately.
65
+ # Start the remote server
66
+ drawlatch start
67
+ ```
82
68
 
83
- Before approving, set the `MCP_CONFIG_DIR` environment variable:
69
+ Verify your setup:
84
70
 
85
71
  ```bash
86
- export MCP_CONFIG_DIR=~/.drawlatch
72
+ drawlatch doctor # Validate full setup
73
+ drawlatch status # Check server is running
74
+ drawlatch config # View configuration and secret status
87
75
  ```
88
76
 
89
- The `.mcp.json` passes this through to the MCP server process. You also need a working setup (keys generated, public keys exchanged, configs in place, remote server running). See [Setup](#setup) below for the full walkthrough.
77
+ The `init` command generates keys, creates configs, exchanges public keys, and scaffolds the `.env` file. All steps are idempotent safe to re-run.
90
78
 
91
- > **Note:** Auto-discovery uses the `dist/mcp/server.js` entrypoint. The `dist/` directory is built automatically when you run `npm install` (via the `prepare` script). If you need to rebuild manually, run `npm run build`.
79
+ ### Connect to Claude Code
92
80
 
93
- ## Setup
81
+ **Option 1: Claude Code Plugin (Recommended)**
94
82
 
95
- ### Prerequisites
96
-
97
- ```bash
98
- git clone <repo-url>
99
- cd drawlatch
100
- npm install
101
- npm run build
83
+ ```shell
84
+ # Install the plugin
85
+ /plugin install drawlatch@drawlatch
102
86
  ```
103
87
 
104
- ### Directory Structure
88
+ The plugin's MCP server starts automatically. The proxy uses `~/.drawlatch/` by default — see [Advanced Configuration](#advanced-configuration) to use a custom path.
105
89
 
106
- All config and key files live inside `~/.drawlatch/` in the user's home directory by default. You can override this by setting the `MCP_CONFIG_DIR` environment variable.
90
+ **Option 2: Auto-Discovery**
107
91
 
108
- ```
109
- ~/.drawlatch/
110
- ├── proxy.config.json # Local proxy config
111
- ├── remote.config.json # Remote server config
112
- └── keys/
113
- ├── local/ # MCP proxy keypairs (one per alias)
114
- │ └── my-laptop/ # Alias-named subdirectory
115
- │ ├── signing.pub.pem # Ed25519 public key (share this)
116
- │ ├── signing.key.pem # Ed25519 private key (keep secret)
117
- │ ├── exchange.pub.pem # X25519 public key (share this)
118
- │ └── exchange.key.pem # X25519 private key (keep secret)
119
- ├── remote/ # Remote server keypair
120
- │ ├── signing.pub.pem
121
- │ ├── signing.key.pem
122
- │ ├── exchange.pub.pem
123
- │ └── exchange.key.pem
124
- └── peers/
125
- ├── alice/ # One subdirectory per caller
126
- │ ├── signing.pub.pem # Caller's public signing key
127
- │ └── exchange.pub.pem # Caller's public exchange key
128
- ├── bob/ # Another caller
129
- │ ├── signing.pub.pem
130
- │ └── exchange.pub.pem
131
- └── remote-server/ # Remote server's public keys (for proxy)
132
- ├── signing.pub.pem
133
- └── exchange.pub.pem
134
- ```
135
-
136
- ### Step 1: Generate Keys
92
+ This repo includes a `.mcp.json` file, so Claude Code automatically discovers the MCP proxy when you open the project. Approve the server when prompted.
137
93
 
138
- Generate keypairs for both the local proxy and the remote server:
94
+ **Option 3: Manual Registration**
139
95
 
140
96
  ```bash
141
- # Generate local MCP proxy keypair (with alias)
142
- npm run generate-keys -- local my-laptop
143
-
144
- # Or use the default alias
145
- npm run generate-keys -- local
146
-
147
- # Generate remote server keypair
148
- npm run generate-keys -- remote
97
+ claude mcp add drawlatch \
98
+ -e MCP_CONFIG_DIR=~/.drawlatch \
99
+ -- node /path/to/drawlatch/dist/mcp/server.js
149
100
  ```
150
101
 
151
- Each command creates four PEM files (Ed25519 signing + X25519 exchange, public + private) in the appropriate directory under `~/.drawlatch/keys/`. Local keys are stored under `keys/local/<alias>/` the alias defaults to `"default"` if omitted.
102
+ > **Note:** Auto-discovery and manual registration use `dist/mcp/server.js`. The `dist/` directory is built automatically via `npm install` (prepare script). Rebuild manually with `npm run build` if needed.
152
103
 
153
- > **Multiple identities:** Generate multiple local keypairs using different aliases (e.g., `my-laptop`, `ci-server`). Set `MCP_KEY_ALIAS` per agent at spawn time or use `localKeyAlias` in `proxy.config.json` to select which identity the proxy uses. The alias directory name should match the caller alias in the remote server's config.
104
+ ### Manual Setup
154
105
 
155
- You can also generate keys to a custom directory:
106
+ For custom setups (different aliases, multiple callers, different machines), you can configure everything manually instead of using `drawlatch init`.
156
107
 
157
- ```bash
158
- npm run generate-keys -- --dir /path/to/custom/keys
159
- ```
160
-
161
- Or inspect the fingerprint of an existing keypair:
108
+ **1. Generate keys:**
162
109
 
163
110
  ```bash
164
- npm run generate-keys -- show ~/.drawlatch/keys/local/my-laptop
111
+ drawlatch generate-keys caller my-laptop
112
+ drawlatch generate-keys server
165
113
  ```
166
114
 
167
- ### Step 2: Exchange Public Keys
115
+ **2. Exchange public keys** — on separate machines, copy `*.pub.pem` files to the matching `keys/callers/<alias>/` or `keys/server/` directory on the other machine. See [Key Exchange](#key-exchange) for details.
168
116
 
169
- The local proxy and remote server need each other's public keys for mutual authentication. Copy the **public** key files (`.pub.pem` only — never share private keys):
170
-
171
- **From local to remote** — copy the proxy's public keys into a caller directory on the remote server. Since local keys are now stored per-alias, the alias directory name naturally matches the peer directory:
117
+ **3. Create configs** copy the example files and edit:
172
118
 
173
119
  ```bash
174
- mkdir -p ~/.drawlatch/keys/peers/my-laptop
175
-
176
- cp ~/.drawlatch/keys/local/my-laptop/signing.pub.pem \
177
- ~/.drawlatch/keys/peers/my-laptop/signing.pub.pem
178
-
179
- cp ~/.drawlatch/keys/local/my-laptop/exchange.pub.pem \
180
- ~/.drawlatch/keys/peers/my-laptop/exchange.pub.pem
120
+ cp remote.config.example.json ~/.drawlatch/remote.config.json
121
+ cp proxy.config.example.json ~/.drawlatch/proxy.config.json
181
122
  ```
182
123
 
183
- **From remote to local** copy the remote server's public keys into the proxy's peer directory:
124
+ **4. Create a `.env` file** with your API secrets:
184
125
 
185
126
  ```bash
186
- mkdir -p ~/.drawlatch/keys/peers/remote-server
187
-
188
- cp ~/.drawlatch/keys/remote/signing.pub.pem \
189
- ~/.drawlatch/keys/peers/remote-server/signing.pub.pem
190
-
191
- cp ~/.drawlatch/keys/remote/exchange.pub.pem \
192
- ~/.drawlatch/keys/peers/remote-server/exchange.pub.pem
127
+ cat > ~/.drawlatch/.env << 'EOF'
128
+ # GITHUB_TOKEN=ghp_your_token_here
129
+ # DISCORD_BOT_TOKEN=your_bot_token_here
130
+ EOF
193
131
  ```
194
132
 
195
- > **Tip:** If the proxy and remote server are on different machines, securely transfer only the `*.pub.pem` files (e.g., via `scp`). Each caller gets its own subdirectory under the peers directory — the directory name becomes the caller's alias used in the remote config and audit logs.
196
-
197
- ### Step 3: Create the Local Proxy Config
198
-
199
- Copy the example and edit the paths to match your setup:
133
+ **5. Start the server:**
200
134
 
201
135
  ```bash
202
- cp proxy.config.example.json ~/.drawlatch/proxy.config.json
136
+ drawlatch start
137
+ drawlatch doctor # Validate full setup
203
138
  ```
204
139
 
205
- Edit `~/.drawlatch/proxy.config.json`:
206
-
207
- ```json
208
- {
209
- "remoteUrl": "http://127.0.0.1:9999",
210
- "localKeyAlias": "my-laptop",
211
- "remotePublicKeysDir": "~/.drawlatch/keys/peers/remote-server",
212
- "connectTimeout": 10000,
213
- "requestTimeout": 30000
214
- }
215
- ```
216
-
217
- | Field | Description | Default |
218
- | --------------------- | ----------------------------------------------------------------------------------------------- | ------------------------------------- |
219
- | `remoteUrl` | URL of the remote secure server | `http://localhost:9999` |
220
- | `localKeyAlias` | Key alias — resolved to `keys/local/<alias>/`. Overridden by `MCP_KEY_ALIAS` env var at runtime | _(none)_ |
221
- | `localKeysDir` | Absolute path to the proxy's own keypair directory. Ignored when `localKeyAlias` is set | `~/.drawlatch/keys/local/default` |
222
- | `remotePublicKeysDir` | Absolute path to the remote server's public keys | `~/.drawlatch/keys/peers/remote-server` |
223
- | `connectTimeout` | Handshake timeout in milliseconds | `10000` (10s) |
224
- | `requestTimeout` | Request timeout in milliseconds | `30000` (30s) |
225
-
226
- **Alias resolution priority:**
227
-
228
- 1. `MCP_KEY_ALIAS` env var (highest — set per agent at spawn time)
229
- 2. `localKeyAlias` in `proxy.config.json`
230
- 3. `localKeysDir` in `proxy.config.json` (explicit full path for custom deployments)
231
- 4. Default: `keys/local/default`
232
-
233
- ### Step 4: Create the Remote Server Config
234
-
235
- Copy the example and edit it to match your setup:
140
+ ## MCP Tools
236
141
 
237
- ```bash
238
- cp remote.config.example.json ~/.drawlatch/remote.config.json
239
- ```
142
+ Once connected, agents get these tools:
240
143
 
241
- Edit `~/.drawlatch/remote.config.json`. This is where you define your callers, their connections, custom connectors, and secrets.
144
+ | Tool | Description |
145
+ |------|-------------|
146
+ | `secure_request` | Make authenticated HTTP requests. Route-level headers (auth tokens, API keys) are injected automatically — the agent never sees secret values. Supports JSON and multipart/form-data file uploads. |
147
+ | `list_routes` | Discover available APIs with metadata, docs links, allowed endpoints, and available secret placeholders. |
148
+ | `poll_events` | Retrieve buffered events from ingestors (Discord messages, GitHub webhooks, etc.) with cursor-based pagination. |
149
+ | `ingestor_status` | Get connection state, buffer sizes, event counts, and errors for all active ingestors. |
150
+ | `test_connection` | Verify API credentials with a pre-configured read-only request. |
151
+ | `control_listener` | Start, stop, or restart an event listener. |
152
+ | `list_listener_configs` | Get configurable fields for event listeners. |
153
+ | `set_listener_params` | Configure listener parameters (filters, buffer sizes, etc.). |
154
+ | `get_listener_params` | Read current listener parameter overrides. |
155
+ | `resolve_listener_options` | Fetch dynamic options for listener config fields (e.g., list of Trello boards). |
156
+ | `list_listener_instances` | List instances of a multi-instance listener. |
157
+ | `delete_listener_instance` | Remove a multi-instance listener instance. |
158
+ | `test_ingestor` | Test event listener configuration and credentials. |
242
159
 
243
- The config is **caller-centric** — each caller is identified by their public key and explicitly declares which connections they can access.
160
+ ## Configuration Reference
244
161
 
245
- #### Example: Single caller with a built-in connection
162
+ ### Remote Server Config (`remote.config.json`)
246
163
 
247
164
  ```json
248
165
  {
249
166
  "host": "0.0.0.0",
250
167
  "port": 9999,
251
- "localKeysDir": "~/.drawlatch/keys/remote",
252
- "callers": {
253
- "my-laptop": {
254
- "name": "Personal Laptop",
255
- "peerKeyDir": "~/.drawlatch/keys/peers/my-laptop",
256
- "connections": ["github"]
257
- }
258
- },
168
+ "connectors": [],
169
+ "callers": {},
259
170
  "rateLimitPerMinute": 60
260
171
  }
261
172
  ```
262
173
 
263
- Set the `GITHUB_TOKEN` environment variable on the remote server and the built-in `github` connection template handles everything else — endpoint patterns, auth headers, docs URLs, and OpenAPI specs.
174
+ | Field | Description | Default |
175
+ |-------|-------------|---------|
176
+ | `host` | Network interface to bind | `127.0.0.1` |
177
+ | `port` | Listen port | `9999` |
178
+ | `connectors` | Custom connector definitions (see below) | `[]` |
179
+ | `callers` | Per-caller access control (see below) | `{}` |
180
+ | `rateLimitPerMinute` | Max requests per minute per session | `60` |
264
181
 
265
- #### Example: Multiple callers with different access levels
182
+ Server keys are always loaded from `keys/server/` inside the config directory.
183
+
184
+ ### Callers
185
+
186
+ Each caller is identified by their public key and declares which connections they can access:
266
187
 
267
188
  ```json
268
189
  {
269
- "host": "0.0.0.0",
270
- "port": 9999,
271
- "localKeysDir": "~/.drawlatch/keys/remote",
272
- "connectors": [
273
- {
274
- "alias": "internal-api",
275
- "name": "Internal Admin API",
276
- "headers": { "Authorization": "Bearer ${ADMIN_KEY}" },
277
- "secrets": { "ADMIN_KEY": "${INTERNAL_ADMIN_KEY}" },
278
- "allowedEndpoints": ["https://admin.internal.com/**"]
279
- }
280
- ],
281
190
  "callers": {
282
191
  "alice": {
283
192
  "name": "Alice (senior engineer)",
284
- "peerKeyDir": "/keys/peers/alice",
285
- "connections": ["github", "stripe", "internal-api"]
193
+ "connections": ["github", "stripe", "internal-api"],
194
+ "env": {
195
+ "GITHUB_TOKEN": "${ALICE_GITHUB_TOKEN}"
196
+ }
286
197
  },
287
198
  "ci-server": {
288
199
  "name": "GitHub Actions CI",
289
- "peerKeyDir": "/keys/peers/ci-server",
290
200
  "connections": ["github"]
291
201
  }
292
- },
293
- "rateLimitPerMinute": 60
202
+ }
294
203
  }
295
204
  ```
296
205
 
297
- Alice gets access to GitHub, Stripe, and the internal API. The CI server only gets GitHub. Each caller is isolated they only see the routes for their declared connections.
206
+ Caller public keys are loaded automatically from `keys/callers/<alias>/`no path configuration needed.
207
+
208
+ | Field | Required | Description |
209
+ |-------|----------|-------------|
210
+ | `connections` | Yes | Array of connection names (built-in or custom connector aliases) |
211
+ | `name` | No | Human-readable name for audit logs |
212
+ | `env` | No | Per-caller env var overrides — redirect secret resolution per caller |
213
+ | `ingestorOverrides` | No | Per-caller ingestor config overrides ([details](INGESTORS.md#caller-level-ingestor-overrides)) |
298
214
 
299
- #### Example: Per-caller env overrides (shared connector, different credentials)
215
+ The `env` map lets multiple callers share the same connection with different credentials:
216
+ - Keys are the env var names connectors reference (e.g., `GITHUB_TOKEN`)
217
+ - Values are `"${REAL_ENV_VAR}"` (redirect) or literal strings (direct injection)
218
+ - Checked before prefixed env vars during secret resolution
300
219
 
301
- When multiple callers use the same connection but need different credentials, use the `env` field to redirect environment variable resolution per caller:
220
+ Without an explicit `env` mapping, secrets resolve via prefixed env vars (e.g., caller "alice" + `GITHUB_TOKEN` `ALICE_GITHUB_TOKEN`).
221
+
222
+ ### Custom Connectors
223
+
224
+ Define reusable route templates for APIs not covered by built-in connections:
302
225
 
303
226
  ```json
304
227
  {
305
- "host": "0.0.0.0",
306
- "port": 9999,
307
- "localKeysDir": "/keys/server",
308
- "callers": {
309
- "alice": {
310
- "name": "Alice",
311
- "peerKeyDir": "/keys/peers/alice",
312
- "connections": ["github"],
313
- "env": {
314
- "GITHUB_TOKEN": "${ALICE_GITHUB_TOKEN}"
315
- }
316
- },
317
- "bob": {
318
- "name": "Bob",
319
- "peerKeyDir": "/keys/peers/bob",
320
- "connections": ["github", "stripe"],
321
- "env": {
322
- "GITHUB_TOKEN": "${BOB_GITHUB_TOKEN}",
323
- "STRIPE_SECRET_KEY": "sk_test_bob_dev_key"
324
- }
228
+ "connectors": [
229
+ {
230
+ "alias": "internal-api",
231
+ "name": "Internal Admin API",
232
+ "allowedEndpoints": ["https://admin.internal.com/**"],
233
+ "headers": { "Authorization": "Bearer ${ADMIN_KEY}" },
234
+ "secrets": { "ADMIN_KEY": "${INTERNAL_ADMIN_KEY}" }
325
235
  }
326
- },
327
- "rateLimitPerMinute": 60
236
+ ]
328
237
  }
329
238
  ```
330
239
 
331
- The `env` map works as follows:
332
-
333
- - **Keys** are the env var names that connectors reference (e.g., `GITHUB_TOKEN`)
334
- - **Values** are either `"${REAL_ENV_VAR}"` (redirect to a different env var) or a literal string (direct injection)
335
- - When resolving secrets, the caller's `env` is checked **before** `process.env`
336
-
337
- In this example, both Alice and Bob use the same built-in `github` connection, but Alice's requests use `process.env.ALICE_GITHUB_TOKEN` while Bob's use `process.env.BOB_GITHUB_TOKEN`. Bob also gets a hardcoded Stripe test key without needing an env var.
338
-
339
- #### Remote Config Reference
240
+ | Field | Required | Description |
241
+ |-------|----------|-------------|
242
+ | `alias` | Yes | Unique name for referencing from caller `connections` lists |
243
+ | `allowedEndpoints` | Yes | Glob patterns for allowed URLs |
244
+ | `name` | No | Human-readable name |
245
+ | `description` | No | Short description |
246
+ | `docsUrl` | No | URL to API documentation |
247
+ | `headers` | No | Headers to auto-inject (`${VAR}` placeholders resolved from `secrets`) |
248
+ | `secrets` | No | Key-value pairs — literal strings or `${ENV_VAR}` references |
249
+ | `resolveSecretsInBody` | No | Resolve `${VAR}` in request bodies (default: `false`) |
340
250
 
341
- | Field | Description | Default |
342
- | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ |
343
- | `host` | Network interface to bind to. Use `0.0.0.0` for all interfaces or `127.0.0.1` for local only | `127.0.0.1` |
344
- | `port` | Port to listen on | `9999` |
345
- | `localKeysDir` | Absolute path to the remote server's own keypair | `~/.drawlatch/keys/remote` |
346
- | `connectors` | Array of custom connector definitions, each with an `alias` for referencing from callers (see [Connector Definition](#connector-definition)) | `[]` |
347
- | `callers` | Per-caller access control. Keys are caller aliases used in audit logs (see [Caller Definition](#caller-definition)) | `{}` |
348
- | `rateLimitPerMinute` | Max requests per minute per session | `60` |
251
+ Custom connectors with an `alias` matching a built-in connection name take precedence.
349
252
 
350
- #### Connector Definition
253
+ ### Proxy Config (`proxy.config.json`)
351
254
 
352
- Custom connectors define reusable route templates referenced by `alias` from caller connection lists. They follow the same structure as routes:
353
-
354
- | Field | Required | Description |
355
- | ---------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------ |
356
- | `alias` | Yes | Unique name for referencing this connector from caller `connections` lists |
357
- | `allowedEndpoints` | Yes | Array of glob patterns for allowed URLs (e.g., `https://api.example.com/**`) |
358
- | `name` | No | Human-readable name (e.g., `"Internal Admin API"`) |
359
- | `description` | No | Short description of what the connector provides |
360
- | `docsUrl` | No | URL to API documentation |
361
- | `openApiUrl` | No | URL to OpenAPI/Swagger spec |
362
- | `headers` | No | Headers to auto-inject. Values may contain `${VAR}` placeholders resolved from `secrets` |
363
- | `secrets` | No | Key-value pairs. Values can be literal strings or `${ENV_VAR}` references resolved from environment variables at startup |
364
- | `resolveSecretsInBody` | No | Whether to resolve `${VAR}` placeholders in request bodies. Default: `false` |
365
-
366
- #### Caller Definition
367
-
368
- | Field | Required | Description |
369
- | ------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
370
- | `peerKeyDir` | Yes | Path to this caller's public key files (`signing.pub.pem` + `exchange.pub.pem`) |
371
- | `connections` | Yes | Array of connection names — references built-in templates (e.g., `"github"`) or custom connector aliases |
372
- | `name` | No | Human-readable name for audit logs |
373
- | `env` | No | Per-caller environment variable overrides (see [env overrides example](#example-per-caller-env-overrides-shared-connector-different-credentials)) |
374
- | `ingestorOverrides` | No | Per-caller ingestor config overrides keyed by connection alias. Override event filters, buffer sizes, intents, or disable ingestors entirely. See **[INGESTORS.md](INGESTORS.md#caller-level-ingestor-overrides)** for full reference |
375
-
376
- #### How Secrets Work
377
-
378
- Secret values in the `secrets` map are resolved at session establishment time (per-caller):
379
-
380
- - **Literal values** — used as-is: `"API_TOKEN": "sk_live_abc123"`
381
- - **Environment variable references** — resolved from the server's environment: `"API_TOKEN": "${API_TOKEN}"`
382
- - **Per-caller overrides** — when a caller has an `env` entry for a variable name, that value is used instead of `process.env`
383
-
384
- Header values can reference secrets using `${VAR}` placeholders:
255
+ Used by the local MCP proxy to connect to the remote server:
385
256
 
386
257
  ```json
387
- "headers": {
388
- "Authorization": "Bearer ${API_TOKEN}"
258
+ {
259
+ "remoteUrl": "http://127.0.0.1:9999",
260
+ "connectTimeout": 10000,
261
+ "requestTimeout": 30000
389
262
  }
390
263
  ```
391
264
 
392
- The placeholder `${API_TOKEN}` is resolved against the route's resolved `secrets` map. This means the actual secret value is never exposed to the local proxy or Claude Code — it only exists on the remote server.
265
+ | Field | Description | Default |
266
+ |-------|-------------|---------|
267
+ | `remoteUrl` | URL of the remote server | `http://localhost:9999` |
268
+ | `connectTimeout` | Handshake timeout (ms) | `10000` |
269
+ | `requestTimeout` | Request timeout (ms) | `30000` |
393
270
 
394
- ### Connections (Pre-built Route Templates)
271
+ Key paths are derived automatically — no configuration needed:
272
+ - Caller keys: `keys/callers/{MCP_KEY_ALIAS || "default"}/`
273
+ - Server public keys: `keys/server/`
395
274
 
396
- Instead of manually configuring connectors for popular APIs, you can use **connections** — pre-built route templates that ship with the package (`github`, `stripe`, `openai`, etc.). Reference them by name in a caller's `connections` list:
275
+ ### Advanced Configuration
397
276
 
398
- ```json
399
- {
400
- "callers": {
401
- "my-laptop": {
402
- "peerKeyDir": "/keys/peers/my-laptop",
403
- "connections": ["github", "stripe"]
404
- }
405
- }
406
- }
407
- ```
277
+ #### `MCP_CONFIG_DIR`
408
278
 
409
- Set the required environment variables (e.g., `GITHUB_TOKEN`, `STRIPE_SECRET_KEY`) and the connection templates handle endpoint patterns, auth headers, docs URLs, and OpenAPI specs automatically. Custom connectors with a matching `alias` take precedence over built-in templates.
279
+ By default, all config and key files live in `~/.drawlatch/`. Override with:
410
280
 
411
- See **[CONNECTIONS.md](CONNECTIONS.md)** for the full list of available connections, required environment variables, and usage examples.
281
+ ```bash
282
+ export MCP_CONFIG_DIR=/custom/path/to/config
283
+ ```
412
284
 
413
- ### Step 5: Start the Servers
285
+ Useful for CI environments or running multiple independent setups on the same machine.
414
286
 
415
- **Start the remote server:**
287
+ ## Connections
416
288
 
417
- ```bash
418
- # Development (with hot reload via tsx)
419
- npm run dev:remote
289
+ 22 pre-built connection templates ship with drawlatch. Reference them by name in a caller's `connections` list:
420
290
 
421
- # Production (requires `npm run build` first)
422
- npm run start:remote
423
- ```
291
+ | Connection | API | Required Env Var(s) |
292
+ |------------|-----|---------------------|
293
+ | `anthropic` | Anthropic Claude API | `ANTHROPIC_API_KEY` |
294
+ | `bluesky` | Bluesky (AT Protocol) | `BLUESKY_ACCESS_TOKEN` |
295
+ | `devin` | Devin AI API | `DEVIN_API_KEY` |
296
+ | `discord-bot` | Discord Bot API | `DISCORD_BOT_TOKEN` |
297
+ | `discord-oauth` | Discord OAuth2 API | `DISCORD_OAUTH_TOKEN` |
298
+ | `github` | GitHub REST API | `GITHUB_TOKEN` |
299
+ | `google` | Google Workspace APIs | `GOOGLE_API_TOKEN` |
300
+ | `google-ai` | Google AI (Gemini) | `GOOGLE_AI_API_KEY` |
301
+ | `hex` | Hex API | `HEX_TOKEN` |
302
+ | `lichess` | Lichess API | `LICHESS_API_TOKEN` |
303
+ | `linear` | Linear GraphQL API | `LINEAR_API_KEY` |
304
+ | `mastodon` | Mastodon API | `MASTODON_ACCESS_TOKEN` |
305
+ | `notion` | Notion API | `NOTION_API_KEY` |
306
+ | `openai` | OpenAI API | `OPENAI_API_KEY` |
307
+ | `openrouter` | OpenRouter API | `OPENROUTER_API_KEY` |
308
+ | `reddit` | Reddit API | `REDDIT_ACCESS_TOKEN` |
309
+ | `slack` | Slack Web API | `SLACK_BOT_TOKEN` |
310
+ | `stripe` | Stripe Payments API | `STRIPE_SECRET_KEY` |
311
+ | `telegram` | Telegram Bot API | `TELEGRAM_BOT_TOKEN` |
312
+ | `trello` | Trello API | `TRELLO_API_KEY`, `TRELLO_TOKEN` |
313
+ | `twitch` | Twitch Helix API | `TWITCH_ACCESS_TOKEN`, `TWITCH_CLIENT_ID` |
314
+ | `x` | X (Twitter) API v2 | `X_BEARER_TOKEN` |
424
315
 
425
- **Connect the local MCP proxy to Claude Code:**
316
+ See **[CONNECTIONS.md](CONNECTIONS.md)** for auth details, optional env vars, and usage notes per connection.
426
317
 
427
- The repo includes a `.mcp.json` at the root, so Claude Code auto-discovers the proxy when you open the project directory. Just approve the server when prompted — no manual registration needed.
318
+ ## Event Ingestion
428
319
 
429
- The `.mcp.json` requires the `MCP_CONFIG_DIR` environment variable to be set so the proxy can locate its config and keys. Set it to the absolute path of your `~/.drawlatch/` directory:
320
+ Drawlatch can collect real-time events from external services and buffer them for agents to poll. Three ingestor types are supported:
430
321
 
431
- ```bash
432
- export MCP_CONFIG_DIR=~/.drawlatch
433
- ```
322
+ | Type | How It Works | Connections |
323
+ |------|-------------|-------------|
324
+ | **WebSocket** | Persistent connections to event gateways | Discord Gateway, Slack Socket Mode |
325
+ | **Webhook** | HTTP receivers with signature verification | GitHub, Stripe, Trello |
326
+ | **Poll** | Interval-based HTTP requests | Notion, Linear, Reddit, X, Bluesky, Mastodon, Telegram, Twitch |
434
327
 
435
- **Alternative: manual registration**
328
+ Events are stored in per-caller ring buffers (default 200, max 1000) with monotonic IDs for cursor-based pagination. Agents retrieve events via `poll_events` and check status via `ingestor_status`.
436
329
 
437
- If you prefer not to use auto-discovery, register the MCP server directly:
330
+ For webhook ingestors, the remote server must be publicly accessible (or behind a tunnel). Use `drawlatch start --tunnel` to automatically start a Cloudflare tunnel.
438
331
 
439
- ```bash
440
- claude mcp add secure-proxy \
441
- --transport stdio --scope local \
442
- -e MCP_CONFIG_DIR=~/.drawlatch \
443
- -- node /absolute/path/to/drawlatch/dist/mcp/server.js
444
- ```
332
+ See **[INGESTORS.md](INGESTORS.md)** for full configuration reference.
445
333
 
446
- After connecting (either via auto-discovery or manual registration), the proxy will automatically perform the encrypted handshake with the remote server on first use.
334
+ ## Key Exchange
447
335
 
448
- ### Step 6: Webhook Endpoints (Optional)
336
+ Remote mode requires mutual authentication via Ed25519/X25519 keypairs. Each identity gets four PEM files (signing + exchange, public + private). The `drawlatch init` command handles this automatically for single-machine setups.
449
337
 
450
- If any of your connections use webhook ingestors (e.g., GitHub, Stripe, Trello), the remote server automatically exposes `POST /webhooks/:path` routes on the same port. External services send webhook POSTs to these endpoints, and the server verifies signatures, buffers events in per-caller ring buffers, and makes them available via `poll_events`.
338
+ **Directory structure:**
451
339
 
452
- **Setup:**
340
+ ```
341
+ ~/.drawlatch/keys/
342
+ ├── callers/
343
+ │ ├── default/ # Default caller keypair
344
+ │ └── alice/ # Additional caller keypair
345
+ └── server/ # Server keypair
346
+ ```
453
347
 
454
- 1. The remote server must be **publicly accessible** for webhook delivery (or behind a tunnel like [ngrok](https://ngrok.com/) or [Cloudflare Tunnel](https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/))
455
- 2. Point the external service's webhook URL to `https://<your-server>/webhooks/<path>` (e.g., `https://example.com/webhooks/github`)
456
- 3. Set the webhook signing secret as an environment variable on the remote server (e.g., `GITHUB_WEBHOOK_SECRET`, `STRIPE_WEBHOOK_SECRET`)
348
+ Both sides (caller and server) store their keys in the same directory tree. On a single machine, `drawlatch init` generates both and they can authenticate immediately. On separate machines, copy the `*.pub.pem` files to the corresponding directory on the other machine.
457
349
 
458
- The webhook path is configured in each connection template's `ingestor.webhook.path` field. See **[INGESTORS.md](INGESTORS.md)** for full details on webhook, WebSocket, and poll ingestors.
350
+ **Using [Callboard](https://github.com/WolpertingerLabs/callboard)?** Use `drawlatch sync` to exchange keys automatically via a double-code approval flow no manual file copying needed.
459
351
 
460
- ### Multiple Agents (Multi-Identity)
352
+ ### Multiple Agent Identities
461
353
 
462
- When multiple agents share the same machine, each needs its own key identity. Generate a keypair per agent:
354
+ Generate a keypair per agent and set `MCP_KEY_ALIAS` at spawn time:
463
355
 
464
356
  ```bash
465
- npm run generate-keys -- local alice
466
- npm run generate-keys -- local bob
357
+ drawlatch generate-keys caller alice
358
+ drawlatch generate-keys caller bob
467
359
  ```
468
360
 
469
- Each agent's MCP server config specifies its alias via the `MCP_KEY_ALIAS` env var:
470
-
471
361
  ```json
472
362
  {
473
363
  "mcpServers": {
474
- "secure-proxy": {
364
+ "drawlatch": {
475
365
  "command": "node",
476
366
  "args": ["dist/mcp/server.js"],
477
- "env": {
478
- "MCP_CONFIG_DIR": "~/.drawlatch",
479
- "MCP_KEY_ALIAS": "alice"
480
- }
367
+ "env": { "MCP_CONFIG_DIR": "~/.drawlatch", "MCP_KEY_ALIAS": "alice" }
481
368
  }
482
369
  }
483
370
  }
484
371
  ```
485
372
 
486
- The proxy auto-resolves `MCP_KEY_ALIAS=alice` to `keys/local/alice/`. On the remote server, register each agent as a separate caller with matching alias directories under `keys/peers/`.
373
+ Register each agent as a separate caller in `remote.config.json`.
487
374
 
488
- ## MCP Tools
489
-
490
- Once connected, Claude Code gets access to four tools:
491
-
492
- ### `secure_request`
493
-
494
- Make an authenticated HTTP request through the proxy. Route-level headers (e.g., `Authorization`) are injected automatically — the agent never sees the secret values.
495
-
496
- ```
497
- method: GET | POST | PUT | PATCH | DELETE
498
- url: Full URL (may contain ${VAR} placeholders)
499
- headers: Optional additional headers
500
- body: Optional request body
501
- ```
502
-
503
- ### `list_routes`
504
-
505
- List all available routes for the current caller. Returns metadata (name, description, docs link), allowed endpoint patterns, available secret placeholder names (not values), and auto-injected header names. Different callers may see different routes based on their `connections` configuration.
506
-
507
- ### `poll_events`
508
-
509
- Poll for new events from ingestors (Discord messages, GitHub webhooks, Notion updates, etc.). Returns events received since the given cursor.
375
+ ## CLI Reference
510
376
 
511
377
  ```
512
- connection: Optional — filter by connection alias (e.g., "discord-bot"), omit for all
513
- after_id: Optional — cursor; returns events with id > after_id
514
- ```
515
-
516
- Pass `after_id` from the last event you received to get only new events. Omit to get all buffered events. See **[INGESTORS.md](INGESTORS.md)** for details on configuring event sources.
517
-
518
- ### `ingestor_status`
378
+ drawlatch [command] [options]
519
379
 
520
- Get the status of all active ingestors for the current caller. Returns connection state, buffer sizes, event counts, and any errors. Takes no parameters.
380
+ Commands:
381
+ init Set up drawlatch (keys, config, .env) in one step
382
+ start Start the remote server (background daemon)
383
+ stop Stop the remote server
384
+ restart Restart the remote server
385
+ status Show server status (PID, port, uptime, health, sessions)
386
+ logs View server logs
387
+ config Show effective configuration and secret status
388
+ doctor Validate setup and diagnose issues
389
+ generate-keys Generate Ed25519 + X25519 keypairs
390
+ sync Exchange keys with a callboard instance
521
391
 
522
- ## Library Usage (Local Mode)
392
+ Options:
393
+ -h, --help Show help
394
+ -v, --version Show version
523
395
 
524
- Drawlatch can be imported as a library for in-process use — no separate server, no encryption overhead. The `package.json` exports map provides clean entry points:
396
+ Init options:
397
+ --connections <list> Comma-separated connections to enable (e.g., github,slack)
398
+ --alias <name> Caller alias (default: "default")
525
399
 
526
- ```typescript
527
- // Core request execution (same function the remote server uses)
528
- import { executeProxyRequest } from "drawlatch/remote/server";
400
+ Start options:
401
+ -f, --foreground Run in foreground
402
+ -t, --tunnel Start a Cloudflare tunnel for webhooks
403
+ --port <number> Override configured port
404
+ --host <address> Override configured host
529
405
 
530
- // Config loading and route resolution
531
- import {
532
- loadRemoteConfig,
533
- resolveCallerRoutes,
534
- resolveRoutes,
535
- resolveSecrets,
536
- } from "drawlatch/shared/config";
406
+ Logs options:
407
+ -n, --lines <num> Number of lines (default: 50)
408
+ --follow Tail the log output
537
409
 
538
- // Ingestor management (WebSocket, webhook, poll)
539
- import { IngestorManager } from "drawlatch/remote/ingestors";
410
+ Generate-keys subcommands:
411
+ caller [alias] Generate caller keypair (default alias: "default")
412
+ server Generate server keypair
413
+ show <path> Show fingerprint of existing keypair
414
+ --dir <path> Generate to custom directory
540
415
 
541
- // Crypto primitives (if building custom transport)
542
- import { loadKeyBundle, loadPublicKeys, EncryptedChannel } from "drawlatch/shared/crypto";
416
+ Sync options:
417
+ --ttl <seconds> Session timeout (default: 300)
543
418
  ```
544
419
 
545
- ### Available Exports
546
-
547
- | Export Path | Description |
548
- | ---------------------------- | ------------------------------------------------------------------ |
549
- | `drawlatch` | MCP proxy server (stdio transport) — the default entry point |
550
- | `drawlatch/remote/server` | Remote server functions including `executeProxyRequest()` |
551
- | `drawlatch/remote/ingestors` | `IngestorManager` and all ingestor types |
552
- | `drawlatch/shared/config` | Config loading, caller/route resolution, secret resolution |
553
- | `drawlatch/shared/connections`| Connection template loading |
554
- | `drawlatch/shared/crypto` | Key generation, encrypted channel, key serialization |
555
- | `drawlatch/shared/protocol` | Handshake protocol, message types |
420
+ ## Library Usage (Local Mode)
556
421
 
557
- ### Example: In-Process Proxy
422
+ Import drawlatch directly for in-process use — no server, no encryption:
558
423
 
559
424
  ```typescript
560
425
  import { loadRemoteConfig, resolveCallerRoutes, resolveRoutes, resolveSecrets } from "drawlatch/shared/config";
561
426
  import { executeProxyRequest } from "drawlatch/remote/server";
562
427
 
563
- // Load config and resolve routes for a specific caller
564
428
  const config = loadRemoteConfig();
565
429
  const callerRoutes = resolveCallerRoutes(config, "my-laptop");
566
430
  const callerEnv = resolveSecrets(config.callers["my-laptop"]?.env ?? {});
567
431
  const routes = resolveRoutes(callerRoutes, callerEnv);
568
432
 
569
- // Make a request — same function the remote server uses
570
433
  const result = await executeProxyRequest(
571
434
  { method: "GET", url: "https://api.github.com/user" },
572
435
  routes,
573
436
  );
574
437
  ```
575
438
 
576
- > **Note:** In local mode, secrets are resolved from `process.env` on the same machine. The encryption layer is not used. See [How It Works → Local Mode](#local-mode-in-process-library) for the security tradeoff.
577
-
578
- ## Development
439
+ ### Available Exports
579
440
 
580
- ```bash
581
- # Run tests
582
- npm test
441
+ | Export Path | Description |
442
+ |-------------|-------------|
443
+ | `drawlatch` | MCP proxy server (stdio transport) |
444
+ | `drawlatch/remote/server` | `executeProxyRequest()` and server functions |
445
+ | `drawlatch/remote/ingestors` | `IngestorManager` and ingestor types |
446
+ | `drawlatch/shared/config` | Config loading, route/secret resolution |
447
+ | `drawlatch/shared/connections` | Connection template loading |
448
+ | `drawlatch/shared/env-utils` | Environment variable and secret utilities |
449
+ | `drawlatch/shared/crypto` | Key generation, encrypted channel |
450
+ | `drawlatch/shared/protocol` | Handshake protocol, message types |
583
451
 
584
- # Run tests in watch mode
585
- npm run test:watch
452
+ ## Security Model
586
453
 
587
- # Run tests with coverage
588
- npm run test:coverage
454
+ ### Both Modes
589
455
 
590
- # Lint
591
- npm run lint
592
- npm run lint:fix
456
+ - **Endpoint allowlisting** — requests only proxied to explicitly configured URL patterns
457
+ - **Per-caller access control** — each caller only sees their assigned connections
458
+ - **Per-caller credential isolation** — same connector, different credentials via `env` overrides
459
+ - **Rate limiting** — configurable per-session (default: 60/min)
460
+ - **Audit logging** — all operations logged with caller identity, session ID, timestamps
593
461
 
594
- # Format
595
- npm run format
596
- npm run format:check
597
- ```
462
+ ### Remote Mode Only
598
463
 
599
- ## Architecture
464
+ - **Zero secrets on the client** — the MCP proxy never sees API keys or tokens
465
+ - **Mutual authentication** — Ed25519 signatures before any data exchange
466
+ - **End-to-end encryption** — AES-256-GCM with X25519 ECDH session keys
467
+ - **Replay protection** — monotonic counters on all encrypted messages
468
+ - **Session isolation** — unique session keys per handshake, 30-minute TTL
469
+ - **File permissions** — private keys `0600`, key directories `0700`
600
470
 
601
- ### Plugin Structure
471
+ ## Development
602
472
 
603
- This repo is structured as a Claude Code plugin:
473
+ ```bash
474
+ npm test # Run tests
475
+ npm run test:watch # Watch mode
476
+ npm run test:coverage # Coverage report
477
+ npm run lint # Lint
478
+ npm run format # Format
604
479
 
605
- ```
606
- drawlatch/
607
- ├── .claude-plugin/ # Plugin metadata
608
- │ ├── plugin.json # Plugin manifest (name, version, description)
609
- │ └── marketplace.json # Marketplace catalog for distribution
610
- ├── .mcp.json # MCP server config (used by plugin system + auto-discovery)
611
- ├── dist/ # Compiled JavaScript (built via `npm run build` or `prepare`)
612
- │ └── mcp/server.js # MCP proxy entrypoint
613
- └── src/ # TypeScript source
480
+ npm run dev:remote # Remote server with hot reload
481
+ npm run dev:mcp # MCP proxy with hot reload
614
482
  ```
615
483
 
616
- ### Source Code
484
+ ### Source Structure
617
485
 
618
486
  ```
619
487
  src/
620
- ├── cli/ # Key generation CLI
621
- │ └── generate-keys.ts # Ed25519 + X25519 keypair generation
622
- ├── connections/ # Pre-built route templates (JSON)
623
- │ ├── github.json # GitHub REST API
624
- │ ├── stripe.json # Stripe Payments API
625
- │ └── ... # 22 templates total
626
- ├── mcp/
627
- │ └── server.ts # Local MCP proxy server (stdio transport)
488
+ ├── cli/ # Key generation CLI
489
+ ├── connections/ # 22 pre-built route templates (JSON)
490
+ ├── mcp/server.ts # Local MCP proxy (stdio transport)
628
491
  ├── remote/
629
- │ ├── server.ts # Remote secure server (Express HTTP)
630
- ├── server.test.ts # Unit tests
631
- ├── server.e2e.test.ts # End-to-end tests
632
- └── ingestors/ # Real-time event ingestion system
633
- │ ├── base-ingestor.ts # Abstract base class (state machine, ring buffer)
634
- ├── ring-buffer.ts # Generic bounded circular buffer
635
- │ ├── manager.ts # Lifecycle management, per-caller routing
636
- │ ├── registry.ts # Factory registry for ingestor types
637
- │ ├── types.ts # Shared types and config interfaces
638
- │ ├── discord/ # Discord Gateway WebSocket (v10)
639
- │ ├── slack/ # Slack Socket Mode WebSocket
640
- │ ├── webhook/ # Webhook receivers (GitHub, Stripe, Trello)
641
- │ └── poll/ # Interval-based HTTP polling (Notion, Linear, etc.)
492
+ │ ├── server.ts # Remote secure server (Express)
493
+ └── ingestors/ # Event ingestion system
494
+ ├── discord/ # Discord Gateway WebSocket
495
+ ├── slack/ # Slack Socket Mode WebSocket
496
+ │ ├── webhook/ # GitHub, Stripe, Trello webhooks
497
+ └── poll/ # Interval-based HTTP polling
642
498
  └── shared/
643
- ├── config.ts # Config loading/saving, caller & route resolution
644
- ├── connections.ts # Connection template loading
645
- ├── logger.ts # Structured logging
646
- ├── crypto/
647
- │ ├── keys.ts # Ed25519 + X25519 key generation/serialization
648
- │ ├── channel.ts # AES-256-GCM encrypted channel
649
- │ └── index.ts # Re-exports
650
- └── protocol/
651
- ├── handshake.ts # Mutual auth (Noise NK-inspired)
652
- ├── messages.ts # Application-layer message types
653
- └── index.ts # Re-exports
499
+ ├── config.ts # Config loading, route resolution
500
+ ├── connections.ts # Connection template loading
501
+ ├── env-utils.ts # Environment variable utilities
502
+ ├── crypto/ # Ed25519/X25519 keys, AES-256-GCM channel
503
+ └── protocol/ # Handshake, message types
654
504
  ```
655
505
 
656
- ## Security Model
657
-
658
- ### Both Modes
659
-
660
- These protections apply regardless of whether you use remote or local mode:
661
-
662
- - **Per-caller access control** — each caller only sees and can use the connections explicitly assigned to them
663
- - **Per-caller credential isolation** — callers sharing the same connector can have different credentials via `env` overrides
664
- - **Endpoint allowlisting** — requests are only proxied to explicitly configured URL patterns
665
- - **Rate limiting** — configurable per-session request rate limiting (default: 60/min)
666
- - **Audit logging** — all operations are logged with caller identity, session ID, and timestamps
667
-
668
- ### Remote Mode Only
669
-
670
- These additional protections apply when running the two-component remote architecture:
671
-
672
- - **Zero secrets on the client** — the local MCP proxy never sees API keys or tokens
673
- - **Mutual authentication** — both sides prove their identity using Ed25519 signatures before any data is exchanged
674
- - **End-to-end encryption** — all requests/responses are encrypted with AES-256-GCM session keys derived via X25519 ECDH
675
- - **Replay protection** — monotonic counters prevent replay attacks
676
- - **Session isolation** — each handshake produces unique session keys with a 30-minute TTL
677
- - **File permissions** — private keys are saved with `0600`, directories with `0700`
678
-
679
- ### Local Mode Caveat
680
-
681
- When using drawlatch as an in-process library (local mode), secrets are resolved from `process.env` on the same machine as the agent. The encryption and mutual authentication layers are not used. The security value in local mode comes from **structured access control** (endpoint allowlisting, per-caller route isolation) rather than cryptographic secret isolation.
682
-
683
506
  ## License
684
507
 
685
508
  MIT