httpcat-cli 0.2.11 → 0.2.12-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 (57) hide show
  1. package/.github/workflows/sync-version.yml +19 -3
  2. package/README.md +346 -13
  3. package/Screenshot 2025-12-21 at 8.56.02/342/200/257PM.png +0 -0
  4. package/bun.lock +8 -1
  5. package/cat-spin.sh +417 -0
  6. package/dist/agent/ax-agent.d.ts.map +1 -0
  7. package/dist/agent/ax-agent.js +459 -0
  8. package/dist/agent/ax-agent.js.map +1 -0
  9. package/dist/agent/llm-factory.d.ts.map +1 -0
  10. package/dist/agent/llm-factory.js +82 -0
  11. package/dist/agent/llm-factory.js.map +1 -0
  12. package/dist/agent/setup-wizard.d.ts.map +1 -0
  13. package/dist/agent/setup-wizard.js +114 -0
  14. package/dist/agent/setup-wizard.js.map +1 -0
  15. package/dist/agent/tools.d.ts.map +1 -0
  16. package/dist/agent/tools.js +312 -0
  17. package/dist/agent/tools.js.map +1 -0
  18. package/dist/client.d.ts.map +1 -1
  19. package/dist/client.js +18 -0
  20. package/dist/client.js.map +1 -1
  21. package/dist/commands/balances.d.ts.map +1 -1
  22. package/dist/commands/balances.js +43 -41
  23. package/dist/commands/balances.js.map +1 -1
  24. package/dist/commands/chat.d.ts.map +1 -1
  25. package/dist/commands/chat.js +56 -46
  26. package/dist/commands/chat.js.map +1 -1
  27. package/dist/commands/create.d.ts.map +1 -1
  28. package/dist/commands/create.js +133 -5
  29. package/dist/commands/create.js.map +1 -1
  30. package/dist/commands/positions.d.ts.map +1 -1
  31. package/dist/commands/positions.js +51 -54
  32. package/dist/commands/positions.js.map +1 -1
  33. package/dist/config.d.ts.map +1 -1
  34. package/dist/config.js +296 -20
  35. package/dist/config.js.map +1 -1
  36. package/dist/index.js +316 -15
  37. package/dist/index.js.map +1 -1
  38. package/dist/interactive/cat-spin.d.ts.map +1 -0
  39. package/dist/interactive/cat-spin.js +448 -0
  40. package/dist/interactive/cat-spin.js.map +1 -0
  41. package/dist/interactive/shell.d.ts.map +1 -1
  42. package/dist/interactive/shell.js +2001 -180
  43. package/dist/interactive/shell.js.map +1 -1
  44. package/dist/mcp/tools.d.ts.map +1 -1
  45. package/dist/mcp/tools.js +1 -6
  46. package/dist/mcp/tools.js.map +1 -1
  47. package/dist/mcp/types.d.ts.map +1 -1
  48. package/dist/utils/loading.d.ts.map +1 -1
  49. package/dist/utils/loading.js +30 -0
  50. package/dist/utils/loading.js.map +1 -1
  51. package/dist/utils/privateKeyPrompt.d.ts.map +1 -1
  52. package/dist/utils/privateKeyPrompt.js +13 -9
  53. package/dist/utils/privateKeyPrompt.js.map +1 -1
  54. package/dist/utils/token-resolver.d.ts.map +1 -1
  55. package/dist/utils/token-resolver.js +32 -0
  56. package/dist/utils/token-resolver.js.map +1 -1
  57. package/package.json +3 -1
package/dist/index.js CHANGED
@@ -8,12 +8,14 @@ import { formatAddress, formatCurrency } from "./utils/formatting.js";
8
8
  import { HttpcatClient, HttpcatError } from "./client.js";
9
9
  import { handleError, getExitCode } from "./utils/errors.js";
10
10
  import { startInteractiveShell } from "./interactive/shell.js";
11
+ import { runCatSpinAnimation } from "./interactive/cat-spin.js";
11
12
  import { outputJson, outputError } from "./headless/json-output.js";
12
13
  import { promptForPrivateKey } from "./utils/privateKeyPrompt.js";
