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.
- package/.github/workflows/sync-version.yml +19 -3
- package/README.md +346 -13
- package/Screenshot 2025-12-21 at 8.56.02/342/200/257PM.png +0 -0
- package/bun.lock +8 -1
- package/cat-spin.sh +417 -0
- package/dist/agent/ax-agent.d.ts.map +1 -0
- package/dist/agent/ax-agent.js +459 -0
- package/dist/agent/ax-agent.js.map +1 -0
- package/dist/agent/llm-factory.d.ts.map +1 -0
- package/dist/agent/llm-factory.js +82 -0
- package/dist/agent/llm-factory.js.map +1 -0
- package/dist/agent/setup-wizard.d.ts.map +1 -0
- package/dist/agent/setup-wizard.js +114 -0
- package/dist/agent/setup-wizard.js.map +1 -0
- package/dist/agent/tools.d.ts.map +1 -0
- package/dist/agent/tools.js +312 -0
- package/dist/agent/tools.js.map +1 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +18 -0
- package/dist/client.js.map +1 -1
- package/dist/commands/balances.d.ts.map +1 -1
- package/dist/commands/balances.js +43 -41
- package/dist/commands/balances.js.map +1 -1
- package/dist/commands/chat.d.ts.map +1 -1
- package/dist/commands/chat.js +56 -46
- package/dist/commands/chat.js.map +1 -1
- package/dist/commands/create.d.ts.map +1 -1
- package/dist/commands/create.js +133 -5
- package/dist/commands/create.js.map +1 -1
- package/dist/commands/positions.d.ts.map +1 -1
- package/dist/commands/positions.js +51 -54
- package/dist/commands/positions.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +296 -20
- package/dist/config.js.map +1 -1
- package/dist/index.js +316 -15
- package/dist/index.js.map +1 -1
- package/dist/interactive/cat-spin.d.ts.map +1 -0
- package/dist/interactive/cat-spin.js +448 -0
- package/dist/interactive/cat-spin.js.map +1 -0
- package/dist/interactive/shell.d.ts.map +1 -1
- package/dist/interactive/shell.js +2001 -180
- package/dist/interactive/shell.js.map +1 -1
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +1 -6
- package/dist/mcp/tools.js.map +1 -1
- package/dist/mcp/types.d.ts.map +1 -1
- package/dist/utils/loading.d.ts.map +1 -1
- package/dist/utils/loading.js +30 -0
- package/dist/utils/loading.js.map +1 -1
- package/dist/utils/privateKeyPrompt.d.ts.map +1 -1
- package/dist/utils/privateKeyPrompt.js +13 -9
- package/dist/utils/privateKeyPrompt.js.map +1 -1
- package/dist/utils/token-resolver.d.ts.map +1 -1
- package/dist/utils/token-resolver.js +32 -0
- package/dist/utils/token-resolver.js.map +1 -1
- 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
|
|
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:
|
|
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
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
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
|
|
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
|
}
|