httpcat-cli 0.0.27 → 0.1.1-rc.1

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 (70) hide show
  1. package/.github/dependabot.yml +2 -0
  2. package/.github/workflows/README.md +37 -4
  3. package/.github/workflows/ci.yml +31 -20
  4. package/.github/workflows/homebrew-tap.yml +1 -1
  5. package/.github/workflows/publish-switch.yml +41 -0
  6. package/.github/workflows/rc-publish.yml +196 -0
  7. package/.github/workflows/release.yml +267 -85
  8. package/README.md +107 -108
  9. package/bun.lock +2933 -0
  10. package/dist/commands/account.d.ts.map +1 -1
  11. package/dist/commands/account.js +14 -7
  12. package/dist/commands/account.js.map +1 -1
  13. package/dist/commands/balances.d.ts.map +1 -0
  14. package/dist/commands/balances.js +171 -0
  15. package/dist/commands/balances.js.map +1 -0
  16. package/dist/commands/buy.d.ts.map +1 -1
  17. package/dist/commands/buy.js +743 -35
  18. package/dist/commands/buy.js.map +1 -1
  19. package/dist/commands/chat.d.ts.map +1 -1
  20. package/dist/commands/chat.js +467 -906
  21. package/dist/commands/chat.js.map +1 -1
  22. package/dist/commands/claim.d.ts.map +1 -0
  23. package/dist/commands/claim.js +65 -0
  24. package/dist/commands/claim.js.map +1 -0
  25. package/dist/commands/create.d.ts.map +1 -1
  26. package/dist/commands/create.js +0 -1
  27. package/dist/commands/create.js.map +1 -1
  28. package/dist/commands/info.d.ts.map +1 -1
  29. package/dist/commands/info.js +143 -38
  30. package/dist/commands/info.js.map +1 -1
  31. package/dist/commands/list.d.ts.map +1 -1
  32. package/dist/commands/list.js +31 -27
  33. package/dist/commands/list.js.map +1 -1
  34. package/dist/commands/positions.d.ts.map +1 -1
  35. package/dist/commands/positions.js +178 -106
  36. package/dist/commands/positions.js.map +1 -1
  37. package/dist/commands/sell.d.ts.map +1 -1
  38. package/dist/commands/sell.js +720 -28
  39. package/dist/commands/sell.js.map +1 -1
  40. package/dist/index.js +321 -104
  41. package/dist/index.js.map +1 -1
  42. package/dist/interactive/shell.d.ts.map +1 -1
  43. package/dist/interactive/shell.js +328 -179
  44. package/dist/interactive/shell.js.map +1 -1
  45. package/dist/mcp/tools.d.ts.map +1 -1
  46. package/dist/mcp/tools.js +8 -8
  47. package/dist/mcp/tools.js.map +1 -1
  48. package/dist/utils/constants.d.ts.map +1 -0
  49. package/dist/utils/constants.js +66 -0
  50. package/dist/utils/constants.js.map +1 -0
  51. package/dist/utils/formatting.d.ts.map +1 -1
  52. package/dist/utils/formatting.js +3 -5
  53. package/dist/utils/formatting.js.map +1 -1
  54. package/dist/utils/token-resolver.d.ts.map +1 -1
  55. package/dist/utils/token-resolver.js +70 -68
  56. package/dist/utils/token-resolver.js.map +1 -1
  57. package/dist/utils/validation.d.ts.map +1 -1
  58. package/dist/utils/validation.js +4 -3
  59. package/dist/utils/validation.js.map +1 -1
  60. package/jest.config.js +1 -1
  61. package/package.json +19 -13
  62. package/tests/README.md +0 -1
  63. package/.claude/settings.local.json +0 -41
  64. package/dist/commands/balance.d.ts.map +0 -1
  65. package/dist/commands/balance.js +0 -112
  66. package/dist/commands/balance.js.map +0 -1
  67. package/homebrew-httpcat/Formula/httpcat.rb +0 -18
  68. package/homebrew-httpcat/README.md +0 -31
  69. package/homebrew-httpcat/homebrew-httpcat/Formula/httpcat.rb +0 -18
  70. 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,18 +334,19 @@ Examples:
