webhookagent 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/lib/heartbeat.js +146 -9
  2. package/package.json +1 -1
package/lib/heartbeat.js CHANGED
@@ -3,7 +3,7 @@
3
3
  * WebhookAgent Heartbeat Runtime v1.0
4
4
  * Cross-platform Node.js agent for polling + processing webhook events
5
5
  *
6
- * Inspired by MoltedOpus Agent Runtime v1.4
6
+ * WebhookAgent.com Webhook Queue for AI Agents & Developers
7
7
  * Works on: Windows, macOS, Linux — anywhere Node.js runs
8
8
  *
9
9
  * HOW IT WORKS (Break-on-Action Pattern):
@@ -54,20 +54,40 @@
54
54
  // CONFIG
55
55
  // ============================================================
56
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(/\/$/, '');
57
+ const fs = require('fs');
58
+ const path = require('path');
59
+
60
+ const CONFIG_DIR = path.join(process.env.HOME || process.env.USERPROFILE || '.', '.webhookagent');
61
+ const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
62
+
63
+ // Handle `webhookagent config` subcommand
64
+ const rawArgs = process.argv.slice(2);
65
+ if (rawArgs[0] === 'config') {
66
+ handleConfig(rawArgs.slice(1));
67
+ process.exit(0);
68
+ }
69
+
70
+ const args = parseArgs(rawArgs);
71
+
72
+ // Load saved config (if exists)
73
+ const savedConfig = loadConfig();
74
+
75
+ const API_KEY = args.key || process.env.WHA_API_KEY || savedConfig.api_key || '';
76
+ const BASE_URL = (args.url || process.env.WHA_URL || savedConfig.api_url || 'https://webhookagent.com/api').replace(/\/$/, '');
60
77
  const INTERVAL = parseInt(args.interval || '30') * 1000;
61
78
  const MAX_CYCLES = args.once ? 1 : parseInt(args.cycles || '120');
62
79
  const AUTO_RESTART = !!args['auto-restart'];
63
80
  const QUIET = !!args.quiet;
64
- const WEBHOOKS_FILTER = args.webhooks || 'all';
81
+ const WEBHOOKS_FILTER = args.webhooks || savedConfig.webhooks || 'all';
65
82
  const AUTO_FETCH = !!args.fetch;
66
83
  const AUTO_ACK = !!args.ack;
67
84
  const JSON_MODE = !!args.json;
68
85
 
69
86
  if (!API_KEY) {
70
- console.error('ERROR: API key required. Use --key=wha_... or set WHA_API_KEY env var');
87
+ console.error('ERROR: API key required.');
88
+ console.error(' Option 1: webhookagent config --key=wha_... (saves securely, recommended)');
89
+ console.error(' Option 2: webhookagent --key=wha_... (passed each time)');
90
+ console.error(' Option 3: set WHA_API_KEY env var');
71
91
  process.exit(1);
72
92
  }
73
93
 
@@ -86,6 +106,90 @@ function parseArgs(argv) {
86
106
  return result;
87
107
  }
88
108
 
