httpcat-cli 0.0.26 → 0.0.27-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/.github/workflows/README.md +19 -2
  2. package/.github/workflows/ci.yml +31 -20
  3. package/.github/workflows/homebrew-tap.yml +1 -1
  4. package/.github/workflows/rc-publish.yml +169 -0
  5. package/.github/workflows/release.yml +223 -71
  6. package/README.md +94 -76
  7. package/bun.lock +2933 -0
  8. package/dist/commands/account.d.ts.map +1 -1
  9. package/dist/commands/account.js +14 -7
  10. package/dist/commands/account.js.map +1 -1
  11. package/dist/commands/balances.d.ts.map +1 -0
  12. package/dist/commands/balances.js +171 -0
  13. package/dist/commands/balances.js.map +1 -0
  14. package/dist/commands/buy.d.ts.map +1 -1
  15. package/dist/commands/buy.js +739 -32
  16. package/dist/commands/buy.js.map +1 -1
  17. package/dist/commands/chat.d.ts.map +1 -1
  18. package/dist/commands/chat.js +467 -906
  19. package/dist/commands/chat.js.map +1 -1
  20. package/dist/commands/claim.d.ts.map +1 -0
  21. package/dist/commands/claim.js +65 -0
  22. package/dist/commands/claim.js.map +1 -0
  23. package/dist/commands/create.d.ts.map +1 -1
  24. package/dist/commands/create.js +0 -1
  25. package/dist/commands/create.js.map +1 -1
  26. package/dist/commands/info.d.ts.map +1 -1
  27. package/dist/commands/info.js +128 -26
  28. package/dist/commands/info.js.map +1 -1
  29. package/dist/commands/list.d.ts.map +1 -1
  30. package/dist/commands/list.js +30 -23
  31. package/dist/commands/list.js.map +1 -1
  32. package/dist/commands/positions.d.ts.map +1 -1
  33. package/dist/commands/positions.js +178 -105
  34. package/dist/commands/positions.js.map +1 -1
  35. package/dist/commands/sell.d.ts.map +1 -1
  36. package/dist/commands/sell.js +713 -24
  37. package/dist/commands/sell.js.map +1 -1
  38. package/dist/index.js +315 -99
  39. package/dist/index.js.map +1 -1
  40. package/dist/interactive/shell.d.ts.map +1 -1
  41. package/dist/interactive/shell.js +328 -179
  42. package/dist/interactive/shell.js.map +1 -1
  43. package/dist/mcp/tools.d.ts.map +1 -1
  44. package/dist/mcp/tools.js +8 -8
  45. package/dist/mcp/tools.js.map +1 -1
  46. package/dist/utils/constants.d.ts.map +1 -0
  47. package/dist/utils/constants.js +66 -0
  48. package/dist/utils/constants.js.map +1 -0
  49. package/dist/utils/formatting.d.ts.map +1 -1
  50. package/dist/utils/formatting.js +3 -5
  51. package/dist/utils/formatting.js.map +1 -1
  52. package/dist/utils/token-resolver.d.ts.map +1 -1
  53. package/dist/utils/token-resolver.js +71 -40
  54. package/dist/utils/token-resolver.js.map +1 -1
  55. package/dist/utils/validation.d.ts.map +1 -1
  56. package/dist/utils/validation.js +4 -3
  57. package/dist/utils/validation.js.map +1 -1
  58. package/jest.config.js +1 -1
  59. package/package.json +19 -13
  60. package/.claude/settings.local.json +0 -41
  61. package/dist/commands/balance.d.ts.map +0 -1
  62. package/dist/commands/balance.js +0 -112
  63. package/dist/commands/balance.js.map +0 -1
  64. package/homebrew-httpcat/Formula/httpcat.rb +0 -18
  65. package/homebrew-httpcat/README.md +0 -31
  66. package/homebrew-httpcat/homebrew-httpcat/Formula/httpcat.rb +0 -18
  67. package/homebrew-httpcat/homebrew-httpcat/README.md +0 -31
package/dist/index.js CHANGED
@@ -2,6 +2,7 @@
2
2
  import { Command } from "commander";
3
3
  import chalk from "chalk";
4
4
  import { privateKeyToAccount } from "viem/accounts";
5
+ import { createRequire } from "module";
5
6
  import { config } from "./config.js";
6
7
  import { formatAddress, formatCurrency } from "./utils/formatting.js";
7
8
  import { HttpcatClient, HttpcatError } from "./client.js";
@@ -18,6 +19,8 @@ const __filename = fileURLToPath(import.meta.url);
18
19
  const __dirname = dirname(__filename);
19
20
  const packageJson = JSON.parse(readFileSync(join(__dirname, "../package.json"), "utf-8"));
