@aiaiaichain/agent 0.1.4 → 0.1.6

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.
Files changed (59) hide show
  1. package/dist/cli.js +224 -9
  2. package/dist/core/ChainConfig.js +1 -1
  3. package/dist/core/SystemMonitor.d.ts +1 -4
  4. package/dist/core/SystemMonitor.js +20 -46
  5. package/dist/index.d.ts +10 -0
  6. package/dist/index.js +11 -0
  7. package/dist/models/ModelRegistry.js +12 -4
  8. package/dist/runner/AgentRunner.d.ts +2 -0
  9. package/dist/runner/AgentRunner.js +18 -1
  10. package/dist/runner/ModelClient.js +109 -48
  11. package/dist/session/SessionManager.d.ts +1 -0
  12. package/dist/session/SessionManager.js +8 -2
  13. package/dist/session/SessionStore.js +8 -3
  14. package/dist/tools/CrossTools.js +13 -5
  15. package/dist/tools/MarketSentiment.js +22 -13
  16. package/dist/tools/NewsSentiment.js +9 -3
  17. package/dist/tools/PriceFeed.js +11 -4
  18. package/dist/tools/TechnicalAnalysis.js +2 -1
  19. package/dist/tools/TokenCalendar.d.ts +24 -0
  20. package/dist/tools/TokenCalendar.js +81 -0
  21. package/dist/tools/TokenSecurityScanner.d.ts +22 -0
  22. package/dist/tools/TokenSecurityScanner.js +102 -0
  23. package/dist/tools/TransactionSim.d.ts +17 -0
  24. package/dist/tools/TransactionSim.js +78 -0
  25. package/dist/tui/App.js +145 -23
  26. package/dist/tui/REPL.js +2 -2
  27. package/dist/tui/Sparkline.d.ts +21 -0
  28. package/dist/tui/Sparkline.js +44 -0
  29. package/dist/tui/ThemePresets.d.ts +25 -0
  30. package/dist/tui/ThemePresets.js +117 -0
  31. package/dist/util/clipboard.d.ts +9 -0
  32. package/dist/util/clipboard.js +26 -0
  33. package/dist/util/commandSuggest.d.ts +7 -0
  34. package/dist/util/commandSuggest.js +44 -0
  35. package/dist/util/confirmation.d.ts +6 -0
  36. package/dist/util/confirmation.js +16 -0
  37. package/dist/util/errorHandler.d.ts +3 -0
  38. package/dist/util/errorHandler.js +28 -0
  39. package/dist/util/logger.d.ts +11 -0
  40. package/dist/util/logger.js +43 -0
  41. package/dist/util/processManager.d.ts +5 -0
  42. package/dist/util/processManager.js +39 -0
  43. package/dist/util/resilientFetch.d.ts +21 -0
  44. package/dist/util/resilientFetch.js +94 -0
  45. package/dist/util/responseCache.d.ts +27 -0
  46. package/dist/util/responseCache.js +54 -0
  47. package/dist/util/safeLog.d.ts +4 -5
  48. package/dist/util/safeLog.js +68 -30
  49. package/dist/util/scheduler.d.ts +14 -0
  50. package/dist/util/scheduler.js +75 -0
  51. package/dist/util/webhooks.d.ts +9 -0
  52. package/dist/util/webhooks.js +75 -0
  53. package/dist/wallet/ActionFeed.d.ts +3 -2
  54. package/dist/wallet/ActionFeed.js +97 -76
  55. package/dist/wallet/AgentWallet.d.ts +6 -2
  56. package/dist/wallet/AgentWallet.js +59 -27
  57. package/dist/wallet/ProfitTracker.d.ts +30 -0
  58. package/dist/wallet/ProfitTracker.js +93 -0
  59. package/package.json +2 -2
