@wolpertingerlabs/drawlatch 1.0.0-alpha.2 → 1.0.0-alpha.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/README.md +5 -3
  2. package/bin/drawlatch.js +43 -2
  3. package/dist/connections/anthropic.json +17 -1
  4. package/dist/connections/bluesky.json +39 -0
  5. package/dist/connections/devin.json +5 -1
  6. package/dist/connections/discord-bot.json +88 -0
  7. package/dist/connections/discord-oauth.json +5 -1
  8. package/dist/connections/github.json +57 -0
  9. package/dist/connections/google-ai.json +5 -1
  10. package/dist/connections/google.json +5 -1
  11. package/dist/connections/hex.json +5 -1
  12. package/dist/connections/lichess.json +5 -1
  13. package/dist/connections/linear.json +44 -0
  14. package/dist/connections/mastodon.json +38 -0
  15. package/dist/connections/notion.json +40 -0
  16. package/dist/connections/openai.json +5 -1
  17. package/dist/connections/openrouter.json +5 -1
  18. package/dist/connections/reddit.json +50 -0
  19. package/dist/connections/slack.json +50 -0
  20. package/dist/connections/stripe.json +47 -0
  21. package/dist/connections/telegram.json +38 -0
  22. package/dist/connections/trello.json +87 -1
  23. package/dist/connections/twitch.json +38 -0
  24. package/dist/connections/x.json +38 -0
  25. package/dist/mcp/server.js +279 -1
  26. package/dist/remote/ingestors/base-ingestor.d.ts +14 -3
  27. package/dist/remote/ingestors/base-ingestor.js +10 -2
  28. package/dist/remote/ingestors/discord/discord-gateway.d.ts +2 -2
  29. package/dist/remote/ingestors/discord/discord-gateway.js +5 -5
  30. package/dist/remote/ingestors/manager.d.ts +75 -9
  31. package/dist/remote/ingestors/manager.js +309 -40
  32. package/dist/remote/ingestors/poll/poll-ingestor.d.ts +2 -2
  33. package/dist/remote/ingestors/poll/poll-ingestor.js +5 -5
  34. package/dist/remote/ingestors/registry.d.ts +2 -2
  35. package/dist/remote/ingestors/registry.js +2 -2
  36. package/dist/remote/ingestors/slack/socket-mode.d.ts +2 -2
  37. package/dist/remote/ingestors/slack/socket-mode.js +5 -5
  38. package/dist/remote/ingestors/types.d.ts +17 -0
  39. package/dist/remote/ingestors/webhook/base-webhook-ingestor.d.ts +46 -7
  40. package/dist/remote/ingestors/webhook/base-webhook-ingestor.js +115 -10
  41. package/dist/remote/ingestors/webhook/github-webhook-ingestor.d.ts +14 -0
  42. package/dist/remote/ingestors/webhook/github-webhook-ingestor.js +27 -2
  43. package/dist/remote/ingestors/webhook/lifecycle-types.d.ts +63 -0
  44. package/dist/remote/ingestors/webhook/lifecycle-types.js +12 -0
  45. package/dist/remote/ingestors/webhook/stripe-webhook-ingestor.js +2 -2
  46. package/dist/remote/ingestors/webhook/trello-webhook-ingestor.d.ts +17 -5
  47. package/dist/remote/ingestors/webhook/trello-webhook-ingestor.js +32 -26
  48. package/dist/remote/ingestors/webhook/webhook-lifecycle-manager.d.ts +46 -0
  49. package/dist/remote/ingestors/webhook/webhook-lifecycle-manager.js +261 -0
  50. package/dist/remote/server.js +629 -16
  51. package/dist/remote/tunnel.d.ts +40 -0
  52. package/dist/remote/tunnel.js +116 -0
  53. package/dist/shared/config.d.ts +45 -0
  54. package/dist/shared/config.js +8 -3
  55. package/dist/shared/connections.d.ts +9 -0
  56. package/dist/shared/connections.js +4 -0
  57. package/dist/shared/listener-config.d.ts +157 -0
  58. package/dist/shared/listener-config.js +10 -0
  59. package/package.json +4 -3
package/README.md CHANGED
@@ -1,4 +1,6 @@
1
- # drawlatch
1
+ # Drawlatch
2
+
3
+ > **Alpha Software:** This project is in alpha. Expect breaking changes between updates.
2
4
 
