@darksol/terminal 0.2.1 → 0.3.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 +263 -189
- package/package.json +11 -9
- package/src/cli.js +31 -1
- package/src/config/keys.js +53 -1
- package/src/llm/intent.js +126 -0
- package/src/setup/wizard.js +516 -0
- package/src/ui/banner.js +4 -2
- package/tests/cli.test.js +0 -72
- package/tests/config.test.js +0 -75
- package/tests/dca.test.js +0 -141
- package/tests/keystore.test.js +0 -94
- package/tests/scripts.test.js +0 -136
- package/tests/trading.test.js +0 -21
- package/tests/ui.test.js +0 -27
package/src/config/keys.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { randomBytes, createCipheriv, createDecipheriv, scryptSync } from 'crypto';
|
|
2
2
|
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
|
|
3
3
|
import { join } from 'path';
|
|
4
|
-
import { homedir } from 'os';
|
|
4
|
+
import { homedir, hostname, userInfo } from 'os';
|
|
5
5
|
import { theme } from '../ui/theme.js';
|
|
6
6
|
import { kvDisplay, success, error, warn, info } from '../ui/components.js';
|
|
7
7
|
import { showSection } from '../ui/banner.js';
|
|
@@ -317,4 +317,56 @@ export function listKeys() {
|
|
|
317
317
|
info('Services: ' + Object.keys(SERVICES).join(', '));
|
|
318
318
|
}
|
|
319
319
|
|
|
320
|
+
/**
|
|
321
|
+
* Add a key directly (non-interactive, for setup wizard / OAuth)
|
|
322
|
+
* Uses a machine-derived vault password for seamless storage
|
|
323
|
+
*/
|
|
324
|
+
export function addKeyDirect(service, apiKey) {
|
|
325
|
+
const vaultPass = getMachineVaultPass();
|
|
326
|
+
const vault = loadVault();
|
|
327
|
+
const svc = SERVICES[service];
|
|
328
|
+
vault.keys[service] = {
|
|
329
|
+
encrypted: encrypt(apiKey, vaultPass),
|
|
330
|
+
service: svc?.name || service,
|
|
331
|
+
category: svc?.category || 'custom',
|
|
332
|
+
addedAt: new Date().toISOString(),
|
|
333
|
+
autoStored: true, // flag: stored via wizard, not manual password
|
|
334
|
+
};
|
|
335
|
+
saveVault(vault);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Get a key stored via addKeyDirect (auto-stored, machine password)
|
|
340
|
+
*/
|
|
341
|
+
export function getKeyAuto(service) {
|
|
342
|
+
const vault = loadVault();
|
|
343
|
+
const entry = vault.keys[service];
|
|
344
|
+
if (!entry) return getKeyFromEnv(service);
|
|
345
|
+
if (!entry.autoStored) return getKeyFromEnv(service); // manual entries need password
|
|
346
|
+
try {
|
|
347
|
+
return decrypt(entry.encrypted, getMachineVaultPass());
|
|
348
|
+
} catch {
|
|
349
|
+
return getKeyFromEnv(service);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Check if any key exists for a service (stored or env)
|
|
355
|
+
*/
|
|
356
|
+
export function hasKey(service) {
|
|
357
|
+
const vault = loadVault();
|
|
358
|
+
if (vault.keys[service]) return true;
|
|
359
|
+
const svc = SERVICES[service];
|
|
360
|
+
if (svc?.envVar && process.env[svc.envVar]) return true;
|
|
361
|
+
return false;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Machine-derived vault password for auto-stored keys
|
|
366
|
+
* (derived from hostname + username — not high security, but protects at rest)
|
|
367
|
+
*/
|
|
368
|
+
function getMachineVaultPass() {
|
|
369
|
+
return `darksol-vault-${hostname()}-${userInfo().username}`;
|
|
370
|
+
}
|
|
371
|
+
|
|
320
372
|
export { KEYS_DIR, KEYS_FILE };
|
package/src/llm/intent.js
CHANGED
|
@@ -307,4 +307,130 @@ Provide: sentiment, liquidity assessment, risk level (1-10), key considerations.
|
|
|
307
307
|
}
|
|
308
308
|
}
|
|
309
309
|
|
|
310
|
+
// ──────────────────────────────────────────────────
|
|
311
|
+
// INTENT EXECUTOR — Wire AI intents into real trades
|
|
312
|
+
// ──────────────────────────────────────────────────
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Execute a parsed intent (from parseIntent or ai ask)
|
|
316
|
+
* Bridges natural language → actual swap/snipe/dca/transfer
|
|
317
|
+
*
|
|
318
|
+
* @param {object} intent - Parsed intent object from parseIntent
|
|
319
|
+
* @param {object} opts - { password, confirm, yes }
|
|
320
|
+
* @returns {Promise<object>} Execution result
|
|
321
|
+
*/
|
|
322
|
+
export async function executeIntent(intent, opts = {}) {
|
|
323
|
+
if (!intent || intent.action === 'unknown' || intent.action === 'error') {
|
|
324
|
+
error('Cannot execute: intent not recognized');
|
|
325
|
+
return { success: false, reason: 'unknown intent' };
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (intent.confidence !== undefined && intent.confidence < 0.6) {
|
|
329
|
+
warn(`Low confidence (${(intent.confidence * 100).toFixed(0)}%) — review the intent before executing`);
|
|
330
|
+
if (!opts.yes) {
|
|
331
|
+
const inquirer = (await import('inquirer')).default;
|
|
332
|
+
const { proceed } = await inquirer.prompt([{
|
|
333
|
+
type: 'confirm',
|
|
334
|
+
name: 'proceed',
|
|
335
|
+
message: theme.accent('Execute anyway?'),
|
|
336
|
+
default: false,
|
|
337
|
+
}]);
|
|
338
|
+
if (!proceed) return { success: false, reason: 'user cancelled' };
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Show intent summary for confirmation
|
|
343
|
+
if (!opts.yes) {
|
|
344
|
+
showSection('EXECUTE INTENT');
|
|
345
|
+
kvDisplay([
|
|
346
|
+
['Action', intent.action],
|
|
347
|
+
['Token In', intent.tokenIn || '-'],
|
|
348
|
+
['Token Out', intent.tokenOut || '-'],
|
|
349
|
+
['Amount', intent.amount || '-'],
|
|
350
|
+
['Chain', intent.chain || getConfig('chain') || 'base'],
|
|
351
|
+
['Confidence', intent.confidence ? `${(intent.confidence * 100).toFixed(0)}%` : '-'],
|
|
352
|
+
]);
|
|
353
|
+
|
|
354
|
+
if (intent.warnings?.length > 0) {
|
|
355
|
+
console.log('');
|
|
356
|
+
intent.warnings.forEach(w => warn(w));
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
console.log('');
|
|
360
|
+
const inquirer = (await import('inquirer')).default;
|
|
361
|
+
const { confirm } = await inquirer.prompt([{
|
|
362
|
+
type: 'confirm',
|
|
363
|
+
name: 'confirm',
|
|
364
|
+
message: theme.gold('Execute this trade?'),
|
|
365
|
+
default: false,
|
|
366
|
+
}]);
|
|
367
|
+
if (!confirm) return { success: false, reason: 'user cancelled' };
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
try {
|
|
371
|
+
switch (intent.action) {
|
|
372
|
+
case 'swap': {
|
|
373
|
+
const { executeSwap } = await import('../trading/swap.js');
|
|
374
|
+
return await executeSwap({
|
|
375
|
+
tokenIn: intent.tokenIn,
|
|
376
|
+
tokenOut: intent.tokenOut,
|
|
377
|
+
amount: intent.amount,
|
|
378
|
+
chain: intent.chain,
|
|
379
|
+
password: opts.password,
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
case 'snipe': {
|
|
384
|
+
const { executeSnipe } = await import('../trading/snipe.js');
|
|
385
|
+
return await executeSnipe({
|
|
386
|
+
token: intent.tokenOut || intent.tokenIn,
|
|
387
|
+
amount: intent.amount,
|
|
388
|
+
chain: intent.chain,
|
|
389
|
+
gasMultiplier: intent.gasMultiplier,
|
|
390
|
+
password: opts.password,
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
case 'dca': {
|
|
395
|
+
const { createDCAOrder } = await import('../trading/dca.js');
|
|
396
|
+
return await createDCAOrder({
|
|
397
|
+
token: intent.tokenOut || intent.tokenIn,
|
|
398
|
+
amount: intent.amount,
|
|
399
|
+
interval: intent.interval || '1h',
|
|
400
|
+
orders: intent.orders || 10,
|
|
401
|
+
chain: intent.chain,
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
case 'transfer': {
|
|
406
|
+
// Transfer intent — use execution script engine
|
|
407
|
+
info(`Transfer intent detected. Use: darksol script create (template: transfer)`);
|
|
408
|
+
info(`To: ${intent.to || 'specify address'}, Amount: ${intent.amount || 'specify amount'}`);
|
|
409
|
+
return { success: true, action: 'transfer', note: 'Use script system for transfers' };
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
case 'info':
|
|
413
|
+
case 'analyze': {
|
|
414
|
+
const token = intent.tokenOut || intent.tokenIn || intent.token;
|
|
415
|
+
if (token) {
|
|
416
|
+
await analyzeToken(token, opts);
|
|
417
|
+
return { success: true, action: 'analyze' };
|
|
418
|
+
}
|
|
419
|
+
info('No token specified for analysis');
|
|
420
|
+
return { success: false, reason: 'no token specified' };
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
default:
|
|
424
|
+
warn(`Action "${intent.action}" not yet wired for execution`);
|
|
425
|
+
if (intent.command) {
|
|
426
|
+
info(`Suggested command: ${theme.gold(intent.command)}`);
|
|
427
|
+
}
|
|
428
|
+
return { success: false, reason: `unhandled action: ${intent.action}` };
|
|
429
|
+
}
|
|
430
|
+
} catch (err) {
|
|
431
|
+
error(`Execution failed: ${err.message}`);
|
|
432
|
+
return { success: false, error: err.message };
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
310
436
|
export { INTENT_SYSTEM_PROMPT };
|