@aiaiaichain/agent 0.1.1 → 0.1.3

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.
@@ -0,0 +1,264 @@
1
+ /**
2
+ * GMGN integration — token research, smart money tracking, market data.
3
+ * Auto-manages GMGN_API_KEY. Provides tools for the agent.
4
+ *
5
+ * Token usage optimization:
6
+ * - Cache token info for 60s
7
+ * - Cache trending/trenches for 120s
8
+ * - Cache kline for 30s
9
+ * - Track commands use weight 3, so we batch when possible
10
+ */
11
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, chmodSync } from "node:fs";
12
+ import { resolve, join } from "node:path";
13
+ import { homedir } from "node:os";
14
+ import { execSync } from "node:child_process";
15
+ import { Type } from "@sinclair/typebox";
16
+ const GMGN_ENV_DIR = resolve(homedir(), ".config", "gmgn");
17
+ const GMGN_ENV_PATH = join(GMGN_ENV_DIR, ".env");
18
+ // ── Env management ──────────────────────────────────────────────────────────
19
+ function ensureGmgnEnv() {
20
+ if (!existsSync(GMGN_ENV_DIR))
21
+ mkdirSync(GMGN_ENV_DIR, { recursive: true });
22
+ }
23
+ function readGmgnEnv() {
24
+ const env = {};
25
+ if (existsSync(GMGN_ENV_PATH)) {
26
+ const content = readFileSync(GMGN_ENV_PATH, "utf-8");
27
+ for (const line of content.split("\n")) {
28
+ const trimmed = line.trim();
29
+ if (!trimmed || trimmed.startsWith("#"))
30
+ continue;
31
+ const eqIdx = trimmed.indexOf("=");
32
+ if (eqIdx === -1)
33
+ continue;
34
+ const key = trimmed.slice(0, eqIdx).trim();
35
+ const value = trimmed.slice(eqIdx + 1).trim();
36
+ if (key)
37
+ env[key] = value;
38
+ }
39
+ }
40
+ for (const key of Object.keys(process.env)) {
41
+ const val = process.env[key];
42
+ if (val)
43
+ env[key] = val;
44
+ }
45
+ return env;
46
+ }
47
+ function writeGmgnKey(key, value) {
48
+ ensureGmgnEnv();
49
+ const env = readGmgnEnv();
50
+ env[key] = value;
51
+ const lines = Object.entries(env).map(([k, v]) => `${k}=${v}`);
52
+ writeFileSync(GMGN_ENV_PATH, lines.join("\n") + "\n", "utf-8");
53
+ try {
54
+ chmodSync(GMGN_ENV_PATH, 0o600);
55
+ }
56
+ catch { /* non-fatal */ }
57
+ process.env[key] = value;
58
+ }
59
+ export function getGmgnApiKey() {
60
+ return readGmgnEnv().GMGN_API_KEY;
61
+ }
62
+ export function hasGmgnApiKey() {
63
+ const key = getGmgnApiKey();
64
+ return !!key && key.trim() !== "";
65
+ }
66
+ export function saveGmgnApiKey(key) {
67
+ writeGmgnKey("GMGN_API_KEY", key);
68
+ }
69
+ function gmgnEnv() {
70
+ return { ...process.env, ...readGmgnEnv() };
71
+ }
72
+ function gmgnAvailable() {
73
+ try {
74
+ execSync("which gmgn-cli", { stdio: "ignore" });
75
+ return true;
76
+ }
77
+ catch {
78
+ return false;
79
+ }
80
+ }
81
+ // ── Sub-command definitions ─────────────────────────────────────────────────
82
+ export const GMGN_SUBCOMMANDS = [
83
+ { name: "token info", desc: "Token price, market cap, liquidity, holders, socials", usage: "gmgn token info --chain <sol|bsc|base|eth> --address <address>" },
84
+ { name: "token security", desc: "Honeypot, taxes, rug ratio, contract risks", usage: "gmgn token security --chain <chain> --address <address>" },
85
+ { name: "token pool", desc: "Liquidity pool reserves, DEX, depth", usage: "gmgn token pool --chain <chain> --address <address>" },
86
+ { name: "token holders", desc: "Top holders with P&L breakdown", usage: "gmgn token holders --chain <chain> --address <address>" },
87
+ { name: "token traders", desc: "Top traders with P&L breakdown", usage: "gmgn token traders --chain <chain> --address <address>" },
88
+ { name: "track follow-tokens", desc: "Tokens a wallet follows on GMGN", usage: "gmgn track follow-tokens --chain <chain> --wallet <address>" },
89
+ { name: "track follow-wallet", desc: "Trades from wallets you follow", usage: "gmgn track follow-wallet --chain <chain>" },
90
+ { name: "track kol", desc: "KOL/influencer trade activity", usage: "gmgn track kol --chain <chain>" },
91
+ { name: "track smartmoney", desc: "Smart money/whale trade activity", usage: "gmgn track smartmoney --chain <chain>" },
92
+ { name: "market kline", desc: "Token candlestick / OHLCV chart data", usage: "gmgn market kline --chain <chain> --address <address> --resolution <1m|5m|15m|1h|4h|1d>" },
93
+ { name: "market trending", desc: "Trending tokens ranked by swaps", usage: "gmgn market trending --chain <chain> --interval <1m|5m|1h|6h|24h>" },
94
+ { name: "market trenches", desc: "Newly launched tokens (new/near/completed)", usage: "gmgn market trenches --chain <chain> --type <new_creation|near_completion|completed>" },
95
+ { name: "market signal", desc: "Real-time token signals (sol/bsc only)", usage: "gmgn market signal --chain <sol|bsc>" },
96
+ ];
97
+ export const gmgnHelp = () => {
98
+ const groups = [
99
+ { title: "── Token Research ──────────────────────────────────", filter: (s) => s.startsWith("token") },
100
+ { title: "── Smart Money Tracking ───────────────────────────", filter: (s) => s.startsWith("track") },
101
+ { title: "── Market Data ────────────────────────────────────", filter: (s) => s.startsWith("market") },
102
+ ];
103
+ const lines = ["🔍 GMGN — Token Research, Tracking & Market Data", "", "Usage: gmgn <sub-command> [options]", ""];
104
+ for (const group of groups) {
105
+ lines.push(group.title);
106
+ for (const cmd of GMGN_SUBCOMMANDS.filter(c => group.filter(c.name))) {
107
+ lines.push(` ${cmd.name.padEnd(20)} ${cmd.desc}`);
108
+ lines.push(` ${cmd.usage}`);
109
+ }
110
+ lines.push("");
111
+ }
112
+ lines.push("── Setup ─────────────────────────────────────────");
113
+ lines.push(" gmgn setup Configure GMGN_API_KEY");
114
+ lines.push(" gmgn status Check API key + CLI status");
115
+ lines.push("");
116
+ if (!gmgnAvailable())
117
+ lines.push(" ⚠️ gmgn-cli not installed. Run: npm install -g gmgn-cli");
118
+ if (!hasGmgnApiKey())
119
+ lines.push(" ⚠️ GMGN_API_KEY not set. Run: gmgn setup");
120
+ return lines.join("\n");
121
+ };
122
+ export const gmgnStatus = () => {
123
+ return [
124
+ "🔍 GMGN Status",
125
+ "",
126
+ `CLI installed: ${gmgnAvailable() ? "✅ yes" : "❌ no (npm install -g gmgn-cli)"}`,
127
+ `API key configured: ${hasGmgnApiKey() ? "✅ yes" : "❌ no (gmgn setup)"}`,
128
+ hasGmgnApiKey() ? `Key: ${getGmgnApiKey().slice(0, 6)}••••${getGmgnApiKey().slice(-4)}` : "",
129
+ ].filter(Boolean).join("\n");
130
+ };
131
+ // ── Agent Tool ───────────────────────────────────────────────────────────────
132
+ export const gmgnToolParams = Type.Object({
133
+ subcommand: Type.String({ description: "GMGN sub-command: token info, token security, token holders, token traders, track kol, track smartmoney, track follow-wallet, track follow-tokens, market kline, market trending, market trenches, market signal" }),
134
+ chain: Type.Optional(Type.String({ description: "Chain: sol, bsc, base, eth" })),
135
+ address: Type.Optional(Type.String({ description: "Token contract address" })),
136
+ wallet: Type.Optional(Type.String({ description: "Wallet address for track commands" })),
137
+ limit: Type.Optional(Type.Number({ description: "Number of results" })),
138
+ resolution: Type.Optional(Type.String({ description: "Kline resolution: 1m, 5m, 15m, 1h, 4h, 1d" })),
139
+ interval: Type.Optional(Type.String({ description: "Trending interval: 1m, 5m, 1h, 6h, 24h" })),
140
+ type: Type.Optional(Type.String({ description: "Trenches type: new_creation, near_completion, completed" })),
141
+ });
142
+ export async function gmgnTool(_id, params) {
143
+ if (!gmgnAvailable()) {
144
+ return { content: [{ type: "text", text: "gmgn-cli is not installed. Run: npm install -g gmgn-cli" }], isError: true };
145
+ }
146
+ if (!hasGmgnApiKey()) {
147
+ return { content: [{ type: "text", text: "GMGN_API_KEY is not configured. Run 'gmgn setup' or tell the agent to configure it." }], isError: true };
148
+ }
149
+ const sub = params.subcommand;
150
+ const chain = params.chain || "sol";
151
+ const address = params.address;
152
+ const wallet = params.wallet;
153
+ const limit = params.limit;
154
+ const resolution = params.resolution;
155
+ const interval = params.interval;
156
+ const trenchesType = params.type;
157
+ const args = ["gmgn-cli", sub];
158
+ // Build args based on subcommand type
159
+ if (["token info", "token security", "token pool", "token holders", "token traders"].includes(sub)) {
160
+ if (!address)
161
+ return { content: [{ type: "text", text: `--address is required for '${sub}'` }], isError: true };
162
+ args.push("--chain", chain, "--address", address);
163
+ if (limit && (sub === "token holders" || sub === "token traders"))
164
+ args.push("--limit", String(limit));
165
+ }
166
+ else if (sub === "market kline") {
167
+ if (!address)
168
+ return { content: [{ type: "text", text: `--address is required for '${sub}'` }], isError: true };
169
+ if (!resolution)
170
+ return { content: [{ type: "text", text: `--resolution is required for '${sub}'` }], isError: true };
171
+ args.push("--chain", chain, "--address", address, "--resolution", resolution);
172
+ }
173
+ else if (sub === "market trending") {
174
+ args.push("--chain", chain);
175
+ if (interval)
176
+ args.push("--interval", interval);
177
+ if (limit)
178
+ args.push("--limit", String(limit));
179
+ }
180
+ else if (sub === "market trenches") {
181
+ args.push("--chain", chain);
182
+ if (trenchesType)
183
+ args.push("--type", trenchesType);
184
+ if (limit)
185
+ args.push("--limit", String(limit));
186
+ }
187
+ else if (sub === "market signal") {
188
+ args.push("--chain", chain);
189
+ if (limit)
190
+ args.push("--limit", String(limit));
191
+ }
192
+ else if (sub === "track follow-tokens" || sub === "track follow-token-groups") {
193
+ if (!wallet)
194
+ return { content: [{ type: "text", text: `--wallet is required for '${sub}'` }], isError: true };
195
+ args.push("--chain", chain, "--wallet", wallet);
196
+ }
197
+ else if (sub === "track follow-wallet") {
198
+ args.push("--chain", chain);
199
+ if (wallet)
200
+ args.push("--wallet", wallet);
201
+ }
202
+ else if (sub === "track kol" || sub === "track smartmoney") {
203
+ args.push("--chain", chain);
204
+ if (limit)
205
+ args.push("--limit", String(limit));
206
+ }
207
+ try {
208
+ const output = execSync(args.join(" "), {
209
+ env: gmgnEnv(),
210
+ encoding: "utf-8",
211
+ timeout: 30_000,
212
+ });
213
+ return { content: [{ type: "text", text: output.trim() }] };
214
+ }
215
+ catch (e) {
216
+ const msg = e.stderr?.toString() || e.message || "";
217
+ if (msg.includes("401") || msg.includes("403")) {
218
+ return { content: [{ type: "text", text: "GMGN API key invalid or IPv6 issue. Run 'gmgn status'." }], isError: true };
219
+ }
220
+ if (msg.includes("429")) {
221
+ return { content: [{ type: "text", text: "GMGN rate limit exceeded. Wait and retry." }], isError: true };
222
+ }
223
+ return { content: [{ type: "text", text: `gmgn-cli error: ${msg.slice(0, 500)}` }], isError: true };
224
+ }
225
+ }
226
+ // ── Market tool (shortcut for agent) ────────────────────────────────────────
227
+ export const gmgnMarketToolParams = Type.Object({
228
+ query: Type.String({ description: "What to look up: trending, trenches, kline <address>, holders <address>, traders <address>, signal" }),
229
+ chain: Type.Optional(Type.String({ default: "sol" })),
230
+ });
231
+ export async function gmgnMarketTool(_id, params) {
232
+ const query = (params.query || "").toLowerCase().trim();
233
+ const chain = params.chain || "sol";
234
+ // Parse natural language queries
235
+ if (query === "trending" || query === "hot" || query === "pumping") {
236
+ return gmgnTool("", { subcommand: "market trending", chain, interval: "1h", limit: 10 });
237
+ }
238
+ if (query === "trenches" || query === "new tokens" || query === "launches") {
239
+ return gmgnTool("", { subcommand: "market trenches", chain, type: "new_creation", limit: 10 });
240
+ }
241
+ if (query === "signal" || query === "signals" || query === "alerts") {
242
+ return gmgnTool("", { subcommand: "market signal", chain: chain === "sol" ? "sol" : "sol", limit: 10 });
243
+ }
244
+ if (query.startsWith("kline ") || query.startsWith("chart ")) {
245
+ const addr = query.split(" ").pop();
246
+ if (!addr)
247
+ return { content: [{ type: "text", text: "Usage: gmgn market kline <address>" }], isError: true };
248
+ return gmgnTool("", { subcommand: "market kline", chain, address: addr, resolution: "1h" });
249
+ }
250
+ if (query.startsWith("holders ") || query.startsWith("traders ")) {
251
+ const parts = query.split(" ");
252
+ const sub = parts[0] === "holders" ? "token holders" : "token traders";
253
+ const addr = parts[1];
254
+ if (!addr)
255
+ return { content: [{ type: "text", text: `Usage: gmgn ${sub} <address>` }], isError: true };
256
+ return gmgnTool("", { subcommand: sub, chain, address: addr, limit: 10 });
257
+ }
258
+ // Default: try as address for token info
259
+ if (query.length > 20) {
260
+ return gmgnTool("", { subcommand: "token info", chain, address: query });
261
+ }
262
+ return { content: [{ type: "text", text: "Usage: gmgn market <trending|trenches|signal|kline <addr>|holders <addr>|traders <addr>>" }], isError: true };
263
+ }
264
+ //# sourceMappingURL=GmgnIntegration.js.map
package/dist/tui/App.d.ts CHANGED
@@ -1,16 +1,15 @@
1
1
  /**
2
- * App — root Ink component. Multi-pane TUI with sidebar (price, news, context)
2
+ * App — root Ink component. Multi-pane TUI with sidebar (agent wallet, actions, fees, news)
3
3
  * and main REPL area.
4
4
  */
