@moxxy/cli 0.0.12 → 0.1.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/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 +349 -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 +954 -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/settings.js +224 -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 +392 -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,326 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth commands: token create/list/revoke.
|
|
3
|
+
* Supports both interactive and non-interactive (flag-based) modes.
|
|
4
|
+
*/
|
|
5
|
+
import { isInteractive, handleCancel, withSpinner, showResult, p } from '../ui.js';
|
|
6
|
+
import { resetTokens } from './init.js';
|
|
7
|
+
|
|
8
|
+
const VALID_SCOPES = [
|
|
9
|
+
'*',
|
|
10
|
+
'agents:read', 'agents:write', 'runs:write',
|
|
11
|
+
'vault:read', 'vault:write', 'tokens:admin', 'events:read',
|
|
12
|
+
'channels:read', 'channels:write',
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
const MULTI_VALUE_FLAGS = new Set(['model']);
|
|
16
|
+
|
|
17
|
+
export function parseFlags(args) {
|
|
18
|
+
const flags = {};
|
|
19
|
+
for (let i = 0; i < args.length; i++) {
|
|
20
|
+
const arg = args[i];
|
|
21
|
+
if (arg.startsWith('--')) {
|
|
22
|
+
const rest = arg.slice(2);
|
|
23
|
+
const eqIdx = rest.indexOf('=');
|
|
24
|
+
let key, value;
|
|
25
|
+
if (eqIdx !== -1) {
|
|
26
|
+
key = rest.slice(0, eqIdx);
|
|
27
|
+
value = rest.slice(eqIdx + 1);
|
|
28
|
+
} else if (i + 1 < args.length) {
|
|
29
|
+
key = rest;
|
|
30
|
+
value = args[i + 1];
|
|
31
|
+
i++;
|
|
32
|
+
} else {
|
|
33
|
+
key = rest;
|
|
34
|
+
value = true;
|
|
35
|
+
}
|
|
36
|
+
if (MULTI_VALUE_FLAGS.has(key) && value !== true) {
|
|
37
|
+
if (!Array.isArray(flags[key])) {
|
|
38
|
+
flags[key] = flags[key] !== undefined ? [flags[key]] : [];
|
|
39
|
+
}
|
|
40
|
+
flags[key].push(value);
|
|
41
|
+
} else {
|
|
42
|
+
flags[key] = value;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return flags;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function parseAuthCommand(args) {
|
|
50
|
+
const [sub, action, ...rest] = args;
|
|
51
|
+
const flags = parseFlags(rest);
|
|
52
|
+
|
|
53
|
+
if (sub === 'token' && action === 'create') {
|
|
54
|
+
const scopes = flags.scopes
|
|
55
|
+
? flags.scopes.split(',').map(s => s.trim()).filter(Boolean)
|
|
56
|
+
: undefined;
|
|
57
|
+
return {
|
|
58
|
+
action: 'create',
|
|
59
|
+
scopes,
|
|
60
|
+
ttl: flags.ttl ? parseInt(flags.ttl, 10) : undefined,
|
|
61
|
+
description: flags.description,
|
|
62
|
+
json: flags.json === true,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (sub === 'token' && action === 'list') {
|
|
67
|
+
return { action: 'list', json: flags.json === true };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (sub === 'token' && action === 'revoke') {
|
|
71
|
+
return { action: 'revoke', id: rest[0] };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return { action: sub, sub: action };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function buildTokenPayload(scopes, ttl) {
|
|
78
|
+
const payload = { scopes };
|
|
79
|
+
if (ttl !== undefined && ttl !== null) {
|
|
80
|
+
payload.ttl_seconds = ttl;
|
|
81
|
+
}
|
|
82
|
+
return payload;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export async function runAuth(client, args) {
|
|
86
|
+
const parsed = parseAuthCommand(args);
|
|
87
|
+
|
|
88
|
+
// Interactive sub-menu when no valid action
|
|
89
|
+
if (!['create', 'list', 'revoke'].includes(parsed.action) && isInteractive()) {
|
|
90
|
+
const action = await p.select({
|
|
91
|
+
message: 'Auth action',
|
|
92
|
+
options: [
|
|
93
|
+
{ value: 'create', label: 'Create token', hint: 'generate a new API token' },
|
|
94
|
+
{ value: 'list', label: 'List tokens', hint: 'show all tokens' },
|
|
95
|
+
{ value: 'revoke', label: 'Revoke token', hint: 'revoke an existing token' },
|
|
96
|
+
],
|
|
97
|
+
});
|
|
98
|
+
handleCancel(action);
|
|
99
|
+
parsed.action = action;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
switch (parsed.action) {
|
|
103
|
+
case 'create': {
|
|
104
|
+
let scopes = parsed.scopes;
|
|
105
|
+
let ttl = parsed.ttl;
|
|
106
|
+
|
|
107
|
+
if (!scopes && isInteractive()) {
|
|
108
|
+
scopes = await p.multiselect({
|
|
109
|
+
message: 'Select token scopes',
|
|
110
|
+
options: VALID_SCOPES.map(s => ({ value: s, label: s })),
|
|
111
|
+
required: true,
|
|
112
|
+
});
|
|
113
|
+
handleCancel(scopes);
|
|
114
|
+
|
|
115
|
+
const ttlInput = await p.text({
|
|
116
|
+
message: 'Token TTL in seconds',
|
|
117
|
+
placeholder: 'leave empty for no expiry',
|
|
118
|
+
});
|
|
119
|
+
handleCancel(ttlInput);
|
|
120
|
+
ttl = ttlInput ? parseInt(ttlInput, 10) : undefined;
|
|
121
|
+
|
|
122
|
+
const descInput = await p.text({
|
|
123
|
+
message: 'Token description',
|
|
124
|
+
placeholder: 'optional',
|
|
125
|
+
});
|
|
126
|
+
handleCancel(descInput);
|
|
127
|
+
parsed.description = descInput || undefined;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (!scopes) {
|
|
131
|
+
throw new Error('Scopes are required. Use --scopes or run interactively.');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const invalid = scopes.filter(s => !VALID_SCOPES.includes(s));
|
|
135
|
+
if (invalid.length > 0) throw new Error(`Invalid scopes: ${invalid.join(', ')}`);
|
|
136
|
+
if (scopes.length === 0) throw new Error('At least one scope is required');
|
|
137
|
+
|
|
138
|
+
const payload = buildTokenPayload(scopes, ttl);
|
|
139
|
+
if (parsed.description) payload.description = parsed.description;
|
|
140
|
+
|
|
141
|
+
let result;
|
|
142
|
+
if (isInteractive()) {
|
|
143
|
+
// Try with current token first
|
|
144
|
+
try {
|
|
145
|
+
result = await withSpinner('Creating token...', () =>
|
|
146
|
+
client.request('/v1/auth/tokens', 'POST', payload), 'Token created.');
|
|
147
|
+
} catch (err) {
|
|
148
|
+
if (err.status === 401 && isInteractive()) {
|
|
149
|
+
p.log.warn('Authentication failed. Your token may be missing or invalid.');
|
|
150
|
+
const recovery = await p.select({
|
|
151
|
+
message: 'How would you like to proceed?',
|
|
152
|
+
options: [
|
|
153
|
+
{ value: 'reset', label: 'Reset tokens', hint: 'clear all existing tokens and create a new one' },
|
|
154
|
+
{ value: 'paste', label: 'Paste a token', hint: 'use an existing valid token' },
|
|
155
|
+
{ value: 'abort', label: 'Abort' },
|
|
156
|
+
],
|
|
157
|
+
});
|
|
158
|
+
handleCancel(recovery);
|
|
159
|
+
|
|
160
|
+
if (recovery === 'reset') {
|
|
161
|
+
const confirm = await p.confirm({
|
|
162
|
+
message: 'This will revoke ALL existing tokens. Continue?',
|
|
163
|
+
initialValue: false,
|
|
164
|
+
});
|
|
165
|
+
handleCancel(confirm);
|
|
166
|
+
if (confirm && resetTokens()) {
|
|
167
|
+
p.log.success('All tokens cleared.');
|
|
168
|
+
client.token = '';
|
|
169
|
+
result = await withSpinner('Creating token...', () =>
|
|
170
|
+
client.request('/v1/auth/tokens', 'POST', payload), 'Token created.');
|
|
171
|
+
} else if (confirm) {
|
|
172
|
+
throw new Error('Could not reset tokens. Is sqlite3 installed?');
|
|
173
|
+
} else {
|
|
174
|
+
throw err;
|
|
175
|
+
}
|
|
176
|
+
} else if (recovery === 'paste') {
|
|
177
|
+
const pastedToken = await p.text({
|
|
178
|
+
message: 'Paste a valid API token',
|
|
179
|
+
placeholder: 'mox_...',
|
|
180
|
+
});
|
|
181
|
+
handleCancel(pastedToken);
|
|
182
|
+
if (pastedToken) {
|
|
183
|
+
client.token = pastedToken;
|
|
184
|
+
result = await withSpinner('Retrying...', () =>
|
|
185
|
+
client.request('/v1/auth/tokens', 'POST', payload), 'Token created.');
|
|
186
|
+
} else {
|
|
187
|
+
throw err;
|
|
188
|
+
}
|
|
189
|
+
} else {
|
|
190
|
+
throw err;
|
|
191
|
+
}
|
|
192
|
+
} else {
|
|
193
|
+
throw err;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
showResult('Your API Token', {
|
|
197
|
+
ID: result.id,
|
|
198
|
+
Token: result.token,
|
|
199
|
+
Scopes: scopes.join(', '),
|
|
200
|
+
});
|
|
201
|
+
p.note(
|
|
202
|
+
`# Add to ~/.zshrc or ~/.bashrc:\nexport MOXXY_TOKEN="${result.token}"`,
|
|
203
|
+
'Save your token'
|
|
204
|
+
);
|
|
205
|
+
p.log.warn('This token will not be shown again. Save it now.');
|
|
206
|
+
} else {
|
|
207
|
+
result = await client.request('/v1/auth/tokens', 'POST', payload);
|
|
208
|
+
if (parsed.json) {
|
|
209
|
+
console.log(JSON.stringify(result, null, 2));
|
|
210
|
+
} else {
|
|
211
|
+
console.log(`Token created: ${result.id}`);
|
|
212
|
+
if (result.token) console.log(`Secret: ${result.token}`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return result;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
case 'list': {
|
|
219
|
+
let tokens;
|
|
220
|
+
if (isInteractive()) {
|
|
221
|
+
tokens = await withSpinner('Fetching tokens...', () =>
|
|
222
|
+
client.request('/v1/auth/tokens', 'GET'), 'Tokens loaded.');
|
|
223
|
+
for (const t of (tokens || [])) {
|
|
224
|
+
p.log.info(`${t.id} scopes=[${(t.scopes || []).join(',')}] status=${t.status || 'active'}`);
|
|
225
|
+
}
|
|
226
|
+
if (!tokens || tokens.length === 0) {
|
|
227
|
+
p.log.warn('No tokens found.');
|
|
228
|
+
}
|
|
229
|
+
} else {
|
|
230
|
+
tokens = await client.request('/v1/auth/tokens', 'GET');
|
|
231
|
+
if (parsed.json) {
|
|
232
|
+
console.log(JSON.stringify(tokens, null, 2));
|
|
233
|
+
} else {
|
|
234
|
+
for (const t of (tokens || [])) {
|
|
235
|
+
console.log(` ${t.id} scopes=[${(t.scopes || []).join(',')}] status=${t.status || 'active'}`);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return tokens;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
case 'revoke': {
|
|
243
|
+
let id = parsed.id;
|
|
244
|
+
|
|
245
|
+
if (!id && isInteractive()) {
|
|
246
|
+
const tokens = await withSpinner('Fetching tokens...', () =>
|
|
247
|
+
client.request('/v1/auth/tokens', 'GET'), 'Tokens loaded.');
|
|
248
|
+
|
|
249
|
+
if (!tokens || tokens.length === 0) {
|
|
250
|
+
p.log.warn('No tokens to revoke.');
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
id = await p.select({
|
|
255
|
+
message: 'Select token to revoke',
|
|
256
|
+
options: tokens.map(t => ({
|
|
257
|
+
value: t.id,
|
|
258
|
+
label: t.id,
|
|
259
|
+
hint: `scopes=[${(t.scopes || []).join(',')}]`,
|
|
260
|
+
})),
|
|
261
|
+
});
|
|
262
|
+
handleCancel(id);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (!id) throw new Error('Usage: moxxy auth token revoke <id>');
|
|
266
|
+
|
|
267
|
+
if (isInteractive()) {
|
|
268
|
+
await withSpinner('Revoking token...', () =>
|
|
269
|
+
client.request(`/v1/auth/tokens/${encodeURIComponent(id)}`, 'DELETE'), 'Token revoked.');
|
|
270
|
+
} else {
|
|
271
|
+
await client.request(`/v1/auth/tokens/${encodeURIComponent(id)}`, 'DELETE');
|
|
272
|
+
console.log(`Token ${id} revoked.`);
|
|
273
|
+
}
|
|
274
|
+
break;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
default: {
|
|
278
|
+
const { showHelp } = await import('../help.js');
|
|
279
|
+
showHelp('auth', p);
|
|
280
|
+
break;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export async function tokenCreate(client, args) {
|
|
286
|
+
const flags = parseFlags(args);
|
|
287
|
+
const scopes = flags.scopes
|
|
288
|
+
? flags.scopes.split(',').map(s => s.trim()).filter(Boolean)
|
|
289
|
+
: [];
|
|
290
|
+
|
|
291
|
+
const invalid = scopes.filter(s => !VALID_SCOPES.includes(s));
|
|
292
|
+
if (invalid.length > 0) throw new Error(`Invalid scopes: ${invalid.join(', ')}`);
|
|
293
|
+
if (scopes.length === 0) throw new Error('At least one scope is required');
|
|
294
|
+
|
|
295
|
+
const ttl = flags.ttl ? parseInt(flags.ttl, 10) : undefined;
|
|
296
|
+
const payload = buildTokenPayload(scopes, ttl);
|
|
297
|
+
const result = await client.request('/v1/auth/tokens', 'POST', payload);
|
|
298
|
+
if (flags.json === 'true' || flags.json === true) {
|
|
299
|
+
console.log(JSON.stringify(result, null, 2));
|
|
300
|
+
} else {
|
|
301
|
+
console.log(`Token created: ${result.id}`);
|
|
302
|
+
if (result.token) console.log(`Secret: ${result.token}`);
|
|
303
|
+
}
|
|
304
|
+
return result;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
export async function tokenList(client, args) {
|
|
308
|
+
const flags = parseFlags(args || []);
|
|
309
|
+
const tokens = await client.request('/v1/auth/tokens', 'GET');
|
|
310
|
+
if (flags.json === 'true' || flags.json === true) {
|
|
311
|
+
console.log(JSON.stringify(tokens, null, 2));
|
|
312
|
+
} else {
|
|
313
|
+
for (const t of (tokens || [])) {
|
|
314
|
+
console.log(` ${t.id} scopes=[${(t.scopes || []).join(',')}] status=${t.status || 'active'}`);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
return tokens;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
export async function tokenRevoke(client, id) {
|
|
321
|
+
if (!id) throw new Error('Usage: moxxy auth token revoke <id>');
|
|
322
|
+
await client.request(`/v1/auth/tokens/${encodeURIComponent(id)}`, 'DELETE');
|
|
323
|
+
console.log(`Token ${id} revoked.`);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
export { VALID_SCOPES };
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import { p, handleCancel, withSpinner, showResult } from '../ui.js';
|
|
2
|
+
import { showHelp } from '../help.js';
|
|
3
|
+
import { parseFlags } from './auth.js';
|
|
4
|
+
|
|
5
|
+
export async function runChannel(client, args) {
|
|
6
|
+
const [subcommand, ...rest] = args;
|
|
7
|
+
|
|
8
|
+
switch (subcommand) {
|
|
9
|
+
case 'create':
|
|
10
|
+
await createChannel(client, rest);
|
|
11
|
+
break;
|
|
12
|
+
case 'list':
|
|
13
|
+
await listChannels(client, rest);
|
|
14
|
+
break;
|
|
15
|
+
case 'pair':
|
|
16
|
+
await pairChannel(client, rest);
|
|
17
|
+
break;
|
|
18
|
+
case 'delete':
|
|
19
|
+
await deleteChannel(client, rest);
|
|
20
|
+
break;
|
|
21
|
+
case 'bindings':
|
|
22
|
+
await listBindings(client, rest);
|
|
23
|
+
break;
|
|
24
|
+
case 'unbind':
|
|
25
|
+
await unbindChannel(client, rest);
|
|
26
|
+
break;
|
|
27
|
+
default:
|
|
28
|
+
showHelp('channel', p);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function createChannel(client, args) {
|
|
33
|
+
const channelType = await p.select({
|
|
34
|
+
message: 'Channel type',
|
|
35
|
+
options: [
|
|
36
|
+
{ value: 'telegram', label: 'Telegram', hint: 'BotFather bot token required' },
|
|
37
|
+
{ value: 'discord', label: 'Discord', hint: 'coming soon (scaffold)' },
|
|
38
|
+
],
|
|
39
|
+
});
|
|
40
|
+
handleCancel(channelType);
|
|
41
|
+
|
|
42
|
+
if (channelType === 'discord') {
|
|
43
|
+
p.log.info('Discord channel support is coming soon.');
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Step 1: Select agent to bind
|
|
48
|
+
let agentId;
|
|
49
|
+
try {
|
|
50
|
+
const agents = await client.listAgents();
|
|
51
|
+
if (agents.length === 0) {
|
|
52
|
+
p.log.error('No agents found. Create one first: moxxy agent create');
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
agentId = await p.select({
|
|
56
|
+
message: 'Select agent to bind',
|
|
57
|
+
options: agents.map(a => ({
|
|
58
|
+
value: a.name,
|
|
59
|
+
label: `${a.name} (${a.provider_id}/${a.model_id})`,
|
|
60
|
+
})),
|
|
61
|
+
});
|
|
62
|
+
handleCancel(agentId);
|
|
63
|
+
} catch (err) {
|
|
64
|
+
p.log.error(`Failed to list agents: ${err.message}`);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Step 2: Get bot token
|
|
69
|
+
p.note(
|
|
70
|
+
'1. Open Telegram and talk to @BotFather\n' +
|
|
71
|
+
'2. Send /newbot and follow the prompts\n' +
|
|
72
|
+
'3. Copy the bot token',
|
|
73
|
+
'Telegram Bot Setup'
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
const botToken = await p.password({
|
|
77
|
+
message: 'Paste your Telegram bot token',
|
|
78
|
+
});
|
|
79
|
+
handleCancel(botToken);
|
|
80
|
+
|
|
81
|
+
const displayName = await p.text({
|
|
82
|
+
message: 'Display name for this channel',
|
|
83
|
+
placeholder: 'My Moxxy Bot',
|
|
84
|
+
});
|
|
85
|
+
handleCancel(displayName);
|
|
86
|
+
|
|
87
|
+
// Step 3: Create channel
|
|
88
|
+
let channel;
|
|
89
|
+
try {
|
|
90
|
+
channel = await withSpinner('Creating channel...', () =>
|
|
91
|
+
client.request('/v1/channels', 'POST', {
|
|
92
|
+
channel_type: channelType,
|
|
93
|
+
display_name: displayName || 'Telegram Bot',
|
|
94
|
+
bot_token: botToken,
|
|
95
|
+
}), 'Channel created.');
|
|
96
|
+
|
|
97
|
+
showResult('Channel Created', {
|
|
98
|
+
ID: channel.id,
|
|
99
|
+
Type: channel.channel_type,
|
|
100
|
+
Name: channel.display_name,
|
|
101
|
+
Status: channel.status,
|
|
102
|
+
});
|
|
103
|
+
} catch (err) {
|
|
104
|
+
p.log.error(`Failed to create channel: ${err.message}`);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Step 4: Wait for pairing code
|
|
109
|
+
p.note(
|
|
110
|
+
'1. Open your Telegram bot and send /start\n' +
|
|
111
|
+
'2. Copy the 6-digit pairing code',
|
|
112
|
+
'Pair your chat'
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
const code = await p.text({
|
|
116
|
+
message: 'Enter 6-digit pairing code',
|
|
117
|
+
validate: (v) => {
|
|
118
|
+
if (!v || v.trim().length === 0) return 'Code is required';
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
handleCancel(code);
|
|
122
|
+
|
|
123
|
+
// Step 5: Pair
|
|
124
|
+
try {
|
|
125
|
+
const result = await withSpinner('Pairing...', () =>
|
|
126
|
+
client.request(`/v1/channels/${channel.id}/pair`, 'POST', {
|
|
127
|
+
code: code.trim(),
|
|
128
|
+
agent_id: agentId,
|
|
129
|
+
}), 'Paired successfully.');
|
|
130
|
+
|
|
131
|
+
showResult('Channel Paired', {
|
|
132
|
+
'Binding ID': result.id,
|
|
133
|
+
Channel: result.channel_id,
|
|
134
|
+
Agent: result.agent_id,
|
|
135
|
+
'External Chat': result.external_chat_id,
|
|
136
|
+
});
|
|
137
|
+
} catch (err) {
|
|
138
|
+
p.log.error(`Failed to pair: ${err.message}`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async function listChannels(client, args) {
|
|
143
|
+
const flags = parseFlags(args);
|
|
144
|
+
try {
|
|
145
|
+
const channels = await client.request('/v1/channels', 'GET');
|
|
146
|
+
if (flags.json) {
|
|
147
|
+
console.log(JSON.stringify(channels, null, 2));
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
if (channels.length === 0) {
|
|
151
|
+
p.log.info('No channels found. Create one with: moxxy channel create');
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
for (const ch of channels) {
|
|
155
|
+
p.log.info(`${ch.id} | ${ch.channel_type} | ${ch.display_name} | ${ch.status}`);
|
|
156
|
+
}
|
|
157
|
+
} catch (err) {
|
|
158
|
+
p.log.error(`Failed to list channels: ${err.message}`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async function pairChannel(client, args) {
|
|
163
|
+
const flags = parseFlags(args);
|
|
164
|
+
let code = flags.code;
|
|
165
|
+
let agentId = flags.agent;
|
|
166
|
+
|
|
167
|
+
if (!code) {
|
|
168
|
+
code = await p.text({ message: 'Enter 6-digit pairing code' });
|
|
169
|
+
handleCancel(code);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (!agentId) {
|
|
173
|
+
try {
|
|
174
|
+
const agents = await client.listAgents();
|
|
175
|
+
if (agents.length === 0) {
|
|
176
|
+
p.log.error('No agents found. Create one first: moxxy agent create');
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
agentId = await p.select({
|
|
180
|
+
message: 'Select agent to bind',
|
|
181
|
+
options: agents.map(a => ({
|
|
182
|
+
value: a.name,
|
|
183
|
+
label: `${a.name} (${a.provider_id}/${a.model_id})`,
|
|
184
|
+
})),
|
|
185
|
+
});
|
|
186
|
+
handleCancel(agentId);
|
|
187
|
+
} catch (err) {
|
|
188
|
+
p.log.error(`Failed to list agents: ${err.message}`);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Find the channel to pair with
|
|
194
|
+
let channelId;
|
|
195
|
+
try {
|
|
196
|
+
const channels = await client.request('/v1/channels', 'GET');
|
|
197
|
+
if (channels.length === 0) {
|
|
198
|
+
p.log.error('No channels found. Create one first: moxxy channel create');
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
if (channels.length === 1) {
|
|
202
|
+
channelId = channels[0].id;
|
|
203
|
+
} else {
|
|
204
|
+
channelId = await p.select({
|
|
205
|
+
message: 'Select channel',
|
|
206
|
+
options: channels.map(c => ({
|
|
207
|
+
value: c.id,
|
|
208
|
+
label: `${c.id.substring(0, 8)} (${c.channel_type} - ${c.display_name})`,
|
|
209
|
+
})),
|
|
210
|
+
});
|
|
211
|
+
handleCancel(channelId);
|
|
212
|
+
}
|
|
213
|
+
} catch (err) {
|
|
214
|
+
p.log.error(`Failed to list channels: ${err.message}`);
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
try {
|
|
219
|
+
const result = await withSpinner('Pairing...', () =>
|
|
220
|
+
client.request(`/v1/channels/${channelId}/pair`, 'POST', {
|
|
221
|
+
code,
|
|
222
|
+
agent_id: agentId,
|
|
223
|
+
}), 'Paired successfully.');
|
|
224
|
+
|
|
225
|
+
showResult('Channel Paired', {
|
|
226
|
+
'Binding ID': result.id,
|
|
227
|
+
Channel: result.channel_id,
|
|
228
|
+
Agent: result.agent_id,
|
|
229
|
+
'External Chat': result.external_chat_id,
|
|
230
|
+
});
|
|
231
|
+
} catch (err) {
|
|
232
|
+
p.log.error(`Failed to pair: ${err.message}`);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
async function deleteChannel(client, args) {
|
|
237
|
+
const [channelId] = args;
|
|
238
|
+
if (!channelId) {
|
|
239
|
+
p.log.error('Usage: moxxy channel delete <channel-id>');
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
try {
|
|
244
|
+
await withSpinner('Deleting channel...', () =>
|
|
245
|
+
client.request(`/v1/channels/${channelId}`, 'DELETE'), 'Channel deleted.');
|
|
246
|
+
} catch (err) {
|
|
247
|
+
p.log.error(`Failed to delete channel: ${err.message}`);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
async function listBindings(client, args) {
|
|
252
|
+
const [channelId] = args;
|
|
253
|
+
if (!channelId) {
|
|
254
|
+
p.log.error('Usage: moxxy channel bindings <channel-id>');
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
try {
|
|
259
|
+
const bindings = await client.request(`/v1/channels/${channelId}/bindings`, 'GET');
|
|
260
|
+
if (bindings.length === 0) {
|
|
261
|
+
p.log.info('No bindings found for this channel.');
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
for (const b of bindings) {
|
|
265
|
+
p.log.info(`${b.id} | Agent: ${b.agent_id} | Chat: ${b.external_chat_id} | ${b.status}`);
|
|
266
|
+
}
|
|
267
|
+
} catch (err) {
|
|
268
|
+
p.log.error(`Failed to list bindings: ${err.message}`);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
async function unbindChannel(client, args) {
|
|
273
|
+
const [channelId, bindingId] = args;
|
|
274
|
+
if (!channelId || !bindingId) {
|
|
275
|
+
p.log.error('Usage: moxxy channel unbind <channel-id> <binding-id>');
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
try {
|
|
280
|
+
await withSpinner('Unbinding...', () =>
|
|
281
|
+
client.request(`/v1/channels/${channelId}/bindings/${bindingId}`, 'DELETE'), 'Unbound.');
|
|
282
|
+
} catch (err) {
|
|
283
|
+
p.log.error(`Failed to unbind: ${err.message}`);
|
|
284
|
+
}
|
|
285
|
+
}
|