3
5
  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.
4
6
 
@@ -29,7 +31,7 @@ The crypto layer uses **Ed25519** signatures for authentication and **X25519 ECD
29
31
 
30
32
  ### Local Mode (In-Process Library)
31
33
 
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:
34
+ 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:
33
35
 
34
36
  ```
35
37
  ┌──────────────────────────────────────────┐ Authenticated ┌──────────────┐
@@ -678,7 +680,7 @@ These additional protections apply when running the two-component remote archite
678
680
 
679
681
  ### Local Mode Caveat
680
682
 
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.
683
+ 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
684
 
683
685
  ## License
684
686
 
package/bin/drawlatch.js CHANGED
@@ -57,10 +57,11 @@ try {
57
57
  help: { type: "boolean", short: "h", default: false },
58
58
  version: { type: "boolean", short: "v", default: false },
59
59
  foreground: { type: "boolean", short: "f", default: false },
60
+ tunnel: { type: "boolean", short: "t", default: false },
60
61
  port: { type: "string" },
61
62
  host: { type: "string" },
62
63
  lines: { type: "string", short: "n", default: "50" },
63
- follow: { type: "boolean", default: true },
64
+ follow: { type: "boolean", default: false },
64
65
  path: { type: "boolean", default: false },
65
66
  },
66
67
  strict: false,
@@ -182,6 +183,7 @@ async function cmdStart() {
182
183
  NODE_ENV: "production",
183
184
  ...(values.port ? { DRAWLATCH_PORT: String(port) } : {}),
184
185
  ...(values.host ? { DRAWLATCH_HOST: host } : {}),
186
+ ...(values.tunnel ? { DRAWLATCH_TUNNEL: "1" } : {}),
185
187
  },
186
188
  cwd: PKG_ROOT,
187
189
  });
@@ -195,6 +197,18 @@ async function cmdStart() {
195
197
  if (healthy) {
196
198
  console.log(`\nRemote server is running (PID ${child.pid}).`);
197
199
  console.log(` Listening: ${host}:${port}`);
200
+ if (values.tunnel) {
201
+ // The tunnel starts asynchronously after the server is healthy —
202
+ // poll the health endpoint until the tunnel URL appears (up to 20s).
203
+ console.log(` Tunnel: waiting for cloudflared...`);
204
+ const tunnelUrl = await waitForTunnelUrl(host, port, 20000);
205
+ if (tunnelUrl) {
206
+ console.log(` Tunnel: ${tunnelUrl}`);
207
+ console.log(` Webhooks: ${tunnelUrl}/webhooks/<path>`);
208
+ } else {
209
+ console.log(` Tunnel: not available (check logs: drawlatch logs)`);
210
+ }
211
+ }
198
212
  console.log(` Logs: drawlatch logs`);
199
213
  } else {
200
214
  console.log(
@@ -209,6 +223,7 @@ async function cmdStartForeground() {
209
223
  process.env.NODE_ENV = process.env.NODE_ENV || "production";
210
224
  if (values.port) process.env.DRAWLATCH_PORT = values.port;
211
225
  if (values.host) process.env.DRAWLATCH_HOST = values.host;
226
+ if (values.tunnel) process.env.DRAWLATCH_TUNNEL = "1";
212
227
 
213
228
  ensureConfigDir();
214
229
 
@@ -250,6 +265,17 @@ async function cmdStop() {
250
265
  async function cmdRestart() {
251
266
  const pid = readPid();
252
267
  if (pid) {
268
+ // If the previous server had an active tunnel, carry the flag forward
269
+ // so the restarted server also starts a tunnel (unless --tunnel is
270
+ // already set or the user explicitly omitted it).
271
+ if (!values.tunnel) {
272
+ const config = loadRemoteConfig();
273
+ const prevHealth = await healthCheckFull(config.host, config.port);
274
+ if (prevHealth?.tunnelUrl) {
275
+ console.log("Previous server had an active tunnel — re-enabling --tunnel.");
276
+ values.tunnel = true;
277
+ }
278
+ }
253
279
  await cmdStop();
254
280
  }
255
281
  await cmdStart();
@@ -285,6 +311,9 @@ async function cmdStatus() {
285
311
  );
286
312
  if (healthData) {
287
313
  console.log(` Active sessions: ${healthData.activeSessions}`);
314
+ if (healthData.tunnelUrl) {
315
+ console.log(` Tunnel: ${healthData.tunnelUrl}`);
316
+ }
288
317
  }
289
318
  }
290
319
 
@@ -449,6 +478,16 @@ async function healthCheckFull(host, port) {
449
478
  }
450
479
  }
451
480
 
481
+ async function waitForTunnelUrl(host, port, timeoutMs) {
482
+ const start = Date.now();
483
+ while (Date.now() - start < timeoutMs) {
484
+ const data = await healthCheckFull(host, port);
485
+ if (data?.tunnelUrl) return data.tunnelUrl;
486
+ await sleep(500);
487
+ }
488
+ return null;
489
+ }
490
+
452
491
  async function waitForHealth(host, port, timeoutMs) {
453
492
  const start = Date.now();
454
493
  while (Date.now() - start < timeoutMs) {
@@ -537,6 +576,7 @@ Examples:
537
576
  drawlatch Show status or help
538
577
  drawlatch start Start remote server in background
539
578
  drawlatch start -f Start remote server in foreground
579
+ drawlatch start -f --tunnel Start with a public tunnel for webhooks
540
580
  drawlatch start --port 8080 Start on a custom port
541
581
  drawlatch status Check if server is running
542
582
  drawlatch logs -n 100 View last 100 log lines
@@ -555,6 +595,7 @@ Usage: drawlatch start [options]
555
595
 
556
596
  Options:
557
597
  -f, --foreground Run in foreground (default when no command given)
598
+ -t, --tunnel Start a Cloudflare tunnel for webhook ingestion (requires cloudflared)
558
599
  --port <number> Override the configured port
559
600
  --host <address> Override the configured host
560
601
  -h, --help Show this help message
@@ -623,7 +664,7 @@ Usage: drawlatch logs [options]
623
664
 
624
665
  Options:
625
666
  -n, --lines <number> Number of lines to show (default: 50)
626
- --no-follow Print lines and exit (default: follow/tail)
667
+ --follow Follow/tail the log output (default: print and exit)
627
668
  -h, --help Show this help message
628
669
 
629
670
  Log file: ~/.drawlatch/logs/drawlatch.log
@@ -12,5 +12,21 @@
12
12
  },
13
13
  "allowedEndpoints": [
14
14
  "https://api.anthropic.com/**"
15
- ]
15
+ ],
16
+ "testConnection": {
17
+ "method": "POST",
18
+ "url": "https://api.anthropic.com/v1/messages",
19
+ "body": {
20
+ "model": "claude-3-haiku-20240307",
21
+ "max_tokens": 1,
22
+ "messages": [
23
+ {
24
+ "role": "user",
25
+ "content": "hi"
26
+ }
27
+ ]
28
+ },
29
+ "description": "Sends a minimal message to verify API key",
30
+ "expectedStatus": [200]
31
+ }
16
32
  }
@@ -22,5 +22,44 @@
22
22
  "deduplicateBy": "uri",
23
23
  "eventType": "notification"
24
24
  }
25
+ },
26
+ "testIngestor": {
27
+ "description": "Fetches notifications to verify Bluesky access token",
28
+ "strategy": "poll_once",
29
+ "request": {
30
+ "url": "https://bsky.social/xrpc/app.bsky.notification.listNotifications?limit=1",
31
+ "expectedStatus": [200]
32
+ }
33
+ },
34
+ "listenerConfig": {
35
+ "name": "Bluesky Poll Listener",
36
+ "description": "Polls Bluesky notifications at a configurable interval.",
37
+ "fields": [
38
+ {
39
+ "key": "intervalMs",
40
+ "label": "Poll Interval (ms)",
41
+ "description": "How often to check for new notifications, in milliseconds.",
42
+ "type": "number",
43
+ "default": 60000,
44
+ "min": 10000,
45
+ "max": 3600000,
46
+ "group": "Connection"
47
+ },
48
+ {
49
+ "key": "bufferSize",
50
+ "label": "Buffer Size",
51
+ "description": "Maximum number of events to keep in memory.",
52
+ "type": "number",
53
+ "default": 200,
54
+ "min": 10,
55
+ "max": 1000,
56
+ "group": "Advanced"
57
+ }
58
+ ]
59
+ },
60
+ "testConnection": {
61
+ "url": "https://bsky.social/xrpc/app.bsky.actor.getProfile?actor=did:self",
62
+ "description": "Fetches the authenticated user's profile",
63
+ "expectedStatus": [200, 400]
25
64
  }
26
65
  }
@@ -11,5 +11,9 @@
11
11
  },
12
12
  "allowedEndpoints": [
13
13
  "https://api.devin.ai/**"
14
- ]
14
+ ],
15
+ "testConnection": {
16
+ "url": "https://api.devin.ai/v1/sessions",
17
+ "description": "Lists Devin sessions to verify API key"
18
+ }
15
19
  }
@@ -20,5 +20,93 @@
20
20
  "protocol": "discord",
21
21
  "intents": 3276799
22
22
  }
23
+ },
24
+ "testIngestor": {
25
+ "description": "Verifies the bot token has Gateway access",
26
+ "strategy": "websocket_auth",
27
+ "request": {
28
+ "url": "https://discord.com/api/v10/gateway/bot",
29
+ "description": "Checks bot token has gateway access"
30
+ }
31
+ },
32
+ "listenerConfig": {
33
+ "name": "Discord Gateway Listener",
34
+ "description": "Real-time events from Discord via the Gateway WebSocket.",
35
+ "fields": [
36
+ {
37
+ "key": "eventFilter",
38
+ "label": "Event Types",
39
+ "description": "Which Gateway event types to capture. Leave empty for all.",
40
+ "type": "multiselect",
41
+ "default": [],
42
+ "options": [
43
+ {"value": "MESSAGE_CREATE", "label": "Message Create"},
44
+ {"value": "MESSAGE_UPDATE", "label": "Message Update"},
45
+ {"value": "MESSAGE_DELETE", "label": "Message Delete"},
46
+ {"value": "MESSAGE_REACTION_ADD", "label": "Reaction Add"},
47
+ {"value": "MESSAGE_REACTION_REMOVE", "label": "Reaction Remove"},
48
+ {"value": "GUILD_MEMBER_ADD", "label": "Member Join"},
49
+ {"value": "GUILD_MEMBER_REMOVE", "label": "Member Leave"},
50
+ {"value": "CHANNEL_CREATE", "label": "Channel Create"},
51
+ {"value": "CHANNEL_UPDATE", "label": "Channel Update"},
52
+ {"value": "CHANNEL_DELETE", "label": "Channel Delete"},
53
+ {"value": "PRESENCE_UPDATE", "label": "Presence Update"},
54
+ {"value": "TYPING_START", "label": "Typing Start"},
55
+ {"value": "VOICE_STATE_UPDATE", "label": "Voice State Update"}
56
+ ],
57
+ "group": "Filtering"
58
+ },
59
+ {
60
+ "key": "guildIds",
61
+ "label": "Server (Guild) IDs",
62
+ "description": "Only receive events from these Discord servers. Leave empty for all.",
63
+ "type": "text[]",
64
+ "placeholder": "e.g., 123456789012345678",
65
+ "dynamicOptions": {
66
+ "url": "https://discord.com/api/v10/users/@me/guilds",
67
+ "labelField": "name",
68
+ "valueField": "id"
69
+ },
70
+ "group": "Filtering"
71
+ },
72
+ {
73
+ "key": "channelIds",
74
+ "label": "Channel IDs",
75
+ "description": "Only receive events from these channels. Leave empty for all.",
76
+ "type": "text[]",
77
+ "placeholder": "e.g., 123456789012345678",
78
+ "group": "Filtering"
79
+ },
80
+ {
81
+ "key": "userIds",
82
+ "label": "User IDs",
83
+ "description": "Only receive events from these users. Leave empty for all.",
84
+ "type": "text[]",
85
+ "placeholder": "e.g., 123456789012345678",
86
+ "group": "Filtering"
87
+ },
88
+ {
89
+ "key": "intents",
90
+ "label": "Gateway Intents",
91
+ "description": "Bitmask controlling which events the bot receives. See Discord docs for values.",
92
+ "type": "number",
93
+ "default": 3276799,
94
+ "group": "Advanced"
95
+ },
96
+ {
97
+ "key": "bufferSize",
98
+ "label": "Buffer Size",
99
+ "description": "Maximum number of events to keep in memory.",
100
+ "type": "number",
101
+ "default": 200,
102
+ "min": 10,
103
+ "max": 1000,
104
+ "group": "Advanced"
105
+ }
106
+ ]
107
+ },
108
+ "testConnection": {
109
+ "url": "https://discord.com/api/v10/users/@me",
110
+ "description": "Fetches the authenticated bot user"
23
111
  }
24
112
  }
@@ -12,5 +12,9 @@
12
12
  },
13
13
  "allowedEndpoints": [
14
14
  "https://discord.com/api/v10/**"
15
- ]
15
+ ],
16
+ "testConnection": {
17
+ "url": "https://discord.com/api/v10/users/@me",
18
+ "description": "Fetches the authenticated OAuth2 user"
19
+ }
16
20
  }
@@ -21,5 +21,62 @@
21
21
  "signatureHeader": "X-Hub-Signature-256",
22
22
  "signatureSecret": "GITHUB_WEBHOOK_SECRET"
23
23
  }
24
+ },
25
+ "testIngestor": {
26
+ "description": "Verifies that the GitHub webhook secret is configured",
27
+ "strategy": "webhook_verify",
28
+ "requireSecrets": ["GITHUB_WEBHOOK_SECRET"]
29
+ },
30
+ "listenerConfig": {
31
+ "name": "GitHub Webhook Listener",
32
+ "description": "Receives real-time events from GitHub via webhooks. Supports multiple concurrent instances to filter events from different repositories.",
33
+ "supportsMultiInstance": true,
34
+ "fields": [
35
+ {
36
+ "key": "repoFilter",
37
+ "label": "Repository Filter",
38
+ "description": "Only capture webhook events from these repositories (owner/repo format). Leave empty for all.",
39
+ "type": "text[]",
40
+ "instanceKey": true,
41
+ "placeholder": "e.g., octocat/Hello-World",
42
+ "group": "Filtering"
43
+ },
44
+ {
45
+ "key": "eventFilter",
46
+ "label": "Event Types",
47
+ "description": "Which GitHub event types to capture. Leave empty for all.",
48
+ "type": "multiselect",
49
+ "default": [],
50
+ "options": [
51
+ {"value": "push", "label": "Push"},
52
+ {"value": "pull_request", "label": "Pull Request"},
53
+ {"value": "issues", "label": "Issues"},
54
+ {"value": "issue_comment", "label": "Issue Comment"},
55
+ {"value": "create", "label": "Branch/Tag Created"},
56
+ {"value": "delete", "label": "Branch/Tag Deleted"},
57
+ {"value": "release", "label": "Release"},
58
+ {"value": "workflow_run", "label": "Workflow Run"},
59
+ {"value": "check_run", "label": "Check Run"},
60
+ {"value": "star", "label": "Star"},
61
+ {"value": "fork", "label": "Fork"},
62
+ {"value": "deployment", "label": "Deployment"}
63
+ ],
64
+ "group": "Filtering"
65
+ },
66
+ {
67
+ "key": "bufferSize",
68
+ "label": "Buffer Size",
69
+ "description": "Maximum number of events to keep in memory.",
70
+ "type": "number",
71
+ "default": 200,
72
+ "min": 10,
73
+ "max": 1000,
74
+ "group": "Advanced"
75
+ }
76
+ ]
77
+ },
78
+ "testConnection": {
79
+ "url": "https://api.github.com/user",
80
+ "description": "Fetches the authenticated user profile"
24
81
  }
25
82
  }
@@ -11,5 +11,9 @@
11
11
  },
12
12
  "allowedEndpoints": [
13
13
  "https://generativelanguage.googleapis.com/**"
14
- ]
14
+ ],
15
+ "testConnection": {
16
+ "url": "https://generativelanguage.googleapis.com/v1beta/models",
17
+ "description": "Lists available Gemini models"
18
+ }
15
19
  }
@@ -24,5 +24,9 @@
24
24
  "https://youtube.googleapis.com/**",
25
25
  "https://youtubeanalytics.googleapis.com/**",
26
26
  "https://cloudresourcemanager.googleapis.com/**"
27
- ]
27
+ ],
28
+ "testConnection": {
29
+ "url": "https://www.googleapis.com/oauth2/v1/userinfo",
30
+ "description": "Fetches the authenticated user info"
31
+ }
28
32
  }
@@ -10,5 +10,9 @@
10
10
  },
11
11
  "allowedEndpoints": [
12
12
  "https://app.hex.tech/api/**"
13
- ]
13
+ ],
14
+ "testConnection": {
15
+ "url": "https://app.hex.tech/api/v1/user/me",
16
+ "description": "Fetches the authenticated Hex user"
17
+ }
14
18
  }
@@ -11,5 +11,9 @@
11
11
  "allowedEndpoints": [
12
12
  "https://explorer.lichess.ovh/**",
13
13
  "https://lichess.org/api/**"
14
- ]
14
+ ],
15
+ "testConnection": {
16
+ "url": "https://lichess.org/api/account",
17
+ "description": "Fetches the authenticated Lichess account"
18
+ }
15
19
  }
@@ -25,5 +25,49 @@
25
25
  "deduplicateBy": "id",
26
26
  "eventType": "issue_updated"
27
27
  }
28
+ },
29
+ "testIngestor": {
30
+ "description": "Executes a minimal GraphQL query to verify Linear API access",
31
+ "strategy": "poll_once",
32
+ "request": {
33
+ "method": "POST",
34
+ "url": "https://api.linear.app/graphql",
35
+ "body": {"query": "{ viewer { id } }"},
36
+ "expectedStatus": [200]
37
+ }
38
+ },
39
+ "listenerConfig": {
40
+ "name": "Linear Poll Listener",
41
+ "description": "Polls Linear for recently updated issues at a configurable interval.",
42
+ "fields": [
43
+ {
44
+ "key": "intervalMs",
45
+ "label": "Poll Interval (ms)",
46
+ "description": "How often to check for new events, in milliseconds.",
47
+ "type": "number",
48
+ "default": 60000,
49
+ "min": 10000,
50
+ "max": 3600000,
51
+ "group": "Connection"
52
+ },
53
+ {
54
+ "key": "bufferSize",
55
+ "label": "Buffer Size",
56
+ "description": "Maximum number of events to keep in memory.",
57
+ "type": "number",
58
+ "default": 200,
59
+ "min": 10,
60
+ "max": 1000,
61
+ "group": "Advanced"
62
+ }
63
+ ]
64
+ },
65
+ "testConnection": {
66
+ "method": "POST",
67
+ "url": "https://api.linear.app/graphql",
68
+ "body": {
69
+ "query": "{ viewer { id name } }"
70
+ },
71
+ "description": "Fetches the authenticated user via GraphQL"
28
72
  }
29
73
  }
@@ -21,5 +21,43 @@
21
21
  "deduplicateBy": "id",
22
22
  "eventType": "status"
23
23
  }
24
+ },
25
+ "testIngestor": {
26
+ "description": "Verifies Mastodon credentials by checking account",
27
+ "strategy": "poll_once",
28
+ "request": {
29
+ "url": "https://mastodon.social/api/v1/accounts/verify_credentials",
30
+ "expectedStatus": [200]
31
+ }
32
+ },
33
+ "listenerConfig": {
34
+ "name": "Mastodon Poll Listener",
35
+ "description": "Polls the Mastodon home timeline at a configurable interval.",
36
+ "fields": [
37
+ {
38
+ "key": "intervalMs",
39
+ "label": "Poll Interval (ms)",
40
+ "description": "How often to check for new statuses, in milliseconds.",
41
+ "type": "number",
42
+ "default": 60000,
43
+ "min": 10000,
44
+ "max": 3600000,
45
+ "group": "Connection"
46
+ },
47
+ {
48
+ "key": "bufferSize",
49
+ "label": "Buffer Size",
50
+ "description": "Maximum number of events to keep in memory.",
51
+ "type": "number",
52
+ "default": 200,
53
+ "min": 10,
54
+ "max": 1000,
55
+ "group": "Advanced"
56
+ }
57
+ ]
58
+ },
59
+ "testConnection": {
60
+ "url": "https://mastodon.social/api/v1/accounts/verify_credentials",
61
+ "description": "Verifies Mastodon account credentials"
24
62
  }
25
63
  }
@@ -29,5 +29,45 @@
29
29
  "deduplicateBy": "id",
30
30
  "eventType": "page_updated"
31
31
  }
32
+ },
33
+ "testIngestor": {
34
+ "description": "Executes a single search poll to verify Notion API access",
35
+ "strategy": "poll_once",
36
+ "request": {
37
+ "method": "POST",
38
+ "url": "https://api.notion.com/v1/search",
39
+ "body": {"page_size": 1},
40
+ "expectedStatus": [200]
41
+ }
42
+ },
43
+ "listenerConfig": {
44
+ "name": "Notion Poll Listener",
45
+ "description": "Polls Notion for recently edited pages at a configurable interval.",
46
+ "fields": [
47
+ {
48
+ "key": "intervalMs",
49
+ "label": "Poll Interval (ms)",
50
+ "description": "How often to check for new events, in milliseconds.",
51
+ "type": "number",
52
+ "default": 60000,
53
+ "min": 10000,
54
+ "max": 3600000,
55
+ "group": "Connection"
56
+ },
57
+ {
58
+ "key": "bufferSize",
59
+ "label": "Buffer Size",
60
+ "description": "Maximum number of events to keep in memory.",
61
+ "type": "number",
62
+ "default": 200,
63
+ "min": 10,
64
+ "max": 1000,
65
+ "group": "Advanced"
66
+ }
67
+ ]
68
+ },
69
+ "testConnection": {
70
+ "url": "https://api.notion.com/v1/users/me",
71
+ "description": "Fetches the authenticated bot user"
32
72
  }
33
73
  }
@@ -12,5 +12,9 @@
12
12
  },
13
13
  "allowedEndpoints": [
14
14
  "https://api.openai.com/**"
15
- ]
15
+ ],
16
+ "testConnection": {
17
+ "url": "https://api.openai.com/v1/models",
18
+ "description": "Lists available models"
19
+ }
16
20
  }
@@ -12,5 +12,9 @@
12
12
  },
13
13
  "allowedEndpoints": [
14
14
  "https://openrouter.ai/api/**"
15
- ]
15
+ ],
16
+ "testConnection": {
17
+ "url": "https://openrouter.ai/api/v1/models",
18
+ "description": "Lists available models"
19
+ }
16
20
  }
@@ -24,5 +24,55 @@
24
24
  "deduplicateBy": "data.name",
25
25
  "eventType": "new_post"
26
26
  }
27
+ },
28
+ "testIngestor": {
29
+ "description": "Fetches the authenticated user to verify Reddit API access",
30
+ "strategy": "poll_once",
31
+ "request": {
32
+ "url": "https://oauth.reddit.com/api/v1/me",
33
+ "expectedStatus": [200]
34
+ }
35
+ },
36
+ "listenerConfig": {
37
+ "name": "Reddit Poll Listener",
38
+ "description": "Polls a subreddit for new posts at a configurable interval. Supports multiple concurrent instances to watch different subreddits simultaneously.",
39
+ "supportsMultiInstance": true,
40
+ "fields": [
41
+ {
42
+ "key": "subreddit",
43
+ "label": "Subreddit",
44
+ "description": "The subreddit to watch for new posts (without the r/ prefix).",
45
+ "type": "text",
46
+ "required": true,
47
+ "instanceKey": true,
48
+ "overrideKey": "REDDIT_SUBREDDIT",
49
+ "placeholder": "e.g., rust",
50
+ "group": "Connection"
51
+ },
52
+ {
53
+ "key": "intervalMs",
54
+ "label": "Poll Interval (ms)",
55
+ "description": "How often to check for new posts, in milliseconds.",
56
+ "type": "number",
57
+ "default": 60000,
58
+ "min": 10000,
59
+ "max": 3600000,
60
+ "group": "Connection"
61
+ },
62
+ {
63
+ "key": "bufferSize",
64
+ "label": "Buffer Size",
65
+ "description": "Maximum number of events to keep in memory.",
66
+ "type": "number",
67
+ "default": 200,
68
+ "min": 10,
69
+ "max": 1000,
70
+ "group": "Advanced"
71
+ }
72
+ ]
73
+ },
74
+ "testConnection": {
75
+ "url": "https://oauth.reddit.com/api/v1/me",
76
+ "description": "Fetches the authenticated Reddit user"
27
77
  }
28
78
  }