@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.
- package/README.md +278 -112
- package/bin/moxxy +10 -0
- package/package.json +36 -53
- package/src/api-client.js +286 -0
- package/src/cli.js +341 -0
- package/src/commands/agent.js +413 -0
- package/src/commands/auth.js +326 -0
- package/src/commands/channel.js +285 -0
- package/src/commands/doctor.js +261 -0
- package/src/commands/events.js +80 -0
- package/src/commands/gateway.js +428 -0
- package/src/commands/heartbeat.js +145 -0
- package/src/commands/init.js +767 -0
- package/src/commands/mcp.js +278 -0
- package/src/commands/plugin.js +583 -0
- package/src/commands/provider.js +1934 -0
- package/src/commands/skill.js +125 -0
- package/src/commands/template.js +237 -0
- package/src/commands/uninstall.js +196 -0
- package/src/commands/update.js +406 -0
- package/src/commands/vault.js +219 -0
- package/src/help.js +368 -0
- package/src/lib/plugin-registry.js +98 -0
- package/src/platform.js +40 -0
- package/src/sse-client.js +79 -0
- package/src/tui/action-wizards.js +130 -0
- package/src/tui/app.jsx +859 -0
- package/src/tui/components/action-picker.jsx +86 -0
- package/src/tui/components/chat-panel.jsx +120 -0
- package/src/tui/components/footer.jsx +13 -0
- package/src/tui/components/header.jsx +45 -0
- package/src/tui/components/input-area.jsx +384 -0
- package/src/tui/components/messages/ask-message.jsx +13 -0
- package/src/tui/components/messages/assistant-message.jsx +165 -0
- package/src/tui/components/messages/channel-message.jsx +18 -0
- package/src/tui/components/messages/event-message.jsx +22 -0
- package/src/tui/components/messages/hive-status.jsx +34 -0
- package/src/tui/components/messages/skill-message.jsx +31 -0
- package/src/tui/components/messages/system-message.jsx +12 -0
- package/src/tui/components/messages/thinking.jsx +25 -0
- package/src/tui/components/messages/tool-group.jsx +62 -0
- package/src/tui/components/messages/tool-message.jsx +66 -0
- package/src/tui/components/messages/user-message.jsx +12 -0
- package/src/tui/components/model-picker.jsx +138 -0
- package/src/tui/components/multiline-input.jsx +72 -0
- package/src/tui/events-handler.js +730 -0
- package/src/tui/helpers.js +59 -0
- package/src/tui/hooks/use-command-handler.js +451 -0
- package/src/tui/index.jsx +55 -0
- package/src/tui/input-utils.js +26 -0
- package/src/tui/markdown-renderer.js +66 -0
- package/src/tui/mcp-wizard.js +136 -0
- package/src/tui/model-picker.js +174 -0
- package/src/tui/slash-commands.js +26 -0
- package/src/tui/store.js +12 -0
- package/src/tui/theme.js +17 -0
- package/src/ui.js +109 -0
- package/bin/moxxy.js +0 -2
- package/dist/chunk-23LZYKQ6.mjs +0 -1131
- package/dist/chunk-2FZEA3NG.mjs +0 -457
- package/dist/chunk-3KDPLS22.mjs +0 -1131
- package/dist/chunk-3QRJTRBT.mjs +0 -1102
- package/dist/chunk-6DZX6EAA.mjs +0 -37
- package/dist/chunk-A4WRDUNY.mjs +0 -1242
- package/dist/chunk-C46NSEKG.mjs +0 -211
- package/dist/chunk-CAUXONEF.mjs +0 -1131
- package/dist/chunk-CPL5V56X.mjs +0 -1131
- package/dist/chunk-CTBVTTBG.mjs +0 -440
- package/dist/chunk-FHHLXTEZ.mjs +0 -1121
- package/dist/chunk-FXY3GPVA.mjs +0 -1126
- package/dist/chunk-GSNMMI3H.mjs +0 -530
- package/dist/chunk-HHOAOGUS.mjs +0 -1242
- package/dist/chunk-ITBO7BKI.mjs +0 -1243
- package/dist/chunk-J33O35WX.mjs +0 -532
- package/dist/chunk-N5JTPB6U.mjs +0 -820
- package/dist/chunk-NGVL4Q5C.mjs +0 -1102
- package/dist/chunk-Q2OCMNYI.mjs +0 -1131
- package/dist/chunk-QDVRLN6D.mjs +0 -1121
- package/dist/chunk-QO2JONHP.mjs +0 -1131
- package/dist/chunk-RVAPILHA.mjs +0 -1242
- package/dist/chunk-S7YBOV7E.mjs +0 -1131
- package/dist/chunk-SHIG6Y5L.mjs +0 -1074
- package/dist/chunk-SOFST2PV.mjs +0 -1242
- package/dist/chunk-SUNUYS6G.mjs +0 -1243
- package/dist/chunk-TMZWETMH.mjs +0 -1242
- package/dist/chunk-TYD7NMMI.mjs +0 -581
- package/dist/chunk-TYQ3YS42.mjs +0 -1068
- package/dist/chunk-UALWCJ7F.mjs +0 -1131
- package/dist/chunk-UQZKODNW.mjs +0 -1124
- package/dist/chunk-USC6R2ON.mjs +0 -1242
- package/dist/chunk-W32EQCVC.mjs +0 -823
- package/dist/chunk-WMB5ENMC.mjs +0 -1242
- package/dist/chunk-WNHA5JAP.mjs +0 -1242
- package/dist/cli-2AIWTL6F.mjs +0 -8
- package/dist/cli-2QKJ5UUL.mjs +0 -8
- package/dist/cli-4RIS6DQX.mjs +0 -8
- package/dist/cli-5RH4VBBL.mjs +0 -7
- package/dist/cli-7MK4YGOP.mjs +0 -7
- package/dist/cli-B4KH6MZI.mjs +0 -8
- package/dist/cli-CGO2LZ6Z.mjs +0 -8
- package/dist/cli-CVP26EL2.mjs +0 -8
- package/dist/cli-DDRVVNAV.mjs +0 -8
- package/dist/cli-E7U56QVQ.mjs +0 -8
- package/dist/cli-EQNRMLL3.mjs +0 -8
- package/dist/cli-F5RUHHH4.mjs +0 -8
- package/dist/cli-LX6FFSEF.mjs +0 -8
- package/dist/cli-LY74GWKR.mjs +0 -6
- package/dist/cli-MAT3ZJHI.mjs +0 -8
- package/dist/cli-NJXXTQYF.mjs +0 -8
- package/dist/cli-O4ZGFAZG.mjs +0 -8
- package/dist/cli-ORVLI3UQ.mjs +0 -8
- package/dist/cli-PV43ZVKA.mjs +0 -8
- package/dist/cli-REVD6ISM.mjs +0 -8
- package/dist/cli-TBX76KQX.mjs +0 -8
- package/dist/cli-THCGF7SQ.mjs +0 -8
- package/dist/cli-TLX5ENVM.mjs +0 -8
- package/dist/cli-TMNI5ZYE.mjs +0 -8
- package/dist/cli-TNJHCBQA.mjs +0 -6
- package/dist/cli-TUX22CZP.mjs +0 -8
- package/dist/cli-XJVH7EEP.mjs +0 -8
- package/dist/cli-XXOW4VXJ.mjs +0 -8
- package/dist/cli-XZ5RESNB.mjs +0 -6
- package/dist/cli-YCBYZ76Q.mjs +0 -8
- package/dist/cli-ZLMQCU7X.mjs +0 -8
- package/dist/dist-2VGKJRBH.mjs +0 -6820
- package/dist/dist-37BNX4QG.mjs +0 -7081
- package/dist/dist-7LTHRYKA.mjs +0 -11569
- package/dist/dist-7XJPQW5C.mjs +0 -6950
- package/dist/dist-AYMVOW7T.mjs +0 -7123
- package/dist/dist-BHUWCDRS.mjs +0 -7132
- package/dist/dist-FAXRJMEN.mjs +0 -6812
- package/dist/dist-HQGANM3P.mjs +0 -6976
- package/dist/dist-KATLOZQV.mjs +0 -7054
- package/dist/dist-KLSB6YHV.mjs +0 -6964
- package/dist/dist-LKIOZQ42.mjs +0 -17
- package/dist/dist-UYA4RJUH.mjs +0 -2792
- package/dist/dist-ZYHCBILM.mjs +0 -6993
- package/dist/index.d.mts +0 -23
- package/dist/index.d.ts +0 -23
- package/dist/index.js +0 -25531
- package/dist/index.mjs +0 -18
- package/dist/src-APP5P3UD.mjs +0 -1386
- package/dist/src-D5HMDDVE.mjs +0 -1324
- package/dist/src-EK3WD4AU.mjs +0 -1327
- package/dist/src-LSZFLMFN.mjs +0 -1400
- package/dist/src-T77DFTFP.mjs +0 -1407
- package/dist/src-WIOCZRAC.mjs +0 -1397
- 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();
|