@darksol/terminal 0.11.0 → 0.12.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 CHANGED
@@ -15,7 +15,7 @@ A unified CLI for market intel, trading, AI-powered analysis, on-chain oracle, c
15
15
  [![License: GPL-3.0](https://img.shields.io/badge/License-GPL--3.0-gold.svg)](https://www.gnu.org/licenses/gpl-3.0)
16
16
  [![Node](https://img.shields.io/badge/node-%3E%3D18.0.0-green.svg)](https://nodejs.org/)
17
17
 
18
- - Current release: **0.11.0**
18
+ - Current release: **0.12.0**
19
19
  - Changelog: `CHANGELOG.md`
20
20
 
21
21
  ## Install
@@ -150,6 +150,9 @@ ai <prompt> # chat with trading assistant
150
150
  | `dca` | Dollar-cost averaging engine | Gas only |
151
151
  | `soul` | Agent identity & personality configuration | Free |
152
152
  | `memory` | Persistent cross-session memory store | Free |
153
+ | `whale` | Whale Radar — track wallets, copy-trade, live feed | Free |
154
+ | `dash` | Live TUI dashboard — portfolio, prices, gas, whale feed | Free |
155
+ | `auto` | Autonomous Trader — goal-based automated execution | Provider dependent |
153
156
  | `agent task` | Autonomous ReAct agent loop with tool use | Provider dependent |
154
157
  | `ai` | LLM-powered trading assistant & intent execution | Provider dependent |
155
158
  | `agent` | Secure agent signer (PK-isolated proxy) | Free |
@@ -176,6 +179,98 @@ ai <prompt> # chat with trading assistant
176
179
 
177
180
  ---
178
181
 
182
+ ## 🐋 Whale Radar
183
+
184
+ Track any wallet across 5 chains. Get alerts on swaps, transfers, new tokens. Enable copy-trading to mirror a whale's moves automatically.
185
+
186
+ ```bash
187
+ # Track a wallet
188
+ darksol whale track 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 --label "vitalik" --chain ethereum
189
+
190
+ # List all tracked wallets
191
+ darksol whale list
192
+
193
+ # View recent activity
194
+ darksol whale activity 0xd8dA... --limit 20
195
+
196
+ # Enable copy-trading (mirrors swaps with your own limits)
197
+ darksol whale mirror 0xd8dA... --max 50 --slippage 2 --dry-run
198
+
199
+ # Open the live feed (blessed TUI)
200
+ darksol whale feed
201
+
202
+ # Stop tracking
203
+ darksol whale stop 0xd8dA...
204
+ ```
205
+
206
+ - **5-chain support:** Base, Ethereum, Arbitrum, Polygon, Optimism
207
+ - **Swap decoding:** Uniswap V2 + V3 router signatures automatically parsed
208
+ - **Copy-trading:** Mirror whale swaps with budget caps, slippage limits, dry-run mode
209
+ - **Live feed:** Real-time blessed terminal UI with whale events streaming
210
+ - **Daemon integration:** Runs as a background service, feeds alerts to Telegram bot
211
+ - **Event system:** Subscribe to `whale:swap`, `whale:transfer`, `whale:newtoken`, `whale:mirror-executed`
212
+
213
+ ---
214
+
215
+ ## 📊 Live Dashboard
216
+
217
+ Full-screen terminal dashboard. Portfolio, prices, gas, transactions, whale alerts — all updating in real-time.
218
+
219
+ ```bash
220
+ # Launch the dashboard
221
+ darksol dash
222
+
223
+ # Custom refresh interval
224
+ darksol dash --refresh 15
225
+
226
+ # Compact mode (portfolio + prices only)
227
+ darksol dash --compact
228
+ ```
229
+
230
+ - **Portfolio summary** — total value, token balances, chain breakdown
231
+ - **Price ticker** — sparkline micro-charts for tracked tokens
232
+ - **Gas gauge** — current gas prices across all 5 chains
233
+ - **Recent transactions** — last 10 txs from wallet history
234
+ - **Whale feed** — live alerts when whale monitor is running
235
+ - **Keyboard shortcuts:** `q` quit, `r` refresh, `tab` cycle focus, `w` toggle whales, `1-5` switch chains
236
+ - **DARKSOL gold/dark theme** throughout
237
+
238
+ ---
239
+
240
+ ## 🤖 Autonomous Trader
241
+
242
+ Set a goal in plain English. The AI builds a strategy, monitors the market, and executes trades within your budget and risk limits. Full audit trail on every decision.
243
+
244
+ ```bash
245
+ # Start an autonomous strategy
246
+ darksol auto start "accumulate ETH under 2400" --budget 500 --max-per-trade 50 --risk moderate
247
+
248
+ # DCA into memecoins
249
+ darksol auto start "DCA into BASE memecoins with >1M liquidity" --budget 200 --interval 15 --dry-run
250
+
251
+ # Check status
252
+ darksol auto status
253
+ darksol auto status auto_1741...
254
+
255
+ # View audit trail
256
+ darksol auto log auto_1741... --limit 20
257
+
258
+ # Stop a strategy
259
+ darksol auto stop auto_1741...
260
+
261
+ # List all strategies
262
+ darksol auto list
263
+ ```
264
+
265
+ - **Natural language goals** — parsed by LLM intent system into executable strategies
266
+ - **Three risk levels:** conservative (5% stop-loss), moderate (10%), aggressive (20%)
267
+ - **Kill switches:** budget exhaustion, max loss, error threshold — auto-stops immediately
268
+ - **Dry-run mode** — test strategies without executing real trades
269
+ - **Full audit log** — every decision, trade, and skip logged to `~/.darksol/autonomous/<id>/audit.json`
270
+ - **Event system:** `auto:started`, `auto:trade`, `auto:skipped`, `auto:stopped`, `auto:budget-hit`, `auto:error`
271
+
272
+ ---
273
+
179
274
  ## 📱 Telegram Bot
180
275
 
181
276
  Turn your terminal into a Telegram AI agent. Same brain (LLM + soul + memory), different mouth.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@darksol/terminal",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "description": "DARKSOL Terminal — unified CLI for all DARKSOL services. Market intel, trading, oracle, casino, and more.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,465 @@
1
+ import { EventEmitter } from 'node:events';
2
+ import { mkdirSync, readFileSync, writeFileSync, existsSync } from 'node:fs';
3
+ import { homedir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import { parseIntent, adviseStrategy } from '../llm/intent.js';
6
+ import { getConfig, setConfig } from '../config/store.js';
7
+ import { executeSwap } from '../trading/swap.js';
8
+ import { runDCA } from '../trading/dca.js';
9
+ import { evaluateConditions, shouldTrade } from './strategy-evaluator.js';
10
+
11
+ const STRATEGIES_KEY = 'autonomous.strategies';
12
+ const AUTONOMOUS_DIR = join(homedir(), '.darksol', 'autonomous');
13
+ const runtimeTimers = new Map();
14
+ let strategySequence = 0;
15
+
16
+ export const autonomousEvents = new EventEmitter();
17
+
18
+ const autonomousDeps = {
19
+ parseIntent,
20
+ adviseStrategy,
21
+ evaluateConditions,
22
+ shouldTrade,
23
+ executeSwap,
24
+ runDCA,
25
+ now: () => Date.now(),
26
+ setInterval: global.setInterval.bind(global),
27
+ clearInterval: global.clearInterval.bind(global),
28
+ };
29
+
30
+ function riskProfile(level = 'moderate') {
31
+ const profiles = {
32
+ conservative: { stopLossPct: 5, maxErrors: 2, tradeShare: 0.1 },
33
+ moderate: { stopLossPct: 10, maxErrors: 3, tradeShare: 0.2 },
34
+ aggressive: { stopLossPct: 20, maxErrors: 5, tradeShare: 0.35 },
35
+ };
36
+ return profiles[level] || profiles.moderate;
37
+ }
38
+
39
+ function ensureDir(path) {
40
+ if (!existsSync(path)) mkdirSync(path, { recursive: true });
41
+ }
42
+
43
+ function ensureRootDir() {
44
+ ensureDir(AUTONOMOUS_DIR);
45
+ }
46
+
47
+ function strategyDir(id) {
48
+ return join(AUTONOMOUS_DIR, id);
49
+ }
50
+
51
+ function auditPath(id) {
52
+ return join(strategyDir(id), 'audit.json');
53
+ }
54
+
55
+ function loadStrategies() {
56
+ return getConfig(STRATEGIES_KEY) || [];
57
+ }
58
+
59
+ function saveStrategies(strategies) {
60
+ setConfig(STRATEGIES_KEY, strategies);
61
+ }
62
+
63
+ function findStrategy(id) {
64
+ return loadStrategies().find((item) => item.id === id || item.id.startsWith(id)) || null;
65
+ }
66
+
67
+ function persistStrategy(strategy) {
68
+ const strategies = loadStrategies();
69
+ const index = strategies.findIndex((item) => item.id === strategy.id);
70
+ if (index === -1) strategies.push(strategy);
71
+ else strategies[index] = strategy;
72
+ saveStrategies(strategies);
73
+ return strategy;
74
+ }
75
+
76
+ function safeJsonParse(raw, fallback) {
77
+ try {
78
+ return JSON.parse(raw);
79
+ } catch {
80
+ return fallback;
81
+ }
82
+ }
83
+
84
+ function appendAudit(id, entry) {
85
+ ensureRootDir();
86
+ const dir = strategyDir(id);
87
+ ensureDir(dir);
88
+ const file = auditPath(id);
89
+ const history = existsSync(file) ? safeJsonParse(readFileSync(file, 'utf8'), []) : [];
90
+ history.push(entry);
91
+ writeFileSync(file, JSON.stringify(history, null, 2));
92
+ }
93
+
94
+ function parseThreshold(goal, operator) {
95
+ const regex = operator === 'under'
96
+ ? /\b(?:under|below|<)\s*\$?([\d,.]+(?:\.\d+)?)(?:\s*[mk])?\b/i
97
+ : /\b(?:over|above|>)\s*\$?([\d,.]+(?:\.\d+)?)(?:\s*[mk])?\b/i;
98
+ const match = goal.match(regex);
99
+ if (!match) return null;
100
+ return Number(match[1].replace(/,/g, ''));
101
+ }
102
+
103
+ function parseLiquidity(goal) {
104
+ const match = goal.match(/>\s*([\d.]+)\s*([mk])?\s+liquidity/i) || goal.match(/liquidity\s*(?:above|over)\s*([\d.]+)\s*([mk])?/i);
105
+ if (!match) return null;
106
+ const base = Number(match[1]);
107
+ if (match[2]?.toLowerCase() === 'm') return base * 1_000_000;
108
+ if (match[2]?.toLowerCase() === 'k') return base * 1_000;
109
+ return base;
110
+ }
111
+
112
+ function inferPrimaryToken(goal, intent = {}) {
113
+ const candidates = [
114
+ intent.tokenOut,
115
+ intent.token,
116
+ goal.match(/\baccumulate\s+([A-Za-z0-9]+)/i)?.[1],
117
+ goal.match(/\binto\s+([A-Za-z0-9]+)/i)?.[1],
118
+ ].filter(Boolean);
119
+ return String(candidates[0] || 'ETH').toUpperCase();
120
+ }
121
+
122
+ function buildPlan(goal, intent, advisoryText, options) {
123
+ const risk = riskProfile(options.riskLevel);
124
+ const primaryToken = inferPrimaryToken(goal, intent);
125
+ const quoteToken = String(intent.tokenIn || 'USDC').toUpperCase();
126
+ const entryBelow = parseThreshold(goal, 'under');
127
+ const entryAbove = parseThreshold(goal, 'over');
128
+ const minLiquidity = parseLiquidity(goal) || (goal.match(/memecoin/i) ? 1_000_000 : 100_000);
129
+ const mode = /\bdca\b/i.test(goal) ? 'dca' : 'swing';
130
+ const takeProfitPct = options.riskLevel === 'aggressive' ? 20 : options.riskLevel === 'conservative' ? 8 : 12;
131
+
132
+ return {
133
+ summary: advisoryText || `Autonomous ${mode} strategy for ${primaryToken}`,
134
+ mode,
135
+ primaryToken,
136
+ quoteToken,
137
+ entry: {
138
+ token: primaryToken,
139
+ priceBelow: entryBelow,
140
+ priceAbove: entryAbove,
141
+ },
142
+ exit: {
143
+ takeProfitPct,
144
+ stopLossPct: risk.stopLossPct,
145
+ },
146
+ filters: {
147
+ minLiquidity,
148
+ category: goal.match(/memecoin/i) ? 'memecoins' : 'general',
149
+ },
150
+ cooldownMs: options.riskLevel === 'aggressive' ? 5 * 60 * 1000 : 15 * 60 * 1000,
151
+ };
152
+ }
153
+
154
+ function hydrateDerivedFields(strategy) {
155
+ const avgEntry = strategy.tradesExecuted > 0 && strategy.positionSize > 0
156
+ ? Number(strategy.costBasis || 0) / Number(strategy.positionSize || 1)
157
+ : null;
158
+ const takeProfitPrice = avgEntry ? avgEntry * (1 + Number(strategy.plan.exit.takeProfitPct || 0) / 100) : null;
159
+ const stopLossPrice = avgEntry ? avgEntry * (1 - Number(strategy.plan.exit.stopLossPct || 0) / 100) : null;
160
+ strategy.plan.exit.takeProfitPrice = takeProfitPrice;
161
+ strategy.plan.exit.stopLossPrice = stopLossPrice;
162
+ return strategy;
163
+ }
164
+
165
+ function computeTradeBudget(strategy, conditions) {
166
+ const profile = riskProfile(strategy.riskLevel);
167
+ const remaining = Math.max(0, Number(strategy.budget) - Number(strategy.spent));
168
+ const suggested = Math.min(Number(strategy.maxPerTrade), Number(strategy.budget) * profile.tradeShare, remaining);
169
+ return Number(conditions?.tradeAmount || suggested || remaining);
170
+ }
171
+
172
+ function updatePosition(strategy, decision, conditions, tradeAmount) {
173
+ const price = Number(conditions.price || 0);
174
+ if (!price || !tradeAmount) return strategy;
175
+
176
+ if (decision.action === 'buy') {
177
+ const units = tradeAmount / price;
178
+ strategy.spent = Number(strategy.spent) + tradeAmount;
179
+ strategy.costBasis = Number(strategy.costBasis || 0) + tradeAmount;
180
+ strategy.positionSize = Number(strategy.positionSize || 0) + units;
181
+ }
182
+
183
+ if (decision.action === 'sell') {
184
+ const currentPosition = Number(strategy.positionSize || 0);
185
+ const unitsToSell = currentPosition;
186
+ const proceeds = unitsToSell * price;
187
+ strategy.realizedPnl = Number(strategy.realizedPnl || 0) + (proceeds - Number(strategy.costBasis || 0));
188
+ strategy.positionSize = 0;
189
+ strategy.costBasis = 0;
190
+ }
191
+
192
+ strategy.pnl = Number(strategy.realizedPnl || 0);
193
+ strategy.lastPrice = price;
194
+ strategy.lastTradeAt = new Date(autonomousDeps.now()).toISOString();
195
+ strategy.tradesExecuted = Number(strategy.tradesExecuted || 0) + 1;
196
+ hydrateDerivedFields(strategy);
197
+ return strategy;
198
+ }
199
+
200
+ function finalizeStrategy(strategy, status, reason, eventName) {
201
+ strategy.status = status;
202
+ strategy.stopReason = reason;
203
+ strategy.nextCheckAt = null;
204
+ strategy.updatedAt = new Date(autonomousDeps.now()).toISOString();
205
+ persistStrategy(strategy);
206
+
207
+ const timer = runtimeTimers.get(strategy.id);
208
+ if (timer) {
209
+ autonomousDeps.clearInterval(timer);
210
+ runtimeTimers.delete(strategy.id);
211
+ }
212
+
213
+ appendAudit(strategy.id, {
214
+ timestamp: strategy.updatedAt,
215
+ type: 'stopped',
216
+ reason,
217
+ status,
218
+ });
219
+ autonomousEvents.emit(eventName || 'auto:stopped', { id: strategy.id, reason, strategy });
220
+ return strategy;
221
+ }
222
+
223
+ function scheduleStrategy(id) {
224
+ const existing = runtimeTimers.get(id);
225
+ if (existing) autonomousDeps.clearInterval(existing);
226
+ const strategy = findStrategy(id);
227
+ if (!strategy || strategy.status !== 'active') return;
228
+ const timer = autonomousDeps.setInterval(() => {
229
+ runStrategyCycle(id).catch(() => {});
230
+ }, strategy.intervalMs);
231
+ runtimeTimers.set(id, timer);
232
+ }
233
+
234
+ async function executeDecision(strategy, decision, conditions) {
235
+ const token = strategy.plan.primaryToken;
236
+ const quoteToken = strategy.plan.quoteToken;
237
+ const tradeAmount = computeTradeBudget(strategy, conditions);
238
+ const tradeEvent = {
239
+ timestamp: new Date(autonomousDeps.now()).toISOString(),
240
+ type: 'trade',
241
+ action: decision.action,
242
+ token,
243
+ amount: tradeAmount,
244
+ reason: decision.reason,
245
+ confidence: decision.confidence,
246
+ price: conditions.price,
247
+ dryRun: strategy.dryRun,
248
+ };
249
+
250
+ if (strategy.dryRun) {
251
+ updatePosition(strategy, decision, conditions, tradeAmount);
252
+ strategy.tradeHistory.push({ ...tradeEvent, result: { success: true, dryRun: true } });
253
+ appendAudit(strategy.id, { ...tradeEvent, result: { success: true, dryRun: true } });
254
+ autonomousEvents.emit('auto:trade', { id: strategy.id, trade: tradeEvent, dryRun: true });
255
+ return strategy;
256
+ }
257
+
258
+ const tradeOpts = decision.action === 'buy'
259
+ ? { tokenIn: quoteToken, tokenOut: token, amount: tradeAmount.toFixed(2), confirm: true }
260
+ : { tokenIn: token, tokenOut: quoteToken, amount: String(Number(strategy.positionSize || 0)), confirm: true };
261
+ const result = strategy.plan.mode === 'dca' && strategy.plan.useDcaExecutor
262
+ ? await autonomousDeps.runDCA({ password: strategy.password })
263
+ : await autonomousDeps.executeSwap(tradeOpts);
264
+
265
+ updatePosition(strategy, decision, conditions, tradeAmount);
266
+ strategy.tradeHistory.push({ ...tradeEvent, result: result || null });
267
+ appendAudit(strategy.id, { ...tradeEvent, result: result || null });
268
+ autonomousEvents.emit('auto:trade', { id: strategy.id, trade: tradeEvent, result });
269
+ return strategy;
270
+ }
271
+
272
+ function checkKillSwitch(strategy) {
273
+ const maxLoss = -Math.abs(Number(strategy.maxLoss || 0));
274
+ if (Number(strategy.spent) >= Number(strategy.budget)) {
275
+ finalizeStrategy(strategy, 'completed', 'budget_exhausted', 'auto:budget-hit');
276
+ return true;
277
+ }
278
+ if (Number(strategy.pnl || 0) <= maxLoss) {
279
+ finalizeStrategy(strategy, 'completed', 'max_loss_hit', 'auto:stopped');
280
+ return true;
281
+ }
282
+ if (Number(strategy.errorCount || 0) >= Number(strategy.maxErrors || 0)) {
283
+ finalizeStrategy(strategy, 'completed', 'error_threshold', 'auto:error');
284
+ return true;
285
+ }
286
+ return false;
287
+ }
288
+
289
+ export async function runStrategyCycle(id) {
290
+ const strategy = findStrategy(id);
291
+ if (!strategy || strategy.status !== 'active') return null;
292
+ if (checkKillSwitch(strategy)) return strategy;
293
+
294
+ strategy.updatedAt = new Date(autonomousDeps.now()).toISOString();
295
+
296
+ try {
297
+ const conditions = await autonomousDeps.evaluateConditions(strategy);
298
+ const decision = await autonomousDeps.shouldTrade(strategy, conditions);
299
+
300
+ appendAudit(strategy.id, {
301
+ timestamp: strategy.updatedAt,
302
+ type: 'decision',
303
+ conditions,
304
+ decision,
305
+ });
306
+
307
+ if (decision.action === 'hold') {
308
+ strategy.lastDecision = decision.reason;
309
+ strategy.nextCheckAt = new Date(autonomousDeps.now() + strategy.intervalMs).toISOString();
310
+ persistStrategy(strategy);
311
+ autonomousEvents.emit('auto:skipped', { id: strategy.id, decision, conditions });
312
+ return strategy;
313
+ }
314
+
315
+ await executeDecision(strategy, decision, conditions);
316
+ strategy.lastDecision = decision.reason;
317
+ strategy.nextCheckAt = new Date(autonomousDeps.now() + strategy.intervalMs).toISOString();
318
+ persistStrategy(strategy);
319
+
320
+ if (checkKillSwitch(strategy)) return strategy;
321
+ return strategy;
322
+ } catch (err) {
323
+ strategy.errorCount = Number(strategy.errorCount || 0) + 1;
324
+ strategy.lastError = err.message;
325
+ strategy.nextCheckAt = new Date(autonomousDeps.now() + strategy.intervalMs).toISOString();
326
+ persistStrategy(strategy);
327
+ appendAudit(strategy.id, {
328
+ timestamp: strategy.updatedAt,
329
+ type: 'error',
330
+ message: err.message,
331
+ errorCount: strategy.errorCount,
332
+ });
333
+ autonomousEvents.emit('auto:error', { id: strategy.id, error: err });
334
+ checkKillSwitch(strategy);
335
+ return strategy;
336
+ }
337
+ }
338
+
339
+ export async function startAutonomous(goal, options = {}) {
340
+ ensureRootDir();
341
+ const parsedOptions = {
342
+ budget: Number(options.budget || 0),
343
+ maxPerTrade: Number(options.maxPerTrade || options.budget || 0),
344
+ riskLevel: options.riskLevel || 'moderate',
345
+ intervalMs: Math.max(1, Number(options.interval || 5)) * 60 * 1000,
346
+ chains: Array.isArray(options.chains) && options.chains.length ? options.chains : ['base'],
347
+ dryRun: Boolean(options.dryRun),
348
+ };
349
+
350
+ const intent = await autonomousDeps.parseIntent(goal, options);
351
+ const strategyAdvice = await autonomousDeps.adviseStrategy(
352
+ intent.tokenOut || intent.token || inferPrimaryToken(goal, intent),
353
+ parsedOptions.budget || parsedOptions.maxPerTrade || 0,
354
+ `${Math.max(1, Number(options.interval || 5))} minute cadence`,
355
+ options,
356
+ ).catch(() => null);
357
+
358
+ const id = `auto_${autonomousDeps.now()}_${++strategySequence}`;
359
+ const profile = riskProfile(parsedOptions.riskLevel);
360
+ const plan = buildPlan(goal, intent, strategyAdvice?.content || strategyAdvice?.summary || '', parsedOptions);
361
+ const strategy = hydrateDerivedFields({
362
+ id,
363
+ goal,
364
+ intent,
365
+ plan,
366
+ status: 'active',
367
+ budget: parsedOptions.budget,
368
+ spent: 0,
369
+ costBasis: 0,
370
+ positionSize: 0,
371
+ tradesExecuted: 0,
372
+ tradeHistory: [],
373
+ pnl: 0,
374
+ realizedPnl: 0,
375
+ maxPerTrade: parsedOptions.maxPerTrade,
376
+ maxLoss: parsedOptions.budget * (profile.stopLossPct / 100),
377
+ maxErrors: profile.maxErrors,
378
+ errorCount: 0,
379
+ riskLevel: parsedOptions.riskLevel,
380
+ intervalMs: parsedOptions.intervalMs,
381
+ chains: parsedOptions.chains,
382
+ dryRun: parsedOptions.dryRun,
383
+ createdAt: new Date(autonomousDeps.now()).toISOString(),
384
+ startedAt: new Date(autonomousDeps.now()).toISOString(),
385
+ updatedAt: new Date(autonomousDeps.now()).toISOString(),
386
+ nextCheckAt: new Date(autonomousDeps.now() + parsedOptions.intervalMs).toISOString(),
387
+ lastDecision: '',
388
+ stopReason: '',
389
+ lastError: '',
390
+ });
391
+
392
+ persistStrategy(strategy);
393
+ appendAudit(id, {
394
+ timestamp: strategy.createdAt,
395
+ type: 'started',
396
+ goal,
397
+ options: parsedOptions,
398
+ intent,
399
+ plan,
400
+ });
401
+ autonomousEvents.emit('auto:started', { id, strategy });
402
+ scheduleStrategy(id);
403
+ return strategy;
404
+ }
405
+
406
+ export function stopAutonomous(id) {
407
+ const strategy = findStrategy(id);
408
+ if (!strategy) return null;
409
+ return finalizeStrategy(strategy, 'paused', 'manual_stop', 'auto:stopped');
410
+ }
411
+
412
+ export function getStatus(id) {
413
+ if (!id) return listStrategies();
414
+ const strategy = findStrategy(id);
415
+ if (!strategy) return null;
416
+ return {
417
+ id: strategy.id,
418
+ goal: strategy.goal,
419
+ status: strategy.status,
420
+ spent: strategy.spent,
421
+ budget: strategy.budget,
422
+ tradesExecuted: strategy.tradesExecuted,
423
+ pnl: strategy.pnl,
424
+ nextCheckAt: strategy.nextCheckAt,
425
+ riskLevel: strategy.riskLevel,
426
+ dryRun: strategy.dryRun,
427
+ lastDecision: strategy.lastDecision,
428
+ };
429
+ }
430
+
431
+ export function listStrategies() {
432
+ return loadStrategies().map((strategy) => ({
433
+ id: strategy.id,
434
+ goal: strategy.goal,
435
+ status: strategy.status,
436
+ spent: strategy.spent,
437
+ budget: strategy.budget,
438
+ tradesExecuted: strategy.tradesExecuted,
439
+ pnl: strategy.pnl,
440
+ nextCheckAt: strategy.nextCheckAt,
441
+ }));
442
+ }
443
+
444
+ export function getAuditLog(id, limit = 50) {
445
+ const file = auditPath(id);
446
+ if (!existsSync(file)) return [];
447
+ const history = safeJsonParse(readFileSync(file, 'utf8'), []);
448
+ return history.slice(-Math.max(1, Number(limit || 50)));
449
+ }
450
+
451
+ export function __setAutonomousDeps(overrides = {}) {
452
+ Object.assign(autonomousDeps, overrides);
453
+ }
454
+
455
+ export function __resetAutonomousDeps() {
456
+ autonomousDeps.parseIntent = parseIntent;
457
+ autonomousDeps.adviseStrategy = adviseStrategy;
458
+ autonomousDeps.evaluateConditions = evaluateConditions;
459
+ autonomousDeps.shouldTrade = shouldTrade;
460
+ autonomousDeps.executeSwap = executeSwap;
461
+ autonomousDeps.runDCA = runDCA;
462
+ autonomousDeps.now = () => Date.now();
463
+ autonomousDeps.setInterval = global.setInterval.bind(global);
464
+ autonomousDeps.clearInterval = global.clearInterval.bind(global);
465
+ }