20
21
  const VERSION = packageJson.version;
22
+ const require = createRequire(import.meta.url);
23
+ const { version: PACKAGE_VERSION } = require("../package.json");
21
24
  // Import commands
22
25
  import { createToken, displayCreateResult } from "./commands/create.js";
23
26
  import { buyToken, displayBuyResult, displayBuyResultCompact, } from "./commands/buy.js";
@@ -27,17 +30,18 @@ import { listTokens, displayTokenList } from "./commands/list.js";
27
30
  import { getPositions, displayPositions } from "./commands/positions.js";
28
31
  import { getTransactions, displayTransactions, } from "./commands/transactions.js";
29
32
  import { checkHealth, displayHealthStatus } from "./commands/health.js";
30
- import { checkBalance, displayBalance } from "./commands/balance.js";
33
+ import { checkBalance, displayBalance } from "./commands/balances.js";
31
34
  import { startChatStream } from "./commands/chat.js";
32
35
  import { runMcpServer } from "./commands/mcp-server.js";
33
- import { getAccountInfo, displayAccountInfo, listAccounts, switchAccount, addAccount } from "./commands/account.js";
36
+ import { getAccountInfo, displayAccountInfo, listAccounts, switchAccount, addAccount, } from "./commands/account.js";
37
+ import { viewFees, claimFees, displayFees, displayClaimResult, } from "./commands/claim.js";
34
38
  // Check for --version --json before parsing
35
39
  const args = process.argv;
