@moxxy/cli 1.2.10 → 1.3.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moxxy/cli",
3
- "version": "1.2.10",
3
+ "version": "1.3.0",
4
4
  "description": "CLI for the Moxxy agentic framework — manage agents, skills, plugins, channels, and vaults from the terminal",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/api-client.js CHANGED
@@ -256,6 +256,23 @@ export class ApiClient {
256
256
  return this.request(`/v1/agents/${encodeURIComponent(agentName)}/mcp/${encodeURIComponent(serverId)}/test`, 'POST');
257
257
  }
258
258
 
259
+ async listWebhooks(agentName) {
260
+ const payload = await this.request(`/v1/agents/${encodeURIComponent(agentName)}/webhooks`, 'GET');
261
+ return Array.isArray(payload) ? payload : [];
262
+ }
263
+
264
+ async createWebhook(agentName, config) {
265
+ return this.request(`/v1/agents/${encodeURIComponent(agentName)}/webhooks`, 'POST', config);
266
+ }
267
+
268
+ async updateWebhook(agentName, slug, patch) {
269
+ return this.request(`/v1/agents/${encodeURIComponent(agentName)}/webhooks/${encodeURIComponent(slug)}`, 'PATCH', patch);
270
+ }
271
+
272
+ async deleteWebhook(agentName, slug) {
273
+ return this.request(`/v1/agents/${encodeURIComponent(agentName)}/webhooks/${encodeURIComponent(slug)}`, 'DELETE');
274
+ }
275
+
259
276
  async listTemplates() {
260
277
  return this.request('/v1/templates', 'GET');
261
278
  }
package/src/cli.js CHANGED
@@ -11,6 +11,7 @@ import { runVault } from './commands/vault.js';
11
11
  import { runHeartbeat } from './commands/heartbeat.js';
12
12
  import { runChannel } from './commands/channel.js';
13
13
  import { runMcp } from './commands/mcp.js';
14
+ import { runWebhooks } from './commands/webhooks.js';
14
15
  import { runEvents } from './commands/events.js';
15
16
  import { runDoctor } from './commands/doctor.js';
16
17
  import { runUpdate } from './commands/update.js';
@@ -77,6 +78,10 @@ Usage:
77
78
  moxxy mcp add --agent <name> --id <id> --transport sse --url <url>
78
79
  moxxy mcp remove --agent <name> --id <id> Remove an MCP server
79
80
  moxxy mcp test --agent <name> --id <id> Test an MCP server
81
+ moxxy webhooks list --agent <name> List webhooks for an agent
82
+ moxxy webhooks add --agent <name> --label <l> [--secret <s>] [--event_filter <ef>] [--body <b>]
83
+ moxxy webhooks update --agent <name> --slug <s> [--label <l>] [--enabled <bool>] [--event_filter <ef>] [--body <b>]
84
+ moxxy webhooks remove --agent <name> --slug <s> Remove a webhook
80
85
  moxxy plugin list List installed plugins
81
86
  moxxy plugin install <package> Install a plugin
82
87
  moxxy plugin start <name> Start a plugin
