@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.
@@ -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 };