@moxxy/cli 0.0.12 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/README.md +278 -112
  2. package/bin/moxxy +10 -0
  3. package/package.json +36 -53
  4. package/src/api-client.js +286 -0
  5. package/src/cli.js +341 -0
  6. package/src/commands/agent.js +413 -0
  7. package/src/commands/auth.js +326 -0
  8. package/src/commands/channel.js +285 -0
  9. package/src/commands/doctor.js +261 -0
  10. package/src/commands/events.js +80 -0
  11. package/src/commands/gateway.js +428 -0
  12. package/src/commands/heartbeat.js +145 -0
  13. package/src/commands/init.js +767 -0
  14. package/src/commands/mcp.js +278 -0
  15. package/src/commands/plugin.js +583 -0
  16. package/src/commands/provider.js +1934 -0
  17. package/src/commands/skill.js +125 -0
  18. package/src/commands/template.js +237 -0
  19. package/src/commands/uninstall.js +196 -0
  20. package/src/commands/update.js +406 -0
  21. package/src/commands/vault.js +219 -0
  22. package/src/help.js +368 -0
  23. package/src/lib/plugin-registry.js +98 -0
  24. package/src/platform.js +40 -0
  25. package/src/sse-client.js +79 -0
  26. package/src/tui/action-wizards.js +130 -0
  27. package/src/tui/app.jsx +859 -0
  28. package/src/tui/components/action-picker.jsx +86 -0
  29. package/src/tui/components/chat-panel.jsx +120 -0
  30. package/src/tui/components/footer.jsx +13 -0
  31. package/src/tui/components/header.jsx +45 -0
  32. package/src/tui/components/input-area.jsx +384 -0
  33. package/src/tui/components/messages/ask-message.jsx +13 -0
  34. package/src/tui/components/messages/assistant-message.jsx +165 -0
  35. package/src/tui/components/messages/channel-message.jsx +18 -0
  36. package/src/tui/components/messages/event-message.jsx +22 -0
  37. package/src/tui/components/messages/hive-status.jsx +34 -0
  38. package/src/tui/components/messages/skill-message.jsx +31 -0
  39. package/src/tui/components/messages/system-message.jsx +12 -0
  40. package/src/tui/components/messages/thinking.jsx +25 -0
  41. package/src/tui/components/messages/tool-group.jsx +62 -0
  42. package/src/tui/components/messages/tool-message.jsx +66 -0
  43. package/src/tui/components/messages/user-message.jsx +12 -0
  44. package/src/tui/components/model-picker.jsx +138 -0
  45. package/src/tui/components/multiline-input.jsx +72 -0
  46. package/src/tui/events-handler.js +730 -0
  47. package/src/tui/helpers.js +59 -0
  48. package/src/tui/hooks/use-command-handler.js +451 -0
  49. package/src/tui/index.jsx +55 -0
  50. package/src/tui/input-utils.js +26 -0
  51. package/src/tui/markdown-renderer.js +66 -0
  52. package/src/tui/mcp-wizard.js +136 -0
  53. package/src/tui/model-picker.js +174 -0
  54. package/src/tui/slash-commands.js +26 -0
  55. package/src/tui/store.js +12 -0
  56. package/src/tui/theme.js +17 -0
  57. package/src/ui.js +109 -0
  58. package/bin/moxxy.js +0 -2
  59. package/dist/chunk-23LZYKQ6.mjs +0 -1131
  60. package/dist/chunk-2FZEA3NG.mjs +0 -457
  61. package/dist/chunk-3KDPLS22.mjs +0 -1131
  62. package/dist/chunk-3QRJTRBT.mjs +0 -1102
  63. package/dist/chunk-6DZX6EAA.mjs +0 -37
  64. package/dist/chunk-A4WRDUNY.mjs +0 -1242
  65. package/dist/chunk-C46NSEKG.mjs +0 -211
  66. package/dist/chunk-CAUXONEF.mjs +0 -1131
  67. package/dist/chunk-CPL5V56X.mjs +0 -1131
  68. package/dist/chunk-CTBVTTBG.mjs +0 -440
  69. package/dist/chunk-FHHLXTEZ.mjs +0 -1121
  70. package/dist/chunk-FXY3GPVA.mjs +0 -1126
  71. package/dist/chunk-GSNMMI3H.mjs +0 -530
  72. package/dist/chunk-HHOAOGUS.mjs +0 -1242
  73. package/dist/chunk-ITBO7BKI.mjs +0 -1243
  74. package/dist/chunk-J33O35WX.mjs +0 -532
  75. package/dist/chunk-N5JTPB6U.mjs +0 -820
  76. package/dist/chunk-NGVL4Q5C.mjs +0 -1102
  77. package/dist/chunk-Q2OCMNYI.mjs +0 -1131
  78. package/dist/chunk-QDVRLN6D.mjs +0 -1121
  79. package/dist/chunk-QO2JONHP.mjs +0 -1131
  80. package/dist/chunk-RVAPILHA.mjs +0 -1242
  81. package/dist/chunk-S7YBOV7E.mjs +0 -1131
  82. package/dist/chunk-SHIG6Y5L.mjs +0 -1074
  83. package/dist/chunk-SOFST2PV.mjs +0 -1242
  84. package/dist/chunk-SUNUYS6G.mjs +0 -1243
  85. package/dist/chunk-TMZWETMH.mjs +0 -1242
  86. package/dist/chunk-TYD7NMMI.mjs +0 -581
  87. package/dist/chunk-TYQ3YS42.mjs +0 -1068
  88. package/dist/chunk-UALWCJ7F.mjs +0 -1131
  89. package/dist/chunk-UQZKODNW.mjs +0 -1124
  90. package/dist/chunk-USC6R2ON.mjs +0 -1242
  91. package/dist/chunk-W32EQCVC.mjs +0 -823
  92. package/dist/chunk-WMB5ENMC.mjs +0 -1242
  93. package/dist/chunk-WNHA5JAP.mjs +0 -1242
  94. package/dist/cli-2AIWTL6F.mjs +0 -8
  95. package/dist/cli-2QKJ5UUL.mjs +0 -8
  96. package/dist/cli-4RIS6DQX.mjs +0 -8
  97. package/dist/cli-5RH4VBBL.mjs +0 -7
  98. package/dist/cli-7MK4YGOP.mjs +0 -7
  99. package/dist/cli-B4KH6MZI.mjs +0 -8
  100. package/dist/cli-CGO2LZ6Z.mjs +0 -8
  101. package/dist/cli-CVP26EL2.mjs +0 -8
  102. package/dist/cli-DDRVVNAV.mjs +0 -8
  103. package/dist/cli-E7U56QVQ.mjs +0 -8
  104. package/dist/cli-EQNRMLL3.mjs +0 -8
  105. package/dist/cli-F5RUHHH4.mjs +0 -8
  106. package/dist/cli-LX6FFSEF.mjs +0 -8
  107. package/dist/cli-LY74GWKR.mjs +0 -6
  108. package/dist/cli-MAT3ZJHI.mjs +0 -8
  109. package/dist/cli-NJXXTQYF.mjs +0 -8
  110. package/dist/cli-O4ZGFAZG.mjs +0 -8
  111. package/dist/cli-ORVLI3UQ.mjs +0 -8
  112. package/dist/cli-PV43ZVKA.mjs +0 -8
  113. package/dist/cli-REVD6ISM.mjs +0 -8
  114. package/dist/cli-TBX76KQX.mjs +0 -8
  115. package/dist/cli-THCGF7SQ.mjs +0 -8
  116. package/dist/cli-TLX5ENVM.mjs +0 -8
  117. package/dist/cli-TMNI5ZYE.mjs +0 -8
  118. package/dist/cli-TNJHCBQA.mjs +0 -6
  119. package/dist/cli-TUX22CZP.mjs +0 -8
  120. package/dist/cli-XJVH7EEP.mjs +0 -8
  121. package/dist/cli-XXOW4VXJ.mjs +0 -8
  122. package/dist/cli-XZ5RESNB.mjs +0 -6
  123. package/dist/cli-YCBYZ76Q.mjs +0 -8
  124. package/dist/cli-ZLMQCU7X.mjs +0 -8
  125. package/dist/dist-2VGKJRBH.mjs +0 -6820
  126. package/dist/dist-37BNX4QG.mjs +0 -7081
  127. package/dist/dist-7LTHRYKA.mjs +0 -11569
  128. package/dist/dist-7XJPQW5C.mjs +0 -6950
  129. package/dist/dist-AYMVOW7T.mjs +0 -7123
  130. package/dist/dist-BHUWCDRS.mjs +0 -7132
  131. package/dist/dist-FAXRJMEN.mjs +0 -6812
  132. package/dist/dist-HQGANM3P.mjs +0 -6976
  133. package/dist/dist-KATLOZQV.mjs +0 -7054
  134. package/dist/dist-KLSB6YHV.mjs +0 -6964
  135. package/dist/dist-LKIOZQ42.mjs +0 -17
  136. package/dist/dist-UYA4RJUH.mjs +0 -2792
  137. package/dist/dist-ZYHCBILM.mjs +0 -6993
  138. package/dist/index.d.mts +0 -23
  139. package/dist/index.d.ts +0 -23
  140. package/dist/index.js +0 -25531
  141. package/dist/index.mjs +0 -18
  142. package/dist/src-APP5P3UD.mjs +0 -1386
  143. package/dist/src-D5HMDDVE.mjs +0 -1324
  144. package/dist/src-EK3WD4AU.mjs +0 -1327
  145. package/dist/src-LSZFLMFN.mjs +0 -1400
  146. package/dist/src-T77DFTFP.mjs +0 -1407
  147. package/dist/src-WIOCZRAC.mjs +0 -1397
  148. package/dist/src-YK6CHCMW.mjs +0 -1400