13
14
  import { withLoading } from "./utils/loading.js";
14
- import { readFileSync } from "fs";
15
+ import { readFileSync, writeFileSync, existsSync, statSync, unlinkSync, readlinkSync, } from "fs";
15
16
  import { fileURLToPath } from "url";
16
17
  import { dirname, join } from "path";
18
+ import { tmpdir } from "os";
17
19
  // Get version from package.json
18
20
  const __filename = fileURLToPath(import.meta.url);
19
21
  const __dirname = dirname(__filename);
@@ -22,7 +24,7 @@ const VERSION = packageJson.version;
22
24
  const require = createRequire(import.meta.url);
23
25
  const { version: PACKAGE_VERSION } = require("../package.json");
24
26
  // Import commands
25
- import { createToken, displayCreateResult } from "./commands/create.js";
27
+ import { createToken, displayCreateResult, processPhotoUrl, isFilePath, } from "./commands/create.js";
26
28
  import { buyToken, displayBuyResult, displayBuyResultCompact, } from "./commands/buy.js";
27
29
  import { sellToken, displaySellResult, parseTokenAmount, } from "./commands/sell.js";
28
30
  import { getTokenInfo, displayTokenInfo } from "./commands/info.js";
@@ -273,18 +275,31 @@ program
273
275
  .description("Create a new token")
274
276
  .argument("<name>", 'Token name (e.g., "My Token")')
275
277
  .argument("<symbol>", 'Token symbol/ticker (e.g., "MTK", 3-10 characters)')
276
- .option("-p, --photo <url>", "Photo URL for token logo")
277
- .option("-b, --banner <url>", "Banner image URL")
278
+ .option("-p, --photo <url|path>", "Photo URL or file path for token logo")
278
279
  .option("-w, --website <url>", "Website URL")
279
280
  .addHelpText("after", `
280
281
  Examples:
281
282
  httpcat create "My Token" "MTK"
282
283
  httpcat create "Moon Cat" "MOON" --website https://mooncat.io
283
- httpcat create "Test" "TEST" --photo https://example.com/logo.png --banner https://example.com/banner.png
284
+ httpcat create "Test" "TEST" --photo https://example.com/logo.png
285
+ httpcat create "Test" "TEST" --photo ./logo.png
284
286
  httpcat --json create "AI Token" "AI"
285
287
  `)