36
40
  if (args.includes("--version") || args.includes("-V")) {
37
41
  if (args.includes("--json")) {
38
42
  console.log(JSON.stringify({
39
43
  name: "httpcat-cli",
40
- version: VERSION,
44
+ version: PACKAGE_VERSION,
41
45
  }, null, 2));
42
46
  process.exit(0);
43
47
  }
@@ -46,13 +50,13 @@ const program = new Command();
46
50
  program
47
51
  .name("httpcat")
48
52
  .description("CLI tool for interacting with httpcat agent")
49
- .version(VERSION)
50
- .option("--json", "Output in JSON format")
51
- .option("--quiet", "Minimal output (exit codes only)")
52
- .option("--verbose", "Verbose error messages")
53
+ .version(PACKAGE_VERSION)
54
+ .option("-j, --json", "Output in JSON format")
55
+ .option("-q, --quiet", "Minimal output (exit codes only)")
56
+ .option("-v, --verbose", "Verbose error messages")
53
57
  .option("--no-art", "Disable ASCII art")
54
- .option("--private-key <key>", "Private key (overrides config and env var)")
55
- .option("--account <index>", "Account index to use (overrides active account)", (value) => parseInt(value, 10));
58
+ .option("-k, --private-key <key>", "Private key (overrides config and env var)")
59
+ .option("-a, --account <index>", "Account index to use (overrides active account)", (value) => parseInt(value, 10));
56
60
  // Helper function to get private key with priority: CLI flag > env var > config
57
61
  function getPrivateKey(cliPrivateKey, accountIndex) {
58
62
  if (cliPrivateKey)
@@ -62,7 +66,9 @@ function getPrivateKey(cliPrivateKey, accountIndex) {
62
66
  return envKey;
63
67
  // Use account system
64
68
  try {
65
- const index = accountIndex !== undefined ? accountIndex : config.getActiveAccountIndex();
69
+ const index = accountIndex !== undefined
70
+ ? accountIndex
71
+ : config.getActiveAccountIndex();
66
72
  return config.getAccountPrivateKey(index);
67
73
  }
68
74
  catch (error) {
@@ -186,7 +192,7 @@ envCommand
186
192
  .description("Add a custom environment")
187
193
  .argument("<name>", "Environment name")
188
194
  .argument("<agentUrl>", "Agent URL (e.g., http://localhost:8787)")
189
- .option("--network <network>", "Network (default: base-sepolia)", "base-sepolia")
195
+ .option("-n, --network <network>", "Network (default: base-sepolia)", "base-sepolia")
190
196
  .action((name, agentUrl, options) => {
191
197
  try {
192
198
  config.addEnvironment(name, agentUrl, options.network);
@@ -207,7 +213,7 @@ envCommand
207
213
  .description("Update an existing environment")
208
214
  .argument("<name>", "Environment name")
209
215
  .argument("<agentUrl>", "Agent URL (e.g., http://localhost:8787)")
210
- .option("--network <network>", "Network (default: base-sepolia)", "base-sepolia")
216
+ .option("-n, --network <network>", "Network (default: base-sepolia)", "base-sepolia")
211
217
  .action((name, agentUrl, options) => {
212
218
  try {
213
219
  config.updateEnvironment(name, agentUrl, options.network);
@@ -225,9 +231,9 @@ envCommand
225
231
  program
226
232
  .command("config")
227
233
  .description("Configure httpcat")
228
- .option("--show", "Show current configuration")
229
- .option("--set <key=value>", "Set configuration value")
230
- .option("--reset", "Reset configuration")
234
+ .option("-s, --show", "Show current configuration")
235
+ .option("-S, --set <key=value>", "Set configuration value")
236
+ .option("-r, --reset", "Reset configuration")
231
237
  .addHelpText("after", `
232
238
  Examples:
233
239
  httpcat config # Run setup wizard
@@ -267,9 +273,9 @@ program
267
273
  .description("Create a new token")
268
274
  .argument("<name>", 'Token name (e.g., "My Token")')
269
275
  .argument("<symbol>", 'Token symbol/ticker (e.g., "MTK", 3-10 characters)')
270
- .option("--photo <url>", "Photo URL for token logo")
271
- .option("--banner <url>", "Banner image URL")
272
- .option("--website <url>", "Website URL")
276
+ .option("-p, --photo <url>", "Photo URL for token logo")
277
+ .option("-b, --banner <url>", "Banner image URL")
278
+ .option("-w, --website <url>", "Website URL")
273
279
  .addHelpText("after", `
274
280
  Examples:
275
281
  httpcat create "My Token" "MTK"
@@ -328,16 +334,18 @@ Examples:
328
334
  // Buy command
329
335
  program
330
336
  .command("buy")
331
- .description("Buy tokens from the bonding curve")
332
- .argument("<identifier>", 'Token ID (UUID), name, or symbol (e.g., abc123-..., "My Token", or MTK)')
333
- .argument("<amount>", "Amount in USDC: 0.05, 0.10, or 0.20 (testnet) | 50, 100, or 200 (mainnet)")
334
- .option("--repeat <count>", "Number of times to repeat the buy operation", (value) => parseInt(value, 10))
335
- .option("--delay <ms>", "Delay in milliseconds between repeat operations (default: 0)", (value) => parseInt(value, 10), 0)
337
+ .description("Buy tokens (auto-routes to bonding curve or Uniswap V2)")
338
+ .argument("<identifier>", 'Address, name, or symbol (e.g., 0x1234..., "My Token", or MTK)')
339
+ .argument("<amount>", "Amount in USDC or percentage: 0.05, 0.10, 0.20, 10, 50% (% of your USDC balance)")
340
+ .option("-r, --repeat <count>", "Number of times to repeat the buy operation", (value) => parseInt(value, 10))
341
+ .option("-d, --delay <ms>", "Delay in milliseconds between repeat operations (default: 0)", (value) => parseInt(value, 10), 0)
336
342
  .addHelpText("after", `
337
343
  Examples:
338
344
  httpcat buy abc123-4567-89ab-cdef-0123456789ab 0.20
339
345
  httpcat buy "My Token" 0.10
340
- httpcat buy MTK 0.05
346
+ httpcat buy MTK 10 # Buy with $10 USDC
347
+ httpcat buy MTK 50% # Buy with 50% of your USDC balance
348
+ httpcat buy MTK 0.05 --repeat 10
341
349
  httpcat --json buy abc123-4567-89ab-cdef-0123456789ab 0.10
342
350
  httpcat --private-key 0x... buy "PurrfecTT" 0.20
343
351
  httpcat buy MTK 0.10 --repeat 10
@@ -364,6 +372,27 @@ Examples:
364
372
  const client = await HttpcatClient.create(privateKey);
365
373
  const isTestMode = client.getNetwork().includes("sepolia");
366
374
  const silent = globalOpts.json || globalOpts.quiet;
375
+ // Handle percentage amounts for graduated tokens
376
+ let finalAmount = amount;
377
+ if (amount.endsWith("%")) {
378
+ if (!silent) {
379
+ console.log("💰 Checking USDC balance for percentage buy...");
380
+ }
381
+ const balance = await checkBalance(privateKey);
382
+ const usdcBalance = parseFloat(balance.usdcFormatted.replace("$", "").replace(",", ""));
383
+ const percentage = parseFloat(amount.replace("%", ""));
384
+ if (isNaN(percentage) || percentage <= 0 || percentage > 100) {
385
+ throw new Error("Percentage must be between 0 and 100");
386
+ }
387
+ finalAmount = ((usdcBalance * percentage) / 100).toFixed(6);
388
+ if (!silent) {
389
+ console.log(`💵 USDC Balance: $${usdcBalance}`);
390
+ console.log(`📊 Using ${percentage}% = $${finalAmount}`);
391
+ }
392
+ if (parseFloat(finalAmount) === 0) {
393
+ throw new Error("Insufficient USDC balance");
394
+ }
395
+ }
367
396
  // If repeat is specified, execute multiple buys
368
397
  if (repeatCount && repeatCount > 0) {
369
398
  const results = [];
@@ -376,7 +405,7 @@ Examples:
376
405
  if (!globalOpts.json && !globalOpts.quiet) {
377
406
  console.log(chalk.dim(`Buy ${i}/${repeatCount}...`));
378
407
  }
379
- const result = await withLoading(() => buyToken(client, tokenId, amount, isTestMode, silent || i > 1), {
408
+ const result = await withLoading(() => buyToken(client, tokenId, finalAmount, isTestMode, silent || i > 1, privateKey), {
380
409
  message: `Buying tokens (${i}/${repeatCount})...`,
381
410
  json: globalOpts.json,
382
411
  quiet: globalOpts.quiet || i > 1,
@@ -427,7 +456,7 @@ Examples:
427
456
  const lastResult = results[results.length - 1];
428
457
  const graduationStatus = lastResult.graduationReached
429
458
  ? "✅ GRADUATED!"
430
- : `${lastResult.graduationProgress.toFixed(2)}%`;
459
+ : `${(lastResult.graduationProgress || 0).toFixed(2)}%`;
431
460
  console.log();
432
461
  console.log(chalk.cyan.bold("📊 Repeat Buy Summary"));
433
462
  console.log(chalk.dim("─".repeat(50)));
@@ -445,7 +474,7 @@ Examples:
445
474
  }
446
475
  else {
447
476
  // Normal single buy execution
448
- const result = await withLoading(() => buyToken(client, tokenId, amount, isTestMode, silent), {
477
+ const result = await withLoading(() => buyToken(client, tokenId, finalAmount, isTestMode, silent, privateKey), {
449
478
  message: "Buying tokens...",
450
479
  json: globalOpts.json,
451
480
  quiet: globalOpts.quiet,
@@ -474,8 +503,8 @@ Examples:
474
503
  // Sell command
475
504
  program
476
505
  .command("sell")
477
- .description("Sell tokens back to the bonding curve")
478
- .argument("<identifier>", 'Token ID (UUID), name, or symbol (e.g., abc123-..., "My Token", or MTK)')
506
+ .description("Sell tokens (auto-routes to bonding curve or Uniswap V2)")
507
+ .argument("<identifier>", 'Address, name, or symbol (e.g., 0x1234..., "My Token", or MTK)')
479
508
  .argument("<amount>", 'Amount: number (e.g., 1000), percentage (e.g., 50%), or "all"')
480
509
  .addHelpText("after", `
481
510
  Examples:
@@ -505,23 +534,99 @@ Examples:
505
534
  // Derive user address from private key
506
535
  const account = privateKeyToAccount(privateKey);
507
536
  const userAddress = account.address;
508
- // Get current holdings (this also resolves the identifier to tokenId)
509
- const info = await withLoading(() => getTokenInfo(client, tokenId, userAddress, silent), {
510
- message: "Checking token info...",
511
- json: globalOpts.json,
512
- quiet: globalOpts.quiet,
513
- spinner: "cat",
514
- });
515
- if (!info.userPosition || info.userPosition.tokensOwned === "0") {
516
- throw new Error("You do not own any of this token");
537
+ // Check if it's a contract address
538
+ const addressRegex = /^0x[0-9a-f]{40}$/i;
539
+ const isContractAddress = addressRegex.test(tokenId);
540
+ let actualBalance = "0";
541
+ let info = null;
542
+ // If it's a contract address, try to look it up in database first
543
+ if (isContractAddress) {
544
+ try {
545
+ // Try to get token info - if it exists in our DB, use normal flow
546
+ info = await withLoading(() => getTokenInfo(client, tokenId, userAddress, silent), {
547
+ message: "Checking token info...",
548
+ json: globalOpts.json,
549
+ quiet: globalOpts.quiet,
550
+ spinner: "cat",
551
+ });
552
+ // Found in database! Continue with normal flow below
553
+ actualBalance = info.userPosition?.tokensOwned || "0";
554
+ }
555
+ catch (error) {
556
+ // Not in database - treat as external token
557
+ if (error.message?.includes("not found") ||
558
+ error.message?.includes("Invalid UUID")) {
559
+ if (!silent) {
560
+ console.log("📊 Checking on-chain balance for external token...");
561
+ }
562
+ const { createPublicClient, http, parseAbi } = await import("viem");
563
+ const { baseSepolia } = await import("viem/chains");
564
+ const publicClient = createPublicClient({
565
+ chain: baseSepolia,
566
+ transport: http(),
567
+ });
568
+ const ERC20_ABI = parseAbi([
569
+ "function balanceOf(address owner) view returns (uint256)",
570
+ ]);
571
+ const balance = await publicClient.readContract({
572
+ address: tokenId,
573
+ abi: ERC20_ABI,
574
+ functionName: "balanceOf",
575
+ args: [userAddress],
576
+ });
577
+ actualBalance = balance.toString();
578
+ if (!silent) {
579
+ const { formatUnits } = await import("viem");
580
+ console.log(`💼 On-chain balance: ${formatUnits(balance, 18)} tokens`);
581
+ }
582
+ }
583
+ else {
584
+ // Some other error - rethrow
585
+ throw error;
586
+ }
587
+ }
588
+ }
589
+ else {
590
+ // UUID token - get token info
591
+ info = await withLoading(() => getTokenInfo(client, tokenId, userAddress, silent), {
592
+ message: "Checking token info...",
593
+ json: globalOpts.json,
594
+ quiet: globalOpts.quiet,
595
+ spinner: "cat",
596
+ });
597
+ actualBalance = info.userPosition?.tokensOwned || "0";
598
+ // For graduated tokens, check actual on-chain balance
599
+ if (info && info.status === "graduated") {
600
+ if (!silent) {
601
+ console.log("📊 Checking on-chain balance for graduated token...");
602
+ }
603
+ const { createPublicClient, http, parseAbi } = await import("viem");
604
+ const { baseSepolia } = await import("viem/chains");
605
+ const publicClient = createPublicClient({
606
+ chain: baseSepolia,
607
+ transport: http(),
608
+ });
609
+ const ERC20_ABI = parseAbi([
610
+ "function balanceOf(address owner) view returns (uint256)",
611
+ ]);
612
+ const balance = await publicClient.readContract({
613
+ address: info.address,
614
+ abi: ERC20_ABI,
615
+ functionName: "balanceOf",
616
+ args: [userAddress],
617
+ });
618
+ actualBalance = balance.toString();
619
+ if (!silent) {
620
+ const { formatUnits } = await import("viem");
621
+ console.log(`💼 On-chain balance: ${formatUnits(balance, 18)} tokens`);
622
+ }
623
+ }
517
624
  }
518
- // Use the resolved tokenId from info instead of resolving again
519
- const resolvedTokenId = info.tokenId;
520
- if (!resolvedTokenId) {
521
- throw new Error(`Failed to get token ID from token info. Original identifier: ${tokenId}`);
625
+ if (actualBalance === "0") {
626
+ throw new Error("You do not own any of this token");
522
627
  }
523
- const tokenAmount = parseTokenAmount(amountInput, info.userPosition.tokensOwned);
524
- const result = await withLoading(() => sellToken(client, resolvedTokenId, tokenAmount, silent), {
628
+ const tokenAmount = parseTokenAmount(amountInput, actualBalance);
629
+ const result = await withLoading(() => sellToken(client, tokenId, tokenAmount, silent, privateKey), {
525
630
  message: "Selling tokens...",
526
631
  json: globalOpts.json,
527
632
  quiet: globalOpts.quiet,
@@ -546,11 +651,89 @@ Examples:
546
651
  process.exit(getExitCode(error));
547
652
  }
548
653
  });
654
+ // Claim command
655
+ program
656
+ .command("claim")
657
+ .description("View and claim accumulated LP fees for graduated tokens")
658
+ .argument("<identifier>", 'Address, name, or symbol (e.g., 0x1234..., "My Token", or MTK)')
659
+ .option("-e, --execute", "Execute the claim (default: view only)")
660
+ .option("-A, --address <address>", "Caller address (defaults to wallet address)")
661
+ .addHelpText("after", `
662
+ Examples:
663
+ httpcat claim "MyToken" # View accumulated fees
664
+ httpcat claim MTK --execute # Claim fees
665
+ httpcat claim 0x4C64... --execute # Claim using contract address
666
+ httpcat --json claim "MyToken"
667
+ `)
668
+ .action(async (identifier, options, command) => {
669
+ try {
670
+ const globalOpts = command.parent?.opts() || {};
671
+ const accountIndex = globalOpts.account;
672
+ // Ensure wallet is unlocked
673
+ await ensureWalletUnlocked();
674
+ let privateKey = getPrivateKey(globalOpts.privateKey, accountIndex);
675
+ // If not configured and not in JSON mode, prompt interactively
676
+ if (!isConfigured(globalOpts.privateKey)) {
677
+ if (globalOpts.json) {
678
+ console.error('❌ Not configured. Run "httpcat config" first or use --private-key flag.');
679
+ process.exit(2);
680
+ }
681
+ // Interactive prompt for private key
682
+ privateKey = await promptForPrivateKey();
683
+ }
684
+ const client = await HttpcatClient.create(privateKey);
685
+ const silent = globalOpts.json || globalOpts.quiet;
686
+ if (options.execute) {
687
+ // Claim fees
688
+ // Derive caller address from private key if not provided
689
+ const account = privateKeyToAccount(privateKey);
690
+ const callerAddress = options.address || account.address;
691
+ const result = await withLoading(() => claimFees(client, identifier, callerAddress, silent), {
692
+ message: "Claiming fees...",
693
+ json: globalOpts.json,
694
+ quiet: globalOpts.quiet,
695
+ spinner: "cat",
696
+ });
697
+ if (globalOpts.json) {
698
+ outputJson("claim_fees", result);
699
+ }
700
+ else if (!globalOpts.quiet) {
701
+ displayClaimResult(result);
702
+ }
703
+ }
704
+ else {
705
+ // View fees only
706
+ const result = await withLoading(() => viewFees(client, identifier, silent), {
707
+ message: "Fetching fee information...",
708
+ json: globalOpts.json,
709
+ quiet: globalOpts.quiet,
710
+ spinner: "cat",
711
+ });
712
+ if (globalOpts.json) {
713
+ outputJson("view_fees", result);
714
+ }
715
+ else if (!globalOpts.quiet) {
716
+ displayFees(result);
717
+ }
718
+ }
719
+ process.exit(0);
720
+ }
721
+ catch (error) {
722
+ const globalOpts = command.parent?.opts() || {};
723
+ if (globalOpts.json) {
724
+ outputError("claim", error, getExitCode(error));
725
+ }
726
+ else {
727
+ handleError(error, globalOpts.verbose);
728
+ }
729
+ process.exit(getExitCode(error));
730
+ }
731
+ });
549
732
  // Info command
550
733
  program
551
734
  .command("info")
552
735
  .description("Get detailed information about a token")
553
- .argument("<identifier>", 'Token ID (UUID), name, or symbol (e.g., abc123-..., "My Token", or MTK)')
736
+ .argument("<identifier>", 'Address, name, or symbol (e.g., 0x1234..., "My Token", or MTK)')
554
737
  .addHelpText("after", `
555
738
  Examples:
556
739
  httpcat info abc123-4567-89ab-cdef-0123456789ab
@@ -589,7 +772,7 @@ Examples:
589
772
  outputJson("token_info", result);
590
773
  }
591
774
  else if (!globalOpts.quiet) {
592
- displayTokenInfo(result);
775
+ await displayTokenInfo(result, userAddress);
593
776
  }
594
777
  process.exit(0);
595
778
  }
@@ -608,9 +791,9 @@ Examples:
608
791
  program
609
792
  .command("list")
610
793
  .description("List all tokens with pagination and sorting")
611
- .option("--page <number>", "Page number (default: 1)", "1")
612
- .option("--limit <number>", "Items per page (default: 20, max: 100)", "20")
613
- .option("--sort <field>", "Sort by: mcap (market cap), created (date), or name (alphabetical)", "mcap")
794
+ .option("-p, --page <number>", "Page number (default: 1)", "1")
795
+ .option("-l, --limit <number>", "Items per page (default: 20, max: 100)", "20")
796
+ .option("-s, --sort <field>", "Sort by: mcap (market cap), created (date), or name (alphabetical)", "mcap")
614
797
  .addHelpText("after", `
615
798
  Examples:
616
799
  httpcat list
@@ -663,18 +846,18 @@ Examples:
663
846
  // Transactions command
664
847
  program
665
848
  .command("transactions")
666
- .description("Get paginated list of transactions with optional filtering")
667
- .option("--user <address>", "Filter by user address")
668
- .option("--token <tokenId>", "Filter by token ID")
669
- .option("--type <type>", "Filter by type: buy, sell, or airdrop")
670
- .option("--limit <number>", "Number of results (default: 50, max: 100)", "50")
671
- .option("--offset <number>", "Pagination offset (default: 0)", "0")
849
+ .description("Get paginated list of transactions (defaults to selected account)")
850
+ .option("-u, --user <address>", "Filter by user address (defaults to selected account if not provided)")
851
+ .option("-t, --token <tokenId>", "Filter by token ID")
852
+ .option("-T, --type <type>", "Filter by type: buy, sell, or airdrop")
853
+ .option("-l, --limit <number>", "Number of results (default: 50, max: 100)", "50")
854
+ .option("-o, --offset <number>", "Pagination offset (default: 0)", "0")
672
855
  .addHelpText("after", `
673
856
  Examples:
674
- httpcat transactions
675
- httpcat transactions --user 0x1234...
676
- httpcat transactions --token abc123-...
677
- httpcat transactions --type buy --limit 20
857
+ httpcat transactions # Get transactions for selected account
858
+ httpcat transactions --user 0x1234... # Get transactions for specific address
859
+ httpcat transactions --token abc123-... # Get transactions for a token
860
+ httpcat transactions --type buy --limit 20 # Get buy transactions for selected account
678
861
  httpcat --json transactions --user 0x1234... --offset 50
679
862
  `)
680
863
  .action(async (options, command) => {
@@ -694,9 +877,37 @@ Examples:
694
877
  privateKey = await promptForPrivateKey();
695
878
  }
696
879
  const client = await HttpcatClient.create(privateKey);
880
+ // Derive user address from private key (default to selected account if no --user option)
881
+ const account = privateKeyToAccount(privateKey);
882
+ const userAddress = account.address;
883
+ // Show which account is being used (if not quiet and not json)
884
+ if (!globalOpts.quiet && !globalOpts.json) {
885
+ const activeIndex = accountIndex !== undefined
886
+ ? accountIndex
887
+ : config.getActiveAccountIndex();
888
+ const accounts = config.getAllAccounts();
889
+ const accountInfo = accounts.find((acc) => acc.index === activeIndex);
890
+ if (accountInfo) {
891
+ // Verify the address matches
892
+ if (accountInfo.address.toLowerCase() !== userAddress.toLowerCase()) {
893
+ console.log(chalk.red(`⚠️ Warning: Account address mismatch!`));
894
+ console.log(chalk.red(` Expected: ${accountInfo.address}`));
895
+ console.log(chalk.red(` Got: ${userAddress}`));
896
+ console.log();
897
+ }
898
+ console.log(chalk.dim(`Using account ${activeIndex} (${accountInfo.type === "custom" ? "Custom" : "Seed-Derived"}): ${formatAddress(userAddress, 12)}`));
899
+ console.log();
900
+ }
901
+ }
697
902
  const input = {};
698
- if (options.user)
903
+ // Use selected account's address by default, unless --user is explicitly provided
904
+ if (options.user) {
699
905
  input.userAddress = options.user;
906
+ }
907
+ else {
908
+ // Default to selected account's address
909
+ input.userAddress = userAddress;
910
+ }
700
911
  if (options.token)
701
912
  input.tokenId = options.token;
702
913
  if (options.type) {
@@ -738,9 +949,13 @@ Examples:
738
949
  program
739
950
  .command("positions")
740
951
  .description("Get all your positions with comprehensive information")
952
+ .option("-a, --active", "Show only active (non-graduated) positions")
953
+ .option("-g, --graduated", "Show only graduated positions")
741
954
  .addHelpText("after", `
742
955
  Examples:
743
956
  httpcat positions
957
+ httpcat positions --active
958
+ httpcat positions --graduated
744
959
  httpcat --json positions
745
960
  httpcat --private-key 0x... positions
746
961
  `)
@@ -766,7 +981,9 @@ Examples:
766
981
  const userAddress = account.address;
767
982
  // Show which account is being used and verify it matches (if not quiet and not json)
768
983
  if (!globalOpts.quiet && !globalOpts.json) {
769
- const activeIndex = accountIndex !== undefined ? accountIndex : config.getActiveAccountIndex();
984
+ const activeIndex = accountIndex !== undefined
985
+ ? accountIndex
986
+ : config.getActiveAccountIndex();
770
987
  const accounts = config.getAllAccounts();
771
988
  const accountInfo = accounts.find((acc) => acc.index === activeIndex);
772
989
  if (accountInfo) {
@@ -781,17 +998,33 @@ Examples:
781
998
  console.log();
782
999
  }
783
1000
  }
784
- const result = await withLoading(() => getPositions(client, userAddress), {
1001
+ let result = await withLoading(() => getPositions(client, userAddress), {
785
1002
  message: "Fetching positions...",
786
1003
  json: globalOpts.json,
787
1004
  quiet: globalOpts.quiet,
788
1005
  spinner: "cat",
789
1006
  });
1007
+ const filter = command.active
1008
+ ? "active"
1009
+ : command.graduated
1010
+ ? "graduated"
1011
+ : "all";
790
1012
  if (globalOpts.json) {
1013
+ // For JSON output, filter the positions
1014
+ if (filter !== "all") {
1015
+ const filteredPositions = result.positions.filter((p) => filter === "active"
1016
+ ? p.token.status !== "graduated"
1017
+ : p.token.status === "graduated");
1018
+ result = {
1019
+ ...result,
1020
+ positions: filteredPositions,
1021
+ total: filteredPositions.length,
1022
+ };
1023
+ }
791
1024
  outputJson("positions", result);
792
1025
  }
793
1026
  else if (!globalOpts.quiet) {
794
- displayPositions(result);
1027
+ displayPositions(result, filter);
795
1028
  }
796
1029
  process.exit(0);
797
1030
  }
@@ -850,15 +1083,15 @@ Examples:
850
1083
  process.exit(getExitCode(error));
851
1084
  }
852
1085
  });
853
- // Balance command
1086
+ // Balances command
854
1087
  program
855
- .command("balance")
856
- .description("Check wallet balance (ETH and USDC)")
1088
+ .command("balances")
1089
+ .description("Check wallet balances (ETH and USDC)")
857
1090
  .addHelpText("after", `
858
1091
  Examples:
859
- httpcat balance
860
- httpcat balance --private-key 0x...
861
- httpcat --json balance
1092
+ httpcat balances
1093
+ httpcat balances --private-key 0x...
1094
+ httpcat --json balances
862
1095
  `)
863
1096
  .action(async (command) => {
864
1097
  try {
@@ -877,13 +1110,13 @@ Examples:
877
1110
  privateKey = undefined;
878
1111
  }
879
1112
  const result = await withLoading(() => checkBalance(privateKey), {
880
- message: "Checking balance...",
1113
+ message: "Checking balances...",
881
1114
  json: globalOpts.json,
882
1115
  quiet: globalOpts.quiet,
883
1116
  spinner: "cat",
884
1117
  });
885
1118
  if (globalOpts.json) {
886
- outputJson("balance", result);
1119
+ outputJson("balances", result);
887
1120
  }
888
1121
  else if (!globalOpts.quiet) {
889
1122
  displayBalance(result);
@@ -893,7 +1126,7 @@ Examples:
893
1126
  catch (error) {
894
1127
  const globalOpts = command.parent?.opts() || {};
895
1128
  if (globalOpts.json) {
896
- outputError("balance", error, getExitCode(error));
1129
+ outputError("balances", error, getExitCode(error));
897
1130
  }
898
1131
  else {
899
1132
  handleError(error, globalOpts.verbose);
@@ -929,7 +1162,7 @@ Examples:
929
1162
  outputJson("account", result);
930
1163
  }
931
1164
  else if (!globalOpts.quiet) {
932
- displayAccountInfo(result);
1165
+ await displayAccountInfo(result);
933
1166
  }
934
1167
  process.exit(0);
935
1168
  }
@@ -983,31 +1216,6 @@ accountCommand
983
1216
  process.exit(getExitCode(error));
984
1217
  }
985
1218
  });
986
- accountCommand
987
- .command("add")
988
- .description("Add a new seed-derived account")
989
- .addHelpText("after", `
990
- Examples:
991
- httpcat account add # Add a new account from existing seed phrase
992
- httpcat account add # If no seed phrase, prompts to generate/import one
993
- `)
994
- .action(async () => {
995
- try {
996
- const { addAccount } = await import("./commands/account.js");
997
- await addAccount();
998
- process.exit(0);
999
- }
1000
- catch (error) {
1001
- const globalOpts = accountCommand.parent?.opts() || {};
1002
- if (globalOpts.json) {
1003
- outputError("account_add", error, getExitCode(error));
1004
- }
1005
- else {
1006
- handleError(error, globalOpts.verbose);
1007
- }
1008
- process.exit(getExitCode(error));
1009
- }
1010
- });
1011
1219
  accountCommand
1012
1220
  .command("add")
1013
1221
  .description("Add a new seed-derived account")
@@ -1037,7 +1245,7 @@ program
1037
1245
  .command("chat")
1038
1246
  .description("Start streaming chat ($0.01 to join, $0.0001 per message, 10 min lease)")
1039
1247
  .argument("[token]", "Optional: Token symbol, name, or address for token-specific chat")
1040
- .option("--input-format <format>", 'Input format (only works with --json): "text" (default), or "stream-json" (realtime streaming input)', "text")
1248
+ .option("-f, --input-format <format>", 'Input format (only works with --json): "text" (default), or "stream-json" (realtime streaming input)', "text")
1041
1249
  .addHelpText("after", `
1042
1250
  Examples:
1043
1251
  httpcat chat # Join general chat
@@ -1111,6 +1319,14 @@ Configuration:
1111
1319
  .action(async () => {
1112
1320
  await runMcpServer();
1113
1321
  });
1322
+ // Help command
1323
+ program
1324
+ .command("help")
1325
+ .description("Display help for httpcat")
1326
+ .action(() => {
1327
+ program.outputHelp();
1328
+ process.exit(0);
1329
+ });
1114
1330
  // Interactive shell (default when no command)
1115
1331
  program.action(async (options) => {
1116
1332
  try {