@@ -0,0 +1,286 @@
1
+ const GATEWAY_DOWN_MSG = 'Gateway is not running. Start it with: moxxy gateway start';
2
+
3
+ function isConnectionError(err) {
4
+ const cause = err.cause;
5
+ if (cause && (cause.code === 'ECONNREFUSED' || cause.code === 'ECONNRESET')) return true;
6
+ const msg = (err.message || '').toLowerCase();
7
+ return msg.includes('econnrefused') || msg.includes('fetch failed')
8
+ || msg.includes('unable to connect') || msg.includes('connection refused');
9
+ }
10
+
11
+ function gatewayDownError() {
12
+ const error = new Error(GATEWAY_DOWN_MSG);
13
+ error.isGatewayDown = true;
14
+ return error;
15
+ }
16
+
17
+ function normalizeBaseUrl(baseUrl) {
18
+ const raw = (baseUrl || '').trim();
19
+ if (!raw) return 'http://localhost:3000';
20
+ const withoutTrailingSlash = raw.replace(/\/+$/, '');
21
+ const withoutV1Suffix = withoutTrailingSlash.replace(/\/v1$/i, '');
22
+ return withoutV1Suffix || withoutTrailingSlash;
23
+ }
24
+
25
+ function normalizeMcpServersResponse(payload) {
26
+ if (Array.isArray(payload)) return payload;
27
+ if (Array.isArray(payload?.servers)) return payload.servers;
28
+ return [];
29
+ }
30
+
31
+ /**
32
+ * Moxxy API client.
33
+ * Uses native fetch with bearer token injection.
34
+ */
35
+ export class ApiClient {
36
+ constructor(baseUrl, token, authMode = 'token') {
37
+ this.baseUrl = normalizeBaseUrl(baseUrl);
38
+ this.token = token;
39
+ this.authMode = authMode;
40
+ }
41
+
42
+ buildUrl(path) {
43
+ return `${this.baseUrl}${path}`;
44
+ }
45
+
46
+ buildRequest(path, method, body) {
47
+ const headers = {
48
+ 'content-type': 'application/json',
49
+ };
50
+ if (this.token) {
51
+ headers['authorization'] = `Bearer ${this.token}`;
52
+ }
53
+ return new Request(this.buildUrl(path), {
54
+ method,
55
+ headers,
56
+ body: body ? JSON.stringify(body) : undefined,
57
+ });
58
+ }
59
+
60
+ async request(path, method, body) {
61
+ const req = this.buildRequest(path, method, body);
62
+ let resp;
63
+ try {
64
+ resp = await fetch(req);
65
+ } catch (err) {
66
+ if (isConnectionError(err)) throw gatewayDownError();
67
+ throw err;
68
+ }
69
+ if (!resp.ok) {
70
+ const err = await resp.json().catch(() => ({
71
+ error: 'unknown',
72
+ message: resp.statusText,
73
+ }));
74
+ let msg = err.message || `API error ${resp.status}`;
75
+ if (resp.status === 401 && this.authMode === 'loopback') {
76
+ msg += '\nLoopback mode is enabled but the gateway rejected the request. Ensure the gateway is running with auth_mode: loopback.';
77
+ } else if (resp.status === 401 && !this.token) {
78
+ msg += '\nMOXXY_TOKEN is not set. Run `moxxy init` to create a token, or set it with:\n export MOXXY_TOKEN="<your-token>"';
79
+ } else if (resp.status === 401) {
80
+ msg += '\nYour token may be expired or revoked. Create a new one with: moxxy auth token create';
81
+ } else if (resp.status === 404) {
82
+ msg += `\nEndpoint not found (${path}). Verify MOXXY_API_URL points to a Moxxy gateway with /v1 routes.`;
83
+ }
84
+ const error = new Error(msg);
85
+ error.status = resp.status;
86
+ throw error;
87
+ }
88
+ const text = await resp.text();
89
+ if (!text) return {};
90
+ return JSON.parse(text);
91
+ }
92
+
93
+ async createToken(scopes, ttlSeconds, description) {
94
+ const body = { scopes };
95
+ if (ttlSeconds !== undefined && ttlSeconds !== null) body.ttl_seconds = ttlSeconds;
96
+ if (description) body.description = description;
97
+ return this.request('/v1/auth/tokens', 'POST', body);
98
+ }
99
+
100
+ async listTokens() {
101
+ return this.request('/v1/auth/tokens', 'GET');
102
+ }
103
+
104
+ async revokeToken(id) {
105
+ return this.request(`/v1/auth/tokens/${encodeURIComponent(id)}`, 'DELETE');
106
+ }
107
+
108
+ async createAgent(providerId, modelId, name, opts = {}) {
109
+ const body = {
110
+ provider_id: providerId,
111
+ model_id: modelId,
112
+ name,
113
+ ...opts,
114
+ };
115
+ return this.request('/v1/agents', 'POST', body);
116
+ }
117
+
118
+ async getAgent(id) {
119
+ return this.request(`/v1/agents/${encodeURIComponent(id)}`, 'GET');
120
+ }
121
+
122
+ async updateAgent(id, updates) {
123
+ return this.request(`/v1/agents/${encodeURIComponent(id)}`, 'PATCH', updates);
124
+ }
125
+
126
+ async startRun(agentId, task) {
127
+ return this.request(`/v1/agents/${encodeURIComponent(agentId)}/runs`, 'POST', { task });
128
+ }
129
+
130
+ async stopAgent(agentId) {
131
+ return this.request(`/v1/agents/${encodeURIComponent(agentId)}/stop`, 'POST');
132
+ }
133
+
134
+ async resetSession(agentId) {
135
+ return this.request(`/v1/agents/${encodeURIComponent(agentId)}/reset`, 'POST');
136
+ }
137
+
138
+ async deleteAgent(agentId) {
139
+ return this.request(`/v1/agents/${encodeURIComponent(agentId)}`, 'DELETE');
140
+ }
141
+
142
+ eventStreamUrl(filters = {}) {
143
+ const url = new URL('/v1/events/stream', this.baseUrl);
144
+ for (const [k, v] of Object.entries(filters)) {
145
+ if (v) url.searchParams.set(k, v);
146
+ }
147
+ return url.toString();
148
+ }
149
+
150
+ async getHistory(agentId, limit = 50) {
151
+ return this.request(`/v1/agents/${encodeURIComponent(agentId)}/history?limit=${limit}`, 'GET');
152
+ }
153
+
154
+ async listAgents() {
155
+ return this.request('/v1/agents', 'GET');
156
+ }
157
+
158
+ async listProviders() {
159
+ return this.request('/v1/providers', 'GET');
160
+ }
161
+
162
+ async listModels(providerId) {
163
+ return this.request(`/v1/providers/${encodeURIComponent(providerId)}/models`, 'GET');
164
+ }
165
+
166
+ async listSecrets() {
167
+ return this.request('/v1/vault/secrets', 'GET');
168
+ }
169
+
170
+ async createSecret(body) {
171
+ return this.request('/v1/vault/secrets', 'POST', body);
172
+ }
173
+
174
+ async deleteSecret(id) {
175
+ return this.request(`/v1/vault/secrets/${encodeURIComponent(id)}`, 'DELETE');
176
+ }
177
+
178
+ async respondToAsk(agentId, questionId, answer) {
179
+ return this.request(
180
+ `/v1/agents/${encodeURIComponent(agentId)}/ask-responses/${encodeURIComponent(questionId)}`,
181
+ 'POST',
182
+ { answer },
183
+ );
184
+ }
185
+
186
+ async listSkills(agentId) {
187
+ return this.request(`/v1/agents/${encodeURIComponent(agentId)}/skills`, 'GET');
188
+ }
189
+
190
+ async deleteSkill(agentId, skillId) {
191
+ return this.request(`/v1/agents/${encodeURIComponent(agentId)}/skills/${encodeURIComponent(skillId)}`, 'DELETE');
192
+ }
193
+
194
+ async disableHeartbeat(agentId, heartbeatId) {
195
+ return this.request(`/v1/agents/${encodeURIComponent(agentId)}/heartbeats/${encodeURIComponent(heartbeatId)}`, 'DELETE');
196
+ }
197
+
198
+ async listGrants() {
199
+ return this.request('/v1/vault/grants', 'GET');
200
+ }
201
+
202
+ async revokeGrant(grantId) {
203
+ return this.request(`/v1/vault/grants/${encodeURIComponent(grantId)}`, 'DELETE');
204
+ }
205
+
206
+ async installProvider(id, displayName, models) {
207
+ return this.request('/v1/providers', 'POST', {
208
+ id,
209
+ display_name: displayName,
210
+ models,
211
+ });
212
+ }
213
+
214
+ async listChannels() {
215
+ return this.request('/v1/channels', 'GET');
216
+ }
217
+
218
+ async createChannel(channelType, displayName, botToken, config) {
219
+ return this.request('/v1/channels', 'POST', {
220
+ channel_type: channelType,
221
+ display_name: displayName,
222
+ bot_token: botToken,
223
+ config,
224
+ });
225
+ }
226
+
227
+ async pairChannel(channelId, code, agentId) {
228
+ return this.request(`/v1/channels/${encodeURIComponent(channelId)}/pair`, 'POST', {
229
+ code,
230
+ agent_id: agentId,
231
+ });
232
+ }
233
+
234
+ async deleteChannel(channelId) {
235
+ return this.request(`/v1/channels/${encodeURIComponent(channelId)}`, 'DELETE');
236
+ }
237
+
238
+ async listChannelBindings(channelId) {
239
+ return this.request(`/v1/channels/${encodeURIComponent(channelId)}/bindings`, 'GET');
240
+ }
241
+
242
+ async listMcpServers(agentName) {
243
+ const payload = await this.request(`/v1/agents/${encodeURIComponent(agentName)}/mcp`, 'GET');
244
+ return normalizeMcpServersResponse(payload);
245
+ }
246
+
247
+ async addMcpServer(agentName, config) {
248
+ return this.request(`/v1/agents/${encodeURIComponent(agentName)}/mcp`, 'POST', config);
249
+ }
250
+
251
+ async removeMcpServer(agentName, serverId) {
252
+ return this.request(`/v1/agents/${encodeURIComponent(agentName)}/mcp/${encodeURIComponent(serverId)}`, 'DELETE');
253
+ }
254
+
255
+ async testMcpServer(agentName, serverId) {
256
+ return this.request(`/v1/agents/${encodeURIComponent(agentName)}/mcp/${encodeURIComponent(serverId)}/test`, 'POST');
257
+ }
258
+
259
+ async listTemplates() {
260
+ return this.request('/v1/templates', 'GET');
261
+ }
262
+
263
+ async getTemplate(slug) {
264
+ return this.request(`/v1/templates/${encodeURIComponent(slug)}`, 'GET');
265
+ }
266
+
267
+ async createTemplate(content) {
268
+ return this.request('/v1/templates', 'POST', { content });
269
+ }
270
+
271
+ async updateTemplate(slug, content) {
272
+ return this.request(`/v1/templates/${encodeURIComponent(slug)}`, 'PUT', { content });
273
+ }
274
+
275
+ async deleteTemplate(slug) {
276
+ return this.request(`/v1/templates/${encodeURIComponent(slug)}`, 'DELETE');
277
+ }
278
+
279
+ async setAgentTemplate(name, template) {
280
+ return this.request(`/v1/agents/${encodeURIComponent(name)}/template`, 'PATCH', { template });
281
+ }
282
+ }
283
+
284
+ export function createApiClient(baseUrl, token, authMode = 'token') {
285
+ return new ApiClient(baseUrl, token, authMode);
286
+ }
package/src/cli.js ADDED
@@ -0,0 +1,341 @@
1
+ import { createApiClient } from './api-client.js';
2
+ import { isInteractive, CancelledError, p } from './ui.js';
3
+ import { runInit, readAuthMode } from './commands/init.js';
4
+ import { runGateway } from './commands/gateway.js';
5
+ import { runAuth } from './commands/auth.js';
6
+ import { runProvider } from './commands/provider.js';
7
+ import { runAgent } from './commands/agent.js';
8
+ import { runSkill } from './commands/skill.js';
9
+ import { runTemplate } from './commands/template.js';
10
+ import { runVault } from './commands/vault.js';
11
+ import { runHeartbeat } from './commands/heartbeat.js';
12
+ import { runChannel } from './commands/channel.js';
13
+ import { runMcp } from './commands/mcp.js';
14
+ import { runEvents } from './commands/events.js';
15
+ import { runDoctor } from './commands/doctor.js';
16
+ import { runUpdate } from './commands/update.js';
17
+ import { runUninstall } from './commands/uninstall.js';
18
+ import { runPlugin } from './commands/plugin.js';
19
+ import { COMMAND_HELP, showHelp } from './help.js';
20
+ import chalk from 'chalk';
21
+ import { createInterface, cursorTo, clearScreenDown } from 'node:readline';
22
+ import pkg from '../package.json' with { type: 'json' };
23
+
24
+ const { version } = pkg;
25
+
26
+ export const LOGO = `\n\n\n\n\n
27
+ ███╗ ███╗ ██████╗ ██╗ ██╗██╗ ██╗██╗ ██╗
28
+ ████╗ ████║██╔═══██╗╚██╗██╔╝╚██╗██╔╝╚██╗ ██╔╝
29
+ ██╔████╔██║██║ ██║ ╚███╔╝ ╚███╔╝ ╚████╔╝
30
+ ██║╚██╔╝██║██║ ██║ ██╔██╗ ██╔██╗ ╚██╔╝
31
+ ██║ ╚═╝ ██║╚██████╔╝██╔╝ ██╗██╔╝ ██╗ ██║
32
+ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝
33
+ ${chalk.italic.dim('Agents that work while you sleep.')} ${chalk.gray(`v${version}`)}
34
+ `;
35
+
36
+ const HELP = `${LOGO}
37
+ Agentic Framework CLI
38
+
39
+ Usage:
40
+ moxxy Interactive menu
41
+ moxxy init First-time setup wizard
42
+ moxxy gateway start Start the gateway
43
+ moxxy gateway stop Stop the gateway
44
+ moxxy gateway restart Restart the gateway
45
+ moxxy gateway status Show gateway status
46
+ moxxy gateway logs Tail gateway logs
47
+ moxxy auth token create [--scopes <s>] [--ttl <n>] [--json]
48
+ moxxy auth token list [--json]
49
+ moxxy auth token revoke <id>
50
+ moxxy provider list
51
+ moxxy provider install --id <provider-id>
52
+ moxxy provider login --id openai-codex --method browser|headless
53
+ moxxy agent create --provider <p> --model <m> --workspace <w> [--json]
54
+ moxxy agent run --id <id> --task "task" [--json]
55
+ moxxy agent stop --id <id>
56
+ moxxy agent status --id <id> [--json]
57
+ moxxy skill create --agent <id> --content <c>
58
+ moxxy skill list --agent <id>
59
+ moxxy template list
60
+ moxxy template get <slug>
61
+ moxxy template create --content <c>
62
+ moxxy template remove <slug>
63
+ moxxy template assign --agent <id> --template <slug>
64
+ moxxy vault add --key <k> --backend <b>
65
+ moxxy vault grant --agent <id> --secret <id>
66
+ moxxy heartbeat set --agent <id> --interval <n> [--action_type <t>]
67
+ moxxy heartbeat list --agent <id>
68
+ moxxy channel create Create a channel (Telegram/Discord)
69
+ moxxy channel list List channels
70
+ moxxy channel pair --code <code> --agent <id> Pair a chat to an agent
71
+ moxxy channel delete <id> Delete a channel
72
+ moxxy channel bindings <id> List bindings for a channel
73
+ moxxy channel unbind <channel-id> <binding-id> Unbind a chat
74
+ moxxy mcp list --agent <name> List MCP servers
75
+ moxxy mcp add --agent <name> --id <id> --transport stdio --command <cmd> [--args ...]
76
+ moxxy mcp add --agent <name> --id <id> --transport sse --url <url>
77
+ moxxy mcp remove --agent <name> --id <id> Remove an MCP server
78
+ moxxy mcp test --agent <name> --id <id> Test an MCP server
79
+ moxxy plugin list List installed plugins
80
+ moxxy plugin install <package> Install a plugin
81
+ moxxy plugin start <name> Start a plugin
82
+ moxxy plugin stop <name> Stop a plugin
83
+ moxxy plugin restart <name> Restart a plugin
84
+ moxxy plugin update <name> Update a plugin to latest
85
+ moxxy plugin uninstall <name> Remove a plugin
86
+ moxxy plugin logs <name> Tail plugin logs
87
+ moxxy tui [--agent <id>] Full-screen chat interface
88
+ moxxy chat [--agent <id>] Alias for tui
89
+ moxxy events tail [--agent <id>] [--run <id>] [--json]
90
+ moxxy doctor Diagnose installation
91
+ moxxy update [--check] [--force] [--json] Check for and install updates
92
+ moxxy update --rollback Restore previous gateway version
93
+ moxxy uninstall Remove all Moxxy data
94
+
95
+ Environment:
96
+ MOXXY_API_URL API base URL (default: http://localhost:3000)
97
+ MOXXY_TOKEN API token for authentication
98
+ `.trim();
99
+
100
+
101
+ export function clearScreen() {
102
+ if (!isInteractive()) return;
103
+ const rows = process.stdout.rows - 2;
104
+ const blank = rows > 0 ? '\n'.repeat(rows) : '';
105
+ console.log(blank);
106
+ cursorTo(process.stdout, 0, 0);
107
+ clearScreenDown(process.stdout);
108
+ }
109
+
110
+ function waitForEnter() {
111
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
112
+ return new Promise(resolve => {
113
+ rl.question(chalk.dim('\n Press Enter to continue… '), () => {
114
+ rl.close();
115
+ resolve();
116
+ });
117
+ });
118
+ }
119
+
120
+ function hasHelpFlag(args) {
121
+ return args.includes('--help') || args.includes('-h');
122
+ }
123
+
124
+ async function routeCommand(client, command, rest) {
125
+ const helpKey = command === 'chat' ? 'tui' : command;
126
+ if (hasHelpFlag(rest) && COMMAND_HELP[helpKey]) {
127
+ showHelp(helpKey, p);
128
+ return;
129
+ }
130
+
131
+ clearScreen();
132
+ console.log(LOGO);
133
+
134
+ switch (command) {
135
+ case 'init':
136
+ await runInit(client, rest);
137
+ break;
138
+ case 'gateway':
139
+ await runGateway(client, rest);
140
+ break;
141
+ case 'auth':
142
+ await runAuth(client, rest);
143
+ break;
144
+ case 'provider':
145
+ await runProvider(client, rest);
146
+ break;
147
+ case 'agent':
148
+ await runAgent(client, rest);
149
+ break;
150
+ case 'skill':
151
+ await runSkill(client, rest);
152
+ break;
153
+ case 'template':
154
+ await runTemplate(client, rest);
155
+ break;
156
+ case 'vault':
157
+ await runVault(client, rest);
158
+ break;
159
+ case 'heartbeat':
160
+ await runHeartbeat(client, rest);
161
+ break;
162
+ case 'channel':
163
+ await runChannel(client, rest);
164
+ break;
165
+ case 'mcp':
166
+ await runMcp(client, rest);
167
+ break;
168
+ case 'plugin':
169
+ await runPlugin(client, rest);
170
+ break;
171
+ case 'tui':
172
+ case 'chat': {
173
+ const { startTui } = await import('./tui/index.jsx');
174
+ await startTui(client, rest);
175
+ break;
176
+ }
177
+ case 'events':
178
+ await runEvents(client, rest);
179
+ break;
180
+ case 'doctor':
181
+ await runDoctor(client, rest);
182
+ break;
183
+ case 'update':
184
+ await runUpdate(client, rest);
185
+ break;
186
+ case 'uninstall':
187
+ await runUninstall(client, rest);
188
+ break;
189
+ default:
190
+ console.error(`Unknown command: ${command}`);
191
+ console.log(HELP);
192
+ process.exitCode = 1;
193
+ }
194
+ }
195
+
196
+ async function main() {
197
+ const [,, command, ...rest] = process.argv;
198
+
199
+ if (command === '--version' || command === '-V') {
200
+ console.log(`moxxy v${version}`);
201
+ return;
202
+ }
203
+
204
+ if (command === 'help' || command === '--help' || command === '-h') {
205
+ console.log(HELP);
206
+ return;
207
+ }
208
+
209
+ const baseUrl = process.env.MOXXY_API_URL || 'http://localhost:3000';
210
+ const authMode = readAuthMode();
211
+ const token = process.env.MOXXY_TOKEN || '';
212
+ const client = createApiClient(baseUrl, token, authMode);
213
+
214
+ const MENU_GROUPS = {
215
+ setup: { label: 'Setup', hint: 'init, gateway, doctor' },
216
+ agents: { label: 'Agents', hint: 'agents, skills, templates' },
217
+ security: { label: 'Security', hint: 'auth tokens & secrets' },
218
+ integrations: { label: 'Integrations', hint: 'providers, channels, MCP, plugins' },
219
+ tools: { label: 'Tools', hint: 'events stream' },
220
+ system: { label: 'System', hint: 'update & uninstall' },
221
+ };
222
+
223
+ const SUBMENUS = {
224
+ setup: [
225
+ { value: 'init', label: 'Init', hint: 'first-time setup' },
226
+ { value: 'gateway', label: 'Gateway', hint: 'start/stop/manage gateway' },
227
+ { value: 'doctor', label: 'Doctor', hint: 'diagnose installation' },
228
+ ],
229
+ agents: [
230
+ { value: 'agent', label: 'Agent', hint: 'create & manage agents' },
231
+ { value: 'skill', label: 'Skill', hint: 'create & manage skills' },
232
+ { value: 'template', label: 'Template', hint: 'manage agent templates' },
233
+ ],
234
+ security: [
235
+ { value: 'auth', label: 'Auth', hint: 'manage API tokens' },
236
+ { value: 'vault', label: 'Vault', hint: 'manage secrets' },
237
+ ],
238
+ integrations: [
239
+ { value: 'provider', label: 'Provider', hint: 'list providers' },
240
+ { value: 'channel', label: 'Channel', hint: 'manage Telegram/Discord channels' },
241
+ { value: 'mcp', label: 'MCP', hint: 'manage MCP servers for agents' },
242
+ { value: 'plugin', label: 'Plugin', hint: 'manage plugins & extensions' },
243
+ { value: 'heartbeat', label: 'Heartbeat', hint: 'schedule heartbeat rules' },
244
+ ],
245
+ tools: [
246
+ { value: 'events', label: 'Events', hint: 'stream live events' },
247
+ ],
248
+ system: [
249
+ { value: 'update', label: 'Update', hint: 'check for and install updates' },
250
+ { value: 'uninstall', label: 'Uninstall', hint: 'remove all Moxxy data' },
251
+ ],
252
+ };
253
+
254
+ if (!command && isInteractive()) {
255
+ while (true) {
256
+ clearScreen();
257
+ console.log(LOGO);
258
+ p.intro();
259
+
260
+ const selected = await p.select({
261
+ message: 'What would you like to do?',
262
+ options: [
263
+ { value: 'tui', label: 'Chat', hint: 'full-screen TUI' },
264
+ ...Object.entries(MENU_GROUPS).map(([key, { label, hint }]) => ({
265
+ value: key, label, hint,
266
+ })),
267
+ ],
268
+ });
269
+
270
+ if (p.isCancel(selected)) {
271
+ p.cancel('Goodbye.');
272
+ break;
273
+ }
274
+
275
+ // Chat goes straight to the command
276
+ if (selected === 'tui') {
277
+ try {
278
+ await routeCommand(client, 'tui', []);
279
+ continue;
280
+ } catch (err) {
281
+ if (err instanceof CancelledError) continue;
282
+ if (err.isGatewayDown) p.log.info(err.message);
283
+ else p.log.error(err.message);
284
+ await waitForEnter();
285
+ process.exitCode = 1;
286
+ continue;
287
+ }
288
+ }
289
+
290
+ const submenu = SUBMENUS[selected];
291
+ if (!submenu) continue;
292
+
293
+ const subSelected = await p.select({
294
+ message: `${MENU_GROUPS[selected].label}`,
295
+ options: submenu,
296
+ });
297
+
298
+ if (p.isCancel(subSelected)) {
299
+ continue;
300
+ }
301
+
302
+ try {
303
+ await routeCommand(client, subSelected, []);
304
+ await waitForEnter();
305
+ } catch (err) {
306
+ if (err instanceof CancelledError) {
307
+ continue;
308
+ }
309
+ if (err.isGatewayDown) {
310
+ p.log.info(err.message);
311
+ } else {
312
+ p.log.error(err.message);
313
+ }
314
+ await waitForEnter();
315
+ process.exitCode = 1;
316
+ }
317
+ }
318
+ return;
319
+ }
320
+
321
+ if (!command) {
322
+ console.log(HELP);
323
+ return;
324
+ }
325
+
326
+ try {
327
+ await routeCommand(client, command, rest);
328
+ } catch (err) {
329
+ if (err instanceof CancelledError) {
330
+ return;
331
+ }
332
+ if (err.isGatewayDown) {
333
+ console.log(err.message);
334
+ } else {
335
+ console.error(`Error: ${err.message}`);
336
+ }
337
+ process.exitCode = 1;
338
+ }
339
+ }
340
+
341
+ main();