286
288
  .action(async (name, symbol, options, command) => {
287
289
  try {
290
+ // Fallback: get arguments from command object if not provided as parameters
291
+ // This handles cases where Commander.js doesn't parse arguments correctly
292
+ // In Commander.js v9+, use processedArgs; fallback to args for older versions
293
+ const args = command.processedArgs || command.args || [];
294
+ const actualName = name || args[0];
295
+ const actualSymbol = symbol || args[1];
296
+ // Validate required arguments
297
+ if (!actualName || typeof actualName !== "string") {
298
+ throw new Error("Token name is required. Usage: httpcat create <name> <symbol> [options]");
299
+ }
300
+ if (!actualSymbol || typeof actualSymbol !== "string") {
301
+ throw new Error("Token symbol is required. Usage: httpcat create <name> <symbol> [options]");
302
+ }
288
303
  const globalOpts = command.parent?.opts() || {};
289
304
  const accountIndex = globalOpts.account;
290
305
  // Ensure wallet is unlocked
@@ -300,11 +315,21 @@ Examples:
300
315
  privateKey = await promptForPrivateKey();
301
316
  }
302
317
  const client = await HttpcatClient.create(privateKey);
318
+ // Process photo if it's a file path (show loading state)
319
+ let processedPhotoUrl = options.photo;
320
+ if (options.photo && isFilePath(options.photo)) {
321
+ processedPhotoUrl = await withLoading(async () => processPhotoUrl(options.photo), {
322
+ message: "Uploading image...",
323
+ json: globalOpts.json,
324
+ quiet: globalOpts.quiet,
325
+ spinner: "cat",
326
+ });
327
+ }
328
+ // Create token (show loading state)
303
329
  const result = await withLoading(() => createToken(client, {
304
- name,
305
- symbol,
306
- photoUrl: options.photo,
307
- bannerUrl: options.banner,
330
+ name: actualName.trim(),
331
+ symbol: actualSymbol.trim(),
332
+ photoUrl: processedPhotoUrl,
308
333
  websiteUrl: options.website,
309
334
  }), {
310
335
  message: "Creating token...",
@@ -444,6 +469,38 @@ Examples:
444
469
  if (!globalOpts.json && !globalOpts.quiet) {
445
470
  console.log();
446
471
  console.log(chalk.yellow("💡 Insufficient funds. Stopping buy loop."));
472
+ console.log();
473
+ // Show current balance to help diagnose the issue
474
+ try {
475
+ const balance = await checkBalance(privateKey, true);
476
+ console.log(chalk.cyan("💰 Current Wallet Balances:"));
477
+ console.log(chalk.dim(` ETH: ${balance.ethFormatted}`));
478
+ console.log(chalk.dim(` USDC: ${balance.usdcFormatted}`));
479
+ if (balance.cat402Formatted) {
480
+ console.log(chalk.dim(` CAT: ${balance.cat402Formatted}`));
481
+ }
482
+ console.log();
483
+ // Provide guidance based on what might be insufficient
484
+ const usdcBalance = parseFloat(balance.usdcFormatted.replace("$", "").replace(",", ""));
485
+ const ethBalance = parseFloat(balance.ethFormatted.replace(" ETH", "").replace(",", ""));
486
+ const buyAmount = parseFloat(finalAmount);
487
+ console.log(chalk.yellow("💡 Possible issues:"));
488
+ if (usdcBalance < buyAmount) {
489
+ console.log(chalk.dim(` • Insufficient USDC for purchase: Need $${buyAmount.toFixed(6)}, have $${usdcBalance.toFixed(6)}`));
490
+ }
491
+ if (usdcBalance < 0.01) {
492
+ console.log(chalk.dim(` • Low USDC for API payments: x402 protocol requires USDC for API calls`));
493
+ }
494
+ if (ethBalance < 0.001) {
495
+ console.log(chalk.dim(` • Low ETH for gas: Need ETH to pay for transaction fees`));
496
+ }
497
+ console.log();
498
+ console.log(chalk.dim(" Check your balance with: httpcat balances"));
499
+ }
500
+ catch (balanceError) {
501
+ // If we can't fetch balance, just show generic message
502
+ console.log(chalk.dim(" Check your balance with: httpcat balances"));
503
+ }
447
504
  }
448
505
  break;
449
506
  }
@@ -1276,10 +1333,16 @@ Examples:
1276
1333
  privateKey = await promptForPrivateKey();
1277
1334
  }
1278
1335
  const client = await HttpcatClient.create(privateKey);
1279
- const inputFormat = globalOpts.json
1280
- ? chatOpts.inputFormat || "text"
1281
- : "text";
1282
- await startChatStream(client, globalOpts.json || false, tokenIdentifier || undefined, inputFormat);
1336
+ // If JSON mode, use the original chat stream (for non-interactive usage)
1337
+ if (globalOpts.json) {
1338
+ const inputFormat = chatOpts.inputFormat || "text";
1339
+ await startChatStream(client, true, // jsonMode
1340
+ tokenIdentifier || undefined, inputFormat);
1341
+ }
1342
+ else {
1343
+ // Interactive mode: launch shell and auto-enter chat
1344
+ await startInteractiveShell(client, tokenIdentifier || undefined);
1345
+ }
1283
1346
  }
1284
1347
  catch (error) {
1285
1348
  const globalOpts = command.parent?.opts() || {};
@@ -1292,6 +1355,54 @@ Examples:
1292
1355
  process.exit(getExitCode(error));
1293
1356
  }
1294
1357
  });
