webhookagent 1.0.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Avni Yayin
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,84 @@
1
+ # @avniyayin/webhookagent
2
+
3
+ Cross-platform Node.js heartbeat agent for [WebhookAgent.com](https://webhookagent.com) — receive webhooks, queue them, process at your pace.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install -g @avniyayin/webhookagent
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```bash
14
+ # Sign up at https://webhookagent.com/dashboard and get your API key
15
+
16
+ # Start the heartbeat agent
17
+ webhookagent --key=wha_your_key --auto-restart
18
+
19
+ # Single check
20
+ webhookagent --key=wha_your_key --once
21
+
22
+ # Fetch + auto-ack items
23
+ webhookagent --key=wha_your_key --auto-restart --fetch --ack
24
+ ```
25
+
26
+ ## How It Works (Break-on-Action)
27
+
28
+ 1. **Poll** — Agent sends heartbeat every N seconds (default: 30)
29
+ 2. **Break** — When events arrive, the polling loop exits
30
+ 3. **Process** — Your logic handles the events
31
+ 4. **Reconnect** — Agent restarts the heartbeat loop
32
+
33
+ No open ports needed. Works behind any firewall. Events are queued so nothing is lost.
34
+
35
+ ## Options
36
+
37
+ | Flag | Default | Description |
38
+ |------|---------|-------------|
39
+ | `--key=KEY` | - | API key (or set `WHA_API_KEY` env var) |
40
+ | `--url=URL` | `https://webhookagent.com/api` | Base API URL |
41
+ | `--interval=N` | 30 | Seconds between polls |
42
+ | `--cycles=N` | 120 | Max polls before exit (~1 hour) |
43
+ | `--once` | - | Single check then exit |
44
+ | `--auto-restart` | - | Never exit — restart after break + max cycles |
45
+ | `--quiet` | - | Only output ACTION lines (for piping) |
46
+ | `--webhooks=IDS` | all | Comma-separated webhook IDs to filter |
47
+ | `--fetch` | - | Auto-fetch full queue items when actions arrive |
48
+ | `--ack` | - | Auto-acknowledge items after outputting them |
49
+ | `--json` | - | Output full heartbeat JSON |
50
+
51
+ ## Output Format
52
+
53
+ Status logs go to `stderr`, actions go to `stdout` as `ACTION:{json}` lines.
54
+
55
+ ```bash
56
+ # Pipe to your processor
57
+ webhookagent --key=wha_abc123 --quiet --auto-restart | node my-processor.js
58
+ ```
59
+
60
+ ## Programmatic Usage
61
+
62
+ ```javascript
63
+ const { execSync } = require('child_process');
64
+
65
+ // Quick single check
66
+ const result = execSync('webhookagent --key=wha_abc --once --json', { encoding: 'utf8' });
67
+ const data = JSON.parse(result);
68
+ ```
69
+
70
+ ## Environment Variables
71
+
72
+ - `WHA_API_KEY` — API key (alternative to `--key` flag)
73
+ - `WHA_URL` — Base URL override
74
+
75
+ ## Links
76
+
77
+ - [WebhookAgent.com](https://webhookagent.com)
78
+ - [API Docs](https://webhookagent.com/docs)
79
+ - [Dashboard](https://webhookagent.com/dashboard)
80
+ - [@avniyayin](https://x.com/avniyayin)
81
+
82
+ ## License
83
+
84
+ MIT
@@ -0,0 +1,260 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * WebhookAgent Heartbeat Runtime v1.0
4
+ * Cross-platform Node.js agent for polling + processing webhook events
5
+ *
6
+ * Inspired by MoltedOpus Agent Runtime v1.4
7
+ * Works on: Windows, macOS, Linux — anywhere Node.js runs
8
+ *
9
+ * HOW IT WORKS (Break-on-Action Pattern):
10
+ * 1. Polls /api/heartbeat every N seconds (default: 30)
11
+ * 2. When the heartbeat returns actions (pending webhook events), it BREAKS
12
+ * 3. Outputs ACTION:{json} lines for your parent process to handle
13
+ * 4. After processing, your parent restarts the heartbeat (or use --auto-restart)
14
+ * 5. This ensures your agent processes events at its own pace
15
+ *
16
+ * USAGE:
17
+ * node heartbeat.js --key=wha_... [options]
18
+ *
19
+ * OPTIONS:
20
+ * --key=KEY API key (or set WHA_API_KEY env var)
21
+ * --url=URL Base URL (default: https://webhookagent.com/api)
22
+ * --interval=N Seconds between polls (default: 30)
23
+ * --cycles=N Max polls before exit (default: 120 = ~1 hour)
24
+ * --once Single heartbeat check, then exit
25
+ * --auto-restart Never exit — restart after break + after max cycles
26
+ * --quiet Only output ACTION lines, no status logs
27
+ * --webhooks=IDS Comma-separated webhook IDs to filter (default: all)
28
+ * --fetch Auto-fetch full queue items when actions arrive
29
+ * --ack Auto-acknowledge items after outputting them
30
+ * --json Output full heartbeat JSON instead of ACTION lines
31
+ *
32
+ * OUTPUT FORMAT:
33
+ * Status lines go to stderr (so you can pipe stdout cleanly)
34
+ * Actions print to stdout as: ACTION:{json}
35
+ *
36
+ * EXAMPLES:
37
+ * # Simple poll loop
38
+ * node heartbeat.js --key=wha_abc123
39
+ *
40
+ * # Continuous with auto-restart
41
+ * node heartbeat.js --key=wha_abc123 --auto-restart
42
+ *
43
+ * # Single check, fetch items, auto-ack
44
+ * node heartbeat.js --key=wha_abc123 --once --fetch --ack
45
+ *
46
+ * # Pipe actions to another script
47
+ * node heartbeat.js --key=wha_abc123 --quiet --auto-restart | node my-processor.js
48
+ *
49
+ * # Filter specific webhooks
50
+ * node heartbeat.js --key=wha_abc123 --webhooks=wh-abc-123,wh-def-456
51
+ */
52
+
53
+ // ============================================================
54
+ // CONFIG
55
+ // ============================================================
56
+
57
+ const args = parseArgs(process.argv.slice(2));
58
+ const API_KEY = args.key || process.env.WHA_API_KEY || '';
59
+ const BASE_URL = (args.url || process.env.WHA_URL || 'https://webhookagent.com/api').replace(/\/$/, '');
60
+ const INTERVAL = parseInt(args.interval || '30') * 1000;
61
+ const MAX_CYCLES = args.once ? 1 : parseInt(args.cycles || '120');
62
+ const AUTO_RESTART = !!args['auto-restart'];
63
+ const QUIET = !!args.quiet;
64
+ const WEBHOOKS_FILTER = args.webhooks || 'all';
65
+ const AUTO_FETCH = !!args.fetch;
66
+ const AUTO_ACK = !!args.ack;
67
+ const JSON_MODE = !!args.json;
68
+
69
+ if (!API_KEY) {
70
+ console.error('ERROR: API key required. Use --key=wha_... or set WHA_API_KEY env var');
71
+ process.exit(1);
72
+ }
73
+
74
+ // ============================================================
75
+ // HELPERS
76
+ // ============================================================
77
+
78
+ function parseArgs(argv) {
79
+ const result = {};
80
+ for (const arg of argv) {
81
+ if (arg.startsWith('--')) {
82
+ const [key, ...valParts] = arg.slice(2).split('=');
83
+ result[key] = valParts.length ? valParts.join('=') : true;
84
+ }
85
+ }
86
+ return result;
87
+ }
88
+
89
+ function log(msg) {
90
+ if (QUIET) return;
91
+ process.stderr.write(`${new Date().toLocaleTimeString()} ${msg}\n`);
92
+ }
93
+
94
+ async function api(path, method = 'GET', body = null) {
95
+ const headers = {
96
+ 'Authorization': `Bearer ${API_KEY}`,
97
+ 'Content-Type': 'application/json',
98
+ 'User-Agent': 'WebhookAgent-Heartbeat/1.0 (Node.js)',
99
+ };
100
+ const opts = { method, headers };
101
+ if (body) opts.body = JSON.stringify(body);
102
+
103
+ try {
104
+ const res = await fetch(`${BASE_URL}${path}`, opts);
105
+ const data = await res.json();
106
+ if (!res.ok) {
107
+ log(`ERROR: HTTP ${res.status} on ${method} ${path}: ${data.error || 'Unknown'}`);
108
+ return null;
109
+ }
110
+ return data;
111
+ } catch (err) {
112
+ log(`ERROR: ${err.message}`);
113
+ return null;
114
+ }
115
+ }
116
+
117
+ function sleep(ms) {
118
+ return new Promise(resolve => setTimeout(resolve, ms));
119
+ }
120
+
121
+ // ============================================================
122
+ // ACTION PROCESSING
123
+ // ============================================================
124
+
125
+ async function processActions(actions, heartbeatData) {
126
+ if (JSON_MODE) {
127
+ console.log(JSON.stringify(heartbeatData));
128
+ return;
129
+ }
130
+
131
+ for (const action of actions) {
132
+ const whId = action.webhook_id;
133
+ const whName = action.webhook_name;
134
+ const pending = action.pending;
135
+
136
+ log(` >> ${whName}: ${pending} pending events`);
137
+
138
+ if (AUTO_FETCH) {
139
+ // Fetch full queue items
140
+ const queue = await api(`/webhooks/${whId}/queue?status=pending&limit=50`);
141
+ if (queue && queue.items) {
142
+ const ids = [];
143
+ for (const item of queue.items) {
144
+ ids.push(item.id);
145
+ const output = {
146
+ type: 'webhook_event',
147
+ webhook_id: whId,
148
+ webhook_name: whName,
149
+ item_id: item.id,
150
+ method: item.method,
151
+ content_type: item.content_type,
152
+ headers: item.headers,
153
+ body: item.body_json || item.body,
154
+ query_params: item.query_params,
155
+ source_ip: item.source_ip,
156
+ received_at: item.received_at,
157
+ };
158
+ console.log('ACTION:' + JSON.stringify(output));
159
+ }
160
+
161
+ if (AUTO_ACK && ids.length > 0) {
162
+ await api(`/webhooks/${whId}/queue/ack`, 'POST', { item_ids: ids });
163
+ log(` >> Acked ${ids.length} items from ${whName}`);
164
+ }
165
+ }
166
+ } else {
167
+ // Just output the action summary with preview
168
+ console.log('ACTION:' + JSON.stringify({
169
+ type: 'webhook_events',
170
+ webhook_id: whId,
171
+ webhook_name: whName,
172
+ pending: pending,
173
+ items_preview: action.items_preview || [],
174
+ fetch: action.fetch,
175
+ ack: action.ack,
176
+ }));
177
+ }
178
+ }
179
+ }
180
+
181
+ // ============================================================
182
+ // MAIN HEARTBEAT LOOP
183
+ // ============================================================
184
+
185
+ async function main() {
186
+ log('WebhookAgent Heartbeat Runtime v1.0');
187
+ log(`Polling ${BASE_URL} every ${INTERVAL / 1000}s, max ${MAX_CYCLES} cycles${AUTO_RESTART ? ' (continuous)' : ''}`);
188
+ if (WEBHOOKS_FILTER !== 'all') log(`Filtering webhooks: ${WEBHOOKS_FILTER}`);
189
+ if (AUTO_FETCH) log('Auto-fetch: ON');
190
+ if (AUTO_ACK) log('Auto-ack: ON');
191
+ log('---');
192
+
193
+ do {
194
+ let retries = 0;
195
+ let brokeOnAction = false;
196
+
197
+ for (let cycle = 1; cycle <= MAX_CYCLES; cycle++) {
198
+ const path = `/heartbeat?webhooks=${encodeURIComponent(WEBHOOKS_FILTER)}`;
199
+ const data = await api(path);
200
+
201
+ if (!data) {
202
+ retries++;
203
+ log(`WARN: heartbeat failed (retry ${retries}/3)`);
204
+ if (retries >= 3) {
205
+ log('FATAL: 3 consecutive failures, exiting');
206
+ process.exit(1);
207
+ }
208
+ await sleep(10000);
209
+ continue;
210
+ }
211
+ retries = 0;
212
+
213
+ const user = data.user || {};
214
+ const summary = data.summary || {};
215
+ const actions = data.actions || [];
216
+
217
+ if (actions.length === 0) {
218
+ log(`ok | plan=${user.plan} events=${user.events_used}/${user.events_limit} (${user.events_remaining} left) | pending=${summary.total_pending}`);
219
+ } else {
220
+ log(`BREAK | ${actions.length} webhook(s) with pending events | plan=${user.plan} events_left=${user.events_remaining}`);
221
+
222
+ await processActions(actions, data);
223
+
224
+ brokeOnAction = true;
225
+ break; // ← THE BREAK — exit loop so parent process can handle
226
+ }
227
+
228
+ if (cycle < MAX_CYCLES) {
229
+ await sleep(INTERVAL);
230
+ }
231
+ }
232
+
233
+ if (!brokeOnAction) {
234
+ log(`Max cycles reached (${MAX_CYCLES}), exiting cleanly`);
235
+ }
236
+
237
+ if (AUTO_RESTART) {
238
+ const wait = brokeOnAction ? 5000 : INTERVAL;
239
+ log(`Auto-restart: sleeping ${wait / 1000}s...`);
240
+ await sleep(wait);
241
+ }
242
+
243
+ } while (AUTO_RESTART);
244
+ }
245
+
246
+ // Handle clean exit
247
+ process.on('SIGINT', () => {
248
+ log('Interrupted, shutting down...');
249
+ process.exit(0);
250
+ });
251
+
252
+ process.on('SIGTERM', () => {
253
+ log('Terminated, shutting down...');
254
+ process.exit(0);
255
+ });
256
+
257
+ main().catch(err => {
258
+ console.error('Fatal error:', err);
259
+ process.exit(1);
260
+ });
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "webhookagent",
3
+ "version": "1.0.0",
4
+ "description": "WebhookAgent heartbeat runtime — poll, break, process webhook events at your agent's pace",
5
+ "main": "lib/heartbeat.js",
6
+ "bin": {
7
+ "webhookagent": "lib/heartbeat.js"
8
+ },
9
+ "scripts": {
10
+ "start": "node lib/heartbeat.js"
11
+ },
12
+ "keywords": [
13
+ "webhook",
14
+ "agent",
15
+ "heartbeat",
16
+ "queue",
17
+ "polling",
18
+ "break-on-action",
19
+ "ai-agent",
20
+ "webhook-queue",
21
+ "event-processing"
22
+ ],
23
+ "author": "Avni Yayin <avni.yayin@gmail.com>",
24
+ "license": "MIT",
25
+ "homepage": "https://webhookagent.com",
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "https://github.com/avniyayin/webhookagent"
29
+ },
30
+ "engines": {
31
+ "node": ">=18.0.0"
32
+ },
33
+ "files": [
34
+ "lib/",
35
+ "README.md",
36
+ "LICENSE"
37
+ ]
38
+ }