109
+ function loadConfig() {
110
+ try {
111
+ if (fs.existsSync(CONFIG_FILE)) {
112
+ return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
113
+ }
114
+ } catch (e) { /* ignore */ }
115
+ return {};
116
+ }
117
+
118
+ function saveConfig(data) {
119
+ if (!fs.existsSync(CONFIG_DIR)) {
120
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
121
+ }
122
+ const existing = loadConfig();
123
+ const merged = { ...existing, ...data };
124
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(merged, null, 2) + '\n');
125
+ return merged;
126
+ }
127
+
128
+ function handleConfig(argv) {
129
+ const configArgs = parseArgs(argv);
130
+
131
+ // webhookagent config --key=wha_...
132
+ if (configArgs.key) {
133
+ saveConfig({ api_key: configArgs.key });
134
+ console.log(`API key saved to ${CONFIG_FILE}`);
135
+ console.log('You can now run: webhookagent --fetch --ack');
136
+ return;
137
+ }
138
+
139
+ // webhookagent config --webhooks=wh-abc,wh-def
140
+ if (configArgs.webhooks) {
141
+ saveConfig({ webhooks: configArgs.webhooks });
142
+ console.log(`Webhook filter saved: ${configArgs.webhooks}`);
143
+ return;
144
+ }
145
+
146
+ // webhookagent config --url=https://...
147
+ if (configArgs.url) {
148
+ saveConfig({ api_url: configArgs.url });
149
+ console.log(`API URL saved: ${configArgs.url}`);
150
+ return;
151
+ }
152
+
153
+ // webhookagent config --show
154
+ if (configArgs.show) {
155
+ const cfg = loadConfig();
156
+ if (Object.keys(cfg).length === 0) {
157
+ console.log('No config saved. Use: webhookagent config --key=wha_...');
158
+ } else {
159
+ const display = { ...cfg };
160
+ if (display.api_key) display.api_key = display.api_key.slice(0, 8) + '...' + display.api_key.slice(-4);
161
+ console.log(JSON.stringify(display, null, 2));
162
+ }
163
+ return;
164
+ }
165
+
166
+ // webhookagent config --clear
167
+ if (configArgs.clear) {
168
+ try { fs.unlinkSync(CONFIG_FILE); } catch (e) { /* ignore */ }
169
+ console.log('Config cleared.');
170
+ return;
171
+ }
172
+
173
+ // No args — show help
174
+ console.log('WebhookAgent Config');
175
+ console.log(' webhookagent config --key=wha_... Save API key');
176
+ console.log(' webhookagent config --webhooks=ID1,ID2 Save webhook filter');
177
+ console.log(' webhookagent config --show Show saved config');
178
+ console.log(' webhookagent config --clear Delete saved config');
179
+ }
180
+
181
+ function buildRestartCommand() {
182
+ const parts = ['webhookagent'];
183
+ // Only include --key if it was passed explicitly (not from config/env)
184
+ if (args.key) parts.push(`--key=${args.key}`);
185
+ if (AUTO_FETCH) parts.push('--fetch');
186
+ if (AUTO_ACK) parts.push('--ack');
187
+ if (WEBHOOKS_FILTER !== 'all' && args.webhooks) parts.push(`--webhooks=${WEBHOOKS_FILTER}`);
188
+ if (args.interval) parts.push(`--interval=${args.interval}`);
189
+ if (QUIET) parts.push('--quiet');
190
+ return parts.join(' ');
191
+ }
192
+
89
193
  function log(msg) {
90
194
  if (QUIET) return;
91
195
  process.stderr.write(`${new Date().toLocaleTimeString()} ${msg}\n`);
@@ -183,9 +287,34 @@ async function processActions(actions, heartbeatData) {
183
287
  // ============================================================
184
288
 
185
289
  async function main() {
186
- log('WebhookAgent Heartbeat Runtime v1.0');
290
+ log('WebhookAgent Heartbeat Runtime v1.1');
187
291
  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}`);
292
+
293
+ // Resolve webhook names to IDs if needed
294
+ let resolvedFilter = WEBHOOKS_FILTER;
295
+ if (WEBHOOKS_FILTER !== 'all') {
296
+ const filterParts = WEBHOOKS_FILTER.split(',').map(s => s.trim());
297
+ const hasNames = filterParts.some(p => !p.startsWith('wh-') && !/^\d+$/.test(p));
298
+ if (hasNames) {
299
+ const webhooks = await api('/webhooks');
300
+ if (webhooks && webhooks.webhooks) {
301
+ const resolved = filterParts.map(f => {
302
+ // If it looks like an ID already, keep it
303
+ if (f.startsWith('wh-') || /^\d+$/.test(f)) return f;
304
+ // Otherwise match by name (case-insensitive)
305
+ const match = webhooks.webhooks.find(w => w.name.toLowerCase() === f.toLowerCase());
306
+ if (match) {
307
+ log(`Resolved "${f}" → ${match.webhook_id}`);
308
+ return match.webhook_id;
309
+ }
310
+ log(`WARN: webhook name "${f}" not found, skipping`);
311
+ return null;
312
+ }).filter(Boolean);
313
+ resolvedFilter = resolved.join(',');
314
+ }
315
+ }
316
+ log(`Filtering webhooks: ${resolvedFilter}`);
317
+ }
189
318
  if (AUTO_FETCH) log('Auto-fetch: ON');
190
319
  if (AUTO_ACK) log('Auto-ack: ON');
191
320
  log('---');
@@ -195,7 +324,7 @@ async function main() {
195
324
  let brokeOnAction = false;
196
325
 
197
326
  for (let cycle = 1; cycle <= MAX_CYCLES; cycle++) {
198
- const path = `/heartbeat?webhooks=${encodeURIComponent(WEBHOOKS_FILTER)}`;
327
+ const path = `/heartbeat?webhooks=${encodeURIComponent(resolvedFilter)}`;
199
328
  const data = await api(path);
200
329
 
201
330
  if (!data) {
@@ -222,6 +351,14 @@ async function main() {
222
351
  await processActions(actions, data);
223
352
 
224
353
  brokeOnAction = true;
354
+
355
+ // Tell the agent exactly how to restart
356
+ if (!AUTO_RESTART) {
357
+ const cmd = buildRestartCommand();
358
+ console.log('RESTART:' + cmd);
359
+ log(`Process events above, then run: ${cmd}`);
360
+ }
361
+
225
362
  break; // ← THE BREAK — exit loop so parent process can handle
226
363
  }
227
364
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "webhookagent",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "WebhookAgent heartbeat runtime — poll, break, process webhook events at your agent's pace",
5
5
  "main": "lib/heartbeat.js",
6
6
  "bin": {