@@ -0,0 +1,22 @@
1
+ /**
2
+ * src/tools/TokenSecurityScanner.ts - honeypot/rugpull detection for Solana tokens
3
+ */
4
+ import type { ToolResult } from "../api/ExtensionAPI.js";
5
+ export interface TokenSecurityResult {
6
+ address: string;
7
+ symbol: string;
8
+ name: string;
9
+ isHoneypot: boolean;
10
+ isRugRisk: boolean;
11
+ mintAuthorityRenounced: boolean;
12
+ freezeAuthorityRenounced: boolean;
13
+ liquidityLocked: boolean;
14
+ top10HolderPct: number;
15
+ warnings: string[];
16
+ score: number;
17
+ }
18
+ export declare const tokenSecurityParams: import("@sinclair/typebox").TObject<{
19
+ address: import("@sinclair/typebox").TString;
20
+ }>;
21
+ export declare function scanTokenSecurityTool(_id: string, params: Record<string, unknown>): Promise<ToolResult>;
22
+ //# sourceMappingURL=TokenSecurityScanner.d.ts.map
@@ -0,0 +1,102 @@
1
+ /**
2
+ * src/tools/TokenSecurityScanner.ts - honeypot/rugpull detection for Solana tokens
3
+ */
4
+ import { Type } from "@sinclair/typebox";
5
+ import { resilientFetch } from "../util/resilientFetch.js";
6
+ import { logger } from "../util/logger.js";
7
+ export const tokenSecurityParams = Type.Object({
8
+ address: Type.String({ description: "Token mint address on Solana" }),
9
+ });
10
+ export async function scanTokenSecurityTool(_id, params) {
11
+ const address = params.address;
12
+ if (!address || address.length < 32 || address.length > 44) {
13
+ return {
14
+ content: [{ type: "text", text: "Invalid token address format." }],
15
+ isError: true,
16
+ };
17
+ }
18
+ const warnings = [];
19
+ let score = 100;
20
+ let isHoneypot = false;
21
+ let isRugRisk = false;
22
+ let dexData = null;
23
+ try {
24
+ const resp = await resilientFetch(`https://api.dexscreener.com/tokens/v1/solana/${address}`, {
25
+ timeout: 10_000,
26
+ retries: 1,
27
+ });
28
+ if (resp.ok) {
29
+ const data = await resp.json();
30
+ if (data && data.length > 0)
31
+ dexData = data[0];
32
+ }
33
+ }
34
+ catch (error) {
35
+ logger.warn("TokenSecurity", "DexScreener lookup failed", { address, error: error.message });
36
+ }
37
+ if (!dexData) {
38
+ warnings.push("No DEX liquidity found - token may be dead or not traded");
39
+ score -= 50;
40
+ isRugRisk = true;
41
+ }
42
+ else {
43
+ const liquidity = dexData.liquidity?.usd ?? 0;
44
+ if (liquidity < 1000) {
45
+ warnings.push(`Low liquidity: $${liquidity.toFixed(0)}`);
46
+ score -= 30;
47
+ isRugRisk = true;
48
+ }
49
+ else if (liquidity < 10000) {
50
+ warnings.push(`Moderate liquidity: $${liquidity.toFixed(0)}`);
51
+ score -= 10;
52
+ }
53
+ const volume = dexData.volume?.h24 ?? 0;
54
+ if (volume < 100) {
55
+ warnings.push(`Very low 24h volume: $${volume.toFixed(0)}`);
56
+ score -= 20;
57
+ }
58
+ }
59
+ try {
60
+ const rugResp = await resilientFetch(`https://api.rugcheck.xyz/v1/tokens/${address}/report`, {
61
+ timeout: 8_000,
62
+ retries: 0,
63
+ });
64
+ if (rugResp.ok) {
65
+ const report = await rugResp.json();
66
+ if (report.status === "WARNING" || report.status === "BLOCKED") {
67
+ warnings.push(`RugCheck status: ${report.status}`);
68
+ score -= 40;
69
+ isRugRisk = true;
70
+ }
71
+ }
72
+ }
73
+ catch (error) {
74
+ logger.warn('TokenSecurity', 'RugCheck lookup failed (optional)', { address, error: error.message });
75
+ }
76
+ const symbol = dexData?.baseToken?.symbol ?? "???";
77
+ const name = dexData?.baseToken?.name ?? "Unknown";
78
+ const scoreLabel = score >= 70 ? "SAFE" : score >= 40 ? "CAUTION" : "DANGEROUS";
79
+ const lines = [
80
+ "Token Security Scan",
81
+ "",
82
+ `Token: ${name} ($${symbol})`,
83
+ `Address: ${address.slice(0, 8)}...${address.slice(-6)}`,
84
+ `Score: ${scoreLabel} (${score}/100)`,
85
+ "",
86
+ ];
87
+ if (warnings.length > 0) {
88
+ lines.push("Warnings:");
89
+ for (const w of warnings)
90
+ lines.push(" - " + w);
91
+ }
92
+ else {
93
+ lines.push("No immediate risks detected.");
94
+ }
95
+ lines.push("");
96
+ lines.push("This is not financial advice. Always verify before trading.");
97
+ return {
98
+ content: [{ type: "text", text: lines.join("\n") }],
99
+ details: { address, symbol, name, score, isHoneypot, isRugRisk, warnings },
100
+ };
101
+ }
102
+ //# sourceMappingURL=TokenSecurityScanner.js.map
@@ -0,0 +1,17 @@
1
+ /**
2
+ * src/tools/TransactionSim.ts — Simulate Solana transactions before execution.
3
+ * Uses public RPC simulateTransaction for pre-flight checks.
4
+ */
5
+ import type { ToolResult } from "../api/ExtensionAPI.js";
6
+ export interface SimResult {
7
+ ok: boolean;
8
+ logs: string[];
9
+ unitsConsumed: number;
10
+ fee: number;
11
+ error?: string;
12
+ }
13
+ export declare const simulateTxParams: import("@sinclair/typebox").TObject<{
14
+ transaction: import("@sinclair/typebox").TString;
15
+ }>;
16
+ export declare function simulateTxTool(_id: string, params: Record<string, unknown>): Promise<ToolResult>;
17
+ //# sourceMappingURL=TransactionSim.d.ts.map
@@ -0,0 +1,78 @@
1
+ /**
2
+ * src/tools/TransactionSim.ts — Simulate Solana transactions before execution.
3
+ * Uses public RPC simulateTransaction for pre-flight checks.
4
+ */
5
+ import { Type } from "@sinclair/typebox";
6
+ import { resilientFetch } from "../util/resilientFetch.js";
7
+ import { logger } from "../util/logger.js";
8
+ function getRpcUrl() {
9
+ return process.env.SOLANA_RPC_URL || "https://api.mainnet-beta.solana.com";
10
+ }
11
+ export const simulateTxParams = Type.Object({
12
+ transaction: Type.String({ description: "Base64-encoded transaction to simulate" }),
13
+ });
14
+ export async function simulateTxTool(_id, params) {
15
+ const tx = params.transaction || "";
16
+ if (!tx || tx.length < 10) {
17
+ return {
18
+ content: [{ type: "text", text: "Invalid transaction: must be base64-encoded Solana transaction." }],
19
+ isError: true,
20
+ };
21
+ }
22
+ try {
23
+ const body = JSON.stringify({
24
+ jsonrpc: "2.0", id: 1,
25
+ method: "simulateTransaction",
26
+ params: [
27
+ tx,
28
+ { sigVerify: false, commitment: "processed", encoding: "base64" },
29
+ ],
30
+ });
31
+ const response = await resilientFetch(getRpcUrl(), {
32
+ timeout: 15_000, retries: 1,
33
+ method: "POST",
34
+ headers: { "Content-Type": "application/json" },
35
+ body,
36
+ });
37
+ const data = await response.json();
38
+ const result = data?.result?.value ?? data?.result;
39
+ if (!result) {
40
+ const errMsg = data?.error?.message ?? "Unknown simulation error";
41
+ return {
42
+ content: [{ type: "text", text: `❌ Simulation failed: ${errMsg}` }],
43
+ isError: true,
44
+ details: { ok: false, error: errMsg, logs: [], unitsConsumed: 0, fee: 0 },
45
+ };
46
+ }
47
+ const ok = !result.err;
48
+ const unitsConsumed = result.unitsConsumed ?? 0;
49
+ const logs = (result.logs ?? []);
50
+ const fee = (result.fee ?? 0) / 1e9 * 0.000005; // rough estimate
51
+ const error = result.err ? JSON.stringify(result.err) : undefined;
52
+ const lines = [
53
+ ok ? "✅ Transaction simulation: SUCCESS" : "❌ Transaction simulation: FAILED",
54
+ "",
55
+ `Units: ${unitsConsumed.toLocaleString()}`,
56
+ `Est. Fee: ${fee.toFixed(6)} SOL`,
57
+ "",
58
+ ];
59
+ if (logs.length > 0) {
60
+ lines.push("Logs:");
61
+ logs.slice(-8).forEach(l => lines.push(` ${l}`));
62
+ }
63
+ if (error)
64
+ lines.push(`Error: ${error}`);
65
+ return {
66
+ content: [{ type: "text", text: lines.join("\n") }],
67
+ details: { ok, error: error, logs, unitsConsumed, fee },
68
+ };
69
+ }
70
+ catch (error) {
71
+ logger.warn("TransactionSim", "Simulation failed", { error: error.message });
72
+ return {
73
+ content: [{ type: "text", text: "⚠️ Transaction simulation unavailable (RPC may be rate-limited)." }],
74
+ isError: true,
75
+ };
76
+ }
77
+ }
78
+ //# sourceMappingURL=TransactionSim.js.map
package/dist/tui/App.js CHANGED
@@ -30,6 +30,13 @@ import { actionFeed } from "../wallet/ActionFeed.js";
30
30
  import { gmgnTool, gmgnToolParams, gmgnHelp, gmgnStatus, gmgnMarketTool, gmgnMarketToolParams } from "../tools/GmgnIntegration.js";