328
334
  // Buy command
329
335
  program
330
336
  .command("buy")
331
- .description("Buy tokens from the bonding curve")
332
- .argument("<identifier>", 'Token identifier: UUID, contract address (0x...), name, or symbol (e.g., 0x1234..., 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
- httpcat buy 0x1234567890123456789012345678901234567890 0.20
339
344
  httpcat buy abc123-4567-89ab-cdef-0123456789ab 0.20
340
345
  httpcat buy "My Token" 0.10
341
- httpcat buy MTK 0.05
342
- httpcat --json buy 0x1234... 0.10
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
349
+ httpcat --json buy abc123-4567-89ab-cdef-0123456789ab 0.10
343
350
  httpcat --private-key 0x... buy "PurrfecTT" 0.20
344
351
  httpcat buy MTK 0.10 --repeat 10
345
352
  httpcat buy MTK 0.10 --repeat 10 --delay 500
@@ -365,6 +372,27 @@ Examples:
365
372
  const client = await HttpcatClient.create(privateKey);
366
373
  const isTestMode = client.getNetwork().includes("sepolia");
367
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
+ }
368
396
  // If repeat is specified, execute multiple buys
369
397
  if (repeatCount && repeatCount > 0) {
370
398
  const results = [];
@@ -377,7 +405,7 @@ Examples:
377
405
  if (!globalOpts.json && !globalOpts.quiet) {
378
406
  console.log(chalk.dim(`Buy ${i}/${repeatCount}...`));
379
407
  }
380
- 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), {
381
409
  message: `Buying tokens (${i}/${repeatCount})...`,
382
410
  json: globalOpts.json,
383
411
  quiet: globalOpts.quiet || i > 1,
@@ -428,7 +456,7 @@ Examples:
428
456
  const lastResult = results[results.length - 1];
429
457
  const graduationStatus = lastResult.graduationReached
430
458
  ? "✅ GRADUATED!"
431
- : `${lastResult.graduationProgress.toFixed(2)}%`;
459
+ : `${(lastResult.graduationProgress || 0).toFixed(2)}%`;
432
460
  console.log();
433
461
  console.log(chalk.cyan.bold("📊 Repeat Buy Summary"));
434
462
  console.log(chalk.dim("─".repeat(50)));
@@ -446,7 +474,7 @@ Examples:
446
474
  }