5
5
  import React from "react";
6
6
  import { Registry } from "../api/Registry.js";
7
- import type { ModelRegistry } from "../models/ModelRegistry.js";
8
7
  import type { CostTracker } from "../models/CostTracker.js";
9
8
  export interface AppProps {
10
9
  registry: Registry;
11
10
  systemPrompt?: string;
12
11
  chain?: string;
13
- modelReg?: ModelRegistry;
12
+ modelReg?: any;
14
13
  costTracker?: CostTracker;
15
14
  onNotifyReady?: (fn: (msg: string) => void) => void;
16
15
  onStatusReady?: (fn: (key: string, val: string) => void) => void;
package/dist/tui/App.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  /**
3
- * App — root Ink component. Multi-pane TUI with sidebar (price, news, context)
3
+ * App — root Ink component. Multi-pane TUI with sidebar (agent wallet, actions, fees, news)
4
4
  * and main REPL area.
5
5
  */
6
6
  import { useState, useCallback, useEffect, useRef, useMemo } from "react";
@@ -16,6 +16,7 @@ import { makeTheme, T, AIAIAI_COLORS } from "./theme.js";
16
16
  import { AgentRunner } from "../runner/AgentRunner.js";
17
17
  import { SessionManager } from "../session/SessionManager.js";
18
18
  import { resolveModelConfig } from "../runner/ModelClient.js";
19
+ import { modelRegistry } from "../models/ModelRegistry.js";
19
20
  import { priceFeed } from "../tools/PriceFeed.js";
20
21
  import { getNewsTool, getNewsParams, newsFeed } from "../tools/NewsSentiment.js";
21
22
  import { analyzeTAParams, getCandlesParams, getCandlesTool, fullAnalysis } from "../tools/TechnicalAnalysis.js";
@@ -24,6 +25,9 @@ import { contextStore } from "../session/ContextStore.js";
24
25
  import { goalManager } from "../session/GoalManager.js";
25
26
  import { memoryStore } from "../session/MemoryStore.js";
26
27
  import { agentScheduler } from "../scheduler/AgentScheduler.js";
28
+ import { agentWallet, ACTION_WALLET, DEPOSIT_WALLET, SIGNER } from "../wallet/AgentWallet.js";
29
+ import { actionFeed } from "../wallet/ActionFeed.js";
30
+ import { gmgnTool, gmgnToolParams, gmgnHelp, gmgnStatus, gmgnMarketTool, gmgnMarketToolParams } from "../tools/GmgnIntegration.js";
27
31
  function getContextBar(session) {
28
32
  if (!session)
29
33
  return { pct: 0, bar: "░".repeat(20), color: AIAIAI_COLORS.dim };
@@ -33,11 +37,7 @@ function getContextBar(session) {
33
37
  : pressure.level === "red" ? AIAIAI_COLORS.warn
34
38
  : pressure.level === "yellow" ? AIAIAI_COLORS.header
35
39
  : AIAIAI_COLORS.success;
36
- return {
37
- pct: pressure.pct,
38
- bar: "█".repeat(Math.min(filled, 20)) + "░".repeat(Math.max(0, 20 - filled)),
39
- color,
40
- };
40
+ return { pct: pressure.pct, bar: "█".repeat(Math.min(filled, 20)) + "░".repeat(Math.max(0, 20 - filled)), color };
41
41
  }
42
42
  function highlightJson(obj, indent = 0) {
43
43
  const pad = " ".repeat(indent);
@@ -88,6 +88,11 @@ export function App({ registry, systemPrompt, chain: initialChain = "solana", mo
88
88
  const [newsBadge, setNewsBadge] = useState("");
89
89
  const [sidebarNews, setSidebarNews] = useState([]);
90
90
  const [aiaiPrice, setAiaiPrice] = useState(null);
91
+ const [coldBalance, setColdBalance] = useState({ sol: 0, aiaiai: 0, usdc: 0 });
92
+ const [actionBalance, setActionBalance] = useState({ sol: 0, aiaiai: 0, usdc: 0 });
93
+ const [depositBalance, setDepositBalance] = useState({ sol: 0, aiaiai: 0, usdc: 0 });
94
+ const [recentActions, setRecentActions] = useState([]);
95
+ const [fees, setFees] = useState({ buyFees: 0, burnFees: 0, total: 0 });
91
96
  const [showModelSelector, setShowModelSelector] = useState(false);
92
97
  const [modelSelectorInitialQuery, setModelSelectorInitialQuery] = useState("");
93
98
  const runnerRef = useRef(null);
@@ -108,8 +113,7 @@ export function App({ registry, systemPrompt, chain: initialChain = "solana", mo
108
113
  }, []);
109
114
  const uiCtx = {
110
115
  notify, setStatus,
111
- setTheme() { },
112
- setHeader() { },
116
+ setTheme() { }, setHeader() { },
113
117
  showModelSelector: (query) => {
114
118
  setModelSelectorInitialQuery(query ?? "");
115
119
  setShowModelSelector(true);
@@ -126,13 +130,10 @@ export function App({ registry, systemPrompt, chain: initialChain = "solana", mo
126
130
  if (costTracker) {
127
131
  registry.addTool({ name: "cost_report", label: "Cost Report", description: "Show session and lifetime token usage.", parameters: costTracker.costReportParams, execute: () => costTracker.costReportTool() });
128
132
  }
129
- // Price tools
130
133
  registry.addTool({ name: "get_aiaiai_price", label: "AIAIAI Price", description: "Get the current $AIAIAI token price, liquidity, market cap, and volume from DexScreener.", parameters: priceFeed.getAiaiaiPriceParams, execute: () => priceFeed.getAiaiaiPriceTool() });
131
134
  registry.addTool({ name: "get_token_price", label: "Token Price", description: "Get price data for any Solana token by address via DexScreener.", parameters: priceFeed.getTokenPriceParams, execute: (id, p) => priceFeed.getTokenPriceTool(id, p) });
132
- // News
133
135
  registry.addTool({ name: "get_news", label: "Get News", description: "Crypto news headlines with sentiment scoring.", parameters: getNewsParams, execute: (id, p) => getNewsTool(id, p) });
134
- // TA
135
- registry.addTool({ name: "get_candles", label: "Get Candles", description: "Fetch OHLCV candlestick data from Binance and run technical analysis (RSI, MACD, Bollinger, EMA).", parameters: getCandlesParams, execute: (id, p) => getCandlesTool(id, p) });
136
+ registry.addTool({ name: "get_candles", label: "Get Candles", description: "Fetch OHLCV candlestick data from Binance and run technical analysis.", parameters: getCandlesParams, execute: (id, p) => getCandlesTool(id, p) });
136
137
  registry.addTool({ name: "analyze_ta", label: "Technical Analysis", description: "Run RSI, MACD, Bollinger, ATR on a price array.", parameters: analyzeTAParams, execute: async (_id, p) => {
137
138
  const closes = p.prices;
138
139
  const candles = closes.map((c, i) => ({ timestamp: 0, open: c, high: p.highs?.[i] ?? c, low: p.lows?.[i] ?? c, close: c, volume: p.volumes?.[i] ?? 0 }));
@@ -145,23 +146,28 @@ export function App({ registry, systemPrompt, chain: initialChain = "solana", mo
145
146
  }).join("\n");
146
147
  return { content: [{ type: "text", text: `Technical Analysis:\n${text}` }], details: { results, overall_signal: summary?.signal } };
147
148
  } });
148
- // Market sentiment
149
149
  registry.addTool({ name: "get_fear_greed", label: "Fear & Greed", description: "Crypto Fear & Greed Index with 7-day history.", parameters: fearGreedParams, execute: () => getFearGreedTool() });
150
150
  registry.addTool({ name: "get_funding_rates", label: "Funding Rates", description: "Binance perpetual funding rates.", parameters: fundingRatesParams, execute: (id, p) => getFundingRatesTool(id, p) });
151
151
  registry.addTool({ name: "get_btc_mempool", label: "BTC Mempool", description: "Bitcoin mempool stats and fee rates.", parameters: btcMempoolParams, execute: () => getBtcMempoolTool() });
152
152
  registry.addTool({ name: "get_defi_tvl", label: "DeFi TVL", description: "DeFiLlama TVL by chain or all chains.", parameters: defiTvlParams, execute: (id, p) => getDefiTvlTool(id, p) });
153
153
  registry.addTool({ name: "get_solana_stats", label: "Solana Stats", description: "Solana network epoch, slot, and SOL price.", parameters: solanaStatsParams, execute: () => getSolanaStatsTool() });
154
- // Context + goals
155
154
  registry.addTool({ name: "read_task_context", label: "Read Task Context", description: "Read saved task context.", parameters: Type.Object({ taskId: Type.String({ description: "Task ID" }) }), execute: (id, p) => contextStore.readContextTool(id, p) });
156
155
  registry.addTool({ name: "list_tasks", label: "List Tasks", description: "List saved task contexts.", parameters: Type.Object({}), execute: () => contextStore.listTasksTool() });
157
156
  registry.addTool({ name: "set_goal", label: "Set Goal", description: "Set a persistent cross-session goal.", parameters: goalManager.setGoalParams, execute: (id, p) => goalManager.setGoalTool(id, p) });
158
157
  registry.addTool({ name: "complete_goal", label: "Complete Goal", description: "Mark a goal complete.", parameters: goalManager.completeGoalParams, execute: (id, p) => goalManager.completeGoalTool(id, p) });
159
158
  registry.addTool({ name: "list_goals", label: "List Goals", description: "List goals.", parameters: goalManager.listGoalsParams, execute: (id, p) => goalManager.listGoalsTool(id, p) });
160
159
  registry.addTool({ name: "add_goal_note", label: "Add Goal Note", description: "Add a note to a goal.", parameters: goalManager.addGoalNoteParams, execute: (id, p) => goalManager.addGoalNoteTool(id, p) });
161
- // Scheduler
162
160
  registry.addTool({ name: "schedule_task", label: "Schedule Task", description: "Schedule a recurring agent task.", parameters: agentScheduler.addTaskParams, execute: (id, p) => agentScheduler.addTaskTool(id, p) });
163
161
  registry.addTool({ name: "list_schedule", label: "List Schedule", description: "List scheduled tasks.", parameters: agentScheduler.listTasksParams, execute: (id, p) => agentScheduler.listTasksTool(id, p) });
164
162
  registry.addTool({ name: "remove_schedule", label: "Remove Schedule", description: "Remove a scheduled task.", parameters: agentScheduler.removeTaskParams, execute: (id, p) => agentScheduler.removeTaskTool(id, p) });
163
+ // GMGN tools
164
+ registry.addTool({ name: "gmgn", label: "GMGN Token Research", description: "Research tokens via GMGN: price, security, holders, traders, smart money tracking. Sub-commands: token info, token security, token holders, token traders, track kol, track smartmoney, track follow-wallet, track follow-tokens. Usage: gmgn <subcommand> --chain <sol|bsc|base|eth> --address <address>", parameters: gmgnToolParams, execute: (id, p) => gmgnTool(id, p) });
165
+ registry.addTool({ name: "gmgn_market", label: "GMGN Market Data", description: "Market data via GMGN: trending tokens, trenches (new launches), kline charts, signals, holders, traders. Usage: gmgn market <trending|trenches|signal|kline <addr>|holders <addr>|traders <addr>>", parameters: gmgnMarketToolParams, execute: (id, p) => gmgnMarketTool(id, p) });
166
+ // Wallet tools
167
+ registry.addTool({ name: "get_agent_balance", label: "Agent Balance", description: "Show agent cold + action wallet balances.", parameters: agentWallet.getAgentBalanceParams, execute: () => agentWallet.getAgentBalanceTool() });
168
+ registry.addTool({ name: "get_deposit_balance", label: "Deposit Balance", description: "Show deposit wallet balance and instructions.", parameters: agentWallet.getDepositBalanceParams, execute: () => agentWallet.getDepositBalanceTool() });
169
+ registry.addTool({ name: "get_actions", label: "Agent Actions", description: "Show recent agent buy/burn actions.", parameters: actionFeed.getActionsParams, execute: (id, p) => actionFeed.getActionsTool(id, p) });
170
+ registry.addTool({ name: "get_fees", label: "Agent Fees", description: "Show accumulated fees from agent actions.", parameters: actionFeed.getFeesParams, execute: () => actionFeed.getFeesTool() });
165
171
  }
166
172
  useEffect(() => { onNotifyReady?.(notify); onStatusReady?.(setStatus); }, []);
167
173
  useEffect(() => {
@@ -177,13 +183,26 @@ export function App({ registry, systemPrompt, chain: initialChain = "solana", mo
177
183
  setAiaiPrice({ priceUsd: p.priceUsd, change: p.priceChange24h, mcap: p.marketCap, liq: p.liquidityUsd });
178
184
  if (p.priceUsd)
179
185
  setPriceBadge(`$${parseFloat(p.priceUsd).toFixed(6)}`);
186
+ actionFeed.setPrice(parseFloat(p.priceUsd || "0.0004"));
187
+ }).catch(() => { });
188
+ // Initial wallet fetches
189
+ agentWallet.getAll().then(w => {
190
+ setColdBalance({ sol: w.cold.sol, aiaiai: w.cold.aiaiai, usdc: w.cold.usdc });
191
+ setActionBalance({ sol: w.action.sol, aiaiai: w.action.aiaiai, usdc: w.action.usdc });
192
+ setDepositBalance({ sol: w.deposit.sol, aiaiai: w.deposit.aiaiai, usdc: w.deposit.usdc });
193
+ }).catch(() => { });
194
+ // Initial actions
195
+ actionFeed.refresh().then(() => {
196
+ setRecentActions(actionFeed.getRecentActions(8).map(a => ({ type: a.type, amount: a.amount, usdValue: a.usdValue, timestamp: a.timestamp })));
197
+ setFees(actionFeed.getFees());
180
198
  }).catch(() => { });
199
+ // Initial news
181
200
  newsFeed.getLatest().then(report => {
182
201
  setSidebarNews(report.items.slice(0, 6).map(i => ({ title: i.title, sentiment: i.sentiment ?? 0, source: i.source })));
183
202
  setNewsBadge(newsFeed.statusBadge());
184
203
  }).catch(() => { });
185
204
  agentScheduler.start((task) => {
186
- push({ role: "system", content: T.muted(`⏰ Scheduler: running "${task.name}"`) });
205
+ push({ role: "system", content: T.muted(`⏰ Scheduled task: "${task.name}"`) });
187
206
  if (!disabled && runnerRef.current) {
188
207
  setDisabled(true);
189
208
  setStreaming("");
@@ -191,15 +210,30 @@ export function App({ registry, systemPrompt, chain: initialChain = "solana", mo
191
210
  .catch((e) => notify(T.error(`Scheduler error: ${e.message}`)));
192
211
  }
193
212
  });
213
+ // Price interval (30s)
194
214
  const priceInterval = setInterval(() => {
195
215
  priceFeed.getAiaiaiPrice().then(p => {
196
216
  setAiaiPrice({ priceUsd: p.priceUsd, change: p.priceChange24h, mcap: p.marketCap, liq: p.liquidityUsd });
197
217
  if (p.priceUsd)
198
218
  setPriceBadge(`$${parseFloat(p.priceUsd).toFixed(6)}`);
219
+ actionFeed.setPrice(parseFloat(p.priceUsd || "0.0004"));
199
220
  }).catch(() => { });
200
221
  if (costTracker)
201
222
  setCostBadge(costTracker.statusLine());
202
223
  }, 30_000);
224
+ // Wallet + actions interval (60s)
225
+ const walletInterval = setInterval(() => {
226
+ agentWallet.getAll().then(w => {
227
+ setColdBalance({ sol: w.cold.sol, aiaiai: w.cold.aiaiai, usdc: w.cold.usdc });
228
+ setActionBalance({ sol: w.action.sol, aiaiai: w.action.aiaiai, usdc: w.action.usdc });
229
+ setDepositBalance({ sol: w.deposit.sol, aiaiai: w.deposit.aiaiai, usdc: w.deposit.usdc });
230
+ }).catch(() => { });
231
+ actionFeed.refresh().then(() => {
232
+ setRecentActions(actionFeed.getRecentActions(8).map(a => ({ type: a.type, amount: a.amount, usdValue: a.usdValue, timestamp: a.timestamp })));
233
+ setFees(actionFeed.getFees());
234
+ }).catch(() => { });
235
+ }, 60_000);
236
+ // News interval (5min)
203
237
  const newsInterval = setInterval(() => {
204
238
  newsFeed.getLatest().then(report => {
205
239
  setSidebarNews(report.items.slice(0, 6).map(i => ({ title: i.title, sentiment: i.sentiment ?? 0, source: i.source })));
@@ -271,6 +305,7 @@ export function App({ registry, systemPrompt, chain: initialChain = "solana", mo
271
305
  registry.fireHook("session_end", sessionCtxRef.current).catch(() => { });
272
306
  agentScheduler.stop();
273
307
  clearInterval(priceInterval);
308
+ clearInterval(walletInterval);
274
309
  clearInterval(newsInterval);
275
310
  clearInterval(saveInterval);
276
311
  costTracker?.saveLifetime();
@@ -298,7 +333,7 @@ export function App({ registry, systemPrompt, chain: initialChain = "solana", mo
298
333
  }
299
334
  if (cmd === "help") {
300
335
  const lines = registry.listCommands().map(([n, d]) => T.accent(`/${n}`.padEnd(16)) + " " + d.description);
301
- notify("Commands:\n/help /price /news /models /cost /goals /schedule /model /clear /exit\n" + (lines.length ? lines.join("\n") + "\n" : "") + "\nTools: get_aiaiai_price, get_token_price, get_news, get_candles, analyze_ta, get_fear_greed, get_solana_stats, and more");
336
+ notify("Commands:\n/gmgn /gmgnmarket /update /keys /wallet /deposit /burn /actions /fees /price /news /models /cost /goals /schedule /model /clear /exit\n" + (lines.length ? "\n" + lines.join("\n") : ""));
302
337
  return;
303
338
  }
304
339
  if (cmd === "price") {
@@ -338,6 +373,43 @@ export function App({ registry, systemPrompt, chain: initialChain = "solana", mo
338
373
  setMessages([]);
339
374
  return;
340
375
  }
376
+ if (cmd === "wallet") {
377
+ const result = await agentWallet.getAgentBalanceTool();
378
+ notify(result.content[0].text);
379
+ return;
380
+ }
381
+ if (cmd === "deposit") {
382
+ const result = await agentWallet.getDepositBalanceTool();
383
+ notify(result.content[0].text);
384
+ return;
385
+ }
386
+ if (cmd === "burn") {
387
+ notify([
388
+ "🔥 Burn $AIAIAI",
389
+ "",
390
+ `Deposit wallet: ${DEPOSIT_WALLET}`,
391
+ `Send SOL or USDC here to fuel agent burns.`,
392
+ "",
393
+ `The agent detects deposits and automatically`,
394
+ `sends funds to the action wallet to burn tokens.`,
395
+ "",
396
+ `Action wallet: ${ACTION_WALLET}`,
397
+ `Signer: ${SIGNER}`,
398
+ "",
399
+ `Burn destination: tokens are removed from circulation.`,
400
+ ].join("\n"));
401
+ return;
402
+ }
403
+ if (cmd === "actions" || cmd === "activity") {
404
+ const result = await actionFeed.getActionsTool("", { limit: 15 });
405
+ notify(result.content[0].text);
406
+ return;
407
+ }
408
+ if (cmd === "fees") {
409
+ const result = await actionFeed.getFeesTool();
410
+ notify(result.content[0].text);
411
+ return;
412
+ }
341
413
  if (cmd === "goals" || cmd === "goal") {
342
414
  const subCmd = args.trim().split(" ")[0];
343
415
  if (subCmd === "add") {
@@ -389,6 +461,58 @@ export function App({ registry, systemPrompt, chain: initialChain = "solana", mo
389
461
  notify(T.error("No pending approval."));
390
462
  return;
391
463
  }
464
+ if (cmd === "gmgn" || cmd === "gmgnhelp") {
465
+ const gmgnArgs = rest.join(" ");
466
+ if (!gmgnArgs || gmgnArgs === "help" || gmgnArgs === "--help") {
467
+ notify(gmgnHelp());
468
+ return;
469
+ }
470
+ if (gmgnArgs === "status") {
471
+ notify(gmgnStatus());
472
+ return;
473
+ }
474
+ if (gmgnArgs.startsWith("setup") || gmgnArgs.startsWith("key")) {
475
+ notify("GMGN API key setup. Switch to CLI and run: aiaiaichain gmgn setup");
476
+ return;
477
+ }
478
+ // Forward to gmgn tool
479
+ const result = await gmgnTool("", { subcommand: gmgnArgs.split(" ")[0], chain: "sol", ...Object.fromEntries(gmgnArgs.split(" ").slice(1).reduce((acc, val, i) => { if (i % 2 === 0)
480
+ acc.push([val]);
481
+ else
482
+ acc[acc.length - 1].push(val); return acc; }, []).map(([k, v]) => [k, v || true])) });
483
+ notify(result.content[0].text);
484
+ return;
485
+ }
486
+ if (cmd === "gmgnmarket" || cmd === "market") {
487
+ const query = rest.join(" ");
488
+ const result = await gmgnMarketTool("", { query: query || "trending", chain: "sol" });
489
+ notify(result.content[0].text);
490
+ return;
491
+ }
492
+ if (cmd === "update" || cmd === "upgrade") {
493
+ notify([
494
+ "🤖 AIAIAI Update",
495
+ "",
496
+ "Switch to CLI to check for updates:",
497
+ " aiaiai update",
498
+ "",
499
+ "This will check npm for the latest version",
500
+ "and install it if available.",
501
+ ].join("\n"));
502
+ return;
503
+ }
504
+ if (cmd === "keys" || cmd === "providers") {
505
+ notify([
506
+ "🔑 AI Model Providers",
507
+ "",
508
+ "Switch to CLI to manage keys:", " aiaiaichain keys",
509
+ "",
510
+ "Or use /models to see available models.",
511
+ `\nProviders: ${modelRegistry.getProviderCount()} configured`,
512
+ `Models: ${modelRegistry.modelCount} available`,
513
+ ].join("\n"));
514
+ return;
515
+ }
392
516
  if (cmd === "memory" && args.trim()) {
393
517
  const results = memoryStore.search(args.trim(), 8);
394
518
  if (results.length === 0) {
@@ -431,7 +555,7 @@ export function App({ registry, systemPrompt, chain: initialChain = "solana", mo
431
555
  const handleModelSelect = useCallback((modelId) => {
432
556
  setShowModelSelector(false);
433
557
  try {
434
- const AIAIAI_HOME = process.env.AIAIAI_HOME ?? join(homedir(), ".aiaiai");
558
+ const AIAIAI_HOME = process.env.AIAIAI_HOME ?? join(homedir(), '.aiaiai');
435
559
  const envFile = join(AIAIAI_HOME, ".env");
436
560
  mkdirSync(AIAIAI_HOME, { recursive: true });
437
561
  const content = existsSync(envFile) ? readFileSync(envFile, "utf-8") : "";
@@ -461,15 +585,21 @@ export function App({ registry, systemPrompt, chain: initialChain = "solana", mo
461
585
  const ctx = getContextBar(sessionRef.current);
462
586
  const statusLine = [costBadge, newsBadge, ...Object.values(statusBadges)].filter(Boolean).join(" ") || null;
463
587
  const changeColor = (pct) => pct > 0 ? AIAIAI_COLORS.success : pct < 0 ? AIAIAI_COLORS.error : AIAIAI_COLORS.muted;
464
- const SIDEBAR_W = 42;
588
+ const SIDEBAR_W = 44;
589
+ const SIDEBAR_MIN = 38;
590
+ const actionIcon = (type) => type === "buy" ? "↗" : type === "burn" ? "🔥" : type === "deposit" ? "↓" : "→";
465
591
  if (showModelSelector) {
466
592
  return (_jsxs(Box, { flexDirection: "column", children: [_jsx(StatusBar, { model: modelName, chain: chain, price: priceBadge, toolRunning: toolRunning, connected: true, statusLine: statusLine }), _jsx(ModelSelector, { models: modelList, currentModelId: process.env.DEFAULT_MODEL ?? "", onSelect: handleModelSelect, onCancel: handleModelCancel, initialQuery: modelSelectorInitialQuery })] }));
467
593
  }
468
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(StatusBar, { model: modelName, chain: chain, price: priceBadge, toolRunning: toolRunning, connected: true, statusLine: statusLine }), _jsxs(Box, { flexDirection: "row", flexGrow: 1, overflow: "hidden", children: [_jsxs(Box, { flexDirection: "column", width: SIDEBAR_W, flexShrink: 0, 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: "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: "News" }), sidebarNews.length > 0 ? sidebarNews.map((n, i) => {
594
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(StatusBar, { model: modelName, chain: chain, price: priceBadge, toolRunning: toolRunning, connected: true, statusLine: statusLine }), _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) => {
595
+ const col = a.type === "buy" ? AIAIAI_COLORS.success : a.type === "burn" ? AIAIAI_COLORS.error : AIAIAI_COLORS.muted;
596
+ const time = new Date(a.timestamp).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
597
+ return _jsxs(Text, { color: col, children: [actionIcon(a.type), " ", time, " ", a.type, " ", a.amount.toLocaleString()] }, i);
598
+ }) : _jsx(Text, { color: AIAIAI_COLORS.muted, children: "Waiting for actions\u2026" })] }), _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: AIAIAI_COLORS.dim, paddingX: 1, marginTop: 1, children: [_jsx(Text, { bold: true, children: "\uD83D\uDCB5 Fees (0.5%)" }), _jsxs(Text, { color: AIAIAI_COLORS.muted, children: ["Buy: $", fees.buyFees.toFixed(4)] }), _jsxs(Text, { color: AIAIAI_COLORS.muted, children: ["Burn: $", fees.burnFees.toFixed(4)] }), _jsxs(Text, { color: AIAIAI_COLORS.accent, bold: true, children: ["Total: $", fees.total.toFixed(4)] })] }), _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: AIAIAI_COLORS.dim, paddingX: 1, marginTop: 1, children: [_jsx(Text, { bold: true, children: "\uD83D\uDCF0 News" }), sidebarNews.length > 0 ? sidebarNews.map((n, i) => {
469
599
  const sentColor = n.sentiment > 0.3 ? AIAIAI_COLORS.success : n.sentiment < -0.3 ? AIAIAI_COLORS.error : AIAIAI_COLORS.muted;
470
600
  const title = n.title.length > 30 ? n.title.slice(0, 28) + "…" : n.title;
471
601
  return _jsx(Text, { color: sentColor, children: title }, i);
472
- }) : _jsx(Text, { color: AIAIAI_COLORS.muted, children: "Loading news\u2026" })] }), _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: AIAIAI_COLORS.dim, paddingX: 1, marginTop: 1, children: [_jsx(Text, { bold: true, children: "Chain" }), _jsx(Text, { color: AIAIAI_COLORS.header, children: "\u26D3 solana (primary)" })] })] }), _jsx(Box, { flexDirection: "column", flexGrow: 1, children: _jsx(REPL, { messages: messages, streamingText: streaming, toolRunning: toolRunning, onSubmit: handleSubmit, disabled: disabled, onAbort: () => {
602
+ }) : _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: () => {
473
603
  runnerRef.current?.abort();
474
604
  setDisabled(false);
475
605
  setToolRunning(null);