@moxxy/cli 1.2.11 → 1.3.1

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.11",
3
+ "version": "1.3.1",
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
@@ -132,8 +137,10 @@ async function routeCommand(client, command, rest) {
132
137
  return;
133
138
  }
134
139
 
135
- clearScreen();
136
- console.log(LOGO);
140
+ if (isInteractive()) {
141
+ clearScreen();
142
+ console.log(LOGO);
143
+ }
137
144
 
138
145
  switch (command) {
139
146
  case 'init':
@@ -169,6 +176,10 @@ async function routeCommand(client, command, rest) {
169
176
  case 'mcp':
170
177
  await runMcp(client, rest);
171
178
  break;
179
+ case 'webhooks':
180
+ case 'webhook':
181
+ await runWebhooks(client, rest);
182
+ break;
172
183
  case 'plugin':
173
184
  await runPlugin(client, rest);
174
185
  break;
@@ -247,6 +258,7 @@ async function main() {
247
258
  integrations: [
248
259
  { value: 'channel', label: 'Channel', hint: 'manage Telegram/Discord channels' },
249
260
  { value: 'mcp', label: 'MCP', hint: 'manage MCP servers for agents' },
261
+ { value: 'webhooks', label: 'Webhooks', hint: 'manage inbound webhooks for agents' },
250
262
  { value: 'heartbeat', label: 'Heartbeat', hint: 'schedule heartbeat rules' },
251
263
  ],
252
264
  providers: [
@@ -7,6 +7,7 @@ import { isInteractive, handleCancel, withSpinner, showResult, p } from '../ui.j
7
7
  import { spawn } from 'node:child_process';
8
8
  import { createHash, randomBytes } from 'node:crypto';
9
9
  import { createServer } from 'node:http';
10
+ import { BUILTIN_PROVIDERS } from './providers/index.js';
10
11
 
11
12
  const OPENAI_API_BASE = 'https://api.openai.com/v1';
12
13
  export const OPENAI_CODEX_PROVIDER_ID = 'openai-codex';
@@ -41,12 +42,12 @@ const ANTHROPIC_OAUTH_SESSION_MODE = 'anthropic_oauth_session';
41
42
  const OLLAMA_PROVIDER_ID = 'ollama';
42
43
  const OLLAMA_API_BASE = 'http://127.0.0.1:11434/v1';
43
44
  const OPENAI_CODEX_MODEL_IDS = [
45
+ 'gpt-5.4',
46
+ 'gpt-5.4-mini',
47
+ 'gpt-5.4-nano',
44
48
  'gpt-5.3-codex',
45
- 'gpt-5.2-codex',
46
- 'gpt-5.1-codex',
47
- 'gpt-5.1-codex-mini',
48
- 'gpt-5.1-codex-max',
49
49
  'gpt-5.2',
50
+ 'gpt-5.2-codex',
50
51
  'gpt-4.1',
51
52
  'gpt-4.1-mini',
52
53
  'gpt-4.1-nano',
@@ -813,6 +814,22 @@ async function fetchOllamaModels(apiBase = OLLAMA_API_BASE) {
813
814
  return [];
814
815
  }
815
816
 
817
+ export function getProviderCatalog() {
818
+ return BUILTIN_PROVIDERS.map(bp => ({
819
+ id: bp.id,
820
+ display_name: bp.display_name,
821
+ api_base: bp.api_base ?? null,
822
+ api_key_env: bp.api_key_env ?? null,
823
+ cli_binary: bp.cli_binary ?? null,
824
+ api_key_login: Boolean(bp.api_key_login),
825
+ oauth_login: Boolean(bp.oauth_login),
826
+ models: (bp.models ?? []).map(m => ({
827
+ model_id: m.model_id,
828
+ display_name: m.display_name ?? m.model_id,
829
+ })),
830
+ }));
831
+ }
832
+
816
833
  export async function resolveBuiltinProviderModels(builtin) {
817
834
  const fallbackModels = builtin.models.map(model => ({
818
835
  ...model,
@@ -1500,135 +1517,10 @@ export async function checkProviderCredentials(builtin, client) {
1500
1517
  }
1501
1518
 
1502
1519
  // ── Built-in Provider Catalog ────────────────────────────────────────────────
1520
+ // Each provider lives in its own file under ./providers/ — see providers/index.js.
1521
+ // Re-exported here for back-compat with existing importers.
1503
1522
 
1504
- export const BUILTIN_PROVIDERS = [
1505
- {
1506
- id: 'anthropic',
1507
- display_name: 'Anthropic',
1508
- api_key_env: 'ANTHROPIC_API_KEY',
1509
- api_base: 'https://api.anthropic.com',
1510
- api_key_login: true,
1511
- oauth_login: true,
1512
- models: [
1513
- { model_id: 'claude-sonnet-5-20260203', display_name: 'Claude Sonnet 5 "Fennec"' },
1514
- { model_id: 'claude-opus-4-20250514', display_name: 'Claude Opus 4' },
1515
- { model_id: 'claude-sonnet-4-20250514', display_name: 'Claude Sonnet 4' },
1516
- { model_id: 'claude-haiku-4-20250506', display_name: 'Claude Haiku 4' },
1517
- { model_id: 'claude-3-5-sonnet-20241022', display_name: 'Claude 3.5 Sonnet' },
1518
- { model_id: 'claude-3-5-haiku-20241022', display_name: 'Claude 3.5 Haiku' },
1519
- ],
1520
- },
1521
- {
1522
- id: 'openai',
1523
- display_name: 'OpenAI',
1524
- api_key_env: 'OPENAI_API_KEY',
1525
- api_base: OPENAI_API_BASE,
1526
- models: [
1527
- { model_id: 'gpt-5.2', display_name: 'GPT-5.2' },
1528
- { model_id: 'gpt-4.1', display_name: 'GPT-4.1' },
1529
- { model_id: 'gpt-4.1-mini', display_name: 'GPT-4.1 Mini' },
1530
- { model_id: 'gpt-4.1-nano', display_name: 'GPT-4.1 Nano' },
1531
- { model_id: 'o3', display_name: 'o3' },
1532
- { model_id: 'o4-mini', display_name: 'o4-mini' },
1533
- { model_id: 'gpt-4o', display_name: 'GPT-4o' },
1534
- { model_id: 'gpt-4o-mini', display_name: 'GPT-4o Mini' },
1535
- ],
1536
- },
1537
- {
1538
- id: OPENAI_CODEX_PROVIDER_ID,
1539
- display_name: OPENAI_CODEX_DISPLAY_NAME,
1540
- api_key_env: OPENAI_CODEX_SECRET_KEY_NAME,
1541
- api_base: OPENAI_API_BASE,
1542
- oauth_login: true,
1543
- models: [
1544
- { model_id: 'gpt-5.2', display_name: 'GPT-5.2' },
1545
- { model_id: 'gpt-4.1', display_name: 'GPT-4.1' },
1546
- { model_id: 'gpt-4.1-mini', display_name: 'GPT-4.1 Mini' },
1547
- { model_id: 'gpt-4.1-nano', display_name: 'GPT-4.1 Nano' },
1548
- { model_id: 'o3', display_name: 'o3' },
1549
- { model_id: 'o4-mini', display_name: 'o4-mini' },
1550
- { model_id: 'gpt-4o', display_name: 'GPT-4o' },
1551
- { model_id: 'gpt-4o-mini', display_name: 'GPT-4o Mini' },
1552
- ],
1553
- },
1554
- {
1555
- id: OLLAMA_PROVIDER_ID,
1556
- display_name: 'Ollama',
1557
- api_base: OLLAMA_API_BASE,
1558
- models: [
1559
- { model_id: 'qwen3:8b', display_name: 'Qwen 3 8B' },
1560
- { model_id: 'gemma3', display_name: 'Gemma 3' },
1561
- { model_id: 'gpt-oss:20b', display_name: 'GPT OSS 20B' },
1562
- ],
1563
- },
1564
- {
1565
- id: 'xai',
1566
- display_name: 'xAI',
1567
- api_key_env: 'XAI_API_KEY',
1568
- api_base: 'https://api.x.ai/v1',
1569
- models: [
1570
- { model_id: 'grok-4', display_name: 'Grok 4' },
1571
- { model_id: 'grok-3', display_name: 'Grok 3' },
1572
- { model_id: 'grok-3-mini', display_name: 'Grok 3 Mini' },
1573
- { model_id: 'grok-3-fast', display_name: 'Grok 3 Fast' },
1574
- { model_id: 'grok-2', display_name: 'Grok 2' },
1575
- ],
1576
- },
1577
- {
1578
- id: 'google',
1579
- display_name: 'Google Gemini',
1580
- api_key_env: 'GOOGLE_API_KEY',
1581
- api_base: 'https://generativelanguage.googleapis.com/v1beta',
1582
- models: [
1583
- { model_id: 'gemini-3.1-pro', display_name: 'Gemini 3.1 Pro' },
1584
- { model_id: 'gemini-2.5-pro', display_name: 'Gemini 2.5 Pro' },
1585
- { model_id: 'gemini-2.5-flash', display_name: 'Gemini 2.5 Flash' },
1586
- { model_id: 'gemini-2.0-flash', display_name: 'Gemini 2.0 Flash' },
1587
- ],
1588
- },
1589
- {
1590
- id: 'deepseek',
1591
- display_name: 'DeepSeek',
1592
- api_key_env: 'DEEPSEEK_API_KEY',
1593
- api_base: 'https://api.deepseek.com',
1594
- models: [
1595
- { model_id: 'deepseek-v4', display_name: 'DeepSeek V4' },
1596
- { model_id: 'deepseek-r1', display_name: 'DeepSeek R1' },
1597
- { model_id: 'deepseek-v3', display_name: 'DeepSeek V3' },
1598
- ],
1599
- },
1600
- {
1601
- id: 'zai',
1602
- display_name: 'ZAI',
1603
- api_key_env: 'ZAI_API_KEY',
1604
- api_base: 'https://api.zai.com/v1',
1605
- models: [
1606
- { model_id: 'zai-pro', display_name: 'ZAI Pro' },
1607
- { model_id: 'zai-standard', display_name: 'ZAI Standard' },
1608
- { model_id: 'zai-fast', display_name: 'ZAI Fast' },
1609
- ],
1610
- },
1611
- {
1612
- id: 'zai-plan',
1613
- display_name: 'ZAI Plan',
1614
- api_key_env: 'ZAI_API_KEY',
1615
- api_base: 'https://api.zai.com/v1',
1616
- models: [
1617
- { model_id: 'zai-plan-pro', display_name: 'ZAI Plan Pro' },
1618
- { model_id: 'zai-plan-standard', display_name: 'ZAI Plan Standard' },
1619
- ],
1620
- },
1621
- {
1622
- id: 'claude-cli',
1623
- display_name: 'Claude Code CLI',
1624
- cli_binary: 'claude',
1625
- models: [
1626
- { model_id: 'opus', display_name: 'Claude Opus' },
1627
- { model_id: 'sonnet', display_name: 'Claude Sonnet' },
1628
- { model_id: 'haiku', display_name: 'Claude Haiku' },
1629
- ],
1630
- },
1631
- ];
1523
+ export { BUILTIN_PROVIDERS };
1632
1524
 
1633
1525
  // ── CLI Command ──────────────────────────────────────────────────────────────
1634
1526
 
@@ -1644,6 +1536,7 @@ export async function runProvider(client, args) {
1644
1536
  { value: 'install', label: 'Install provider', hint: 'add a built-in or custom provider' },
1645
1537
  { value: 'login', label: 'Login provider', hint: 'OAuth/subscription login for supported providers' },
1646
1538
  { value: 'list', label: 'List providers', hint: 'show installed providers' },
1539
+ { value: 'catalog', label: 'Catalog', hint: 'list all built-in providers and their known models' },
1647
1540
  ],
1648
1541
  });
1649
1542
  handleCancel(action);
@@ -1670,6 +1563,23 @@ export async function runProvider(client, args) {
1670
1563
  return result;
1671
1564
  }
1672
1565
 
1566
+ case 'catalog': {
1567
+ const catalog = getProviderCatalog();
1568
+ if (isInteractive() && !flags.json) {
1569
+ p.intro('Built-in provider catalog');
1570
+ for (const pr of catalog) {
1571
+ p.log.info(`${pr.display_name} (${pr.id})`);
1572
+ for (const m of pr.models) {
1573
+ console.log(` - ${m.model_id}${m.display_name ? ` (${m.display_name})` : ''}`);
1574
+ }
1575
+ }
1576
+ p.outro(`${catalog.length} provider(s)`);
1577
+ } else {
1578
+ console.log(JSON.stringify(catalog, null, 2));
1579
+ }
1580
+ return catalog;
1581
+ }
1582
+
1673
1583
  case 'install': {
1674
1584
  if (isInteractive()) {
1675
1585
  return await installInteractive(client);
@@ -1683,7 +1593,7 @@ export async function runProvider(client, args) {
1683
1593
 
1684
1594
  default:
1685
1595
  if (!action) {
1686
- console.error('Usage: moxxy provider <install|login|list>');
1596
+ console.error('Usage: moxxy provider <install|login|list|catalog>');
1687
1597
  } else {
1688
1598
  console.error(`Unknown provider action: ${action}`);
1689
1599
  }
@@ -1916,7 +1826,7 @@ async function installNonInteractive(client, flags) {
1916
1826
 
1917
1827
  // Custom provider
1918
1828
  if (!providerId) {
1919
- throw new Error('Required: --id (provider id). Built-in: openai, openai-codex, anthropic, ollama, xai, zai, zai-plan, claude-cli');
1829
+ throw new Error('Required: --id (provider id). Built-in: openai, openai-codex, anthropic, ollama, xai, google, deepseek, zai, zai-coding-plan, claude-cli');
1920
1830
  }
1921
1831
 
1922
1832
  const displayName = flags.name || flags.display_name || providerId;
@@ -0,0 +1,18 @@
1
+ export default {
2
+ id: 'anthropic',
3
+ display_name: 'Anthropic',
4
+ api_key_env: 'ANTHROPIC_API_KEY',
5
+ api_base: 'https://api.anthropic.com',
6
+ api_key_login: true,
7
+ oauth_login: true,
8
+ models: [
9
+ { model_id: 'claude-opus-4-6', display_name: 'Claude Opus 4.6' },
10
+ { model_id: 'claude-sonnet-4-6', display_name: 'Claude Sonnet 4.6' },
11
+ { model_id: 'claude-haiku-4-5-20251001', display_name: 'Claude Haiku 4.5' },
12
+ { model_id: 'claude-opus-4-5-20251101', display_name: 'Claude Opus 4.5' },
13
+ { model_id: 'claude-sonnet-4-5-20250929', display_name: 'Claude Sonnet 4.5' },
14
+ { model_id: 'claude-opus-4-1-20250805', display_name: 'Claude Opus 4.1' },
15
+ { model_id: 'claude-sonnet-4-20250514', display_name: 'Claude Sonnet 4' },
16
+ { model_id: 'claude-opus-4-20250514', display_name: 'Claude Opus 4' },
17
+ ],
18
+ };
@@ -0,0 +1,10 @@
1
+ export default {
2
+ id: 'claude-cli',
3
+ display_name: 'Claude Code CLI',
4
+ cli_binary: 'claude',
5
+ models: [
6
+ { model_id: 'opus', display_name: 'Claude Opus' },
7
+ { model_id: 'sonnet', display_name: 'Claude Sonnet' },
8
+ { model_id: 'haiku', display_name: 'Claude Haiku' },
9
+ ],
10
+ };
@@ -0,0 +1,11 @@
1
+ export default {
2
+ id: 'deepseek',
3
+ display_name: 'DeepSeek',
4
+ api_key_env: 'DEEPSEEK_API_KEY',
5
+ api_base: 'https://api.deepseek.com',
6
+ models: [
7
+ { model_id: 'deepseek-v4', display_name: 'DeepSeek V4' },
8
+ { model_id: 'deepseek-r1', display_name: 'DeepSeek R1' },
9
+ { model_id: 'deepseek-v3', display_name: 'DeepSeek V3' },
10
+ ],
11
+ };
@@ -0,0 +1,12 @@
1
+ export default {
2
+ id: 'google',
3
+ display_name: 'Google Gemini',
4
+ api_key_env: 'GOOGLE_API_KEY',
5
+ api_base: 'https://generativelanguage.googleapis.com/v1beta',
6
+ models: [
7
+ { model_id: 'gemini-3.1-pro', display_name: 'Gemini 3.1 Pro' },
8
+ { model_id: 'gemini-2.5-pro', display_name: 'Gemini 2.5 Pro' },
9
+ { model_id: 'gemini-2.5-flash', display_name: 'Gemini 2.5 Flash' },
10
+ { model_id: 'gemini-2.0-flash', display_name: 'Gemini 2.0 Flash' },
11
+ ],
12
+ };
@@ -0,0 +1,23 @@
1
+ import anthropic from './anthropic.js';
2
+ import openai from './openai.js';
3
+ import openaiCodex from './openai-codex.js';
4
+ import ollama from './ollama.js';
5
+ import xai from './xai.js';
6
+ import google from './google.js';
7
+ import deepseek from './deepseek.js';
8
+ import zai from './zai.js';
9
+ import zaiCodingPlan from './zai-coding-plan.js';
10
+ import claudeCli from './claude-cli.js';
11
+
12
+ export const BUILTIN_PROVIDERS = [
13
+ anthropic,
14
+ openai,
15
+ openaiCodex,
16
+ ollama,
17
+ xai,
18
+ google,
19
+ deepseek,
20
+ zai,
21
+ zaiCodingPlan,
22
+ claudeCli,
23
+ ];
@@ -0,0 +1,10 @@
1
+ export default {
2
+ id: 'ollama',
3
+ display_name: 'Ollama',
4
+ api_base: 'http://127.0.0.1:11434/v1',
5
+ models: [
6
+ { model_id: 'qwen3:8b', display_name: 'Qwen 3 8B' },
7
+ { model_id: 'gemma3', display_name: 'Gemma 3' },
8
+ { model_id: 'gpt-oss:20b', display_name: 'GPT OSS 20B' },
9
+ ],
10
+ };
@@ -0,0 +1,22 @@
1
+ export default {
2
+ id: 'openai-codex',
3
+ display_name: 'OpenAI (Codex OAuth)',
4
+ api_key_env: 'OPENAI_CODEX_API_KEY',
5
+ api_base: 'https://api.openai.com/v1',
6
+ oauth_login: true,
7
+ models: [
8
+ { model_id: 'gpt-5.4', display_name: 'GPT-5.4' },
9
+ { model_id: 'gpt-5.4-mini', display_name: 'GPT-5.4 Mini' },
10
+ { model_id: 'gpt-5.4-nano', display_name: 'GPT-5.4 Nano' },
11
+ { model_id: 'gpt-5.3-codex', display_name: 'GPT-5.3 Codex' },
12
+ { model_id: 'gpt-5.2', display_name: 'GPT-5.2' },
13
+ { model_id: 'gpt-5.2-codex', display_name: 'GPT-5.2 Codex' },
14
+ { model_id: 'gpt-4.1', display_name: 'GPT-4.1' },
15
+ { model_id: 'gpt-4.1-mini', display_name: 'GPT-4.1 Mini' },
16
+ { model_id: 'gpt-4.1-nano', display_name: 'GPT-4.1 Nano' },
17
+ { model_id: 'o3', display_name: 'o3' },
18
+ { model_id: 'o4-mini', display_name: 'o4-mini' },
19
+ { model_id: 'gpt-4o', display_name: 'GPT-4o' },
20
+ { model_id: 'gpt-4o-mini', display_name: 'GPT-4o Mini' },
21
+ ],
22
+ };
@@ -0,0 +1,19 @@
1
+ export default {
2
+ id: 'openai',
3
+ display_name: 'OpenAI',
4
+ api_key_env: 'OPENAI_API_KEY',
5
+ api_base: 'https://api.openai.com/v1',
6
+ models: [
7
+ { model_id: 'gpt-5.4', display_name: 'GPT-5.4' },
8
+ { model_id: 'gpt-5.4-mini', display_name: 'GPT-5.4 Mini' },
9
+ { model_id: 'gpt-5.4-nano', display_name: 'GPT-5.4 Nano' },
10
+ { model_id: 'gpt-5.2', display_name: 'GPT-5.2' },
11
+ { model_id: 'gpt-4.1', display_name: 'GPT-4.1' },
12
+ { model_id: 'gpt-4.1-mini', display_name: 'GPT-4.1 Mini' },
13
+ { model_id: 'gpt-4.1-nano', display_name: 'GPT-4.1 Nano' },
14
+ { model_id: 'o3', display_name: 'o3' },
15
+ { model_id: 'o4-mini', display_name: 'o4-mini' },
16
+ { model_id: 'gpt-4o', display_name: 'GPT-4o' },
17
+ { model_id: 'gpt-4o-mini', display_name: 'GPT-4o Mini' },
18
+ ],
19
+ };
@@ -0,0 +1,13 @@
1
+ export default {
2
+ id: 'xai',
3
+ display_name: 'xAI',
4
+ api_key_env: 'XAI_API_KEY',
5
+ api_base: 'https://api.x.ai/v1',
6
+ models: [
7
+ { model_id: 'grok-4', display_name: 'Grok 4' },
8
+ { model_id: 'grok-3', display_name: 'Grok 3' },
9
+ { model_id: 'grok-3-mini', display_name: 'Grok 3 Mini' },
10
+ { model_id: 'grok-3-fast', display_name: 'Grok 3 Fast' },
11
+ { model_id: 'grok-2', display_name: 'Grok 2' },
12
+ ],
13
+ };
@@ -0,0 +1,13 @@
1
+ export default {
2
+ id: 'zai-coding-plan',
3
+ display_name: 'Z.AI GLM Coding Plan',
4
+ api_key_env: 'ZAI_API_KEY',
5
+ api_base: 'https://api.z.ai/api/coding/paas/v4',
6
+ models: [
7
+ { model_id: 'glm-5.1', display_name: 'GLM-5.1' },
8
+ { model_id: 'glm-4.7', display_name: 'GLM-4.7' },
9
+ { model_id: 'glm-4.6', display_name: 'GLM-4.6' },
10
+ { model_id: 'glm-4.5', display_name: 'GLM-4.5' },
11
+ { model_id: 'glm-4.5-air', display_name: 'GLM-4.5 Air' },
12
+ ],
13
+ };
@@ -0,0 +1,16 @@
1
+ export default {
2
+ id: 'zai',
3
+ display_name: 'Z.AI (GLM)',
4
+ api_key_env: 'ZAI_API_KEY',
5
+ api_base: 'https://api.z.ai/api/paas/v4',
6
+ models: [
7
+ { model_id: 'glm-5.1', display_name: 'GLM-5.1' },
8
+ { model_id: 'glm-4.7', display_name: 'GLM-4.7' },
9
+ { model_id: 'glm-4.6', display_name: 'GLM-4.6' },
10
+ { model_id: 'glm-4.5', display_name: 'GLM-4.5' },
11
+ { model_id: 'glm-4.5-x', display_name: 'GLM-4.5 X' },
12
+ { model_id: 'glm-4.5-air', display_name: 'GLM-4.5 Air' },
13
+ { model_id: 'glm-4.5-airx', display_name: 'GLM-4.5 AirX' },
14
+ { model_id: 'glm-4.5-flash', display_name: 'GLM-4.5 Flash' },
15
+ ],
16
+ };
@@ -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
@@ -99,6 +99,7 @@ Actions:
99
99
  install Add a built-in or custom provider
100
100
  login OAuth/subscription login (currently openai-codex)
101
101
  list Show installed providers
102
+ catalog Show all built-in providers and their known models (no gateway needed)
102
103
 
103
104
  Options:
104
105
  --id <id> Provider ID for install (e.g. openai, anthropic, xai)
@@ -125,6 +126,8 @@ Built-in providers:
125
126
 
126
127
  Examples:
127
128
  moxxy provider list
129
+ moxxy provider catalog
130
+ moxxy provider catalog --json
128
131
  moxxy provider install --id openai
129
132
  moxxy provider login --id openai-codex --method browser
130
133
  moxxy provider login --id openai-codex --method headless --no-browser
@@ -339,6 +342,33 @@ Examples:
339
342
  moxxy mcp remove --agent my-agent --id fs-server
340
343
  moxxy mcp test --agent my-agent --id fs-server`,
341
344
 
345
+ webhooks: `Usage: moxxy webhooks <action> [options]
346
+
347
+ Manage inbound webhooks for agents. External services POST JSON to the
348
+ returned URL; the webhook body (with {{placeholder}} variables) becomes the
349
+ agent's task when the webhook fires.
350
+
351
+ Actions:
352
+ list List webhooks for an agent
353
+ add Register a new inbound webhook
354
+ update Update a webhook's label, body, filter, or enabled flag
355
+ remove Delete a webhook
356
+
357
+ Options:
358
+ --agent <name> Agent name
359
+ --label <label> Webhook label (add/update)
360
+ --slug <slug> Webhook slug (update/remove; derived from label)
361
+ --secret <secret> Optional HMAC-SHA256 secret (add)
362
+ --event_filter <csv> Comma-separated event filter (add/update)
363
+ --body <markdown> Task instructions with {{placeholders}} (add/update)
364
+ --enabled <bool> Enable/disable flag (update)
365
+
366
+ Examples:
367
+ moxxy webhooks list --agent my-agent
368
+ moxxy webhooks add --agent my-agent --label "GitHub Push" --secret s3cret --event_filter push
369
+ moxxy webhooks update --agent my-agent --slug github-push --enabled false
370
+ moxxy webhooks remove --agent my-agent --slug github-push`,
371
+
342
372
  plugin: `Usage: moxxy plugin <action> [options]
343
373
 
344
374
  Manage CLI plugins that extend Moxxy functionality.