447
475
  else {
448
476
  // Normal single buy execution
449
- const result = await withLoading(() => buyToken(client, tokenId, amount, isTestMode, silent), {
477
+ const result = await withLoading(() => buyToken(client, tokenId, finalAmount, isTestMode, silent, privateKey), {
450
478
  message: "Buying tokens...",
451
479
  json: globalOpts.json,
452
480
  quiet: globalOpts.quiet,
@@ -475,16 +503,15 @@ Examples:
475
503
  // Sell command
476
504
  program
477
505
  .command("sell")
478
- .description("Sell tokens back to the bonding curve")
479
- .argument("<identifier>", 'Token identifier: UUID, contract address (0x...), name, or symbol (e.g., 0x1234..., 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)')
480
508
  .argument("<amount>", 'Amount: number (e.g., 1000), percentage (e.g., 50%), or "all"')
481
509
  .addHelpText("after", `
482
510
  Examples:
483
- httpcat sell 0x1234567890123456789012345678901234567890 1000
484
511
  httpcat sell abc123-4567-89ab-cdef-0123456789ab 1000
485
512
  httpcat sell "My Token" 50%
486
513
  httpcat sell MTK all
487
- httpcat --json sell 0x1234... 25%
514
+ httpcat --json sell abc123-4567-89ab-cdef-0123456789ab 25%
488
515
  `)
489
516
  .action(async (tokenId, amountInput, command) => {
490
517
  try {
@@ -507,19 +534,99 @@ Examples:
507
534
  // Derive user address from private key
508
535
  const account = privateKeyToAccount(privateKey);
509
536
  const userAddress = account.address;
510
- // Get current holdings (this also validates the identifier)
511
- const info = await withLoading(() => getTokenInfo(client, tokenId, userAddress, silent), {
512
- message: "Checking token info...",
513
- json: globalOpts.json,
514
- quiet: globalOpts.quiet,
515
- spinner: "cat",
516
- });
517
- if (!info.userPosition || info.userPosition.tokensOwned === "0") {
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
+ }
624
+ }
625
+ if (actualBalance === "0") {
518
626
  throw new Error("You do not own any of this token");
519
627
  }
520
- const tokenAmount = parseTokenAmount(amountInput, info.userPosition.tokensOwned);
521
- // Pass the original identifier - sellToken will handle resolution
522
- const result = await withLoading(() => sellToken(client, tokenId, tokenAmount, silent), {
628
+ const tokenAmount = parseTokenAmount(amountInput, actualBalance);
629
+ const result = await withLoading(() => sellToken(client, tokenId, tokenAmount, silent, privateKey), {
523
630
  message: "Selling tokens...",
524
631
  json: globalOpts.json,
525
632
  quiet: globalOpts.quiet,
@@ -544,18 +651,95 @@ Examples:
544
651
  process.exit(getExitCode(error));
545
652
  }
546
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
+ });
547
732
  // Info command
548
733
  program
549
734
  .command("info")
550
735
  .description("Get detailed information about a token")
551
- .argument("<identifier>", 'Token identifier: UUID, contract address (0x...), name, or symbol (e.g., 0x1234..., abc123-..., "My Token", or MTK)')
736
+ .argument("<identifier>", 'Address, name, or symbol (e.g., 0x1234..., "My Token", or MTK)')
552
737
  .addHelpText("after", `
553
738
  Examples:
554
- httpcat info 0x1234567890123456789012345678901234567890
555
739
  httpcat info abc123-4567-89ab-cdef-0123456789ab
556
740
  httpcat info "My Token"
557
741
  httpcat info MTK
558
- httpcat --json info 0x1234...
742
+ httpcat --json info "PurrfecTT"
559
743
  `)
560
744
  .action(async (tokenId, command) => {
561
745
  try {
@@ -588,7 +772,7 @@ Examples:
588
772
  outputJson("token_info", result);
589
773
  }
590
774
  else if (!globalOpts.quiet) {
591
- displayTokenInfo(result);
775
+ await displayTokenInfo(result, userAddress);
592
776
  }
593
777
  process.exit(0);
594
778
  }
@@ -607,9 +791,9 @@ Examples:
607
791
  program
608
792
  .command("list")
609
793
  .description("List all tokens with pagination and sorting")
610
- .option("--page <number>", "Page number (default: 1)", "1")
611
- .option("--limit <number>", "Items per page (default: 20, max: 100)", "20")
612
- .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")
613
797
  .addHelpText("after", `
614
798
  Examples:
615
799
  httpcat list
@@ -662,18 +846,18 @@ Examples:
662
846
  // Transactions command
663
847
  program
664
848
  .command("transactions")
665
- .description("Get paginated list of transactions with optional filtering")
666
- .option("--user <address>", "Filter by user address")
667
- .option("--token <tokenId>", "Filter by token ID")
668
- .option("--type <type>", "Filter by type: buy, sell, or airdrop")
669
- .option("--limit <number>", "Number of results (default: 50, max: 100)", "50")
670
- .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")
671
855
  .addHelpText("after", `
672
856
  Examples:
673
- httpcat transactions
674
- httpcat transactions --user 0x1234...
675
- httpcat transactions --token abc123-...
676
- 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
677
861
  httpcat --json transactions --user 0x1234... --offset 50
678
862
  `)
679
863
  .action(async (options, command) => {
@@ -693,9 +877,37 @@ Examples:
693
877
  privateKey = await promptForPrivateKey();
694
878
  }
695
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
+ }
696
902
  const input = {};
697
- if (options.user)
903
+ // Use selected account's address by default, unless --user is explicitly provided
904
+ if (options.user) {
698
905
  input.userAddress = options.user;
906
+ }
907
+ else {
908
+ // Default to selected account's address
909
+ input.userAddress = userAddress;
910
+ }
699
911
  if (options.token)
700
912
  input.tokenId = options.token;
701
913
  if (options.type) {
@@ -737,9 +949,13 @@ Examples:
737
949
  program
738
950
  .command("positions")
739
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")
740
954
  .addHelpText("after", `
741
955
  Examples:
742
956
  httpcat positions
957
+ httpcat positions --active
958
+ httpcat positions --graduated
743
959
  httpcat --json positions
744
960
  httpcat --private-key 0x... positions
745
961
  `)
@@ -765,7 +981,9 @@ Examples:
765
981
  const userAddress = account.address;
766
982
  // Show which account is being used and verify it matches (if not quiet and not json)
767
983
  if (!globalOpts.quiet && !globalOpts.json) {
768
- const activeIndex = accountIndex !== undefined ? accountIndex : config.getActiveAccountIndex();
984
+ const activeIndex = accountIndex !== undefined
985
+ ? accountIndex
986
+ : config.getActiveAccountIndex();
769
987
  const accounts = config.getAllAccounts();
770
988
  const accountInfo = accounts.find((acc) => acc.index === activeIndex);
771
989
  if (accountInfo) {
@@ -780,17 +998,33 @@ Examples:
780
998
  console.log();
781
999
  }
782
1000
  }
783
- const result = await withLoading(() => getPositions(client, userAddress), {
1001
+ let result = await withLoading(() => getPositions(client, userAddress), {
784
1002
  message: "Fetching positions...",
785
1003
  json: globalOpts.json,
786
1004
  quiet: globalOpts.quiet,
787
1005
  spinner: "cat",
788
1006
  });
1007
+ const filter = command.active
1008
+ ? "active"
1009
+ : command.graduated
1010
+ ? "graduated"
1011
+ : "all";
789
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
+ }
790
1024
  outputJson("positions", result);
791
1025
  }
792
1026
  else if (!globalOpts.quiet) {
793
- displayPositions(result);
1027
+ displayPositions(result, filter);
794
1028
  }
795
1029
  process.exit(0);
796
1030
  }
@@ -849,15 +1083,15 @@ Examples:
849
1083
  process.exit(getExitCode(error));
850
1084
  }
851
1085
  });
852
- // Balance command
1086
+ // Balances command
853
1087
  program
854
- .command("balance")
855
- .description("Check wallet balance (ETH and USDC)")
1088
+ .command("balances")
1089
+ .description("Check wallet balances (ETH and USDC)")
856
1090
  .addHelpText("after", `
857
1091
  Examples:
858
- httpcat balance
859
- httpcat balance --private-key 0x...
860
- httpcat --json balance
1092
+ httpcat balances
1093
+ httpcat balances --private-key 0x...
1094
+ httpcat --json balances
861
1095
  `)
862
1096
  .action(async (command) => {
863
1097
  try {
@@ -876,13 +1110,13 @@ Examples:
876
1110
  privateKey = undefined;
877
1111
  }
878
1112
  const result = await withLoading(() => checkBalance(privateKey), {
879
- message: "Checking balance...",
1113
+ message: "Checking balances...",
880
1114
  json: globalOpts.json,
881
1115
  quiet: globalOpts.quiet,
882
1116
  spinner: "cat",
883
1117
  });
884
1118
  if (globalOpts.json) {
885
- outputJson("balance", result);
1119
+ outputJson("balances", result);
886
1120
  }
887
1121
  else if (!globalOpts.quiet) {
888
1122
  displayBalance(result);
@@ -892,7 +1126,7 @@ Examples:
892
1126
  catch (error) {
893
1127
  const globalOpts = command.parent?.opts() || {};
894
1128
  if (globalOpts.json) {
895
- outputError("balance", error, getExitCode(error));
1129
+ outputError("balances", error, getExitCode(error));
896
1130
  }
897
1131
  else {
898
1132
  handleError(error, globalOpts.verbose);
@@ -928,7 +1162,7 @@ Examples:
928
1162
  outputJson("account", result);
929
1163
  }
930
1164
  else if (!globalOpts.quiet) {
931
- displayAccountInfo(result);
1165
+ await displayAccountInfo(result);
932
1166
  }
933
1167
  process.exit(0);
934
1168
  }
@@ -982,31 +1216,6 @@ accountCommand
982
1216
  process.exit(getExitCode(error));
983
1217
  }
984
1218
  });
985
- accountCommand
986
- .command("add")
987
- .description("Add a new seed-derived account")
988
- .addHelpText("after", `
989
- Examples:
990
- httpcat account add # Add a new account from existing seed phrase
991
- httpcat account add # If no seed phrase, prompts to generate/import one
992
- `)
993
- .action(async () => {
994
- try {
995
- const { addAccount } = await import("./commands/account.js");
996
- await addAccount();
997
- process.exit(0);
998
- }
999
- catch (error) {
1000
- const globalOpts = accountCommand.parent?.opts() || {};
1001
- if (globalOpts.json) {
1002
- outputError("account_add", error, getExitCode(error));
1003
- }
1004
- else {
1005
- handleError(error, globalOpts.verbose);
1006
- }
1007
- process.exit(getExitCode(error));
1008
- }
1009
- });
1010
1219
  accountCommand
1011
1220
  .command("add")
1012
1221
  .description("Add a new seed-derived account")
@@ -1035,16 +1244,16 @@ Examples:
1035
1244
  program
1036
1245
  .command("chat")
1037
1246
  .description("Start streaming chat ($0.01 to join, $0.0001 per message, 10 min lease)")
1038
- .argument("[token]", "Optional: Token identifier (UUID, contract address 0x..., symbol, or name) for token-specific chat")
1039
- .option("--input-format <format>", 'Input format (only works with --json): "text" (default), or "stream-json" (realtime streaming input)', "text")
1247
+ .argument("[token]", "Optional: Token symbol, name, or address for token-specific chat")
1248
+ .option("-f, --input-format <format>", 'Input format (only works with --json): "text" (default), or "stream-json" (realtime streaming input)', "text")
1040
1249
  .addHelpText("after", `
1041
1250
  Examples:
1042
1251
  httpcat chat # Join general chat
1043
- httpcat chat 0x1234567890123456789012345678901234567890 # Join chat for token at address
1044
1252
  httpcat chat MTK # Join chat for token with symbol "MTK"
1045
1253
  httpcat chat "My Token" # Join chat for token with name "My Token"
1254
+ httpcat chat 0x1234... # Join chat for token at address
1046
1255
  httpcat --private-key 0x... chat
1047
- httpcat --json chat 0x1234...
1256
+ httpcat --json chat MTK
1048
1257
  httpcat --json --input-format stream-json chat MTK # Structured JSON input
1049
1258
  `)
1050
1259
  .action(async (tokenIdentifier, options, command) => {
@@ -1110,6 +1319,14 @@ Configuration:
1110
1319
  .action(async () => {
1111
1320
  await runMcpServer();
1112
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
+ });
1113
1330
  // Interactive shell (default when no command)
1114
1331
  program.action(async (options) => {
1115
1332
  try {