@@ -169,6 +174,10 @@ async function routeCommand(client, command, rest) {
169
174
  case 'mcp':
170
175
  await runMcp(client, rest);
171
176
  break;
177
+ case 'webhooks':
178
+ case 'webhook':
179
+ await runWebhooks(client, rest);
180
+ break;
172
181
  case 'plugin':
173
182
  await runPlugin(client, rest);
174
183
  break;
@@ -247,6 +256,7 @@ async function main() {
247
256
  integrations: [
248
257
  { value: 'channel', label: 'Channel', hint: 'manage Telegram/Discord channels' },
249
258
  { value: 'mcp', label: 'MCP', hint: 'manage MCP servers for agents' },
259
+ { value: 'webhooks', label: 'Webhooks', hint: 'manage inbound webhooks for agents' },
250
260
  { value: 'heartbeat', label: 'Heartbeat', hint: 'schedule heartbeat rules' },
251
261
  ],
252
262
  providers: [
@@ -0,0 +1,238 @@
1
+ /**
2
+ * Webhook commands: list/add/update/remove.
3
+ */
4
+ import { parseFlags } from './auth.js';
5
+ import { isInteractive, handleCancel, withSpinner, showResult, pickAgent, p } from '../ui.js';
6
+
7
+ function parseBool(val) {
8
+ if (val === undefined || val === null) return undefined;
9
+ const s = String(val).toLowerCase();
10
+ if (['true', '1', 'yes', 'on'].includes(s)) return true;
11
+ if (['false', '0', 'no', 'off'].includes(s)) return false;
12
+ return undefined;
13
+ }
14
+
15
+ export async function runWebhooks(client, args) {
16
+ let [action, ...rest] = args;
17
+ const flags = parseFlags(rest);
18
+
19
+ if (!['list', 'add', 'update', 'remove'].includes(action) && isInteractive()) {
20
+ action = await p.select({
21
+ message: 'Webhook action',
22
+ options: [
23
+ { value: 'list', label: 'List webhooks', hint: 'show webhooks for an agent' },
24
+ { value: 'add', label: 'Add webhook', hint: 'register a new inbound webhook' },
25
+ { value: 'update', label: 'Update webhook', hint: 'change label/body/filter/enabled' },
26
+ { value: 'remove', label: 'Remove webhook', hint: 'delete an inbound webhook' },
27
+ ],
28
+ });
29
+ handleCancel(action);
30
+ }
31
+
32
+ switch (action) {
33
+ case 'list': {
34
+ let agentName = flags.agent;
35
+ if (!agentName && isInteractive()) {
36
+ agentName = await pickAgent(client, 'Select agent');
37
+ }
38
+ if (!agentName) throw new Error('Required: --agent');
39
+
40
+ const webhooks = isInteractive()
41
+ ? await withSpinner('Fetching webhooks...', () =>
42
+ client.listWebhooks(agentName), 'Webhooks loaded.')
43
+ : await client.listWebhooks(agentName);
44
+
45
+ if (isInteractive()) {
46
+ if (Array.isArray(webhooks) && webhooks.length > 0) {
47
+ for (const w of webhooks) {
48
+ const state = w.enabled ? 'enabled' : 'disabled';
49
+ const filter = w.event_filter ? ` filter=${w.event_filter}` : '';
50
+ p.log.info(` ${w.slug} [${state}]${filter} ${w.url}`);
51
+ }
52
+ } else {
53
+ p.log.warn('No webhooks configured for this agent.');
54
+ }
55
+ } else {
56
+ console.log(JSON.stringify(webhooks, null, 2));
57
+ }
58
+ return webhooks;
59
+ }
60
+
61
+ case 'add': {
62
+ let agentName = flags.agent;
63
+ let label = flags.label;
64
+
65
+ if ((!agentName || !label) && isInteractive()) {
66
+ if (!agentName) {
67
+ agentName = await pickAgent(client, 'Select agent for webhook');
68
+ }
69
+
70
+ if (!label) {
71
+ label = handleCancel(await p.text({
72
+ message: 'Webhook label',
73
+ placeholder: 'GitHub Push Events',
74
+ validate: (val) => { if (!val) return 'Label is required'; },
75
+ }));
76
+ }
77
+
78
+ const body = { label };
79
+
80
+ const secret = flags.secret || handleCancel(await p.text({
81
+ message: 'HMAC secret (optional, leave empty for token-only)',
82
+ placeholder: '',
83
+ }));
84
+ if (secret) body.secret = secret;
85
+
86
+ const eventFilter = flags.event_filter || handleCancel(await p.text({
87
+ message: 'Event filter (optional, comma-separated)',
88
+ placeholder: 'push,pull_request',
89
+ }));
90
+ if (eventFilter) body.event_filter = eventFilter;
91
+
92
+ const taskBody = flags.body || handleCancel(await p.text({
93
+ message: 'Task instructions (optional markdown with {{placeholders}})',
94
+ placeholder: 'Parse commits from {{body.commits}}...',
95
+ }));
96
+ if (taskBody) body.body = taskBody;
97
+
98
+ const result = await withSpinner('Creating webhook...', () =>
99
+ client.createWebhook(agentName, body), 'Webhook created.');
100
+
101
+ showResult('Webhook Created', {
102
+ Agent: agentName,
103
+ Label: label,
104
+ Slug: result.slug,
105
+ URL: result.url,
106
+ Token: result.token,
107
+ });
108
+
109
+ return result;
110
+ }
111
+
112
+ if (!agentName || !label) {
113
+ throw new Error('Required: --agent, --label');
114
+ }
115
+
116
+ const body = { label };
117
+ if (flags.secret) body.secret = flags.secret;
118
+ if (flags.event_filter) body.event_filter = flags.event_filter;
119
+ if (flags.body) body.body = flags.body;
120
+
121
+ const result = await client.createWebhook(agentName, body);
122
+ console.log(JSON.stringify(result, null, 2));
123
+ return result;
124
+ }
125
+
126
+ case 'update': {
127
+ let agentName = flags.agent;
128
+ let slug = flags.slug;
129
+
130
+ if ((!agentName || !slug) && isInteractive()) {
131
+ if (!agentName) {
132
+ agentName = await pickAgent(client, 'Select agent');
133
+ }
134
+
135
+ if (!slug) {
136
+ const webhooks = await withSpinner('Fetching webhooks...', () =>
137
+ client.listWebhooks(agentName), 'Webhooks loaded.');
138
+ if (!Array.isArray(webhooks) || webhooks.length === 0) {
139
+ p.log.warn('No webhooks to update.');
140
+ return;
141
+ }
142
+ slug = handleCancel(await p.select({
143
+ message: 'Select webhook to update',
144
+ options: webhooks.map(w => ({
145
+ value: w.slug,
146
+ label: w.label,
147
+ hint: w.enabled ? 'enabled' : 'disabled',
148
+ })),
149
+ }));
150
+ }
151
+ }
152
+
153
+ if (!agentName || !slug) throw new Error('Required: --agent, --slug');
154
+
155
+ const patch = {};
156
+ if (flags.label !== undefined) patch.label = flags.label;
157
+ if (flags.event_filter !== undefined) patch.event_filter = flags.event_filter;
158
+ if (flags.body !== undefined) patch.body = flags.body;
159
+ const enabled = parseBool(flags.enabled);
160
+ if (enabled !== undefined) patch.enabled = enabled;
161
+
162
+ if (Object.keys(patch).length === 0) {
163
+ throw new Error('Nothing to update. Pass at least one of: --label, --event_filter, --body, --enabled');
164
+ }
165
+
166
+ const result = isInteractive()
167
+ ? await withSpinner('Updating webhook...', () =>
168
+ client.updateWebhook(agentName, slug, patch), 'Webhook updated.')
169
+ : await client.updateWebhook(agentName, slug, patch);
170
+
171
+ if (isInteractive()) {
172
+ showResult('Webhook Updated', {
173
+ Agent: agentName,
174
+ Slug: result.slug,
175
+ Label: result.label,
176
+ Enabled: String(result.enabled),
177
+ });
178
+ } else {
179
+ console.log(JSON.stringify(result, null, 2));
180
+ }
181
+ return result;
182
+ }
183
+
184
+ case 'remove': {
185
+ let agentName = flags.agent;
186
+ let slug = flags.slug;
187
+
188
+ if ((!agentName || !slug) && isInteractive()) {
189
+ if (!agentName) {
190
+ agentName = await pickAgent(client, 'Select agent');
191
+ }
192
+
193
+ if (!slug) {
194
+ const webhooks = await withSpinner('Fetching webhooks...', () =>
195
+ client.listWebhooks(agentName), 'Webhooks loaded.');
196
+ if (!Array.isArray(webhooks) || webhooks.length === 0) {
197
+ p.log.warn('No webhooks to remove.');
198
+ return;
199
+ }
200
+ slug = handleCancel(await p.select({
201
+ message: 'Select webhook to remove',
202
+ options: webhooks.map(w => ({
203
+ value: w.slug,
204
+ label: w.label,
205
+ hint: w.enabled ? 'enabled' : 'disabled',
206
+ })),
207
+ }));
208
+ }
209
+
210
+ const confirmed = await p.confirm({
211
+ message: `Remove webhook "${slug}"?`,
212
+ initialValue: false,
213
+ });
214
+ handleCancel(confirmed);
215
+ if (!confirmed) {
216
+ p.log.info('Cancelled.');
217
+ return;
218
+ }
219
+
220
+ await withSpinner('Removing webhook...', () =>
221
+ client.deleteWebhook(agentName, slug), 'Webhook removed.');
222
+ return;
223
+ }
224
+
225
+ if (!agentName || !slug) throw new Error('Required: --agent, --slug');
226
+
227
+ await client.deleteWebhook(agentName, slug);
228
+ console.log(`Webhook ${slug} removed.`);
229
+ break;
230
+ }
231
+
232
+ default: {
233
+ const { showHelp } = await import('../help.js');
234
+ showHelp('webhooks', p);
235
+ break;
236
+ }
237
+ }
238
+ }
package/src/help.js CHANGED
@@ -339,6 +339,33 @@ Examples:
339
339
  moxxy mcp remove --agent my-agent --id fs-server
340
340
  moxxy mcp test --agent my-agent --id fs-server`,
341
341
 
342
+ webhooks: `Usage: moxxy webhooks <action> [options]
343
+
344
+ Manage inbound webhooks for agents. External services POST JSON to the
345
+ returned URL; the webhook body (with {{placeholder}} variables) becomes the
346
+ agent's task when the webhook fires.
347
+
348
+ Actions:
349
+ list List webhooks for an agent
350
+ add Register a new inbound webhook
351
+ update Update a webhook's label, body, filter, or enabled flag
352
+ remove Delete a webhook
353
+
354
+ Options:
355
+ --agent <name> Agent name
356
+ --label <label> Webhook label (add/update)
357
+ --slug <slug> Webhook slug (update/remove; derived from label)
358
+ --secret <secret> Optional HMAC-SHA256 secret (add)
359
+ --event_filter <csv> Comma-separated event filter (add/update)
360
+ --body <markdown> Task instructions with {{placeholders}} (add/update)
361
+ --enabled <bool> Enable/disable flag (update)
362
+
363
+ Examples:
364
+ moxxy webhooks list --agent my-agent
365
+ moxxy webhooks add --agent my-agent --label "GitHub Push" --secret s3cret --event_filter push
366
+ moxxy webhooks update --agent my-agent --slug github-push --enabled false
367
+ moxxy webhooks remove --agent my-agent --slug github-push`,
368
+
342
369
  plugin: `Usage: moxxy plugin <action> [options]
343
370
 
344
371
  Manage CLI plugins that extend Moxxy functionality.