@emblemvault/agentwallet 1.3.0 → 3.0.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 +157 -44
- package/docs/COMMANDS.md +171 -0
- package/docs/PLUGINS.md +106 -0
- package/docs/SETUP.md +180 -0
- package/emblemai.js +562 -859
- package/package.json +13 -5
- package/src/auth-server.js +300 -0
- package/src/auth.js +816 -0
- package/src/commands.js +754 -0
- package/src/formatter.js +250 -0
- package/src/glow.js +364 -0
- package/src/plugins/loader.js +533 -0
- package/src/session-store.js +94 -0
- package/src/tui.js +171 -0
package/src/commands.js
ADDED
|
@@ -0,0 +1,754 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* commands.js — Slash command router for Emblem Enhanced TUI
|
|
3
|
+
*
|
|
4
|
+
* Handles all /commands in both TUI and simple-readline modes.
|
|
5
|
+
* Each command receives a context object with the full runtime state.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import chalk from 'chalk';
|
|
9
|
+
|
|
10
|
+
// ============================================================================
|
|
11
|
+
// Command Registry (for /help display)
|
|
12
|
+
// ============================================================================
|
|
13
|
+
|
|
14
|
+
export const COMMANDS = [
|
|
15
|
+
{ cmd: '/help', desc: 'Show all commands' },
|
|
16
|
+
{ cmd: '/plugins', desc: 'List all plugins with status' },
|
|
17
|
+
{ cmd: '/plugin <name> on|off', desc: 'Toggle plugin' },
|
|
18
|
+
{ cmd: '/tools', desc: 'List available tools' },
|
|
19
|
+
{ cmd: '/tools add|remove <id>', desc: 'Manage tools' },
|
|
20
|
+
{ cmd: '/tools clear', desc: 'Enable auto-tools mode' },
|
|
21
|
+
{ cmd: '/auth', desc: 'Authentication menu' },
|
|
22
|
+
{ cmd: '/wallet', desc: 'Show wallet info' },
|
|
23
|
+
{ cmd: '/portfolio', desc: 'Show portfolio' },
|
|
24
|
+
{ cmd: '/settings', desc: 'Show current settings' },
|
|
25
|
+
{ cmd: '/model <id>', desc: 'Set model (or "clear")' },
|
|
26
|
+
{ cmd: '/stream on|off', desc: 'Toggle streaming' },
|
|
27
|
+
{ cmd: '/debug on|off', desc: 'Toggle debug mode' },
|
|
28
|
+
{ cmd: '/history on|off', desc: 'Toggle history' },
|
|
29
|
+
{ cmd: '/payment', desc: 'PAYG billing status' },
|
|
30
|
+
{ cmd: '/payment enable|disable', desc: 'Toggle PAYG' },
|
|
31
|
+
{ cmd: '/payment token <T>', desc: 'Set payment token' },
|
|
32
|
+
{ cmd: '/payment mode <M>', desc: 'Set payment mode' },
|
|
33
|
+
{ cmd: '/secrets', desc: 'Manage encrypted plugin secrets' },
|
|
34
|
+
{ cmd: '/glow on|off', desc: 'Toggle markdown rendering' },
|
|
35
|
+
{ cmd: '/log on|off', desc: 'Toggle stream logging to file' },
|
|
36
|
+
{ cmd: '/reset', desc: 'Clear conversation' },
|
|
37
|
+
{ cmd: '/exit', desc: 'Exit' },
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
// ============================================================================
|
|
41
|
+
// Command Handlers
|
|
42
|
+
// ============================================================================
|
|
43
|
+
|
|
44
|
+
function cmdHelp(ctx) {
|
|
45
|
+
const maxCmd = Math.max(...COMMANDS.map(c => c.cmd.length));
|
|
46
|
+
const lines = COMMANDS.map(c => {
|
|
47
|
+
const padded = c.cmd.padEnd(maxCmd + 2);
|
|
48
|
+
return ` ${chalk.cyan(padded)} ${chalk.dim(c.desc)}`;
|
|
49
|
+
});
|
|
50
|
+
const header = chalk.bold.white('Available Commands');
|
|
51
|
+
const text = `\n${header}\n${chalk.dim('─'.repeat(maxCmd + 30))}\n${lines.join('\n')}\n`;
|
|
52
|
+
ctx.appendMessage('system', text);
|
|
53
|
+
return { handled: true };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function cmdPlugins(ctx) {
|
|
57
|
+
const plugins = ctx.pluginManager.list();
|
|
58
|
+
if (!plugins || plugins.length === 0) {
|
|
59
|
+
ctx.appendMessage('system', chalk.dim('No plugins registered.'));
|
|
60
|
+
return { handled: true };
|
|
61
|
+
}
|
|
62
|
+
const lines = plugins.map(p => {
|
|
63
|
+
const icon = p.enabled ? chalk.green('\u2611') : chalk.dim('\u2610');
|
|
64
|
+
const name = p.enabled ? chalk.white(p.name) : chalk.dim(p.name);
|
|
65
|
+
const ver = chalk.dim(`v${p.version}`);
|
|
66
|
+
const tools = chalk.dim(`(${p.toolCount} tools)`);
|
|
67
|
+
return ` ${icon} ${name} ${ver} ${tools}`;
|
|
68
|
+
});
|
|
69
|
+
const header = chalk.bold.white('Plugins');
|
|
70
|
+
ctx.appendMessage('system', `\n${header}\n${lines.join('\n')}\n`);
|
|
71
|
+
return { handled: true };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function cmdPlugin(args, ctx) {
|
|
75
|
+
const parts = args.trim().split(/\s+/);
|
|
76
|
+
if (parts.length < 2) {
|
|
77
|
+
ctx.appendMessage('system', chalk.yellow('Usage: /plugin <name> on|off'));
|
|
78
|
+
return { handled: true };
|
|
79
|
+
}
|
|
80
|
+
const name = parts[0];
|
|
81
|
+
const action = parts[1].toLowerCase();
|
|
82
|
+
|
|
83
|
+
if (action === 'on') {
|
|
84
|
+
const ok = ctx.pluginManager.enable(name);
|
|
85
|
+
if (ok) {
|
|
86
|
+
ctx.appendMessage('system', chalk.green(`Plugin "${name}" enabled.`));
|
|
87
|
+
ctx.addLog('plugin', `Enabled ${name}`);
|
|
88
|
+
} else {
|
|
89
|
+
ctx.appendMessage('system', chalk.red(`Plugin "${name}" not found.`));
|
|
90
|
+
}
|
|
91
|
+
} else if (action === 'off') {
|
|
92
|
+
const ok = ctx.pluginManager.disable(name);
|
|
93
|
+
if (ok) {
|
|
94
|
+
ctx.appendMessage('system', chalk.yellow(`Plugin "${name}" disabled.`));
|
|
95
|
+
ctx.addLog('plugin', `Disabled ${name}`);
|
|
96
|
+
} else {
|
|
97
|
+
ctx.appendMessage('system', chalk.red(`Plugin "${name}" not found.`));
|
|
98
|
+
}
|
|
99
|
+
} else {
|
|
100
|
+
ctx.appendMessage('system', chalk.yellow('Usage: /plugin <name> on|off'));
|
|
101
|
+
return { handled: true };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
ctx.updateSidebar();
|
|
105
|
+
return { handled: true };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async function cmdTools(args, ctx) {
|
|
109
|
+
const parts = args.trim().split(/\s+/).filter(Boolean);
|
|
110
|
+
|
|
111
|
+
// /tools — list all
|
|
112
|
+
if (parts.length === 0) {
|
|
113
|
+
try {
|
|
114
|
+
let tools;
|
|
115
|
+
if (ctx.client && typeof ctx.client.getTools === 'function') {
|
|
116
|
+
tools = await ctx.client.getTools();
|
|
117
|
+
} else {
|
|
118
|
+
tools = ctx.pluginManager.getTools();
|
|
119
|
+
}
|
|
120
|
+
if (!tools || tools.length === 0) {
|
|
121
|
+
ctx.appendMessage('system', chalk.dim('No tools available.'));
|
|
122
|
+
return { handled: true };
|
|
123
|
+
}
|
|
124
|
+
const lines = tools.map(t => {
|
|
125
|
+
const id = t.id || t.name || 'unknown';
|
|
126
|
+
const isSelected = ctx.settings.selectedTools.includes(id);
|
|
127
|
+
const icon = isSelected ? chalk.green('[x]') : chalk.dim('[ ]');
|
|
128
|
+
const title = t.title || t.name || id;
|
|
129
|
+
const desc = t.description ? chalk.dim(` — ${t.description}`) : '';
|
|
130
|
+
return ` ${icon} ${chalk.white(title)} ${chalk.dim(`(${id})`)}${desc}`;
|
|
131
|
+
});
|
|
132
|
+
const mode = ctx.settings.selectedTools.length > 0
|
|
133
|
+
? chalk.cyan(ctx.settings.selectedTools.join(', '))
|
|
134
|
+
: chalk.dim('Auto-tools mode');
|
|
135
|
+
const header = chalk.bold.white('Tools');
|
|
136
|
+
ctx.appendMessage('system', `\n${header}\n${lines.join('\n')}\n\n ${chalk.dim('Selected:')} ${mode}\n`);
|
|
137
|
+
} catch (err) {
|
|
138
|
+
ctx.appendMessage('system', chalk.red(`Error fetching tools: ${err.message}`));
|
|
139
|
+
}
|
|
140
|
+
return { handled: true };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const sub = parts[0];
|
|
144
|
+
const toolId = parts[1];
|
|
145
|
+
|
|
146
|
+
// /tools clear
|
|
147
|
+
if (sub === 'clear') {
|
|
148
|
+
ctx.settings.selectedTools = [];
|
|
149
|
+
ctx.appendMessage('system', chalk.green('Auto-tools mode enabled.'));
|
|
150
|
+
ctx.addLog('tools', 'Cleared tool selection');
|
|
151
|
+
return { handled: true };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// /tools add <id>
|
|
155
|
+
if (sub === 'add' && toolId) {
|
|
156
|
+
if (!ctx.settings.selectedTools.includes(toolId)) {
|
|
157
|
+
ctx.settings.selectedTools.push(toolId);
|
|
158
|
+
ctx.appendMessage('system', chalk.green(`Added tool: ${toolId}`));
|
|
159
|
+
ctx.addLog('tools', `Added ${toolId}`);
|
|
160
|
+
} else {
|
|
161
|
+
ctx.appendMessage('system', chalk.yellow(`Already selected: ${toolId}`));
|
|
162
|
+
}
|
|
163
|
+
return { handled: true };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// /tools remove <id>
|
|
167
|
+
if (sub === 'remove' && toolId) {
|
|
168
|
+
const idx = ctx.settings.selectedTools.indexOf(toolId);
|
|
169
|
+
if (idx > -1) {
|
|
170
|
+
ctx.settings.selectedTools.splice(idx, 1);
|
|
171
|
+
ctx.appendMessage('system', chalk.green(`Removed tool: ${toolId}`));
|
|
172
|
+
ctx.addLog('tools', `Removed ${toolId}`);
|
|
173
|
+
} else {
|
|
174
|
+
ctx.appendMessage('system', chalk.yellow(`Not found: ${toolId}`));
|
|
175
|
+
}
|
|
176
|
+
return { handled: true };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
ctx.appendMessage('system', chalk.yellow('Usage: /tools [add|remove <id>|clear]'));
|
|
180
|
+
return { handled: true };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async function cmdAuth(ctx) {
|
|
184
|
+
// In TUI mode, show auth summary in chat panel
|
|
185
|
+
if (ctx.tui) {
|
|
186
|
+
try {
|
|
187
|
+
const sess = ctx.authSdk.getSession();
|
|
188
|
+
const lines = [
|
|
189
|
+
chalk.bold.white('Authentication Info'),
|
|
190
|
+
'',
|
|
191
|
+
` ${chalk.dim('Vault ID:')} ${sess?.user?.vaultId || 'N/A'}`,
|
|
192
|
+
` ${chalk.dim('Auth Type:')} ${sess?.authType || 'Password'}`,
|
|
193
|
+
` ${chalk.dim('App ID:')} ${sess?.appId || 'emblem-agent-wallet'}`,
|
|
194
|
+
` ${chalk.dim('Expires:')} ${sess?.expiresAt ? new Date(sess.expiresAt).toISOString() : 'N/A'}`,
|
|
195
|
+
'',
|
|
196
|
+
chalk.dim('Use /wallet for address info'),
|
|
197
|
+
];
|
|
198
|
+
ctx.appendMessage('system', lines.join('\n'));
|
|
199
|
+
} catch (err) {
|
|
200
|
+
ctx.appendMessage('system', chalk.red(`Auth error: ${err.message}`));
|
|
201
|
+
}
|
|
202
|
+
return { handled: true };
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Simple mode — import and run the interactive auth menu
|
|
206
|
+
try {
|
|
207
|
+
const { authMenu } = await import('./auth.js');
|
|
208
|
+
const result = await authMenu(ctx.authSdk, ctx.promptText);
|
|
209
|
+
if (result === 'logout') {
|
|
210
|
+
return { handled: true, logout: true };
|
|
211
|
+
}
|
|
212
|
+
} catch (err) {
|
|
213
|
+
ctx.appendMessage('system', chalk.red(`Auth menu error: ${err.message}`));
|
|
214
|
+
}
|
|
215
|
+
return { handled: true };
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
async function cmdWallet(ctx) {
|
|
219
|
+
try {
|
|
220
|
+
const vaultInfo = await ctx.authSdk.getVaultInfo();
|
|
221
|
+
const lines = [
|
|
222
|
+
chalk.bold.white('Wallet Info'),
|
|
223
|
+
'',
|
|
224
|
+
` ${chalk.dim('Vault ID:')} ${vaultInfo.vaultId || 'N/A'}`,
|
|
225
|
+
` ${chalk.dim('Token ID:')} ${vaultInfo.tokenId || vaultInfo.vaultId || 'N/A'}`,
|
|
226
|
+
];
|
|
227
|
+
if (vaultInfo.evmAddress) {
|
|
228
|
+
lines.push(` ${chalk.dim('EVM Address:')} ${chalk.white(vaultInfo.evmAddress)}`);
|
|
229
|
+
}
|
|
230
|
+
const solAddr = vaultInfo.solanaAddress || vaultInfo.address;
|
|
231
|
+
if (solAddr) {
|
|
232
|
+
lines.push(` ${chalk.dim('Solana Address:')} ${chalk.white(solAddr)}`);
|
|
233
|
+
}
|
|
234
|
+
if (vaultInfo.hederaAccountId) {
|
|
235
|
+
lines.push(` ${chalk.dim('Hedera Account:')} ${chalk.white(vaultInfo.hederaAccountId)}`);
|
|
236
|
+
}
|
|
237
|
+
if (vaultInfo.btcAddresses) {
|
|
238
|
+
lines.push(` ${chalk.dim('BTC Addresses:')}`);
|
|
239
|
+
if (vaultInfo.btcAddresses.p2pkh)
|
|
240
|
+
lines.push(` ${chalk.dim('P2PKH:')} ${vaultInfo.btcAddresses.p2pkh}`);
|
|
241
|
+
if (vaultInfo.btcAddresses.p2wpkh)
|
|
242
|
+
lines.push(` ${chalk.dim('P2WPKH:')} ${vaultInfo.btcAddresses.p2wpkh}`);
|
|
243
|
+
if (vaultInfo.btcAddresses.p2tr)
|
|
244
|
+
lines.push(` ${chalk.dim('P2TR:')} ${vaultInfo.btcAddresses.p2tr}`);
|
|
245
|
+
}
|
|
246
|
+
if (vaultInfo.createdAt) {
|
|
247
|
+
lines.push(` ${chalk.dim('Created:')} ${vaultInfo.createdAt}`);
|
|
248
|
+
}
|
|
249
|
+
lines.push('');
|
|
250
|
+
ctx.appendMessage('system', lines.join('\n'));
|
|
251
|
+
ctx.addLog('wallet', 'Displayed wallet info');
|
|
252
|
+
} catch (err) {
|
|
253
|
+
ctx.appendMessage('system', chalk.red(`Wallet error: ${err.message}`));
|
|
254
|
+
}
|
|
255
|
+
return { handled: true };
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function cmdPortfolio() {
|
|
259
|
+
// Return handled:false so the main loop sends "show my portfolio" as a chat message
|
|
260
|
+
return { handled: false };
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function cmdSettings(ctx) {
|
|
264
|
+
const sess = ctx.authSdk.getSession();
|
|
265
|
+
const lines = [
|
|
266
|
+
chalk.bold.white('Current Settings'),
|
|
267
|
+
'',
|
|
268
|
+
` ${chalk.dim('App ID:')} emblem-agent-wallet`,
|
|
269
|
+
` ${chalk.dim('Vault ID:')} ${sess?.user?.vaultId || 'N/A'}`,
|
|
270
|
+
` ${chalk.dim('Auth Mode:')} Password (headless)`,
|
|
271
|
+
` ${chalk.dim('Model:')} ${ctx.settings.model || chalk.dim('API default')}`,
|
|
272
|
+
` ${chalk.dim('Streaming:')} ${ctx.settings.stream ? chalk.green('ON') : chalk.red('OFF')}`,
|
|
273
|
+
` ${chalk.dim('Debug:')} ${ctx.settings.debug ? chalk.green('ON') : chalk.red('OFF')}`,
|
|
274
|
+
` ${chalk.dim('History:')} ${ctx.settings.retainHistory ? chalk.green('ON') : chalk.red('OFF')}`,
|
|
275
|
+
` ${chalk.dim('Glow:')} ${ctx.settings.glowEnabled ? chalk.green('ON') : chalk.dim('OFF')}`,
|
|
276
|
+
` ${chalk.dim('Logging:')} ${ctx.settings.log ? chalk.green('ON') + chalk.dim(` → ${ctx.LOG_FILE}`) : chalk.dim('OFF')}`,
|
|
277
|
+
` ${chalk.dim('Messages:')} ${ctx.history.messages.length}`,
|
|
278
|
+
` ${chalk.dim('Tools:')} ${ctx.settings.selectedTools.length > 0 ? chalk.cyan(ctx.settings.selectedTools.join(', ')) : chalk.dim('Auto-tools mode')}`,
|
|
279
|
+
'',
|
|
280
|
+
];
|
|
281
|
+
ctx.appendMessage('system', lines.join('\n'));
|
|
282
|
+
return { handled: true };
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function cmdModel(args, ctx) {
|
|
286
|
+
const modelArg = args.trim();
|
|
287
|
+
|
|
288
|
+
// /model — show current
|
|
289
|
+
if (!modelArg) {
|
|
290
|
+
ctx.appendMessage('system', `${chalk.dim('Current model:')} ${ctx.settings.model || chalk.dim('API default')}`);
|
|
291
|
+
return { handled: true };
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// /model clear
|
|
295
|
+
if (modelArg === 'clear') {
|
|
296
|
+
ctx.settings.model = null;
|
|
297
|
+
ctx.appendMessage('system', chalk.green('Model selection cleared. Using API default.'));
|
|
298
|
+
ctx.addLog('model', 'Cleared model');
|
|
299
|
+
return { handled: true };
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// /model <id>
|
|
303
|
+
ctx.settings.model = modelArg;
|
|
304
|
+
ctx.appendMessage('system', chalk.green(`Model set to: ${modelArg}`));
|
|
305
|
+
ctx.addLog('model', `Set to ${modelArg}`);
|
|
306
|
+
return { handled: true };
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function cmdStream(args, ctx) {
|
|
310
|
+
const val = args.trim().toLowerCase();
|
|
311
|
+
if (val === 'on') {
|
|
312
|
+
ctx.settings.stream = true;
|
|
313
|
+
ctx.appendMessage('system', chalk.green('Streaming enabled.'));
|
|
314
|
+
} else if (val === 'off') {
|
|
315
|
+
ctx.settings.stream = false;
|
|
316
|
+
ctx.appendMessage('system', chalk.yellow('Streaming disabled.'));
|
|
317
|
+
} else {
|
|
318
|
+
ctx.appendMessage('system', `${chalk.dim('Streaming:')} ${ctx.settings.stream ? chalk.green('ON') : chalk.red('OFF')}`);
|
|
319
|
+
}
|
|
320
|
+
return { handled: true };
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function cmdDebug(args, ctx) {
|
|
324
|
+
const val = args.trim().toLowerCase();
|
|
325
|
+
if (val === 'on') {
|
|
326
|
+
ctx.settings.debug = true;
|
|
327
|
+
ctx.appendMessage('system', chalk.green('Debug mode enabled.'));
|
|
328
|
+
} else if (val === 'off') {
|
|
329
|
+
ctx.settings.debug = false;
|
|
330
|
+
ctx.appendMessage('system', chalk.yellow('Debug mode disabled.'));
|
|
331
|
+
} else {
|
|
332
|
+
ctx.appendMessage('system', `${chalk.dim('Debug:')} ${ctx.settings.debug ? chalk.green('ON') : chalk.red('OFF')}`);
|
|
333
|
+
}
|
|
334
|
+
return { handled: true };
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function cmdHistory(args, ctx) {
|
|
338
|
+
const val = args.trim().toLowerCase();
|
|
339
|
+
if (val === 'on') {
|
|
340
|
+
ctx.settings.retainHistory = true;
|
|
341
|
+
ctx.appendMessage('system', chalk.green('History retention enabled.'));
|
|
342
|
+
} else if (val === 'off') {
|
|
343
|
+
ctx.settings.retainHistory = false;
|
|
344
|
+
ctx.appendMessage('system', chalk.yellow('History retention disabled.'));
|
|
345
|
+
} else {
|
|
346
|
+
const count = ctx.history.messages.length;
|
|
347
|
+
const lines = [
|
|
348
|
+
`${chalk.dim('History:')} ${ctx.settings.retainHistory ? chalk.green('ON') : chalk.red('OFF')}`,
|
|
349
|
+
`${chalk.dim('Messages:')} ${count}`,
|
|
350
|
+
];
|
|
351
|
+
if (count > 0) {
|
|
352
|
+
lines.push('', chalk.dim('Recent:'));
|
|
353
|
+
ctx.history.messages.slice(-4).forEach(m => {
|
|
354
|
+
const preview = m.content.substring(0, 60) + (m.content.length > 60 ? '...' : '');
|
|
355
|
+
lines.push(` ${chalk.cyan(m.role)}: ${preview}`);
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
ctx.appendMessage('system', lines.join('\n'));
|
|
359
|
+
}
|
|
360
|
+
return { handled: true };
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
async function cmdPayment(args, ctx) {
|
|
364
|
+
const parts = args.trim().split(/\s+/).filter(Boolean);
|
|
365
|
+
|
|
366
|
+
// /payment — show status
|
|
367
|
+
if (parts.length === 0) {
|
|
368
|
+
try {
|
|
369
|
+
const status = await ctx.client.getPaygStatus();
|
|
370
|
+
const lines = [
|
|
371
|
+
chalk.bold.white('PAYG Billing Status'),
|
|
372
|
+
'',
|
|
373
|
+
` ${chalk.dim('Enabled:')} ${status.enabled ? chalk.green('YES') : chalk.red('NO')}`,
|
|
374
|
+
` ${chalk.dim('Mode:')} ${status.mode || 'N/A'}`,
|
|
375
|
+
` ${chalk.dim('Payment Token:')} ${status.payment_token || 'N/A'}`,
|
|
376
|
+
` ${chalk.dim('Payment Chain:')} ${status.payment_chain || 'N/A'}`,
|
|
377
|
+
` ${chalk.dim('Blocked:')} ${status.is_blocked ? chalk.red('YES') : chalk.green('NO')}`,
|
|
378
|
+
` ${chalk.dim('Total Debt:')} $${(status.total_debt_usd || 0).toFixed(4)}`,
|
|
379
|
+
` ${chalk.dim('Total Paid:')} $${(status.total_paid_usd || 0).toFixed(4)}`,
|
|
380
|
+
` ${chalk.dim('Debt Ceiling:')} $${(status.debt_ceiling_usd || 0).toFixed(2)}`,
|
|
381
|
+
` ${chalk.dim('Pending Charges:')} ${status.pending_charges || 0}`,
|
|
382
|
+
];
|
|
383
|
+
if (status.available_tokens && status.available_tokens.length > 0) {
|
|
384
|
+
lines.push('', ` ${chalk.dim('Available Tokens:')} ${status.available_tokens.join(', ')}`);
|
|
385
|
+
}
|
|
386
|
+
lines.push('');
|
|
387
|
+
ctx.appendMessage('system', lines.join('\n'));
|
|
388
|
+
} catch (err) {
|
|
389
|
+
ctx.appendMessage('system', chalk.red(`PAYG error: ${err.message}`));
|
|
390
|
+
}
|
|
391
|
+
return { handled: true };
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const sub = parts[0];
|
|
395
|
+
|
|
396
|
+
// /payment enable
|
|
397
|
+
if (sub === 'enable') {
|
|
398
|
+
try {
|
|
399
|
+
const result = await ctx.client.configurePayg({ enabled: true });
|
|
400
|
+
ctx.appendMessage('system', result.success
|
|
401
|
+
? chalk.green('PAYG billing enabled.')
|
|
402
|
+
: chalk.red('Failed to enable PAYG.'));
|
|
403
|
+
ctx.addLog('payment', 'Enabled PAYG');
|
|
404
|
+
} catch (err) {
|
|
405
|
+
ctx.appendMessage('system', chalk.red(`Error: ${err.message}`));
|
|
406
|
+
}
|
|
407
|
+
return { handled: true };
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// /payment disable
|
|
411
|
+
if (sub === 'disable') {
|
|
412
|
+
try {
|
|
413
|
+
const result = await ctx.client.configurePayg({ enabled: false });
|
|
414
|
+
ctx.appendMessage('system', result.success
|
|
415
|
+
? chalk.yellow('PAYG billing disabled.')
|
|
416
|
+
: chalk.red('Failed to disable PAYG.'));
|
|
417
|
+
ctx.addLog('payment', 'Disabled PAYG');
|
|
418
|
+
} catch (err) {
|
|
419
|
+
ctx.appendMessage('system', chalk.red(`Error: ${err.message}`));
|
|
420
|
+
}
|
|
421
|
+
return { handled: true };
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// /payment token <T>
|
|
425
|
+
if (sub === 'token' && parts[1]) {
|
|
426
|
+
const token = parts[1].toUpperCase();
|
|
427
|
+
try {
|
|
428
|
+
const result = await ctx.client.configurePayg({ payment_token: token });
|
|
429
|
+
ctx.appendMessage('system', result.success
|
|
430
|
+
? chalk.green(`Payment token set to: ${token}`)
|
|
431
|
+
: chalk.red('Failed to set payment token.'));
|
|
432
|
+
ctx.addLog('payment', `Token set to ${token}`);
|
|
433
|
+
} catch (err) {
|
|
434
|
+
ctx.appendMessage('system', chalk.red(`Error: ${err.message}`));
|
|
435
|
+
}
|
|
436
|
+
return { handled: true };
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// /payment mode <M>
|
|
440
|
+
if (sub === 'mode' && parts[1]) {
|
|
441
|
+
const mode = parts[1];
|
|
442
|
+
if (mode !== 'pay_per_request' && mode !== 'debt_accumulation') {
|
|
443
|
+
ctx.appendMessage('system', chalk.yellow('Invalid mode. Use: pay_per_request or debt_accumulation'));
|
|
444
|
+
return { handled: true };
|
|
445
|
+
}
|
|
446
|
+
try {
|
|
447
|
+
const result = await ctx.client.configurePayg({ mode });
|
|
448
|
+
ctx.appendMessage('system', result.success
|
|
449
|
+
? chalk.green(`Payment mode set to: ${mode}`)
|
|
450
|
+
: chalk.red('Failed to set payment mode.'));
|
|
451
|
+
ctx.addLog('payment', `Mode set to ${mode}`);
|
|
452
|
+
} catch (err) {
|
|
453
|
+
ctx.appendMessage('system', chalk.red(`Error: ${err.message}`));
|
|
454
|
+
}
|
|
455
|
+
return { handled: true };
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
ctx.appendMessage('system', chalk.yellow('Usage: /payment [enable|disable|token <T>|mode <M>]'));
|
|
459
|
+
return { handled: true };
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
async function cmdSecrets(args, ctx) {
|
|
463
|
+
const allSecrets = ctx.pluginManager.getPluginSecrets();
|
|
464
|
+
if (allSecrets.length === 0) {
|
|
465
|
+
ctx.appendMessage('system', chalk.dim('No plugins declare secrets.'));
|
|
466
|
+
return { handled: true };
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
if (!ctx.promptText) {
|
|
470
|
+
ctx.appendMessage('system', chalk.red('Interactive prompts not available in this mode.'));
|
|
471
|
+
return { handled: true };
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
const { readPluginSecrets, writePluginSecrets } = await import('./auth.js');
|
|
475
|
+
|
|
476
|
+
// Show menu
|
|
477
|
+
const secrets = readPluginSecrets();
|
|
478
|
+
const secretStatus = allSecrets.map(s => ({
|
|
479
|
+
...s,
|
|
480
|
+
isSet: !!secrets[s.name]?.ciphertext,
|
|
481
|
+
}));
|
|
482
|
+
|
|
483
|
+
ctx.appendMessage('system', [
|
|
484
|
+
'',
|
|
485
|
+
chalk.bold.white(' Plugin Secrets'),
|
|
486
|
+
chalk.dim(' ' + '\u2500'.repeat(30)),
|
|
487
|
+
'',
|
|
488
|
+
...secretStatus.map(s => {
|
|
489
|
+
const icon = s.isSet ? chalk.green('\u2713 set') : chalk.dim('\u2717 not set');
|
|
490
|
+
return ` ${chalk.white(s.name)} ${chalk.dim(`(${s.label})`)} ${chalk.dim('\u2014')} ${chalk.dim(s.plugin)} ${chalk.dim('\u2014')} ${icon}`;
|
|
491
|
+
}),
|
|
492
|
+
'',
|
|
493
|
+
` ${chalk.cyan('1.')} Set a secret`,
|
|
494
|
+
` ${chalk.cyan('2.')} Remove a secret`,
|
|
495
|
+
` ${chalk.cyan('3.')} Back`,
|
|
496
|
+
'',
|
|
497
|
+
].join('\n'));
|
|
498
|
+
|
|
499
|
+
const choice = (await ctx.promptText(chalk.cyan(' Select (1-3): '))).trim();
|
|
500
|
+
|
|
501
|
+
// ── Set a secret ──
|
|
502
|
+
if (choice === '1') {
|
|
503
|
+
ctx.appendMessage('system', [
|
|
504
|
+
'',
|
|
505
|
+
chalk.bold.white(' Select secret to set:'),
|
|
506
|
+
...allSecrets.map((s, i) => {
|
|
507
|
+
const icon = secretStatus[i].isSet ? chalk.green('\u2713') : chalk.dim('\u2717');
|
|
508
|
+
return ` ${chalk.cyan(`${i + 1}.`)} ${s.label} ${chalk.dim(`(${s.name})`)} ${icon}`;
|
|
509
|
+
}),
|
|
510
|
+
'',
|
|
511
|
+
].join('\n'));
|
|
512
|
+
|
|
513
|
+
const pick = (await ctx.promptText(chalk.cyan(` Select (1-${allSecrets.length}): `))).trim();
|
|
514
|
+
const idx = parseInt(pick, 10) - 1;
|
|
515
|
+
if (isNaN(idx) || idx < 0 || idx >= allSecrets.length) {
|
|
516
|
+
ctx.appendMessage('system', chalk.yellow(' Cancelled.'));
|
|
517
|
+
return { handled: true };
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
const decl = allSecrets[idx];
|
|
521
|
+
|
|
522
|
+
if (!ctx.authSdk) {
|
|
523
|
+
ctx.appendMessage('system', chalk.red(' Not authenticated — cannot encrypt secrets.'));
|
|
524
|
+
return { handled: true };
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
const value = await ctx.promptPassword(` Enter ${decl.label}: `);
|
|
528
|
+
if (!value) {
|
|
529
|
+
ctx.appendMessage('system', chalk.yellow(' No value entered. Cancelled.'));
|
|
530
|
+
return { handled: true };
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
try {
|
|
534
|
+
const { encrypt } = await import('@emblemvault/auth-sdk/crypto');
|
|
535
|
+
const encrypted = await encrypt(value, { config: { sdk: ctx.authSdk } });
|
|
536
|
+
const freshSecrets = readPluginSecrets();
|
|
537
|
+
freshSecrets[decl.name] = {
|
|
538
|
+
ciphertext: encrypted.ciphertext,
|
|
539
|
+
dataToEncryptHash: encrypted.dataToEncryptHash,
|
|
540
|
+
};
|
|
541
|
+
writePluginSecrets(freshSecrets);
|
|
542
|
+
|
|
543
|
+
// Hot-reload the plugin with the new secret (no restart needed)
|
|
544
|
+
const reloaded = await ctx.pluginManager.reloadPluginWithSecret(decl.plugin, decl.name, value);
|
|
545
|
+
if (reloaded) {
|
|
546
|
+
ctx.appendMessage('system', '\n' + chalk.green(` Secret "${decl.name}" encrypted, stored, and applied.`));
|
|
547
|
+
} else {
|
|
548
|
+
ctx.appendMessage('system', '\n' + chalk.green(` Secret "${decl.name}" encrypted and stored.`) + chalk.dim(' Restart to apply.'));
|
|
549
|
+
}
|
|
550
|
+
ctx.addLog('secrets', `Set ${decl.name}`);
|
|
551
|
+
} catch (err) {
|
|
552
|
+
ctx.appendMessage('system', chalk.red(` Encryption failed: ${err.message}`));
|
|
553
|
+
}
|
|
554
|
+
return { handled: true };
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// ── Remove a secret ──
|
|
558
|
+
if (choice === '2') {
|
|
559
|
+
const setSecrets = secretStatus.filter(s => s.isSet);
|
|
560
|
+
if (setSecrets.length === 0) {
|
|
561
|
+
ctx.appendMessage('system', chalk.dim(' No secrets are currently stored.'));
|
|
562
|
+
return { handled: true };
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
ctx.appendMessage('system', [
|
|
566
|
+
'',
|
|
567
|
+
chalk.bold.white(' Select secret to remove:'),
|
|
568
|
+
...setSecrets.map((s, i) =>
|
|
569
|
+
` ${chalk.cyan(`${i + 1}.`)} ${s.label} ${chalk.dim(`(${s.name})`)} ${chalk.dim('\u2014')} ${chalk.dim(s.plugin)}`
|
|
570
|
+
),
|
|
571
|
+
'',
|
|
572
|
+
].join('\n'));
|
|
573
|
+
|
|
574
|
+
const pick = (await ctx.promptText(chalk.cyan(` Select (1-${setSecrets.length}): `))).trim();
|
|
575
|
+
const idx = parseInt(pick, 10) - 1;
|
|
576
|
+
if (isNaN(idx) || idx < 0 || idx >= setSecrets.length) {
|
|
577
|
+
ctx.appendMessage('system', chalk.yellow(' Cancelled.'));
|
|
578
|
+
return { handled: true };
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
const target = setSecrets[idx];
|
|
582
|
+
const freshSecrets = readPluginSecrets();
|
|
583
|
+
if (freshSecrets[target.name]) {
|
|
584
|
+
delete freshSecrets[target.name];
|
|
585
|
+
writePluginSecrets(freshSecrets);
|
|
586
|
+
ctx.appendMessage('system', chalk.green(` Secret "${target.name}" removed.`));
|
|
587
|
+
ctx.addLog('secrets', `Removed ${target.name}`);
|
|
588
|
+
}
|
|
589
|
+
return { handled: true };
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// ── Back ──
|
|
593
|
+
return { handled: true };
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
function cmdGlow(args, ctx) {
|
|
597
|
+
const val = args.trim().toLowerCase();
|
|
598
|
+
if (val === 'on') {
|
|
599
|
+
const info = ctx.glow.detectGlow();
|
|
600
|
+
if (!info.installed) {
|
|
601
|
+
ctx.appendMessage('system', chalk.yellow('glow is not installed. Install from: https://github.com/charmbracelet/glow'));
|
|
602
|
+
return { handled: true };
|
|
603
|
+
}
|
|
604
|
+
ctx.settings.glowEnabled = true;
|
|
605
|
+
ctx.appendMessage('system', chalk.green('Markdown rendering enabled (via glow).'));
|
|
606
|
+
} else if (val === 'off') {
|
|
607
|
+
ctx.settings.glowEnabled = false;
|
|
608
|
+
ctx.appendMessage('system', chalk.yellow('Markdown rendering disabled.'));
|
|
609
|
+
} else {
|
|
610
|
+
const info = ctx.glow.detectGlow();
|
|
611
|
+
ctx.appendMessage('system', [
|
|
612
|
+
`${chalk.dim('Glow:')} ${ctx.settings.glowEnabled ? chalk.green('ON') : chalk.red('OFF')}`,
|
|
613
|
+
`${chalk.dim('Installed:')} ${info.installed ? chalk.green('YES') : chalk.red('NO')}`,
|
|
614
|
+
info.version ? `${chalk.dim('Version:')} ${info.version}` : '',
|
|
615
|
+
].filter(Boolean).join('\n'));
|
|
616
|
+
}
|
|
617
|
+
return { handled: true };
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
function cmdLog(args, ctx) {
|
|
621
|
+
const val = args.trim().toLowerCase();
|
|
622
|
+
if (val === 'on') {
|
|
623
|
+
ctx.settings.log = true;
|
|
624
|
+
ctx.logOpen();
|
|
625
|
+
ctx.appendMessage('system', chalk.green(`Logging enabled → ${ctx.LOG_FILE}`));
|
|
626
|
+
} else if (val === 'off') {
|
|
627
|
+
ctx.logClose();
|
|
628
|
+
ctx.settings.log = false;
|
|
629
|
+
ctx.appendMessage('system', chalk.yellow('Logging disabled.'));
|
|
630
|
+
} else {
|
|
631
|
+
const lines = [
|
|
632
|
+
`${chalk.dim('Logging:')} ${ctx.settings.log ? chalk.green('ON') : chalk.red('OFF')}`,
|
|
633
|
+
`${chalk.dim('File:')} ${ctx.LOG_FILE}`,
|
|
634
|
+
'',
|
|
635
|
+
chalk.dim('Usage: /log on|off'),
|
|
636
|
+
chalk.dim('CLI: --log --log-file <path>'),
|
|
637
|
+
];
|
|
638
|
+
ctx.appendMessage('system', lines.join('\n'));
|
|
639
|
+
}
|
|
640
|
+
return { handled: true };
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
function cmdReset(ctx) {
|
|
644
|
+
ctx.history.messages = [];
|
|
645
|
+
ctx.history.created = new Date().toISOString();
|
|
646
|
+
ctx.history.lastUpdated = new Date().toISOString();
|
|
647
|
+
|
|
648
|
+
// Persist the cleared state to disk
|
|
649
|
+
if (typeof ctx.saveHistory === 'function') ctx.saveHistory(ctx.history);
|
|
650
|
+
|
|
651
|
+
// Clear chat panel in TUI mode
|
|
652
|
+
if (ctx.tui && ctx.tui.panels && ctx.tui.panels.chat) {
|
|
653
|
+
ctx.tui.panels.chat.setContent('');
|
|
654
|
+
ctx.tui.screen.render();
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
ctx.appendMessage('system', chalk.green('Conversation cleared.'));
|
|
658
|
+
ctx.addLog('reset', 'Conversation reset');
|
|
659
|
+
return { handled: true };
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
function cmdExit(ctx) {
|
|
663
|
+
if (ctx.tui) {
|
|
664
|
+
ctx.tui.destroy();
|
|
665
|
+
}
|
|
666
|
+
process.exit(0);
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// ============================================================================
|
|
670
|
+
// Main Router
|
|
671
|
+
// ============================================================================
|
|
672
|
+
|
|
673
|
+
/**
|
|
674
|
+
* Process a slash command.
|
|
675
|
+
*
|
|
676
|
+
* @param {string} input - The raw user input (e.g. "/tools add foo")
|
|
677
|
+
* @param {object} ctx - Runtime context
|
|
678
|
+
* @returns {Promise<{ handled: boolean }>}
|
|
679
|
+
*/
|
|
680
|
+
export async function processCommand(input, ctx) {
|
|
681
|
+
const trimmed = input.trim();
|
|
682
|
+
if (!trimmed.startsWith('/')) {
|
|
683
|
+
return { handled: false };
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
// Split into command and arguments
|
|
687
|
+
const spaceIdx = trimmed.indexOf(' ');
|
|
688
|
+
const cmd = spaceIdx === -1 ? trimmed.toLowerCase() : trimmed.slice(0, spaceIdx).toLowerCase();
|
|
689
|
+
const args = spaceIdx === -1 ? '' : trimmed.slice(spaceIdx + 1);
|
|
690
|
+
|
|
691
|
+
switch (cmd) {
|
|
692
|
+
case '/help':
|
|
693
|
+
return cmdHelp(ctx);
|
|
694
|
+
|
|
695
|
+
case '/plugins':
|
|
696
|
+
return cmdPlugins(ctx);
|
|
697
|
+
|
|
698
|
+
case '/plugin':
|
|
699
|
+
return cmdPlugin(args, ctx);
|
|
700
|
+
|
|
701
|
+
case '/tools':
|
|
702
|
+
return cmdTools(args, ctx);
|
|
703
|
+
|
|
704
|
+
case '/auth':
|
|
705
|
+
return cmdAuth(ctx);
|
|
706
|
+
|
|
707
|
+
case '/wallet':
|
|
708
|
+
return cmdWallet(ctx);
|
|
709
|
+
|
|
710
|
+
case '/portfolio':
|
|
711
|
+
return cmdPortfolio();
|
|
712
|
+
|
|
713
|
+
case '/settings':
|
|
714
|
+
return cmdSettings(ctx);
|
|
715
|
+
|
|
716
|
+
case '/model':
|
|
717
|
+
return cmdModel(args, ctx);
|
|
718
|
+
|
|
719
|
+
case '/stream':
|
|
720
|
+
return cmdStream(args, ctx);
|
|
721
|
+
|
|
722
|
+
case '/debug':
|
|
723
|
+
return cmdDebug(args, ctx);
|
|
724
|
+
|
|
725
|
+
case '/history':
|
|
726
|
+
return cmdHistory(args, ctx);
|
|
727
|
+
|
|
728
|
+
case '/payment':
|
|
729
|
+
return cmdPayment(args, ctx);
|
|
730
|
+
|
|
731
|
+
case '/secrets':
|
|
732
|
+
return cmdSecrets(args, ctx);
|
|
733
|
+
|
|
734
|
+
case '/glow':
|
|
735
|
+
return cmdGlow(args, ctx);
|
|
736
|
+
|
|
737
|
+
case '/log':
|
|
738
|
+
return cmdLog(args, ctx);
|
|
739
|
+
|
|
740
|
+
case '/reset':
|
|
741
|
+
case '/clear':
|
|
742
|
+
return cmdReset(ctx);
|
|
743
|
+
|
|
744
|
+
case '/exit':
|
|
745
|
+
case '/quit':
|
|
746
|
+
return cmdExit(ctx);
|
|
747
|
+
|
|
748
|
+
default:
|
|
749
|
+
ctx.appendMessage('system', chalk.yellow(`Unknown command: ${cmd}`) + chalk.dim(' Type /help for available commands.'));
|
|
750
|
+
return { handled: true };
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
export default { processCommand, COMMANDS };
|