31
31
  import { watchTokenTool, watchTokenParams, removeWatchTool, removeWatchParams, listWatchTool, listWatchParams, addAlertTool, addAlertParams, checkAlertsTool, checkAlertsParams, compareTokensTool, compareTokensParams, portfolioTool, portfolioParams, getWatchList as _getWatchList, checkAlerts as _checkAlerts, } from "../tools/CrossTools.js";
32
32
  import { systemMonitor } from "../core/SystemMonitor.js";
33
+ import { Sparkline, PriceHistory } from "./Sparkline.js";
34
+ import { launchCalendarTool, getLaunchSummary } from "../tools/TokenCalendar.js";
35
+ import { simulateTxTool } from "../tools/TransactionSim.js";
36
+ import { getPresetColors, listThemes, setTheme as setThemePreset } from "./ThemePresets.js";
37
+ import { copyToClipboard, clipboardSupported } from "../util/clipboard.js";
38
+ import { suggestCommands } from "../util/commandSuggest.js";
39
+ import { notify as notifyWebhook, hasWebhooks } from "../util/webhooks.js";
33
40
  function getContextBar(session) {
34
41
  if (!session)
35
42
  return { pct: 0, bar: "░".repeat(20), color: AIAIAI_COLORS.dim };
@@ -102,9 +109,11 @@ export function App({ registry, systemPrompt, chain: initialChain = "solana", mo
102
109
  const [uptime, setUptime] = useState("0m");
103
110
  const [apiCalls, setApiCalls] = useState(0);
104
111
  const [watchList, setWatchList] = useState([]);
112
+ const priceHistoryRef = useRef(new PriceHistory(40));
105
113
  const runnerRef = useRef(null);
106
114
  const sessionRef = useRef(null);
107
115
  const sessionCtxRef = useRef(null);
116
+ const messagesRef = useRef([]);
108
117
  const theme = makeTheme();
109
118
  let modelName = "no-model";
110
119
  try {
@@ -112,7 +121,11 @@ export function App({ registry, systemPrompt, chain: initialChain = "solana", mo
112
121
  }
113
122
  catch { /* shown via banner */ }
114
123
  const push = useCallback((msg) => {
115
- setMessages(prev => [...prev, { ...msg, id: nextId(), ts: Date.now() }]);
124
+ setMessages(prev => {
125
+ const next = [...prev, { ...msg, id: nextId(), ts: Date.now() }];
126
+ messagesRef.current = next;
127
+ return next;
128
+ });
116
129
  }, []);
117
130
  const notify = useCallback((content) => { push({ role: "notify", content }); }, [push]);
118
131
  const setStatus = useCallback((key, value) => {
@@ -187,18 +200,32 @@ export function App({ registry, systemPrompt, chain: initialChain = "solana", mo
187
200
  // ── Main effect: setup + intervals ──────────────────────────────────────
188
201
  useEffect(() => {
189
202
  registerBuiltinTools();
190
- // Initial price fetch
191
- priceFeed.getAiaiaiPrice().then(p => {
192
- setAiaiPrice({ priceUsd: p.priceUsd, change: p.priceChange24h, mcap: p.marketCap, liq: p.liquidityUsd });
193
- if (p.priceUsd)
194
- setPriceBadge(`$${parseFloat(p.priceUsd).toFixed(6)}`);
195
- actionFeed.setPrice(parseFloat(p.priceUsd || "0.0004"));
196
- }).catch(() => { });
203
+ // Initial price fetch with retry
204
+ const fetchPriceWithRetry = async (retries = 3, delay = 2000) => {
205
+ for (let i = 0; i < retries; i++) {
206
+ try {
207
+ const p = await priceFeed.getAiaiaiPrice();
208
+ setAiaiPrice({ priceUsd: p.priceUsd, change: p.priceChange24h, mcap: p.marketCap, liq: p.liquidityUsd });
209
+ if (p.priceUsd)
210
+ setPriceBadge(`$${parseFloat(p.priceUsd).toFixed(6)}`);
211
+ actionFeed.setPrice(parseFloat(p.priceUsd || "0.0004"));
212
+ return;
213
+ }
214
+ catch (e) {
215
+ if (i === retries - 1)
216
+ throw e;
217
+ await new Promise(r => setTimeout(r, delay * (i + 1)));
218
+ }
219
+ }
220
+ };
221
+ fetchPriceWithRetry().catch(() => {
222
+ console.error('Initial price fetch failed after retries');
223
+ });
197
224
  // Initial wallet fetches
198
225
  agentWallet.getAll().then(w => {
199
- setColdBalance({ sol: w.cold.sol, aiaiai: w.cold.aiaiai, usdc: w.cold.usdc });
200
226
  setActionBalance({ sol: w.action.sol, aiaiai: w.action.aiaiai, usdc: w.action.usdc });
201
227
  setDepositBalance({ sol: w.deposit.sol, aiaiai: w.deposit.aiaiai, usdc: w.deposit.usdc });
228
+ setColdBalance({ sol: w.cold.sol, aiaiai: w.cold.aiaiai, usdc: w.cold.usdc });
202
229
  }).catch(() => { });
203
230
  // Initial actions
204
231
  actionFeed.refresh().then(() => {
@@ -226,8 +253,10 @@ export function App({ registry, systemPrompt, chain: initialChain = "solana", mo
226
253
  const priceInterval = setInterval(() => {
227
254
  priceFeed.getAiaiaiPrice().then(p => {
228
255
  setAiaiPrice({ priceUsd: p.priceUsd, change: p.priceChange24h, mcap: p.marketCap, liq: p.liquidityUsd });
229
- if (p.priceUsd)
256
+ if (p.priceUsd) {
230
257
  setPriceBadge(`$${parseFloat(p.priceUsd).toFixed(6)}`);
258
+ priceHistoryRef.current.push(parseFloat(p.priceUsd));
259
+ }
231
260
  actionFeed.setPrice(parseFloat(p.priceUsd || "0.0004"));
232
261
  systemMonitor.trackApiCall();
233
262
  // Check alerts
@@ -235,6 +264,7 @@ export function App({ registry, systemPrompt, chain: initialChain = "solana", mo
235
264
  const triggered = _checkAlerts(parseFloat(p.priceUsd), "AIAIAI");
236
265
  for (const alert of triggered) {
237
266
  notify(`🔔 Alert triggered: ${alert.type} $${alert.price} — $AIAIAI is now $${p.priceUsd}`);
267
+ notifyWebhook(`🔔 **Alert**: ${alert.type} $${alert.price} — $AIAIAI now $${p.priceUsd}`).catch(() => { });
238
268
  }
239
269
  }
240
270
  }).catch(() => { });
@@ -244,9 +274,9 @@ export function App({ registry, systemPrompt, chain: initialChain = "solana", mo
244
274
  // Wallet + actions interval (60s)
245
275
  const walletInterval = setInterval(() => {
246
276
  agentWallet.getAll().then(w => {
247
- setColdBalance({ sol: w.cold.sol, aiaiai: w.cold.aiaiai, usdc: w.cold.usdc });
248
277
  setActionBalance({ sol: w.action.sol, aiaiai: w.action.aiaiai, usdc: w.action.usdc });
249
278
  setDepositBalance({ sol: w.deposit.sol, aiaiai: w.deposit.aiaiai, usdc: w.deposit.usdc });
279
+ setColdBalance({ sol: w.cold.sol, aiaiai: w.cold.aiaiai, usdc: w.cold.usdc });
250
280
  }).catch(() => { });
251
281
  actionFeed.refresh().then(() => {
252
282
  setRecentActions(actionFeed.getRecentActions(8).map(a => ({ type: a.type, amount: a.amount, usdValue: a.usdValue, timestamp: a.timestamp })));
@@ -268,12 +298,12 @@ export function App({ registry, systemPrompt, chain: initialChain = "solana", mo
268
298
  setUptime(stats.uptime);
269
299
  setApiCalls(systemMonitor.resetApiCounter());
270
300
  }, 10_000);
271
- // Session auto-save (30s)
272
- sessionStore.startAutoSave(() => messages);
301
+ // Session auto-save (30s) — use ref to avoid stale closure
302
+ sessionStore.startAutoSave(() => messagesRef.current);
273
303
  sessionStore.setCurrentId(sessionId);
274
304
  const saveInterval = setInterval(() => {
275
- if (messages.length > 0)
276
- sessionStore.save(messages);
305
+ if (messagesRef.current.length > 0)
306
+ sessionStore.save(messagesRef.current);
277
307
  }, 30_000);
278
308
  const session = new SessionManager();
279
309
  session.setSystemPrompt(registry.getSystemPrompt() || systemPrompt || "You are AIAIAI Chain Agent.");
@@ -375,7 +405,7 @@ export function App({ registry, systemPrompt, chain: initialChain = "solana", mo
375
405
  return;
376
406
  }
377
407
  if (cmd === "help") {
378
- notify("Commands:\n/token info|security|holders|traders\n/track kol|smartmoney|follow-wallet\n/market kline|trending|trenches|signal\n/watch /unwatch /watchlist /alerts /alert /compare /portfolio\n/keys /wallet /deposit /burn /actions /fees\n/models /cost /goals /schedule /update\n/sessions /resume /memory /clear /exit\n\nTab to auto-complete, ↑↓ for history, Shift+Enter for multi-line");
408
+ notify("Commands:\n/token info|security|holders|traders\n/track kol|smartmoney|follow-wallet\n/market kline|trending|trenches|signal\n/watch /unwatch /watchlist /alerts /alert /compare /portfolio\n/keys /wallet /deposit /burn /actions /fees\n/models /cost /goals /schedule /update /diff\n/sessions /resume /memory /clear /exit\n/launches /theme /copy /simulate /explain /webhook /clipboard\n\nTab to auto-complete, ↑↓ for history, Shift+Enter for multi-line");
379
409
  return;
380
410
  }
381
411
  // ── GMGN direct commands ──────────────────────────────────────────
@@ -651,9 +681,103 @@ export function App({ registry, systemPrompt, chain: initialChain = "solana", mo
651
681
  notify(result.content[0].text);
652
682
  return;
653
683
  }
684
+ // ── New feature commands ─────────────────────────────────────────
685
+ if (cmd === "launches" || cmd === "calendar") {
686
+ const result = await launchCalendarTool("", { chain: args || "solana", limit: 15 });
687
+ notify(result.content[0].text);
688
+ return;
689
+ }
690
+ if (cmd === "diff" || cmd === "compared") {
691
+ const sessions = sessionStore.list();
692
+ if (sessions.length < 2) {
693
+ notify("Need at least 2 saved sessions to compare. Start a new session with /exit and restart.");
694
+ return;
695
+ }
696
+ const latest = sessions[0];
697
+ const prev = sessions[1];
698
+ if (!latest || !prev) {
699
+ notify("Could not load sessions for comparison.");
700
+ return;
701
+ }
702
+ const lines = [
703
+ `📊 Session Diff`,
704
+ `Current: ${latest.title} (${latest.messageCount} msgs)`,
705
+ `Previous: ${prev.title} (${prev.messageCount} msgs)`,
706
+ `Difference: ${latest.messageCount - prev.messageCount} messages`,
707
+ `Last active: ${new Date(latest.updatedAt).toLocaleString()}`,
708
+ ];
709
+ notify(lines.join("\n"));
710
+ return;
711
+ }
712
+ if (cmd === "copy" && args.trim()) {
713
+ const ok = copyToClipboard(args.trim());
714
+ notify(ok ? T.success(`✓ Copied to clipboard: ${args.slice(0, 30)}…`) : T.error("Clipboard not supported in this terminal. Try: " + clipboardSupported()));
715
+ return;
716
+ }
717
+ if (cmd === "theme") {
718
+ if (!args || args === "list") {
719
+ const current = getPresetColors();
720
+ notify(`🎨 Themes\n${listThemes()}\n\nApply: /theme <name>`);
721
+ }
722
+ else if (setThemePreset(args)) {
723
+ notify(T.success(`✓ Theme set to "${args}". Restart to apply.`));
724
+ }
725
+ else {
726
+ notify(T.error(`Unknown theme "${args}". Try: /theme list`));
727
+ }
728
+ return;
729
+ }
730
+ if (cmd === "simulate" || cmd === "sim") {
731
+ if (!args.trim()) {
732
+ notify(T.error("Usage: /simulate <base64-tx>"));
733
+ return;
734
+ }
735
+ const result = await simulateTxTool("", { transaction: args.trim() });
736
+ notify(result.content[0].text);
737
+ return;
738
+ }
739
+ if (cmd === "explain") {
740
+ if (!modelReg) {
741
+ notify(T.error("Model registry not available"));
742
+ return;
743
+ }
744
+ const lastAssistant = [...messages].reverse().find(m => m.role === "tool");
745
+ if (!lastAssistant) {
746
+ notify("No tool result to explain. Run a tool first.");
747
+ return;
748
+ }
749
+ push({ role: "user", content: `Explain this tool result in simple terms:\n${lastAssistant.content.slice(0, 2000)}` });
750
+ setDisabled(true);
751
+ setStreaming("");
752
+ try {
753
+ await runnerRef.current.run(`Explain this tool result simply:\n${lastAssistant.content.slice(0, 2000)}`);
754
+ }
755
+ catch (e) {
756
+ setDisabled(false);
757
+ notify(T.error(`Error: ${e.message}`));
758
+ }
759
+ return;
760
+ }
761
+ if (cmd === "webhook" || cmd === "webhooks") {
762
+ if (hasWebhooks()) {
763
+ notify("✅ Webhooks configured\n\nUsage: /webhook test\nConfig: WEBHOOK_DISCORD / WEBHOOK_TELEGRAM in ~/.aiaiai/.env");
764
+ }
765
+ else {
766
+ notify("No webhooks configured.\nAdd WEBHOOK_DISCORD=... or WEBHOOK_TELEGRAM=... to ~/.aiaiai/.env");
767
+ }
768
+ return;
769
+ }
770
+ if (cmd === "clipboard") {
771
+ notify(clipboardSupported());
772
+ return;
773
+ }
654
774
  const def = registry.getCommand(cmd);
655
775
  if (!def) {
656
- notify(T.error(`Unknown: /${cmd} try /help`));
776
+ const suggestions = suggestCommands(cmd);
777
+ const tip = suggestions.length > 0
778
+ ? `\nDid you mean: ${suggestions.map(s => `/${s}`).join(", ")}?`
779
+ : "";
780
+ notify(T.error(`Unknown: /${cmd} — try /help${tip}`));
657
781
  return;
658
782
  }
659
783
  try {
@@ -717,10 +841,8 @@ export function App({ registry, systemPrompt, chain: initialChain = "solana", mo
717
841
  const SIDEBAR_MIN = 38;
718
842
  const changeColor = (pct) => pct > 0 ? AIAIAI_COLORS.success : pct < 0 ? AIAIAI_COLORS.error : AIAIAI_COLORS.muted;
719
843
  const actionIcon = (type) => type === "buy" ? "↗" : type === "burn" ? "🔥" : "→";
720
- if (showModelSelector) {
721
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(StatusBar, { model: modelName, chain: chain, price: priceBadge, toolRunning: toolRunning, connected: true, statusLine: statusLine, cpu: cpu, ram: ram, uptime: uptime, apiCalls: apiCalls }), _jsx(ModelSelector, { models: modelList, currentModelId: process.env.DEFAULT_MODEL ?? "", onSelect: handleModelSelect, onCancel: handleModelCancel, initialQuery: modelSelectorInitialQuery })] }));
722
- }
723
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(StatusBar, { model: modelName, chain: chain, price: priceBadge, toolRunning: toolRunning, connected: true, statusLine: statusLine, cpu: cpu, ram: ram, uptime: uptime, apiCalls: apiCalls }), _jsxs(Box, { flexDirection: "row", flexGrow: 1, overflow: "hidden", children: [_jsxs(Box, { flexDirection: "column", width: SIDEBAR_W, minWidth: SIDEBAR_MIN, flexShrink: 0, overflow: "hidden", children: [_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: AIAIAI_COLORS.accent, paddingX: 1, children: [_jsx(Text, { bold: true, color: AIAIAI_COLORS.accent, children: "\uD83E\uDD16 $AIAIAI" }), aiaiPrice ? (_jsxs(_Fragment, { children: [_jsxs(Text, { color: changeColor(aiaiPrice.change), children: [aiaiPrice.priceUsd ? `$${parseFloat(aiaiPrice.priceUsd).toFixed(8)}` : "N/A", " ", aiaiPrice.change > 0 ? "▲" : aiaiPrice.change < 0 ? "▼" : "─", Math.abs(aiaiPrice.change).toFixed(1), "%"] }), _jsxs(Text, { color: AIAIAI_COLORS.muted, children: ["MCap: ", aiaiPrice.mcap ? `$${(aiaiPrice.mcap / 1000).toFixed(1)}k` : "N/A"] }), _jsxs(Text, { color: AIAIAI_COLORS.muted, children: ["Liq: $", (aiaiPrice.liq / 1000).toFixed(1), "k"] })] })) : _jsx(Text, { color: AIAIAI_COLORS.muted, children: "Loading\u2026" })] }), _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: AIAIAI_COLORS.dim, paddingX: 1, marginTop: 1, children: [_jsx(Text, { bold: true, children: "\uD83D\uDCBC Wallets" }), _jsxs(Text, { color: AIAIAI_COLORS.muted, children: ["Cold: ", coldBalance.sol.toFixed(2), " SOL"] }), _jsxs(Text, { color: AIAIAI_COLORS.muted, children: [" AIAIAI: ", coldBalance.aiaiai.toLocaleString(undefined, { maximumFractionDigits: 0 })] }), _jsxs(Text, { color: AIAIAI_COLORS.muted, children: ["Action: ", actionBalance.sol.toFixed(2), " SOL"] }), _jsxs(Text, { color: AIAIAI_COLORS.muted, children: [" AIAIAI: ", actionBalance.aiaiai.toLocaleString(undefined, { maximumFractionDigits: 0 })] }), _jsxs(Text, { color: AIAIAI_COLORS.muted, children: ["Deposit: ", depositBalance.usdc.toFixed(2), " USDC"] }), _jsxs(Text, { color: AIAIAI_COLORS.muted, children: [" SOL: ", depositBalance.sol.toFixed(2)] })] }), _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: AIAIAI_COLORS.dim, paddingX: 1, marginTop: 1, children: [_jsx(Text, { bold: true, children: "\u26A1 Actions" }), recentActions.length > 0 ? recentActions.map((a, i) => {
844
+ // Single StatusBar wrapper — shared across both views to prevent duplication
845
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(StatusBar, { model: modelName, chain: chain, price: priceBadge, toolRunning: toolRunning, connected: true, statusLine: statusLine, cpu: cpu, ram: ram, uptime: uptime, apiCalls: apiCalls }), showModelSelector ? (_jsx(ModelSelector, { models: modelList, currentModelId: process.env.DEFAULT_MODEL ?? "", onSelect: handleModelSelect, onCancel: handleModelCancel, initialQuery: modelSelectorInitialQuery })) : (_jsxs(Box, { flexDirection: "row", flexGrow: 1, overflow: "hidden", children: [_jsxs(Box, { flexDirection: "column", width: SIDEBAR_W, minWidth: SIDEBAR_MIN, flexShrink: 0, overflow: "hidden", children: [_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: AIAIAI_COLORS.accent, paddingX: 1, children: [_jsx(Text, { bold: true, color: AIAIAI_COLORS.accent, children: "\uD83E\uDD16 $AIAIAI" }), aiaiPrice ? (_jsxs(_Fragment, { children: [_jsxs(Text, { color: changeColor(aiaiPrice.change), children: [aiaiPrice.priceUsd ? `$${parseFloat(aiaiPrice.priceUsd).toFixed(8)}` : "N/A", " ", aiaiPrice.change > 0 ? "▲" : aiaiPrice.change < 0 ? "▼" : "─", Math.abs(aiaiPrice.change).toFixed(1), "%"] }), _jsx(Sparkline, { data: priceHistoryRef.current.getData(), width: 38, color: changeColor(aiaiPrice.change), label: "" }), _jsxs(Text, { color: AIAIAI_COLORS.muted, children: ["MCap: ", aiaiPrice.mcap ? `$${(aiaiPrice.mcap / 1000).toFixed(1)}k` : "N/A"] }), _jsxs(Text, { color: AIAIAI_COLORS.muted, children: ["Liq: $", (aiaiPrice.liq / 1000).toFixed(1), "k"] })] })) : _jsx(Text, { color: AIAIAI_COLORS.muted, children: "Loading\u2026" })] }), _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: AIAIAI_COLORS.dim, paddingX: 1, marginTop: 1, children: [_jsx(Text, { bold: true, children: "\uD83D\uDCBC Action" }), _jsxs(Text, { color: AIAIAI_COLORS.muted, children: ["SOL: ", actionBalance.sol.toFixed(2)] }), _jsxs(Text, { color: AIAIAI_COLORS.muted, children: ["AIAIAI: ", actionBalance.aiaiai.toLocaleString(undefined, { maximumFractionDigits: 0 })] })] }), _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: AIAIAI_COLORS.dim, paddingX: 1, marginTop: 1, children: [_jsx(Text, { bold: true, children: "\uD83D\uDCB0 Deposit" }), _jsxs(Text, { color: AIAIAI_COLORS.muted, children: ["USDC: ", depositBalance.usdc.toFixed(2)] }), _jsxs(Text, { color: AIAIAI_COLORS.muted, children: ["SOL: ", depositBalance.sol.toFixed(2)] })] }), _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: AIAIAI_COLORS.dim, paddingX: 1, marginTop: 1, children: [_jsx(Text, { bold: true, children: "\u26A1 Actions" }), recentActions.length > 0 ? recentActions.map((a, i) => {
724
846
  const col = a.type === "buy" ? AIAIAI_COLORS.success : a.type === "burn" ? AIAIAI_COLORS.error : AIAIAI_COLORS.muted;
725
847
  const time = new Date(a.timestamp).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
726
848
  return _jsxs(Text, { color: col, children: [actionIcon(a.type), " ", time, " ", a.type, " ", a.amount.toLocaleString()] }, i);
@@ -730,7 +852,7 @@ export function App({ registry, systemPrompt, chain: initialChain = "solana", mo
730
852
  const sentColor = n.sentiment > 0.3 ? AIAIAI_COLORS.success : n.sentiment < -0.3 ? AIAIAI_COLORS.error : AIAIAI_COLORS.muted;
731
853
  const title = n.title.length > 30 ? n.title.slice(0, 28) + "…" : n.title;
732
854
  return _jsx(Text, { color: sentColor, children: title }, i);
733
- }) : _jsx(Text, { color: AIAIAI_COLORS.muted, children: "Loading\u2026" })] }), _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: AIAIAI_COLORS.dim, paddingX: 1, marginTop: 1, children: [_jsx(Text, { bold: true, children: "Context" }), _jsxs(Text, { color: ctx.color, children: [ctx.bar, " ", ctx.pct, "%"] })] })] }), _jsx(Box, { flexDirection: "column", flexGrow: 1, children: _jsx(REPL, { messages: messages, streamingText: streaming, toolRunning: toolRunning, onSubmit: handleSubmit, disabled: disabled, onAbort: () => {
855
+ }) : _jsx(Text, { color: AIAIAI_COLORS.muted, children: "Loading\u2026" })] }), _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: AIAIAI_COLORS.dim, paddingX: 1, marginTop: 1, children: [_jsx(Text, { bold: true, children: "Context" }), _jsxs(Text, { color: ctx.color, children: [ctx.bar, " ", ctx.pct, "%"] })] }), _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: AIAIAI_COLORS.dim, paddingX: 1, marginTop: 1, children: [_jsx(Text, { bold: true, children: "\uD83D\uDE80 Recent" }), _jsx(Text, { color: AIAIAI_COLORS.muted, children: getLaunchSummary() })] })] }), _jsx(Box, { flexDirection: "column", flexGrow: 1, children: _jsx(REPL, { messages: messages, streamingText: streaming, toolRunning: toolRunning, onSubmit: handleSubmit, disabled: disabled, onAbort: () => {
734
856
  runnerRef.current?.abort();
735
857
  setDisabled(false);
736
858
  setToolRunning(null);
@@ -740,6 +862,6 @@ export function App({ registry, systemPrompt, chain: initialChain = "solana", mo
740
862
  return "";
741
863
  });
742
864
  push({ role: "system", content: T.dim("— stream interrupted —") });
743
- } }) })] })] }));
865
+ } }) })] }))] }));
744
866
  }
