@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 +1 -1
- package/src/api-client.js +17 -0
- package/src/cli.js +10 -0
- package/src/commands/webhooks.js +238 -0
- package/src/help.js +27 -0
package/package.json
CHANGED
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.
|