1358
+ // Agent command
1359
+ program
1360
+ .command("agent")
1361
+ .alias("cat")
1362
+ .description("Configure AI agent for trading assistance")
1363
+ .option("-s, --setup", "Run setup wizard to configure API key")
1364
+ .addHelpText("after", `
1365
+ Examples:
1366
+ httpcat agent --setup # Configure AI agent
1367
+ httpcat cat --setup # Same, using 'cat' alias
1368
+
1369
+ The AI agent helps you:
1370
+ • Trade tokens using natural language
1371
+ • Check balances and positions
1372
+ • Get market information
1373
+ • Execute commands hands-free
1374
+
1375
+ You can use 'agent' or 'cat' interchangeably.
1376
+ `)
1377
+ .action(async (options) => {
1378
+ try {
1379
+ const { setupAIAgentWizard } = await import("./agent/setup-wizard.js");
1380
+ if (options.setup) {
1381
+ await setupAIAgentWizard();
1382
+ process.exit(0);
1383
+ }
1384
+ else {
1385
+ // Show help for agent command
1386
+ console.log();
1387
+ console.log(chalk.cyan.bold("🐱 AI Agent"));
1388
+ console.log();
1389
+ console.log(chalk.yellow("The AI agent feature is currently available only in interactive mode."));
1390
+ console.log();
1391
+ console.log(chalk.bold("To configure:"));
1392
+ console.log(chalk.dim(" httpcat agent --setup"));
1393
+ console.log();
1394
+ console.log(chalk.bold("Note:"));
1395
+ console.log(chalk.dim(" The agent command has been temporarily disabled in interactive mode."));
1396
+ console.log(chalk.dim(" Use the CLI setup above to configure your API keys for future use."));
1397
+ console.log();
1398
+ process.exit(0);
1399
+ }
1400
+ }
1401
+ catch (error) {
1402
+ handleError(error, program.opts().verbose);
1403
+ process.exit(getExitCode(error));
1404
+ }
1405
+ });
1295
1406
  // MCP Server command
1296
1407
  program
1297
1408
  .command("mcp-server")
@@ -1305,7 +1416,7 @@ The server communicates via stdio and can be used with MCP clients like Cursor o
1305
1416
 
1306
1417
  Configuration:
1307
1418
  Add to your MCP client config (e.g., Cursor settings):