745
867
  //# sourceMappingURL=App.js.map
package/dist/tui/REPL.js CHANGED
@@ -208,7 +208,7 @@ function InputBox({ value, onChange, onSubmit, disabled, onAbort }) {
208
208
  export function REPL({ messages, streamingText, toolRunning, onSubmit, onAbort, disabled }) {
209
209
  const [input, setInput] = useState("");
210
210
  const { stdout } = useStdout();
211
- const termWidth = stdout?.columns ?? 80;
211
+ const termWidth = (stdout?.columns ?? 80) - 2;
212
212
  const handleSubmit = useCallback((val) => {
213
213
  onSubmit(val);
214
214
  }, [onSubmit]);
@@ -217,6 +217,6 @@ export function REPL({ messages, streamingText, toolRunning, onSubmit, onAbort,
217
217
  }, [onAbort]);
218
218
  // Scrolling: show last N messages
219
219
  const visible = messages.slice(-MAX_VISIBLE);
220
- return (_jsxs(Box, { flexDirection: "column", width: termWidth, children: [_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [visible.map(m => _jsx(MessageLine, { msg: m }, m.id)), streamingText && (_jsxs(Box, { flexDirection: "row", gap: 1, marginTop: 1, children: [_jsx(Text, { color: AIAIAI_COLORS.muted, children: " " }), _jsx(Text, { color: AIAIAI_COLORS.header, bold: true, children: "\uD83E\uDD16 " }), _jsx(Text, { wrap: "wrap", children: streamingText })] })), toolRunning && (_jsx(Box, { flexDirection: "row", gap: 1, marginTop: 1, children: _jsxs(Text, { color: AIAIAI_COLORS.warn, children: ["\u2699 running ", toolRunning, "\u2026"] }) }))] }), _jsx(InputBox, { value: input, onChange: setInput, onSubmit: handleSubmit, disabled: !!disabled, onAbort: handleAbort })] }));
220
+ return (_jsxs(Box, { flexDirection: "column", width: "100%", children: [_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [visible.map(m => _jsx(MessageLine, { msg: m }, m.id)), streamingText && (_jsxs(Box, { flexDirection: "row", gap: 1, marginTop: 1, children: [_jsx(Text, { color: AIAIAI_COLORS.muted, children: " " }), _jsx(Text, { color: AIAIAI_COLORS.header, bold: true, children: "\uD83E\uDD16 " }), _jsx(Text, { wrap: "wrap", children: streamingText })] })), toolRunning && (_jsx(Box, { flexDirection: "row", gap: 1, marginTop: 1, children: _jsxs(Text, { color: AIAIAI_COLORS.warn, children: ["\u2699 running ", toolRunning, "\u2026"] }) }))] }), _jsx(InputBox, { value: input, onChange: setInput, onSubmit: handleSubmit, disabled: !!disabled, onAbort: handleAbort })] }));
221
221
  }
