httpcat-cli 0.2.11 → 0.2.12

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.
@@ -10,69 +10,258 @@ import { buyToken, TEST_AMOUNTS, PROD_AMOUNTS } from "../commands/buy.js";
10
10
  import { sellToken, parseTokenAmount } from "../commands/sell.js";
11
11
  import { getTokenInfo } from "../commands/info.js";
12
12
  import { listTokens } from "../commands/list.js";
13
+ import { formatCurrency } from "../utils/formatting.js";
13
14
  import { getPositions } from "../commands/positions.js";
14
15
  import { privateKeyToAccount } from "viem/accounts";
15
16
  import { checkHealth } from "../commands/health.js";
16
17
  import { startChatStream } from "../commands/chat.js";
17
18
  import { checkBalance } from "../commands/balances.js";
19
+ import { viewFees, claimFees } from "../commands/claim.js";
20
+ import { getTransactions } from "../commands/transactions.js";
21
+ import { getAccountInfo, switchAccount, addAccount } from "../commands/account.js";
22
+ import { HttpcatError } from "../client.js";
23
+ // Detect terminal background color
24
+ function detectTerminalBackground() {
25
+ // Check COLORFGBG (format: "foreground;background")
26
+ // Background values: 0-7 are dark, 8-15 are light
27
+ const colorfgbg = process.env.COLORFGBG;
28
+ if (colorfgbg) {
29
+ const parts = colorfgbg.split(";");
30
+ if (parts.length >= 2) {
31
+ const bg = parseInt(parts[1], 10);
32
+ if (!isNaN(bg)) {
33
+ // 0-7 are dark backgrounds, 8-15 are light
34
+ return bg < 8 ? "dark" : "light";
35
+ }
36
+ }
37
+ }
38
+ // Check TERM_PROGRAM for common terminals
39
+ const termProgram = process.env.TERM_PROGRAM?.toLowerCase();
40
+ if (termProgram) {
41
+ // These terminals often have dark backgrounds by default
42
+ if (["iterm2", "vscode", "hyper", "alacritty", "kitty"].includes(termProgram)) {
43
+ return "dark";
44
+ }
45
+ // These often have light backgrounds
46
+ if (["apple_terminal"].includes(termProgram)) {
47
+ return "light";
48
+ }
49
+ }
50
+ // Check for common dark terminal indicators
51
+ const term = process.env.TERM?.toLowerCase() || "";
52
+ if (term.includes("256") || term.includes("xterm")) {
53
+ // Most modern terminals default to dark
54
+ return "dark";
55
+ }
56
+ // Default to dark (safer assumption for modern terminals)
57
+ return "dark";
58
+ }
18
59
  export async function startInteractiveShell(client) {
19
- // Create blessed screen
60
+ // Auto-detect terminal background and set default theme
61
+ const detectedBg = detectTerminalBackground();
62
+ let currentTheme = detectedBg === "dark" ? "dark" : "win95";
63
+ // Create blessed screen with optimized settings
20
64
  const screen = blessed.screen({
21
65
  smartCSR: true,
22
66
  title: "httpcat Interactive Shell",
23
67
  fullUnicode: true,
68
+ fastCSR: false, // Disable fast CSR to prevent rendering issues
69
+ cursor: {
70
+ artificial: true,
71
+ shape: "line",
72
+ blink: true,
73
+ color: "green",
74
+ },
75
+ // Ensure cursor is always visible
76
+ forceUnicode: false,
24
77
  });
25
78
  const network = client.getNetwork();
26
- // Create header box
79
+ // Theme colors - no backgrounds, just borders
80
+ const getThemeColors = (theme) => {
81
+ switch (theme) {
82
+ case "light":
83
+ return {
84
+ bg: "default", // Transparent/default
85
+ fg: "black",
86
+ border: "black",
87
+ inputBg: "default",
88
+ inputFg: "black", // Explicit black for visibility
89
+ inputFocusBg: "default",
90
+ inputFocusFg: "black",
91
+ };
92
+ case "win95":
93
+ return {
94
+ bg: "default",
95
+ fg: "black",
96
+ border: "black",
97
+ inputBg: "default",
98
+ inputFg: "black", // Explicit black for visibility
99
+ inputFocusBg: "default",
100
+ inputFocusFg: "black",
101
+ };
102
+ default: // dark
103
+ return {
104
+ bg: "default",
105
+ fg: "green",
106
+ border: "green",
107
+ inputBg: "default",
108
+ inputFg: "green", // Explicit green for visibility
109
+ inputFocusBg: "default",
110
+ inputFocusFg: "green",
111
+ };
112
+ }
113
+ };
114
+ let themeColors = getThemeColors(currentTheme);
115
+ // Create header box with thick borders, transparent background
27
116
  const headerBox = blessed.box({
28
117
  top: 0,
29
118
  left: 0,
30
119
  width: "100%",
31
- height: 8,
120
+ height: 16,
32
121
  content: "",
33
- tags: false,
122
+ tags: true,
34
123
  style: {
35
- fg: "white",
36
- bg: "black",
124
+ fg: themeColors.fg,
125
+ bg: "default", // Transparent
126
+ bold: false,
127
+ border: {
128
+ fg: themeColors.border,
129
+ bold: true,
130
+ },
37
131
  },
38
132
  padding: {
39
- left: 1,
133
+ left: 0,
40
134
  right: 1,
135
+ top: 1,
136
+ bottom: 1,
41
137
  },
138
+ border: {
139
+ type: "line",
140
+ fg: themeColors.border,
141
+ ch: "═", // Double line for thicker border
142
+ },
143
+ });
144
+ // Helper function to build welcome content with account info
145
+ const buildWelcomeContent = async (theme) => {
146
+ const welcomeLines = [];
147
+ const colorTag = theme === "dark" ? "green-fg" : "black-fg";
148
+ // Old-school pattern in top bar
149
+ const pattern = "═" + "─".repeat(78) + "═";
150
+ welcomeLines.push(`{${colorTag}}${pattern}{/${colorTag}}`);
151
+ // Single cat ASCII art
152
+ welcomeLines.push(`{${colorTag}} /\\_/\\{/${colorTag}}`);
153
+ welcomeLines.push(`{${colorTag}} ( ^.^ ){/${colorTag}}`);
154
+ welcomeLines.push(`{${colorTag}} > ^ <{/${colorTag}}`);
155
+ welcomeLines.push(`{${colorTag}} / \\{/${colorTag}}`);
156
+ welcomeLines.push("");
157
+ // Get account info
158
+ let accountInfo = null;
159
+ try {
160
+ const accounts = config.getAllAccounts();
161
+ const activeIndex = config.getActiveAccountIndex();
162
+ const account = accounts.find((acc) => acc.index === activeIndex);
163
+ if (account) {
164
+ // Get balance info
165
+ try {
166
+ const privateKey = config.getAccountPrivateKey(activeIndex);
167
+ const balance = await checkBalance(privateKey, true); // silent mode
168
+ accountInfo = {
169
+ account,
170
+ balance,
171
+ };
172
+ }
173
+ catch (error) {
174
+ // If balance check fails, just show account info without balance
175
+ accountInfo = { account };
176
+ }
177
+ }
178
+ }
179
+ catch (error) {
180
+ // If account info fails, continue without it
181
+ }
182
+ // Playful cat-like greetings with account info
183
+ const greetings = [
184
+ `{green-fg}Meow! Welcome to httpcat!{/green-fg}`,
185
+ `{green-fg}*purrs* Ready to play with some tokens?{/green-fg}`,
186
+ `{green-fg}Connected to: {cyan-fg}${network}{/cyan-fg}{/green-fg}`,
187
+ ];
188
+ // Add account information
189
+ if (accountInfo) {
190
+ const { account, balance } = accountInfo;
191
+ const accountType = account.type === "custom" ? "Custom" : "Seed-Derived";
192
+ const accountLabel = account.label ? ` (${account.label})` : "";
193
+ greetings.push("");
194
+ greetings.push(`{cyan-fg}Account #{green-fg}${account.index}{/green-fg} | {green-fg}${accountType}${accountLabel}{/green-fg}{/cyan-fg}`);
195
+ if (balance) {
196
+ const ethDisplay = balance.ethFormatted || balance.ethBalance || "0 ETH";
197
+ const usdcDisplay = balance.usdcFormatted || balance.usdcBalance || "$0.00";
198
+ greetings.push(`{cyan-fg}Balance: {yellow-fg}${ethDisplay}{/yellow-fg} | {green-fg}${usdcDisplay}{/green-fg}{/cyan-fg}`);
199
+ }
200
+ }
201
+ greetings.push("");
202
+ greetings.push(`{yellow-fg}Pssst... type {bold}help{/bold} if you want to see what I can do!{/yellow-fg}`);
203
+ greetings.push(`{yellow-fg}Or just start playing - I'm curious like a cat!{/yellow-fg}`);
204
+ welcomeLines.push(...greetings);
205
+ return welcomeLines.join("\n");
206
+ };
207
+ // Set initial header content
208
+ buildWelcomeContent(currentTheme).then((content) => {
209
+ headerBox.setContent(content);
210
+ screen.render();
42
211
  });
43
- // Build welcome content
44
- const welcomeLines = [];
45
- welcomeLines.push(" /\\_/\\");
46
- welcomeLines.push(" ( ^.^ )");
47
- welcomeLines.push(" > ^ <");
48
- welcomeLines.push("");
49
- welcomeLines.push("Welcome to httpcat!");
50
- welcomeLines.push(`Connected to: ${network}`);
51
- welcomeLines.push('Type "help" for available commands or "exit" to quit');
52
- headerBox.setContent(welcomeLines.join("\n"));
53
- // Create output log box (scrollable)
212
+ // Create output log box (scrollable) with thick borders, transparent background
54
213
  const outputBox = blessed.log({
55
- top: 8,
214
+ top: 16,
56
215
  left: 0,
57
216
  width: "100%",
58
- height: "100%-11",
217
+ bottom: 4, // Leave space for input box at bottom
59
218
  tags: true,
60
219
  scrollable: true,
61
220
  alwaysScroll: true,
62
221
  scrollbar: {
63
222
  ch: " ",
64
- inverse: true,
223
+ inverse: currentTheme !== "dark",
65
224
  },
66
225
  style: {
67
- fg: "white",
68
- bg: "black",
226
+ fg: themeColors.fg,
227
+ bg: "default", // Transparent
228
+ border: {
229
+ fg: themeColors.border,
230
+ bold: true,
231
+ },
69
232
  },
70
233
  padding: {
71
- left: 1,
234
+ left: 0,
72
235
  right: 1,
73
236
  },
237
+ mouse: true, // Enable mouse scrolling
238
+ border: {
239
+ type: "line",
240
+ fg: themeColors.border,
241
+ ch: "═", // Double line for thicker border
242
+ },
243
+ });
244
+ // Create prompt label with bold font (appears larger) - positioned inside input box
245
+ const promptLabel = blessed.text({
246
+ bottom: 1,
247
+ left: 2,
248
+ width: 8, // Exactly "httpcat>" (8 characters)
249
+ height: 1,
250
+ content: "",
251
+ tags: true,
252
+ style: {
253
+ fg: themeColors.fg,
254
+ bg: "default", // Transparent
255
+ bold: true,
256
+ },
74
257
  });
75
- // Create input box at bottom
258
+ // Helper to update prompt label content
259
+ const updatePromptLabel = (theme) => {
260
+ const colorTag = theme === "dark" ? "green-fg" : "black-fg";
261
+ promptLabel.content = `{${colorTag}}{bold}httpcat>{/bold}{/${colorTag}}`;
262
+ };
263
+ updatePromptLabel(currentTheme);
264
+ // Create input box with visible cursor and stylish border
76
265
  const inputBox = blessed.textbox({
77
266
  bottom: 0,
78
267
  left: 0,
@@ -80,35 +269,82 @@ export async function startInteractiveShell(client) {
80
269
  height: 3,
81
270
  inputOnFocus: true,
82
271
  keys: true,
83
- style: {
84
- fg: "cyan",
85
- bg: "black",
86
- focus: {
87
- fg: "white",
88
- bg: "blue",
89
- },
90
- },
272
+ secret: false,
273
+ tags: true,
274
+ alwaysScroll: false,
275
+ scrollable: false,
91
276
  padding: {
92
- left: 1,
277
+ left: 10, // Space for "httpcat>" prompt (8 chars) + 2 for spacing
93
278
  right: 1,
279
+ top: 0,
280
+ bottom: 0,
281
+ },
282
+ cursor: {
283
+ artificial: true,
284
+ shape: "block", // Block cursor is more visible than line
285
+ blink: true,
286
+ color: currentTheme === "dark" ? "green" : "black",
94
287
  },
95
- });
96
- // Create a prompt label
97
- const promptLabel = blessed.text({
98
- bottom: 1,
99
- left: 1,
100
- content: "httpcat> ",
101
288
  style: {
102
- fg: "cyan",
103
- bg: "black",
289
+ fg: themeColors.fg,
290
+ bg: "default",
291
+ border: {
292
+ fg: themeColors.border,
293
+ bold: true,
294
+ },
295
+ focus: {
296
+ fg: themeColors.fg,
297
+ bg: "default",
298
+ border: {
299
+ fg: themeColors.border,
300
+ bold: true,
301
+ },
302
+ },
303
+ },
304
+ border: {
305
+ type: "line",
306
+ fg: themeColors.border,
307
+ ch: "─", // Single line border
104
308
  },
105
309
  });
106
- // Append all widgets
107
- screen.append(headerBox);
108
- screen.append(outputBox);
109
- screen.append(promptLabel);
110
- screen.append(inputBox);
111
- // Helper to log output
310
+ // Helper to update theme
311
+ const updateTheme = (newTheme) => {
312
+ currentTheme = newTheme;
313
+ themeColors = getThemeColors(currentTheme);
314
+ // Update screen cursor color
315
+ screen.cursor.color = currentTheme === "dark" ? "green" : "black";
316
+ // Update header content with new theme colors
317
+ buildWelcomeContent(currentTheme).then((content) => {
318
+ headerBox.setContent(content);
319
+ screen.render();
320
+ });
321
+ // Update all widget styles
322
+ headerBox.style.fg = themeColors.fg;
323
+ headerBox.style.bg = "default"; // Transparent
324
+ headerBox.border = { type: "line", fg: themeColors.border, ch: "═" };
325
+ outputBox.style.fg = themeColors.fg;
326
+ outputBox.style.bg = "default"; // Transparent
327
+ outputBox.scrollbar.inverse = currentTheme !== "dark";
328
+ outputBox.border = { type: "line", fg: themeColors.border, ch: "═" };
329
+ updatePromptLabel(currentTheme);
330
+ promptLabel.style.fg = themeColors.fg;
331
+ promptLabel.style.bg = "default"; // Transparent
332
+ promptLabel.style.bold = true;
333
+ // Update input box cursor, style, and border
334
+ inputBox.style.fg = themeColors.fg;
335
+ inputBox.style.bg = "default";
336
+ inputBox.style.focus.fg = themeColors.fg;
337
+ inputBox.style.focus.bg = "default";
338
+ inputBox.style.border.fg = themeColors.border;
339
+ inputBox.style.focus.border.fg = themeColors.border;
340
+ inputBox.border = { type: "line", fg: themeColors.border, ch: "─" };
341
+ if (inputBox.cursor) {
342
+ inputBox.cursor.color = currentTheme === "dark" ? "green" : "black";
343
+ }
344
+ screen.cursor.color = currentTheme === "dark" ? "green" : "black";
345
+ screen.render();
346
+ };
347
+ // Helper to log output (define before use)
112
348
  const log = (text) => {
113
349
  outputBox.log(text);
114
350
  outputBox.setScrollPerc(100);
@@ -120,19 +356,41 @@ export async function startInteractiveShell(client) {
120
356
  outputBox.setScrollPerc(100);
121
357
  screen.render();
122
358
  };
359
+ // Wrap toggleTheme to also log
360
+ const toggleThemeWithLog = () => {
361
+ const themes = ["win95", "dark", "light"];
362
+ const currentIndex = themes.indexOf(currentTheme);
363
+ const nextTheme = themes[(currentIndex + 1) % themes.length];
364
+ updateTheme(nextTheme);
365
+ log(chalk.blue(`Theme switched to: ${nextTheme}`));
366
+ };
367
+ // Append all widgets in correct z-order (last appended is on top)
368
+ screen.append(headerBox);
369
+ screen.append(outputBox);
370
+ screen.append(inputBox);
371
+ screen.append(promptLabel); // Prompt label on top so it's always visible
372
+ // Handle F1 for theme toggle
373
+ screen.key(["f1"], () => {
374
+ toggleThemeWithLog();
375
+ });
376
+ // Store toggle function for command handler
377
+ screen.toggleTheme = toggleThemeWithLog;
378
+ screen.updateTheme = updateTheme;
123
379
  // Handle input submission
124
380
  inputBox.on("submit", async (value) => {
125
381
  const trimmed = value.trim();
126
382
  inputBox.clearValue();
383
+ screen.render(); // Clear input immediately
127
384
  if (!trimmed) {
128
385
  inputBox.focus();
129
386
  screen.render();
130
387
  return;
131
388
  }
132
- log(`httpcat> ${trimmed}`);
389
+ // Log the command with prompt
390
+ log(`{green-fg}httpcat>{/green-fg} ${trimmed}`);
133
391
  const [command, ...args] = trimmed.split(/\s+/);
134
392
  try {
135
- await handleCommand(client, command.toLowerCase(), args, log, logLines, screen, inputBox);
393
+ await handleCommand(client, command.toLowerCase(), args, log, logLines, screen, inputBox, currentTheme, buildWelcomeContent, headerBox);
136
394
  }
137
395
  catch (error) {
138
396
  log(chalk.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
@@ -153,11 +411,20 @@ export async function startInteractiveShell(client) {
153
411
  inputBox.clearValue();
154
412
  screen.render();
155
413
  });
414
+ // Handle Ctrl+C to quit (also works from input box)
415
+ inputBox.key(["C-c"], () => {
416
+ screen.destroy();
417
+ printCat("sleeping");
418
+ console.log(chalk.cyan("Goodbye! 👋"));
419
+ process.exit(0);
420
+ });
156
421
  // Focus input and render
157
422
  inputBox.focus();
158
423
  screen.render();
424
+ // Show available commands on load
425
+ displayHelp(log, logLines);
159
426
  }
160
- async function handleCommand(client, command, args, log, logLines, screen, inputBox) {
427
+ async function handleCommand(client, command, args, log, logLines, screen, inputBox, currentTheme, buildWelcomeContent, headerBox) {
161
428
  switch (command) {
162
429
  case "help":
163
430
  displayHelp(log, logLines);
@@ -171,7 +438,7 @@ async function handleCommand(client, command, args, log, logLines, screen, input
171
438
  const photoUrl = extractFlag(args, "--photo");
172
439
  const bannerUrl = extractFlag(args, "--banner");
173
440
  const websiteUrl = extractFlag(args, "--website");
174
- log(chalk.dim("Creating token..."));
441
+ log(chalk.blue("Creating token..."));
175
442
  screen.render();
176
443
  const result = await createToken(client, {
177
444
  name,
@@ -185,14 +452,21 @@ async function handleCommand(client, command, args, log, logLines, screen, input
185
452
  }
186
453
  case "buy": {
187
454
  if (args.length < 2) {
188
- log(chalk.red("Usage: buy <address|name|symbol> <amount>"));
455
+ log(chalk.red("Usage: buy <address|name|symbol> <amount> [--repeat <count>] [--delay <ms>]"));
189
456
  log(chalk.dim(" amount: 0.05, 0.10, or 0.20 (test mode) or 50, 100, 200 (production)"));
190
- log(chalk.dim(' Examples: buy 0x1234... 0.20, buy "My Token" 0.10, buy MTK 0.05'));
457
+ log(chalk.dim(' Examples: buy 0x1234... 0.20, buy "My Token" 0.10, buy MTK 0.05 --repeat 10'));
191
458
  return;
192
459
  }
193
460
  const [identifier, amountInput] = args;
194
461
  const isTestMode = client.getNetwork().includes("sepolia");
195
462
  const validAmounts = isTestMode ? TEST_AMOUNTS : PROD_AMOUNTS;
463
+ // Parse flags
464
+ const repeatCount = extractFlag(args, "--repeat")
465
+ ? parseInt(extractFlag(args, "--repeat") || "1", 10)
466
+ : undefined;
467
+ const delayMs = extractFlag(args, "--delay")
468
+ ? parseInt(extractFlag(args, "--delay") || "0", 10)
469
+ : 0;
196
470
  // Try to validate and normalize the amount
197
471
  let amount;
198
472
  try {
@@ -204,10 +478,72 @@ async function handleCommand(client, command, args, log, logLines, screen, input
204
478
  log(chalk.dim(`Valid amounts: ${validAmounts.join(", ")}`));
205
479
  return;
206
480
  }
207
- log(chalk.dim("Buying tokens..."));
208
- screen.render();
209
- const result = await buyToken(client, identifier, amount, isTestMode, false);
210
- displayBuyResultToLog(result, log, logLines);
481
+ const privateKey = config.getPrivateKey();
482
+ // Handle repeat mode
483
+ if (repeatCount && repeatCount > 0) {
484
+ const results = [];
485
+ let totalSpent = 0;
486
+ let stoppedEarly = false;
487
+ let stopReason = "";
488
+ for (let i = 1; i <= repeatCount; i++) {
489
+ try {
490
+ log(chalk.blue(`Buy ${i}/${repeatCount}...`));
491
+ screen.render();
492
+ const result = await buyToken(client, identifier, amount, isTestMode, i > 1, // Silent after first buy
493
+ privateKey);
494
+ results.push(result);
495
+ totalSpent += parseFloat(result.amountSpent);
496
+ // Display compact result
497
+ displayBuyResultToLog(result, log, logLines);
498
+ // Check if token graduated
499
+ if (result.graduationReached) {
500
+ stoppedEarly = true;
501
+ stopReason = "Token graduated";
502
+ log(chalk.green("🎓 Token has graduated! Stopping buy loop."));
503
+ break;
504
+ }
505
+ // Apply delay between iterations (except after the last one)
506
+ if (i < repeatCount && delayMs > 0) {
507
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
508
+ }
509
+ }
510
+ catch (error) {
511
+ // Handle insufficient funds (402) gracefully
512
+ if (error instanceof HttpcatError && error.status === 402) {
513
+ stoppedEarly = true;
514
+ stopReason = "Insufficient funds";
515
+ log(chalk.yellow("💡 Insufficient funds. Stopping buy loop."));
516
+ break;
517
+ }
518
+ // For other errors, re-throw to be handled by outer catch
519
+ throw error;
520
+ }
521
+ }
522
+ // Show final summary
523
+ if (results.length > 0) {
524
+ const lastResult = results[results.length - 1];
525
+ const graduationStatus = lastResult.graduationReached
526
+ ? "✅ GRADUATED!"
527
+ : `${(lastResult.graduationProgress || 0).toFixed(2)}%`;
528
+ log("");
529
+ log(chalk.cyan.bold("📊 Repeat Buy Summary"));
530
+ log(chalk.dim("─".repeat(50)));
531
+ log(`Total buys completed: ${chalk.bold(results.length.toString())}${repeatCount ? `/${repeatCount}` : ""}`);
532
+ log(`Total amount spent: ${chalk.bold(`$${totalSpent.toFixed(2)}`)}`);
533
+ log(`Final token price: ${chalk.bold(lastResult.newPrice)}`);
534
+ log(`Final graduation: ${chalk.bold(graduationStatus)}`);
535
+ if (stoppedEarly) {
536
+ log(`Stopped early: ${chalk.yellow(stopReason)}`);
537
+ }
538
+ }
539
+ }
540
+ else {
541
+ // Normal single buy execution
542
+ log(chalk.blue("Buying tokens..."));
543
+ screen.render();
544
+ const result = await buyToken(client, identifier, amount, isTestMode, false, privateKey);
545
+ displayBuyResultToLog(result, log, logLines);
546
+ }
211
547
  break;
212
548
  }
213
549
  case "sell": {
@@ -217,7 +553,7 @@ async function handleCommand(client, command, args, log, logLines, screen, input
217
553
  return;
218
554
  }
219
555
  const [identifier, amountInput] = args;
220
- log(chalk.dim("Checking token info..."));
556
+ log(chalk.blue("Checking token info..."));
221
557
  screen.render();
222
558
  const info = await getTokenInfo(client, identifier);
223
559
  if (!info.userPosition || info.userPosition.tokensOwned === "0") {
@@ -225,7 +561,7 @@ async function handleCommand(client, command, args, log, logLines, screen, input
225
561
  return;
226
562
  }
227
563
  const tokenAmount = parseTokenAmount(amountInput, info.userPosition.tokensOwned);
228
- log(chalk.dim("Selling tokens..."));
564
+ log(chalk.blue("Selling tokens..."));
229
565
  screen.render();
230
566
  const result = await sellToken(client, identifier, tokenAmount, false);
231
567
  displaySellResultToLog(result, log, logLines);
@@ -242,7 +578,7 @@ async function handleCommand(client, command, args, log, logLines, screen, input
242
578
  const privateKey = config.getPrivateKey();
243
579
  const account = privateKeyToAccount(privateKey);
244
580
  const userAddress = account.address;
245
- log(chalk.dim("Fetching token info..."));
581
+ log(chalk.blue("Fetching token info..."));
246
582
  screen.render();
247
583
  const info = await getTokenInfo(client, identifier, userAddress);
248
584
  displayTokenInfoToLog(info, log, logLines);
@@ -252,7 +588,7 @@ async function handleCommand(client, command, args, log, logLines, screen, input
252
588
  const page = parseInt(extractFlag(args, "--page") || "1");
253
589
  const limit = parseInt(extractFlag(args, "--limit") || "20");
254
590
  const sortBy = (extractFlag(args, "--sort") || "mcap");
255
- log(chalk.dim("Fetching token list..."));
591
+ log(chalk.blue("Fetching token list..."));
256
592
  screen.render();
257
593
  const result = await listTokens(client, page, limit, sortBy);
258
594
  displayTokenListToLog(result, log, logLines);
@@ -262,10 +598,24 @@ async function handleCommand(client, command, args, log, logLines, screen, input
262
598
  const privateKey = config.getPrivateKey();
263
599
  const account = privateKeyToAccount(privateKey);
264
600
  const userAddress = account.address;
265
- log(chalk.dim("Fetching positions..."));
601
+ // Parse flags
602
+ const activeOnly = args.includes("--active");
603
+ const graduatedOnly = args.includes("--graduated");
604
+ log(chalk.blue("Fetching positions..."));
266
605
  screen.render();
267
606
  const result = await getPositions(client, userAddress);
268
- displayPositionsToLog(result, log, logLines);
607
+ // Filter positions if flags are set
608
+ let filteredResult = result;
609
+ if (activeOnly || graduatedOnly) {
610
+ filteredResult = {
611
+ ...result,
612
+ positions: result.positions.filter((p) => activeOnly
613
+ ? p.token.status !== "graduated"
614
+ : p.token.status === "graduated"),
615
+ };
616
+ filteredResult.total = filteredResult.positions.length;
617
+ }
618
+ displayPositionsToLog(filteredResult, log, logLines);
269
619
  break;
270
620
  }
271
621
  case "health": {
@@ -278,15 +628,15 @@ async function handleCommand(client, command, args, log, logLines, screen, input
278
628
  case "balances": {
279
629
  try {
280
630
  const privateKey = config.getPrivateKey();
281
- log(chalk.dim("Checking balance..."));
631
+ log(chalk.blue("Checking balance..."));
282
632
  screen.render();
283
- const balance = await checkBalance(privateKey);
633
+ const balance = await checkBalance(privateKey, true); // silent mode
284
634
  displayBalanceToLog(balance, log, logLines);
285
635
  }
286
636
  catch (error) {
287
- log(chalk.dim("Checking balance..."));
637
+ log(chalk.blue("Checking balance..."));
288
638
  screen.render();
289
- const balance = await checkBalance();
639
+ const balance = await checkBalance(undefined, true); // silent mode
290
640
  displayBalanceToLog(balance, log, logLines);
291
641
  }
292
642
  break;
@@ -309,16 +659,18 @@ async function handleCommand(client, command, args, log, logLines, screen, input
309
659
  break;
310
660
  }
311
661
  case "network": {
312
- log(chalk.yellow("Current network: ") + chalk.cyan(config.get("network")));
313
- log(chalk.dim("Note: Only base-sepolia (testnet) is currently supported"));
662
+ log(chalk.yellow("Current network: ") + chalk.green(config.get("network")));
663
+ log(chalk.blue("Note: Only base-sepolia (testnet) is currently supported"));
314
664
  break;
315
665
  }
316
666
  case "chat": {
317
667
  // For chat, we need to exit the blessed shell and start chat stream
318
668
  log(chalk.yellow("Starting chat mode..."));
319
669
  screen.destroy();
320
- const tokenIdentifier = args.length > 0 ? args[0] : undefined;
321
- await startChatStream(client, false, tokenIdentifier);
670
+ const tokenIdentifier = args.length > 0 && !args[0].startsWith("--") ? args[0] : undefined;
671
+ const inputFormatRaw = extractFlag(args, "--input-format") || "text";
672
+ const inputFormat = (inputFormatRaw === "stream-json" ? "stream-json" : "text");
673
+ await startChatStream(client, false, tokenIdentifier, inputFormat);
322
674
  process.exit(0);
323
675
  break;
324
676
  }
@@ -333,7 +685,228 @@ async function handleCommand(client, command, args, log, logLines, screen, input
333
685
  // Clear the output box
334
686
  screen.children[1].setContent("");
335
687
  screen.render();
688
+ // Show playful message with ASCII cat
689
+ const catArt = currentTheme === "dark"
690
+ ? `{green-fg} /\\_/\\{/green-fg}
691
+ {green-fg} ( ^.^ ){/green-fg}
692
+ {green-fg} > ^ <{/green-fg}`
693
+ : `{black-fg} /\\_/\\{/black-fg}
694
+ {black-fg} ( ^.^ ){/black-fg}
695
+ {black-fg} > ^ <{/black-fg}`;
696
+ log("");
697
+ log(catArt);
698
+ log(chalk.green.bold("Terminal cleared!"));
699
+ log("");
336
700
  break;
701
+ case "claim": {
702
+ if (args.length < 1) {
703
+ log(chalk.red("Usage: claim <address|name|symbol> [--execute] [--address <address>]"));
704
+ log(chalk.dim(' Examples: claim MTK, claim "My Token" --execute'));
705
+ return;
706
+ }
707
+ const [identifier] = args;
708
+ const execute = args.includes("--execute");
709
+ const callerAddress = extractFlag(args, "--address");
710
+ if (execute) {
711
+ const privateKey = config.getPrivateKey();
712
+ const account = privateKeyToAccount(privateKey);
713
+ const address = callerAddress || account.address;
714
+ log(chalk.blue("Claiming fees..."));
715
+ screen.render();
716
+ const result = await claimFees(client, identifier, address, false);
717
+ displayClaimResultToLog(result, log, logLines);
718
+ }
719
+ else {
720
+ log(chalk.blue("Fetching fee information..."));
721
+ screen.render();
722
+ const result = await viewFees(client, identifier, false);
723
+ displayFeesToLog(result, log, logLines);
724
+ }
725
+ break;
726
+ }
727
+ case "transactions": {
728
+ const privateKey = config.getPrivateKey();
729
+ const account = privateKeyToAccount(privateKey);
730
+ const userAddress = account.address;
731
+ const input = {};
732
+ if (extractFlag(args, "--user")) {
733
+ input.userAddress = extractFlag(args, "--user");
734
+ }
735
+ else {
736
+ input.userAddress = userAddress;
737
+ }
738
+ if (extractFlag(args, "--token")) {
739
+ input.tokenId = extractFlag(args, "--token");
740
+ }
741
+ if (extractFlag(args, "--type")) {
742
+ const type = extractFlag(args, "--type");
743
+ if (!["buy", "sell", "airdrop"].includes(type || "")) {
744
+ log(chalk.red("Invalid type. Must be: buy, sell, or airdrop"));
745
+ return;
746
+ }
747
+ input.type = type;
748
+ }
749
+ if (extractFlag(args, "--limit")) {
750
+ input.limit = parseInt(extractFlag(args, "--limit") || "50", 10);
751
+ }
752
+ else {
753
+ input.limit = 50;
754
+ }
755
+ if (extractFlag(args, "--offset")) {
756
+ input.offset = parseInt(extractFlag(args, "--offset") || "0", 10);
757
+ }
758
+ else {
759
+ input.offset = 0;
760
+ }
761
+ log(chalk.blue("Fetching transactions..."));
762
+ screen.render();
763
+ const result = await getTransactions(client, input);
764
+ displayTransactionsToLog(result, log, logLines);
765
+ break;
766
+ }
767
+ case "account": {
768
+ if (args.length === 0) {
769
+ // Show account info
770
+ log(chalk.blue("Fetching account information..."));
771
+ screen.render();
772
+ const result = await getAccountInfo();
773
+ displayAccountInfoToLog(result, log, logLines);
774
+ }
775
+ else if (args[0] === "list") {
776
+ // For list, we need to capture console output or recreate the display
777
+ const accounts = config.getAllAccounts();
778
+ const activeIndex = config.getActiveAccountIndex();
779
+ log("");
780
+ log(chalk.green.bold("📋 All Accounts"));
781
+ log("");
782
+ if (accounts.length === 0) {
783
+ log(chalk.yellow("No accounts configured."));
784
+ log(chalk.blue('Run "httpcat config" to set up your wallet.'));
785
+ return;
786
+ }
787
+ for (const account of accounts) {
788
+ const isActive = account.index === activeIndex;
789
+ const status = isActive ? chalk.green("● Active") : chalk.blue("○ Inactive");
790
+ const type = account.type === "custom" ? "Custom" : "Seed-Derived";
791
+ const address = account.address.slice(0, 6) + "..." + account.address.slice(-4);
792
+ log(` ${chalk.green(account.index.toString().padEnd(5))} ${chalk.blue(type.padEnd(12))} ${chalk.green(address.padEnd(15))} ${status}`);
793
+ }
794
+ log("");
795
+ log(chalk.blue(`Active account: ${chalk.green(activeIndex.toString())}`));
796
+ }
797
+ else if (args[0] === "switch") {
798
+ if (args.length < 2) {
799
+ log(chalk.red("Usage: account switch <index>"));
800
+ return;
801
+ }
802
+ const index = parseInt(args[1], 10);
803
+ if (isNaN(index)) {
804
+ log(chalk.red("Invalid account index"));
805
+ return;
806
+ }
807
+ switchAccount(index);
808
+ const accounts = config.getAllAccounts();
809
+ const account = accounts.find((acc) => acc.index === index);
810
+ log(chalk.green(`✅ Switched to account ${index}`));
811
+ if (account) {
812
+ log(chalk.blue(` Address: ${account.address.slice(0, 6)}...${account.address.slice(-4)}`));
813
+ }
814
+ // Refresh header with new account info
815
+ buildWelcomeContent(currentTheme).then((content) => {
816
+ headerBox.setContent(content);
817
+ screen.render();
818
+ });
819
+ }
820
+ else if (args[0] === "add") {
821
+ log(chalk.blue("Adding new account..."));
822
+ screen.render();
823
+ await addAccount();
824
+ log(chalk.green("✅ Account added"));
825
+ // Refresh header with new account info
826
+ buildWelcomeContent(currentTheme).then((content) => {
827
+ headerBox.setContent(content);
828
+ screen.render();
829
+ });
830
+ }
831
+ else {
832
+ log(chalk.red("Usage: account [list|switch <index>|add]"));
833
+ }
834
+ break;
835
+ }
836
+ case "env": {
837
+ if (args.length === 0 || args[0] === "show") {
838
+ const current = config.getCurrentEnvironment();
839
+ if (!current) {
840
+ log(chalk.yellow("No environment selected"));
841
+ return;
842
+ }
843
+ const env = config.getEnvironmentConfig(current);
844
+ if (!env) {
845
+ log(chalk.yellow(`Environment "${current}" not found`));
846
+ return;
847
+ }
848
+ log(chalk.cyan.bold(`Current Environment: ${chalk.green(current)}`));
849
+ log(chalk.dim(`Agent URL: ${env.agentUrl}`));
850
+ log(chalk.dim(`Network: ${env.network}`));
851
+ }
852
+ else if (args[0] === "list") {
853
+ const envs = config.getEnvironments();
854
+ const current = config.getCurrentEnvironment();
855
+ log(chalk.cyan.bold("Available Environments:"));
856
+ log("");
857
+ for (const [name, env] of Object.entries(envs)) {
858
+ const isCurrent = name === current;
859
+ const prefix = isCurrent ? chalk.green("→ ") : " ";
860
+ const nameDisplay = isCurrent ? chalk.green.bold(name) : chalk.bold(name);
861
+ log(`${prefix}${nameDisplay}`);
862
+ log(chalk.dim(` Agent URL: ${env.agentUrl}`));
863
+ log(chalk.dim(` Network: ${env.network}`));
864
+ log("");
865
+ }
866
+ if (current) {
867
+ log(chalk.dim(`Current environment: ${chalk.green(current)}`));
868
+ }
869
+ }
870
+ else if (args[0] === "use") {
871
+ if (args.length < 2) {
872
+ log(chalk.red("Usage: env use <name>"));
873
+ return;
874
+ }
875
+ config.setEnvironment(args[1]);
876
+ const env = config.getEnvironmentConfig(args[1]);
877
+ log(chalk.green(`✅ Switched to environment: ${args[1]}`));
878
+ if (env) {
879
+ log(chalk.dim(` Agent URL: ${env.agentUrl}`));
880
+ log(chalk.dim(` Network: ${env.network}`));
881
+ }
882
+ }
883
+ else if (args[0] === "add") {
884
+ if (args.length < 3) {
885
+ log(chalk.red("Usage: env add <name> <agentUrl> [--network <network>]"));
886
+ return;
887
+ }
888
+ const network = extractFlag(args, "--network") || "base-sepolia";
889
+ config.addEnvironment(args[1], args[2], network);
890
+ log(chalk.green(`✅ Added environment: ${args[1]}`));
891
+ log(chalk.dim(` Agent URL: ${args[2]}`));
892
+ log(chalk.dim(` Network: ${network}`));
893
+ }
894
+ else if (args[0] === "update") {
895
+ if (args.length < 3) {
896
+ log(chalk.red("Usage: env update <name> <agentUrl> [--network <network>]"));
897
+ return;
898
+ }
899
+ const network = extractFlag(args, "--network") || "base-sepolia";
900
+ config.updateEnvironment(args[1], args[2], network);
901
+ log(chalk.green(`✅ Updated environment: ${args[1]}`));
902
+ log(chalk.dim(` Agent URL: ${args[2]}`));
903
+ log(chalk.dim(` Network: ${network}`));
904
+ }
905
+ else {
906
+ log(chalk.red("Usage: env [list|use <name>|show|add <name> <url>|update <name> <url>]"));
907
+ }
908
+ break;
909
+ }
337
910
  default:
338
911
  log(chalk.red(`Unknown command: ${command}`));
339
912
  log(chalk.dim('Type "help" for available commands'));
@@ -341,107 +914,135 @@ async function handleCommand(client, command, args, log, logLines, screen, input
341
914
  }
342
915
  function displayHelp(log, logLines) {
343
916
  log("");
344
- log(chalk.cyan.bold("Available Commands:"));
917
+ log(chalk.green.bold("Available Commands:"));
345
918
  log("");
346
- const commands = [
347
- ["create <name> <symbol>", "Create a new token"],
348
- ["buy <address|name|symbol> <amount>", "Buy tokens ($0.05, $0.10, or $0.20)"],
349
- ["sell <address|name|symbol> <amount|all>", "Sell tokens"],
350
- ["info <address|name|symbol>", "Get token information"],
351
- ["list [--sort mcap|created]", "List all tokens"],
352
- ["positions", "Get all your positions with comprehensive info"],
353
- ["balances", "Check wallet balances (ETH and USDC)"],
354
- [
355
- "chat [token]",
356
- "Start streaming chat (optional: token symbol/name/address)",
357
- ],
358
- ["health", "Check agent health"],
359
- ["config [--show|--set|--reset]", "Manage configuration"],
360
- ["network", "Show current network"],
361
- ["clear", "Clear the output"],
362
- ["help", "Show this help message"],
363
- ["exit", "Exit the shell"],
364
- ];
365
- for (const [cmd, desc] of commands) {
366
- log(` ${chalk.cyan(cmd.padEnd(30))} ${chalk.dim(desc)}`);
367
- }
919
+ log(chalk.green(" create <name> <symbol>"));
920
+ log(chalk.green(" buy <id> <amount> [--repeat N] [--delay MS]"));
921
+ log(chalk.green(" sell <id> <amount|all>"));
922
+ log(chalk.green(" info <id>"));
923
+ log(chalk.green(" list [--sort mcap|created|name] [--page N] [--limit N]"));
924
+ log(chalk.green(" positions [--active|--graduated]"));
925
+ log(chalk.green(" balances"));
926
+ log(chalk.green(" claim <id> [--execute] [--address ADDR]"));
927
+ log(chalk.green(" transactions [--user ADDR] [--token ID] [--type TYPE] [--limit N] [--offset N]"));
928
+ log(chalk.green(" account [list|switch <index>|add]"));
929
+ log(chalk.green(" env [list|use <name>|show|add|update]"));
930
+ log(chalk.green(" chat [token] [--input-format FORMAT]"));
931
+ log(chalk.green(" health"));
932
+ log(chalk.green(" config [--show|--set|--reset]"));
933
+ log(chalk.green(" network"));
934
+ log(chalk.green(" clear"));
935
+ log(chalk.green(" help"));
936
+ log(chalk.green(" exit"));
368
937
  log("");
369
- log(chalk.dim("Examples:"));
370
- log(chalk.dim(' create "My Token" "MTK"'));
371
- log(chalk.dim(" buy 0x1234... 5"));
372
- log(chalk.dim(' buy "My Token" 2'));
373
- log(chalk.dim(" buy MTK 1"));
374
- log(chalk.dim(" sell 0x1234... 50%"));
375
- log(chalk.dim(' info "My Token"'));
376
- log(chalk.dim(" list --sort mcap --limit 10"));
377
- log(chalk.dim(" positions"));
938
+ log(chalk.blue("Tip: Run any command without arguments to see detailed help"));
378
939
  }
379
940
  function displayConfigToLog(log, logLines) {
380
941
  const cfg = config.getAll();
381
942
  log("");
382
- log(chalk.cyan.bold("Current Configuration:"));
943
+ log(chalk.green.bold("Current Configuration:"));
383
944
  log("");
384
- log(chalk.dim("Network: ") + chalk.cyan(cfg.network));
385
- log(chalk.dim("Agent URL: ") + chalk.cyan(cfg.agentUrl));
386
- log(chalk.dim("Facilitator: ") + chalk.cyan(cfg.facilitatorUrl));
387
- log(chalk.dim("Max Payment: ") + chalk.cyan("$" + cfg.defaultMaxPayment));
388
- log(chalk.dim("ASCII Art: ") +
389
- chalk.cyan(cfg.preferences.enableAsciiArt ? "enabled" : "disabled"));
390
- log(chalk.dim("Private Key: ") +
391
- chalk.dim(cfg.privateKey ? "configured" : "not configured"));
945
+ log(chalk.blue("Network: ") + chalk.green(cfg.network));
946
+ log(chalk.blue("Agent URL: ") + chalk.green(cfg.agentUrl));
947
+ log(chalk.blue("Facilitator: ") + chalk.green(cfg.facilitatorUrl));
948
+ log(chalk.blue("Max Payment: ") + chalk.green("$" + cfg.defaultMaxPayment));
949
+ log(chalk.blue("ASCII Art: ") +
950
+ chalk.green(cfg.preferences.enableAsciiArt ? "enabled" : "disabled"));
951
+ log(chalk.blue("Private Key: ") +
952
+ chalk.blue(cfg.privateKey ? "configured" : "not configured"));
392
953
  log("");
393
- log(chalk.dim(`Config file: ${config.getConfigPath()}`));
954
+ log(chalk.blue(`Config file: ${config.getConfigPath()}`));
394
955
  }
395
956
  // Simplified display functions that log to blessed output
396
957
  function displayCreateResultToLog(result, log, logLines) {
397
958
  log(chalk.green("Token created successfully!"));
398
- log(chalk.dim("Name: ") + result.name);
399
- log(chalk.dim("Symbol: ") + result.symbol);
959
+ log(chalk.blue("Name: ") + chalk.green(result.name));
960
+ log(chalk.blue("Symbol: ") + chalk.green(result.symbol));
400
961
  if (result.tokenAddress) {
401
- log(chalk.dim("Address: ") + chalk.cyan(result.tokenAddress));
962
+ log(chalk.blue("Address: ") + chalk.green(result.tokenAddress));
402
963
  }
403
964
  }
404
965
  function displayBuyResultToLog(result, log, logLines) {
405
966
  log(chalk.green("Purchase successful!"));
406
- log(chalk.dim("Tokens received: ") + chalk.cyan(result.tokensReceived));
407
- log(chalk.dim("Amount spent: ") + chalk.yellow(result.amountSpent));
408
- log(chalk.dim("New price: ") + chalk.cyan(result.newPrice));
409
- log(chalk.dim("Graduation: ") +
967
+ log(chalk.blue("Tokens received: ") + chalk.green(result.tokensReceived));
968
+ log(chalk.blue("Amount spent: ") + chalk.yellow(result.amountSpent));
969
+ log(chalk.blue("New price: ") + chalk.green(result.newPrice));
970
+ log(chalk.blue("Graduation: ") +
410
971
  chalk.magenta(result.graduationProgress.toFixed(2) + "%"));
411
972
  }
412
973
  function displaySellResultToLog(result, log, logLines) {
413
974
  log(chalk.green("Sale successful!"));
414
- log(chalk.dim("Tokens sold: ") + chalk.cyan(result.tokensSold));
415
- log(chalk.dim("USDC received: ") + chalk.yellow(result.usdcReceived));
416
- log(chalk.dim("New price: ") + chalk.cyan(result.newPrice));
417
- log(chalk.dim("Graduation: ") +
975
+ log(chalk.blue("Tokens sold: ") + chalk.green(result.tokensSold));
976
+ log(chalk.blue("USDC received: ") + chalk.yellow(result.usdcReceived));
977
+ log(chalk.blue("New price: ") + chalk.green(result.newPrice));
978
+ log(chalk.blue("Graduation: ") +
418
979
  chalk.magenta(result.graduationProgress.toFixed(2) + "%"));
419
980
  }
420
981
  function displayTokenInfoToLog(info, log, logLines) {
421
- log(chalk.cyan.bold(info.name) + ` (${info.symbol})`);
982
+ log(chalk.green.bold(info.name) + ` (${info.symbol})`);
422
983
  if (info.tokenAddress) {
423
- log(chalk.dim("Address: ") + info.tokenAddress);
984
+ log(chalk.blue("Address: ") + chalk.green(info.tokenAddress));
424
985
  }
425
- log(chalk.dim("Price: ") + chalk.cyan(info.currentPrice));
426
- log(chalk.dim("Market Cap: ") + chalk.yellow(info.marketCap));
427
- log(chalk.dim("Supply: ") + info.totalSupply);
986
+ log(chalk.blue("Price: ") + chalk.green(info.currentPrice));
987
+ log(chalk.blue("Market Cap: ") + chalk.yellow(info.marketCap));
988
+ log(chalk.blue("Supply: ") + chalk.green(info.totalSupply));
428
989
  if (info.graduationProgress !== undefined) {
429
- log(chalk.dim("Graduation: ") +
990
+ log(chalk.blue("Graduation: ") +
430
991
  chalk.magenta(info.graduationProgress.toFixed(2) + "%"));
431
992
  }
432
993
  if (info.userPosition && info.userPosition.tokensOwned !== "0") {
433
- log(chalk.dim("Your position: ") +
994
+ log(chalk.blue("Your position: ") +
434
995
  chalk.green(info.userPosition.tokensOwned + " tokens"));
435
996
  }
436
997
  }
437
998
  function displayTokenListToLog(result, log, logLines) {
438
- log(chalk.cyan.bold(`Tokens (${result.tokens.length} total):`));
439
999
  log("");
440
- for (const token of result.tokens.slice(0, 10)) {
441
- log(` ${chalk.cyan(token.symbol.padEnd(8))} ${token.name.padEnd(20)} ${chalk.yellow(token.marketCap || "N/A")}`);
1000
+ log(chalk.green.bold(`Tokens (${result.total || result.tokens.length} total):`));
1001
+ if (result.page && result.pages) {
1002
+ log(chalk.dim(`Page ${result.page} of ${result.pages}`));
442
1003
  }
443
- if (result.tokens.length > 10) {
444
- log(chalk.dim(` ... and ${result.tokens.length - 10} more`));
1004
+ log("");
1005
+ if (result.tokens.length === 0) {
1006
+ log(chalk.yellow("No tokens found."));
1007
+ return;
1008
+ }
1009
+ // Show header
1010
+ log(` ${chalk.cyan.bold("Name".padEnd(20))} ${chalk.cyan.bold("Symbol".padEnd(10))} ${chalk.cyan.bold("Market Cap".padEnd(15))} ${chalk.cyan.bold("Price".padEnd(12))} ${chalk.cyan.bold("Graduation".padEnd(12))} ${chalk.cyan.bold("Status")}`);
1011
+ log(chalk.dim(" " + "─".repeat(90)));
1012
+ // Show tokens
1013
+ for (const token of result.tokens) {
1014
+ const graduationIcon = token.status === "graduated"
1015
+ ? "[G]"
1016
+ : token.graduationProgress >= 90
1017
+ ? "[H]"
1018
+ : token.graduationProgress >= 50
1019
+ ? "[M]"
1020
+ : "[L]";
1021
+ const mcapDisplay = token.status === "graduated"
1022
+ ? chalk.dim("--")
1023
+ : formatCurrency(token.mcap || "0");
1024
+ const priceDisplay = token.status === "graduated"
1025
+ ? chalk.dim("--")
1026
+ : formatCurrency(token.price || "0");
1027
+ const graduationDisplay = `${graduationIcon} ${(token.graduationProgress || 0).toFixed(1)}%`;
1028
+ const statusDisplay = token.status === "graduated"
1029
+ ? chalk.green("✓ Graduated")
1030
+ : chalk.yellow("Active");
1031
+ log(` ${chalk.bold(token.name.padEnd(20))} ${chalk.green(token.symbol.padEnd(10))} ${mcapDisplay.padEnd(15)} ${priceDisplay.padEnd(12)} ${graduationDisplay.padEnd(12)} ${statusDisplay}`);
1032
+ }
1033
+ log("");
1034
+ if (result.page && result.pages && result.page < result.pages) {
1035
+ log(chalk.dim(`Use --page ${result.page + 1} to see more tokens`));
1036
+ log("");
1037
+ }
1038
+ // Show example commands
1039
+ if (result.tokens.length > 0) {
1040
+ const firstToken = result.tokens[0];
1041
+ log(chalk.dim("Example commands:"));
1042
+ log(chalk.dim(` buy ${firstToken.symbol}`));
1043
+ log(chalk.dim(` sell ${firstToken.symbol}`));
1044
+ log(chalk.dim(` info ${firstToken.symbol}`));
1045
+ log("");
445
1046
  }
446
1047
  }
447
1048
  function displayPositionsToLog(result, log, logLines) {
@@ -449,27 +1050,49 @@ function displayPositionsToLog(result, log, logLines) {
449
1050
  log(chalk.yellow("No positions found."));
450
1051
  return;
451
1052
  }
452
- log(chalk.cyan.bold(`Your Positions (${result.positions.length}):`));
1053
+ log(chalk.green.bold(`Your Positions (${result.positions.length}):`));
453
1054
  log("");
454
1055
  for (const pos of result.positions) {
455
- log(` ${chalk.cyan(pos.symbol || "???")} - ${chalk.green(pos.tokensOwned + " tokens")} @ ${chalk.yellow(pos.currentValue || "N/A")}`);
1056
+ log(` ${chalk.green(pos.symbol || "???")} - ${chalk.green(pos.tokensOwned + " tokens")} @ ${chalk.yellow(pos.currentValue || "N/A")}`);
456
1057
  }
457
1058
  }
458
1059
  function displayHealthStatusToLog(health, log, logLines) {
459
- const statusColor = health.status === "healthy" ? chalk.green : chalk.red;
1060
+ const statusColor = health.status === "ok" || health.status === "healthy" ? chalk.green : chalk.red;
460
1061
  log(statusColor(`Agent Status: ${health.status}`));
461
1062
  if (health.version) {
462
- log(chalk.dim("Version: ") + health.version);
1063
+ log(chalk.blue("Version: ") + chalk.green(health.version));
463
1064
  }
464
1065
  if (health.uptime) {
465
- log(chalk.dim("Uptime: ") + health.uptime);
1066
+ log(chalk.blue("Uptime: ") + chalk.green(health.uptime));
466
1067
  }
467
1068
  }
468
1069
  function displayBalanceToLog(balance, log, logLines) {
469
- log(chalk.cyan.bold("Wallet Balance:"));
470
- log(chalk.dim("Address: ") + chalk.cyan(balance.address));
471
- log(chalk.dim("ETH: ") + chalk.yellow(balance.ethBalance));
472
- log(chalk.dim("USDC: ") + chalk.green(balance.usdcBalance));
1070
+ log("");
1071
+ log(chalk.green.bold("Wallet Balance:"));
1072
+ log(chalk.blue("Address: ") + chalk.green(balance.address));
1073
+ log(chalk.blue("ETH: ") + chalk.yellow(balance.ethFormatted || balance.ethBalance));
1074
+ log(chalk.blue("USDC: ") + chalk.green(balance.usdcFormatted || balance.usdcBalance));
1075
+ if (balance.cat402Formatted) {
1076
+ log(chalk.blue("CAT: ") + chalk.cyan(balance.cat402Formatted));
1077
+ }
1078
+ log("");
1079
+ // Show warnings if balances are low
1080
+ const ethNum = Number(balance.ethFormatted || balance.ethBalance);
1081
+ const usdcNum = Number((balance.usdcFormatted || balance.usdcBalance).replace("$", "").replace(",", ""));
1082
+ if (ethNum < 0.001 && usdcNum < 1) {
1083
+ log(chalk.yellow("⚠️ Low balances detected!"));
1084
+ log(chalk.dim(" You need Base Sepolia ETH for gas fees"));
1085
+ log(chalk.dim(" You need Base Sepolia USDC for trading"));
1086
+ log("");
1087
+ }
1088
+ else if (ethNum < 0.001) {
1089
+ log(chalk.yellow("⚠️ Low ETH balance - you may not have enough for gas fees"));
1090
+ log("");
1091
+ }
1092
+ else if (usdcNum < 1) {
1093
+ log(chalk.yellow("⚠️ Low USDC balance - you may not have enough for trades"));
1094
+ log("");
1095
+ }
473
1096
  }
474
1097
  function extractFlag(args, flag) {
475
1098
  const index = args.findIndex((arg) => arg === flag);
@@ -478,4 +1101,107 @@ function extractFlag(args, flag) {
478
1101
  }
479
1102
  return undefined;
480
1103
  }
1104
+ function displayFeesToLog(fees, log, logLines) {
1105
+ const hasFeesToken = BigInt(fees.feeToken || "0") > 0n;
1106
+ const hasFeesPaired = BigInt(fees.feePaired || "0") > 0n;
1107
+ const hasFees = hasFeesToken || hasFeesPaired;
1108
+ log("");
1109
+ log(chalk.green.bold(`💰 Accumulated Fees: ${fees.tokenName} (${fees.tokenSymbol})`));
1110
+ log(chalk.blue("━".repeat(50)));
1111
+ log(chalk.blue("Contract: ") + chalk.green(fees.tokenAddress));
1112
+ log(chalk.blue("LP Status: ") + chalk.green(fees.isLocked ? "🔒 Locked" : "❌ Not Locked"));
1113
+ if (typeof fees.v4TickLower === "number" &&
1114
+ typeof fees.v4TickUpper === "number" &&
1115
+ typeof fees.v4TickCurrent === "number" &&
1116
+ typeof fees.v4InRange === "boolean") {
1117
+ log(chalk.blue("V4 Range: ") +
1118
+ chalk.green(`[${fees.v4TickLower}, ${fees.v4TickUpper}) | Current: ${fees.v4TickCurrent} | In Range: ${fees.v4InRange ? "✅" : "❌"}`));
1119
+ }
1120
+ log("");
1121
+ log(chalk.green("Total Fees:"));
1122
+ log(chalk.blue(" Tokens: ") + chalk.green(hasFeesToken ? fees.feeToken : "0"));
1123
+ log(chalk.blue(" USDC: ") + chalk.green(hasFeesPaired ? fees.feePaired : "$0.00"));
1124
+ log("");
1125
+ log(chalk.green("Creator Share (80%):"));
1126
+ log(chalk.blue(" Tokens: ") + chalk.green(fees.creatorToken || "0"));
1127
+ log(chalk.blue(" USDC: ") + chalk.green(fees.creatorPaired || "$0.00"));
1128
+ log("");
1129
+ log(chalk.green("Platform Share (20%):"));
1130
+ log(chalk.blue(" Tokens: ") + chalk.green(fees.platformToken || "0"));
1131
+ log(chalk.blue(" USDC: ") + chalk.green(fees.platformPaired || "$0.00"));
1132
+ log(chalk.blue("━".repeat(50)));
1133
+ if (!hasFees) {
1134
+ if (fees.v4InRange === false) {
1135
+ log(chalk.yellow("\n⚠️ No fees accrued: position is out of range"));
1136
+ }
1137
+ else {
1138
+ log(chalk.yellow("\n⚠️ No fees accumulated yet. Trade volume needed."));
1139
+ }
1140
+ }
1141
+ else {
1142
+ log(chalk.blue("\n💡 Run with --execute to claim fees"));
1143
+ }
1144
+ }
1145
+ function displayClaimResultToLog(result, log, logLines) {
1146
+ log(chalk.green("✅ Fees claimed successfully!"));
1147
+ log(chalk.blue("Token: ") + chalk.green(result.tokenAddress));
1148
+ log(chalk.blue("Transaction: ") + chalk.green(result.txHash));
1149
+ log(chalk.blue("Claimed:"));
1150
+ log(chalk.blue(" Tokens: ") + chalk.green(result.creatorToken || "0"));
1151
+ log(chalk.blue(" USDC: ") + chalk.green(result.creatorPaired || "$0.00"));
1152
+ }
1153
+ function displayTransactionsToLog(result, log, logLines) {
1154
+ log(chalk.green.bold(`📜 Transactions (${result.total} total)`));
1155
+ log("");
1156
+ if (result.transactions.length === 0) {
1157
+ log(chalk.yellow("No transactions found."));
1158
+ return;
1159
+ }
1160
+ for (let i = 0; i < result.transactions.length; i++) {
1161
+ const tx = result.transactions[i];
1162
+ const typeIcon = tx.type === "buy" ? "🟢" : tx.type === "sell" ? "🔴" : "🎁";
1163
+ const statusIcon = tx.status === "success" ? "✅" : tx.status === "pending" ? "⏳" : "❌";
1164
+ log(chalk.green.bold(`${i + 1}. ${typeIcon} ${tx.type.toUpperCase()} ${statusIcon} ${tx.status.toUpperCase()}`));
1165
+ if (tx.token) {
1166
+ log(chalk.blue(` Token: ${tx.token.name} (${tx.token.symbol})`));
1167
+ }
1168
+ log(chalk.blue(` Amount: ${tx.amount}`));
1169
+ log(chalk.blue(` Fee: ${tx.fee}`));
1170
+ if (tx.txHash) {
1171
+ log(chalk.blue(` TX: ${tx.txHash}`));
1172
+ }
1173
+ log(chalk.blue(` Date: ${new Date(tx.createdAt).toLocaleString()}`));
1174
+ log("");
1175
+ }
1176
+ if (result.hasMore) {
1177
+ log(chalk.blue(`Showing ${result.offset + 1}-${result.offset + result.transactions.length} of ${result.total}`));
1178
+ log(chalk.blue(`Use --offset ${result.offset + result.limit} to see more`));
1179
+ }
1180
+ }
1181
+ function displayAccountInfoToLog(data, log, logLines) {
1182
+ const { account, balance } = data;
1183
+ log("");
1184
+ log(chalk.green.bold("👤 Account Information"));
1185
+ log(chalk.blue("=".repeat(50)));
1186
+ log("");
1187
+ log(chalk.blue("Account Index: ") + chalk.green(account.index.toString()));
1188
+ log(chalk.blue("Type: ") + chalk.green(account.type === "custom" ? "Custom" : "Seed-Derived"));
1189
+ log(chalk.blue("Address: ") + chalk.green(account.address));
1190
+ if (account.label) {
1191
+ log(chalk.blue("Label: ") + chalk.green(account.label));
1192
+ }
1193
+ log("");
1194
+ log(chalk.green.bold("💰 Balances"));
1195
+ log(chalk.blue("ETH: ") + chalk.yellow(balance.ethBalance));
1196
+ log(chalk.blue("USDC: ") + chalk.green(balance.usdcBalance));
1197
+ if (balance.cat402Formatted) {
1198
+ log(chalk.blue("CAT: ") + chalk.green(balance.cat402Formatted));
1199
+ }
1200
+ log("");
1201
+ if (data.positions && data.positions.positions.length > 0) {
1202
+ log(chalk.green.bold("💼 Positions"));
1203
+ log("");
1204
+ displayPositionsToLog(data.positions, log, logLines);
1205
+ }
1206
+ }
481
1207
  //# sourceMappingURL=shell.js.map