@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 +1 -1
- package/src/api-client.js +17 -0
- package/src/cli.js +14 -2
- package/src/commands/provider.js +44 -134
- package/src/commands/providers/anthropic.js +18 -0
- package/src/commands/providers/claude-cli.js +10 -0
- package/src/commands/providers/deepseek.js +11 -0
- package/src/commands/providers/google.js +12 -0
- package/src/commands/providers/index.js +23 -0
- package/src/commands/providers/ollama.js +10 -0
- package/src/commands/providers/openai-codex.js +22 -0
- package/src/commands/providers/openai.js +19 -0
- package/src/commands/providers/xai.js +13 -0
- package/src/commands/providers/zai-coding-plan.js +13 -0
- package/src/commands/providers/zai.js +16 -0
- package/src/commands/webhooks.js +238 -0
- package/src/help.js +30 -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
|
|
@@ -132,8 +137,10 @@ async function routeCommand(client, command, rest) {
|
|
|
132
137
|
return;
|
|
133
138
|
}
|
|
134
139
|
|
|
135
|
-
|
|
136
|
-
|
|
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: [
|
package/src/commands/provider.js
CHANGED
|
@@ -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
|
|
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.
|