222
222
  //# sourceMappingURL=REPL.js.map
@@ -0,0 +1,21 @@
1
+ /**
2
+ * src/tui/Sparkline.tsx — Mini ASCII sparkline chart for sidebar.
3
+ * Uses Unicode block characters: ▁ ▂ ▃ ▄ ▅ ▆ ▇ █
4
+ */
5
+ import React from "react";
6
+ export interface SparklineProps {
7
+ data: number[];
8
+ width?: number;
9
+ label?: string;
10
+ color?: string;
11
+ }
12
+ export declare function Sparkline({ data, width, label, color }: SparklineProps): React.JSX.Element | null;
13
+ /** Track a rolling price history for sparkline use */
14
+ export declare class PriceHistory {
15
+ private prices;
16
+ private maxLength;
17
+ constructor(maxLength?: number);
18
+ push(price: number): void;
19
+ getData(): number[];
20
+ }
21
+ //# sourceMappingURL=Sparkline.d.ts.map
@@ -0,0 +1,44 @@
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ import { Box, Text } from "ink";
3
+ const CHARS = "▁▂▃▄▅▆▇█";
4
+ export function Sparkline({ data, width = 30, label, color }) {
5
+ if (!data || data.length < 2)
6
+ return null;
7
+ const min = Math.min(...data);
8
+ const max = Math.max(...data);
9
+ const range = max - min || 1;
10
+ // Normalize and compress to width
11
+ const compressed = [];
12
+ const step = data.length / width;
13
+ for (let i = 0; i < width; i++) {
14
+ const idx = Math.floor(i * step);
15
+ compressed.push(data[idx]);
16
+ }
17
+ const bars = compressed.map(v => {
18
+ const normalized = (v - min) / range;
19
+ const idx = Math.min(7, Math.floor(normalized * 8));
20
+ return CHARS[Math.max(0, idx)];
21
+ }).join("");
22
+ const trend = data[data.length - 1] > (data[0] ?? data[data.length - 1]) ? "▲" : "▼";
23
+ const minStr = min < 1 ? min.toFixed(6) : min < 100 ? min.toFixed(2) : min.toLocaleString();
24
+ const maxStr = max < 1 ? max.toFixed(6) : max < 100 ? max.toFixed(2) : max.toLocaleString();
25
+ return (_jsxs(Box, { flexDirection: "column", children: [label && _jsxs(Text, { dimColor: true, children: [label, " ", trend] }), _jsx(Text, { color: color, children: bars }), _jsxs(Text, { dimColor: true, children: ["$", minStr, " \u2013 $", maxStr] })] }));
26
+ }
27
+ /** Track a rolling price history for sparkline use */
28
+ export class PriceHistory {
29
+ prices = [];
30
+ maxLength;
31
+ constructor(maxLength = 50) {
32
+ this.maxLength = maxLength;
33
+ }
34
+ push(price) {
35
+ this.prices.push(price);
36
+ if (this.prices.length > this.maxLength) {
37
+ this.prices = this.prices.slice(-this.maxLength);
38
+ }
39
+ }
40
+ getData() {
41
+ return [...this.prices];
42
+ }
43
+ }
44
+ //# sourceMappingURL=Sparkline.js.map
@@ -0,0 +1,25 @@
1
+ /**
2
+ * src/tui/ThemePresets.ts — Theme presets system for AIAIAI TUI.
3
+ * 5 built-in presets + custom via ~/.aiaiai/config/theme.json
4
+ */
5
+ export interface ThemeColors {
6
+ accent: string;
7
+ header: string;
8
+ muted: string;
9
+ dim: string;
10
+ success: string;
11
+ warn: string;
12
+ error: string;
13
+ background: string;
14
+ }
15
+ export interface ThemePreset {
16
+ name: string;
17
+ description: string;
18
+ colors: ThemeColors;
19
+ }
20
+ export declare const PRESETS: Record<string, ThemePreset>;
21
+ export declare function getCurrentTheme(): string;
22
+ export declare function setTheme(name: string): boolean;
23
+ export declare function getPresetColors(): ThemeColors;
24
+ export declare function listThemes(): string;
25
+ //# sourceMappingURL=ThemePresets.d.ts.map