1308
-
1419
+
1309
1420
  {
1310
1421
  "mcpServers": {
1311
1422
  "httpcat": {
@@ -1321,14 +1432,182 @@ Configuration:
1321
1432
  .action(async () => {
1322
1433
  await runMcpServer();
1323
1434
  });
1435
+ // Custom help formatting with grouped commands
1436
+ function displayGroupedHelp(program) {
1437
+ console.log();
1438
+ console.log(chalk.cyan.bold("httpcat - CLI tool for interacting with httpcat agent"));
1439
+ console.log();
1440
+ console.log(chalk.bold("Usage:"));
1441
+ console.log(chalk.dim(" httpcat [options] <command>"));
1442
+ console.log();
1443
+ console.log(chalk.bold("Global Options:"));
1444
+ program.options.forEach((opt) => {
1445
+ const flags = opt.flags;
1446
+ const description = opt.description || "";
1447
+ console.log(` ${chalk.green(flags.padEnd(30))} ${chalk.dim(description)}`);
1448
+ });
1449
+ console.log();
1450
+ // Group commands
1451
+ const commandGroups = {
1452
+ "Token Operations": ["create", "buy", "sell", "claim"],
1453
+ "Token Information": ["info", "list"],
1454
+ Portfolio: ["positions", "transactions", "balances"],
1455
+ "Account Management": ["account", "env"],
1456
+ "AI & Social": ["agent", "chat"],
1457
+ System: ["health", "config", "mcp-server"],
1458
+ };
1459
+ // Display grouped commands
1460
+ for (const [groupName, commandNames] of Object.entries(commandGroups)) {
1461
+ const commands = program.commands.filter((cmd) => commandNames.includes(cmd.name()));
1462
+ if (commands.length > 0) {
1463
+ console.log(chalk.bold(chalk.cyan(`${groupName}:`)));
1464
+ commands.forEach((cmd) => {
1465
+ // Build usage string from command name and arguments
1466
+ const args = cmd.registeredArguments
1467
+ .map((arg) => {
1468
+ const nameOut = arg.name() + (arg.variadic ? "..." : "");
1469
+ return arg.required ? `<${nameOut}>` : `[${nameOut}]`;
1470
+ })
1471
+ .join(" ");
1472
+ const usage = args ? `${cmd.name()} ${args}` : cmd.name();
1473
+ const description = cmd.description() || "";
1474
+ console.log(` ${chalk.green(usage.padEnd(30))} ${chalk.dim(description)}`);
1475
+ });
1476
+ console.log();
1477
+ }
1478
+ }
1479
+ // Show subcommands for account and env
1480
+ const accountCmd = program.commands.find((c) => c.name() === "account");
1481
+ if (accountCmd) {
1482
+ const subcommands = accountCmd.commands;
1483
+ if (subcommands.length > 0) {
1484
+ console.log(chalk.bold(chalk.cyan("Account Subcommands:")));
1485
+ subcommands.forEach((cmd) => {
1486
+ const args = cmd.registeredArguments
1487
+ .map((arg) => {
1488
+ const nameOut = arg.name() + (arg.variadic ? "..." : "");
1489
+ return arg.required ? `<${nameOut}>` : `[${nameOut}]`;
1490
+ })
1491
+ .join(" ");
1492
+ const usage = args
1493
+ ? `account ${cmd.name()} ${args}`
1494
+ : `account ${cmd.name()}`;
1495
+ const description = cmd.description() || "";
1496
+ console.log(` ${chalk.green(usage.padEnd(30))} ${chalk.dim(description)}`);
1497
+ });
1498
+ console.log();
1499
+ }
1500
+ }
1501
+ const envCmd = program.commands.find((c) => c.name() === "env");
1502
+ if (envCmd) {
1503
+ const subcommands = envCmd.commands;
1504
+ if (subcommands.length > 0) {
1505
+ console.log(chalk.bold(chalk.cyan("Environment Subcommands:")));
1506
+ subcommands.forEach((cmd) => {
1507
+ const args = cmd.registeredArguments
1508
+ .map((arg) => {
1509
+ const nameOut = arg.name() + (arg.variadic ? "..." : "");
1510
+ return arg.required ? `<${nameOut}>` : `[${nameOut}]`;
1511
+ })
1512
+ .join(" ");
1513
+ const usage = args ? `env ${cmd.name()} ${args}` : `env ${cmd.name()}`;
1514
+ const description = cmd.description() || "";
1515
+ console.log(` ${chalk.green(usage.padEnd(30))} ${chalk.dim(description)}`);
1516
+ });
1517
+ console.log();
1518
+ }
1519
+ }
1520
+ console.log(chalk.dim("Run 'httpcat <command> --help' for more information on a command."));
1521
+ console.log();
1522
+ }
1523
+ // Override the default help output using configureHelp
1524
+ program.configureHelp({
1525
+ commandUsage: (cmd) => {
1526
+ const name = cmd.name();
1527
+ const args = cmd.registeredArguments
1528
+ .map((arg) => {
1529
+ const nameOut = arg.name() + (arg.variadic ? "..." : "");
1530
+ return arg.required ? `<${nameOut}>` : `[${nameOut}]`;
1531
+ })
1532
+ .join(" ");
1533
+ return `${cmd.parent?.name() || "httpcat"} ${name}${args ? ` ${args}` : ""}`;
1534
+ },
1535
+ subcommandTerm: (cmd) => cmd.name(),
1536
+ });
1324
1537
  // Help command
1325
1538
  program
1326
1539
  .command("help")
1327
1540
  .description("Display help for httpcat")
1328
1541
  .action(() => {
1329
- program.outputHelp();
1542
+ displayGroupedHelp(program);
1330
1543
  process.exit(0);
1331
1544
  });
1545
+ // Helper function to get a unique terminal session identifier
1546
+ function getTerminalSessionId() {
1547
+ // Try to get terminal TTY device (most unique per terminal window)
1548
+ // This works on Linux/Unix systems
1549
+ try {
1550
+ if (process.stdin.isTTY && process.stdin.fd !== undefined) {
1551
+ const ttyPath = readlinkSync(`/proc/self/fd/${process.stdin.fd}`).replace(/^\/dev\//, "");
1552
+ if (ttyPath && ttyPath !== "stdin") {
1553
+ return `${process.env.USER || "default"}-${ttyPath}`;
1554
+ }
1555
+ }
1556
+ }
1557
+ catch {
1558
+ // Fall through to other methods (e.g., on macOS or if /proc doesn't exist)
1559
+ }
1560
+ // Fall back to environment variables or user/term combination
1561
+ return (process.env.TERM_SESSION_ID ||
1562
+ process.env.SESSION_ID ||
1563
+ `${process.env.USER || "default"}-${process.env.TERM || "unknown"}`);
1564
+ }
1565
+ // Helper function to check if animation has been shown in this terminal session
1566
+ function hasAnimationBeenShown() {
1567
+ // Check environment variable first (user can set this manually)
1568
+ if (process.env.HTTPCAT_ANIMATION_SHOWN === "1") {
1569
+ return true;
1570
+ }
1571
+ // Check for marker file in temp directory
1572
+ const sessionId = getTerminalSessionId();
1573
+ const markerFile = join(tmpdir(), `httpcat-animation-${sessionId.replace(/[^a-zA-Z0-9]/g, "-")}.marker`);
1574
+ if (existsSync(markerFile)) {
1575
+ // Check if file is recent (within last hour) to handle stale files
1576
+ try {
1577
+ const stats = statSync(markerFile);
1578
+ const ageMs = Date.now() - stats.mtimeMs;
1579
+ const oneHour = 60 * 60 * 1000;
1580
+ if (ageMs < oneHour) {
1581
+ return true;
1582
+ }
1583
+ // File is stale, remove it
1584
+ try {
1585
+ unlinkSync(markerFile);
1586
+ }
1587
+ catch {
1588
+ // Ignore errors when removing stale file
1589
+ }
1590
+ }
1591
+ catch {
1592
+ // If we can't read the file, assume animation hasn't been shown
1593
+ }
1594
+ }
1595
+ return false;
1596
+ }
1597
+ // Helper function to mark animation as shown
1598
+ function markAnimationAsShown() {
1599
+ // Set environment variable for current process
1600
+ process.env.HTTPCAT_ANIMATION_SHOWN = "1";
1601
+ // Create marker file for terminal session persistence
1602
+ try {
1603
+ const sessionId = getTerminalSessionId();
1604
+ const markerFile = join(tmpdir(), `httpcat-animation-${sessionId.replace(/[^a-zA-Z0-9]/g, "-")}.marker`);
1605
+ writeFileSync(markerFile, Date.now().toString(), { flag: "w" });
1606
+ }
1607
+ catch {
1608
+ // If we can't write the marker file, that's okay - env var will still work for this process
1609
+ }
1610
+ }
1332
1611
  // Interactive shell (default when no command)
1333
1612
  program.action(async (options) => {
1334
1613
  try {
@@ -1343,6 +1622,28 @@ program.action(async (options) => {
1343
1622
  await config.runSetupWizard();
1344
1623
  console.log();
1345
1624
  }
1625
+ // Run cat spin animation if conditions are met
1626
+ // Only show animation once per terminal session
1627
+ const animationShown = hasAnimationBeenShown();
1628
+ const shouldShowAnimation = !animationShown &&
1629
+ isConfigured(options.privateKey) &&
1630
+ options.art !== false &&
1631
+ !options.json &&
1632
+ !options.quiet;
1633
+ if (shouldShowAnimation) {
1634
+ const prefs = config.get("preferences");
1635
+ if (prefs?.enableAsciiArt !== false) {
1636
+ try {
1637
+ await runCatSpinAnimation();
1638
+ // Mark animation as shown for this terminal session
1639
+ markAnimationAsShown();
1640
+ }
1641
+ catch (error) {
1642
+ // If animation is interrupted or fails, continue to shell
1643
+ // Don't show error to user, just proceed
1644
+ }
1645
+ }
1646
+ }
1346
1647
  const client = await HttpcatClient.create(privateKey);
1347
1648
  await startInteractiveShell(client);
1348
1649
  }