httpcat-cli 0.0.25 → 0.0.27-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.
- package/.github/workflows/README.md +19 -2
- package/.github/workflows/ci.yml +31 -20
- package/.github/workflows/homebrew-tap.yml +1 -1
- package/.github/workflows/rc-publish.yml +169 -0
- package/.github/workflows/release.yml +223 -71
- package/README.md +113 -71
- package/bun.lock +2933 -0
- package/dist/commands/account.d.ts.map +1 -1
- package/dist/commands/account.js +14 -7
- package/dist/commands/account.js.map +1 -1
- package/dist/commands/balances.d.ts.map +1 -0
- package/dist/commands/balances.js +171 -0
- package/dist/commands/balances.js.map +1 -0
- package/dist/commands/buy.d.ts.map +1 -1
- package/dist/commands/buy.js +746 -24
- package/dist/commands/buy.js.map +1 -1
- package/dist/commands/chat.d.ts.map +1 -1
- package/dist/commands/chat.js +467 -906
- package/dist/commands/chat.js.map +1 -1
- package/dist/commands/claim.d.ts.map +1 -0
- package/dist/commands/claim.js +65 -0
- package/dist/commands/claim.js.map +1 -0
- package/dist/commands/create.d.ts.map +1 -1
- package/dist/commands/create.js +0 -1
- package/dist/commands/create.js.map +1 -1
- package/dist/commands/info.d.ts.map +1 -1
- package/dist/commands/info.js +128 -26
- package/dist/commands/info.js.map +1 -1
- package/dist/commands/list.d.ts.map +1 -1
- package/dist/commands/list.js +30 -23
- package/dist/commands/list.js.map +1 -1
- package/dist/commands/positions.d.ts.map +1 -1
- package/dist/commands/positions.js +178 -105
- package/dist/commands/positions.js.map +1 -1
- package/dist/commands/sell.d.ts.map +1 -1
- package/dist/commands/sell.js +713 -24
- package/dist/commands/sell.js.map +1 -1
- package/dist/index.js +417 -109
- package/dist/index.js.map +1 -1
- package/dist/interactive/shell.d.ts.map +1 -1
- package/dist/interactive/shell.js +328 -179
- package/dist/interactive/shell.js.map +1 -1
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +8 -8
- package/dist/mcp/tools.js.map +1 -1
- package/dist/utils/constants.d.ts.map +1 -0
- package/dist/utils/constants.js +66 -0
- package/dist/utils/constants.js.map +1 -0
- package/dist/utils/formatting.d.ts.map +1 -1
- package/dist/utils/formatting.js +3 -5
- package/dist/utils/formatting.js.map +1 -1
- package/dist/utils/token-resolver.d.ts.map +1 -1
- package/dist/utils/token-resolver.js +71 -40
- package/dist/utils/token-resolver.js.map +1 -1
- package/dist/utils/validation.d.ts.map +1 -1
- package/dist/utils/validation.js +4 -3
- package/dist/utils/validation.js.map +1 -1
- package/jest.config.js +1 -1
- package/package.json +19 -13
- package/.claude/settings.local.json +0 -41
- package/dist/commands/balance.d.ts.map +0 -1
- package/dist/commands/balance.js +0 -112
- package/dist/commands/balance.js.map +0 -1
- package/homebrew-httpcat/Formula/httpcat.rb +0 -18
- package/homebrew-httpcat/README.md +0 -31
- package/homebrew-httpcat/homebrew-httpcat/Formula/httpcat.rb +0 -18
- package/homebrew-httpcat/homebrew-httpcat/README.md +0 -31
package/dist/index.js
CHANGED
|
@@ -2,9 +2,10 @@
|
|
|
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
|
-
import { formatAddress } from "./utils/formatting.js";
|
|
7
|
-
import { HttpcatClient } from "./client.js";
|
|
7
|
+
import { formatAddress, formatCurrency } from "./utils/formatting.js";
|
|
8
|
+
import { HttpcatClient, HttpcatError } from "./client.js";
|
|
8
9
|
import { handleError, getExitCode } from "./utils/errors.js";
|
|
9
10
|
import { startInteractiveShell } from "./interactive/shell.js";
|
|
10
11
|
import { outputJson, outputError } from "./headless/json-output.js";
|
|
@@ -18,26 +19,29 @@ 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
|
-
import { buyToken, displayBuyResult, } from "./commands/buy.js";
|
|
26
|
+
import { buyToken, displayBuyResult, displayBuyResultCompact, } from "./commands/buy.js";
|
|
24
27
|
import { sellToken, displaySellResult, parseTokenAmount, } from "./commands/sell.js";
|
|
25
28
|
import { getTokenInfo, displayTokenInfo } from "./commands/info.js";
|
|
26
29
|
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/
|
|
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:
|
|
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(
|
|
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
|
|
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
|
|
@@ -239,17 +245,21 @@ Examples:
|
|
|
239
245
|
try {
|
|
240
246
|
if (options.show) {
|
|
241
247
|
console.log(JSON.stringify(config.getAll(), null, 2));
|
|
248
|
+
process.exit(0);
|
|
242
249
|
}
|
|
243
250
|
else if (options.set) {
|
|
244
251
|
const [key, value] = options.set.split("=");
|
|
245
252
|
config.set(key, value);
|
|
246
253
|
console.log(`✅ ${key} set to ${value}`);
|
|
254
|
+
process.exit(0);
|
|
247
255
|
}
|
|
248
256
|
else if (options.reset) {
|
|
249
257
|
await config.runSetupWizard();
|
|
258
|
+
process.exit(0);
|
|
250
259
|
}
|
|
251
260
|
else {
|
|
252
261
|
await config.runSetupWizard();
|
|
262
|
+
process.exit(0);
|
|
253
263
|
}
|
|
254
264
|
}
|
|
255
265
|
catch (error) {
|
|
@@ -263,9 +273,9 @@ program
|
|
|
263
273
|
.description("Create a new token")
|
|
264
274
|
.argument("<name>", 'Token name (e.g., "My Token")')
|
|
265
275
|
.argument("<symbol>", 'Token symbol/ticker (e.g., "MTK", 3-10 characters)')
|
|
266
|
-
.option("--photo <url>", "Photo URL for token logo")
|
|
267
|
-
.option("--banner <url>", "Banner image URL")
|
|
268
|
-
.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")
|
|
269
279
|
.addHelpText("after", `
|
|
270
280
|
Examples:
|
|
271
281
|
httpcat create "My Token" "MTK"
|
|
@@ -324,21 +334,29 @@ Examples:
|
|
|
324
334
|
// Buy command
|
|
325
335
|
program
|
|
326
336
|
.command("buy")
|
|
327
|
-
.description("Buy tokens
|
|
328
|
-
.argument("<identifier>", '
|
|
329
|
-
.argument("<amount>", "Amount in USDC: 0.05, 0.10,
|
|
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)
|
|
330
342
|
.addHelpText("after", `
|
|
331
343
|
Examples:
|
|
332
344
|
httpcat buy abc123-4567-89ab-cdef-0123456789ab 0.20
|
|
333
345
|
httpcat buy "My Token" 0.10
|
|
334
|
-
httpcat buy MTK
|
|
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
|
|
335
349
|
httpcat --json buy abc123-4567-89ab-cdef-0123456789ab 0.10
|
|
336
350
|
httpcat --private-key 0x... buy "PurrfecTT" 0.20
|
|
351
|
+
httpcat buy MTK 0.10 --repeat 10
|
|
352
|
+
httpcat buy MTK 0.10 --repeat 10 --delay 500
|
|
337
353
|
`)
|
|
338
|
-
.action(async (tokenId, amount, command) => {
|
|
354
|
+
.action(async (tokenId, amount, options, command) => {
|
|
339
355
|
try {
|
|
340
356
|
const globalOpts = command.parent?.opts() || {};
|
|
341
357
|
const accountIndex = globalOpts.account;
|
|
358
|
+
const repeatCount = options.repeat;
|
|
359
|
+
const delayMs = options.delay || 0;
|
|
342
360
|
// Ensure wallet is unlocked
|
|
343
361
|
await ensureWalletUnlocked();
|
|
344
362
|
let privateKey = getPrivateKey(globalOpts.privateKey, accountIndex);
|
|
@@ -354,19 +372,122 @@ Examples:
|
|
|
354
372
|
const client = await HttpcatClient.create(privateKey);
|
|
355
373
|
const isTestMode = client.getNetwork().includes("sepolia");
|
|
356
374
|
const silent = globalOpts.json || globalOpts.quiet;
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
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
|
+
}
|
|
365
395
|
}
|
|
366
|
-
|
|
367
|
-
|
|
396
|
+
// If repeat is specified, execute multiple buys
|
|
397
|
+
if (repeatCount && repeatCount > 0) {
|
|
398
|
+
const results = [];
|
|
399
|
+
let totalSpent = 0;
|
|
400
|
+
let stoppedEarly = false;
|
|
401
|
+
let stopReason = "";
|
|
402
|
+
for (let i = 1; i <= repeatCount; i++) {
|
|
403
|
+
try {
|
|
404
|
+
// Show progress for non-JSON mode
|
|
405
|
+
if (!globalOpts.json && !globalOpts.quiet) {
|
|
406
|
+
console.log(chalk.dim(`Buy ${i}/${repeatCount}...`));
|
|
407
|
+
}
|
|
408
|
+
const result = await withLoading(() => buyToken(client, tokenId, finalAmount, isTestMode, silent || i > 1, privateKey), {
|
|
409
|
+
message: `Buying tokens (${i}/${repeatCount})...`,
|
|
410
|
+
json: globalOpts.json,
|
|
411
|
+
quiet: globalOpts.quiet || i > 1,
|
|
412
|
+
spinner: "cat",
|
|
413
|
+
});
|
|
414
|
+
results.push(result);
|
|
415
|
+
totalSpent += parseFloat(result.amountSpent);
|
|
416
|
+
// Display compact result for each buy
|
|
417
|
+
if (globalOpts.json) {
|
|
418
|
+
// In JSON mode, output each result
|
|
419
|
+
outputJson("token_buy", result);
|
|
420
|
+
}
|
|
421
|
+
else if (!globalOpts.quiet) {
|
|
422
|
+
displayBuyResultCompact(result, i, repeatCount);
|
|
423
|
+
}
|
|
424
|
+
// Check if token graduated
|
|
425
|
+
if (result.graduationReached) {
|
|
426
|
+
stoppedEarly = true;
|
|
427
|
+
stopReason = "Token graduated";
|
|
428
|
+
if (!globalOpts.json && !globalOpts.quiet) {
|
|
429
|
+
console.log();
|
|
430
|
+
console.log(chalk.green("🎓 Token has graduated! Stopping buy loop."));
|
|
431
|
+
}
|
|
432
|
+
break;
|
|
433
|
+
}
|
|
434
|
+
// Apply delay between iterations (except after the last one)
|
|
435
|
+
if (i < repeatCount && delayMs > 0) {
|
|
436
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
catch (error) {
|
|
440
|
+
// Handle insufficient funds (402) gracefully
|
|
441
|
+
if (error instanceof HttpcatError && error.status === 402) {
|
|
442
|
+
stoppedEarly = true;
|
|
443
|
+
stopReason = "Insufficient funds";
|
|
444
|
+
if (!globalOpts.json && !globalOpts.quiet) {
|
|
445
|
+
console.log();
|
|
446
|
+
console.log(chalk.yellow("💡 Insufficient funds. Stopping buy loop."));
|
|
447
|
+
}
|
|
448
|
+
break;
|
|
449
|
+
}
|
|
450
|
+
// For other errors, re-throw to be handled by outer catch
|
|
451
|
+
throw error;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
// Show final summary
|
|
455
|
+
if (!globalOpts.json && !globalOpts.quiet && results.length > 0) {
|
|
456
|
+
const lastResult = results[results.length - 1];
|
|
457
|
+
const graduationStatus = lastResult.graduationReached
|
|
458
|
+
? "✅ GRADUATED!"
|
|
459
|
+
: `${(lastResult.graduationProgress || 0).toFixed(2)}%`;
|
|
460
|
+
console.log();
|
|
461
|
+
console.log(chalk.cyan.bold("📊 Repeat Buy Summary"));
|
|
462
|
+
console.log(chalk.dim("─".repeat(50)));
|
|
463
|
+
console.log(`Total buys completed: ${chalk.bold(results.length)}${repeatCount ? `/${repeatCount}` : ""}`);
|
|
464
|
+
console.log(`Total amount spent: ${chalk.bold(formatCurrency(totalSpent.toString()))}`);
|
|
465
|
+
console.log(`Final token price: ${chalk.bold(formatCurrency(lastResult.newPrice))}`);
|
|
466
|
+
console.log(`Final graduation: ${chalk.bold(graduationStatus)}`);
|
|
467
|
+
if (stoppedEarly) {
|
|
468
|
+
console.log(`Stopped early: ${chalk.yellow(stopReason)}`);
|
|
469
|
+
}
|
|
470
|
+
console.log();
|
|
471
|
+
}
|
|
472
|
+
// Exit with success code (0) even if stopped early due to graduation or insufficient funds
|
|
473
|
+
process.exit(0);
|
|
474
|
+
}
|
|
475
|
+
else {
|
|
476
|
+
// Normal single buy execution
|
|
477
|
+
const result = await withLoading(() => buyToken(client, tokenId, finalAmount, isTestMode, silent, privateKey), {
|
|
478
|
+
message: "Buying tokens...",
|
|
479
|
+
json: globalOpts.json,
|
|
480
|
+
quiet: globalOpts.quiet,
|
|
481
|
+
spinner: "cat",
|
|
482
|
+
});
|
|
483
|
+
if (globalOpts.json) {
|
|
484
|
+
outputJson("token_buy", result);
|
|
485
|
+
}
|
|
486
|
+
else if (!globalOpts.quiet) {
|
|
487
|
+
displayBuyResult(result);
|
|
488
|
+
}
|
|
489
|
+
process.exit(0);
|
|
368
490
|
}
|
|
369
|
-
process.exit(0);
|
|
370
491
|
}
|
|
371
492
|
catch (error) {
|
|
372
493
|
const globalOpts = command.parent?.opts() || {};
|
|
@@ -382,8 +503,8 @@ Examples:
|
|
|
382
503
|
// Sell command
|
|
383
504
|
program
|
|
384
505
|
.command("sell")
|
|
385
|
-
.description("Sell tokens
|
|
386
|
-
.argument("<identifier>", '
|
|
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)')
|
|
387
508
|
.argument("<amount>", 'Amount: number (e.g., 1000), percentage (e.g., 50%), or "all"')
|
|
388
509
|
.addHelpText("after", `
|
|
389
510
|
Examples:
|
|
@@ -413,23 +534,99 @@ Examples:
|
|
|
413
534
|
// Derive user address from private key
|
|
414
535
|
const account = privateKeyToAccount(privateKey);
|
|
415
536
|
const userAddress = account.address;
|
|
416
|
-
//
|
|
417
|
-
const
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
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
|
+
}
|
|
425
588
|
}
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
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
|
+
}
|
|
430
624
|
}
|
|
431
|
-
|
|
432
|
-
|
|
625
|
+
if (actualBalance === "0") {
|
|
626
|
+
throw new Error("You do not own any of this token");
|
|
627
|
+
}
|
|
628
|
+
const tokenAmount = parseTokenAmount(amountInput, actualBalance);
|
|
629
|
+
const result = await withLoading(() => sellToken(client, tokenId, tokenAmount, silent, privateKey), {
|
|
433
630
|
message: "Selling tokens...",
|
|
434
631
|
json: globalOpts.json,
|
|
435
632
|
quiet: globalOpts.quiet,
|
|
@@ -454,11 +651,89 @@ Examples:
|
|
|
454
651
|
process.exit(getExitCode(error));
|
|
455
652
|
}
|
|
456
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
|
+
});
|
|
457
732
|
// Info command
|
|
458
733
|
program
|
|
459
734
|
.command("info")
|
|
460
735
|
.description("Get detailed information about a token")
|
|
461
|
-
.argument("<identifier>", '
|
|
736
|
+
.argument("<identifier>", 'Address, name, or symbol (e.g., 0x1234..., "My Token", or MTK)')
|
|
462
737
|
.addHelpText("after", `
|
|
463
738
|
Examples:
|
|
464
739
|
httpcat info abc123-4567-89ab-cdef-0123456789ab
|
|
@@ -497,7 +772,7 @@ Examples:
|
|
|
497
772
|
outputJson("token_info", result);
|
|
498
773
|
}
|
|
499
774
|
else if (!globalOpts.quiet) {
|
|
500
|
-
displayTokenInfo(result);
|
|
775
|
+
await displayTokenInfo(result, userAddress);
|
|
501
776
|
}
|
|
502
777
|
process.exit(0);
|
|
503
778
|
}
|
|
@@ -516,9 +791,9 @@ Examples:
|
|
|
516
791
|
program
|
|
517
792
|
.command("list")
|
|
518
793
|
.description("List all tokens with pagination and sorting")
|
|
519
|
-
.option("--page <number>", "Page number (default: 1)", "1")
|
|
520
|
-
.option("--limit <number>", "Items per page (default: 20, max: 100)", "20")
|
|
521
|
-
.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")
|
|
522
797
|
.addHelpText("after", `
|
|
523
798
|
Examples:
|
|
524
799
|
httpcat list
|
|
@@ -571,18 +846,18 @@ Examples:
|
|
|
571
846
|
// Transactions command
|
|
572
847
|
program
|
|
573
848
|
.command("transactions")
|
|
574
|
-
.description("Get paginated list of transactions
|
|
575
|
-
.option("--user <address>", "Filter by user address")
|
|
576
|
-
.option("--token <tokenId>", "Filter by token ID")
|
|
577
|
-
.option("--type <type>", "Filter by type: buy, sell, or airdrop")
|
|
578
|
-
.option("--limit <number>", "Number of results (default: 50, max: 100)", "50")
|
|
579
|
-
.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")
|
|
580
855
|
.addHelpText("after", `
|
|
581
856
|
Examples:
|
|
582
|
-
httpcat transactions
|
|
583
|
-
httpcat transactions --user 0x1234...
|
|
584
|
-
httpcat transactions --token abc123-...
|
|
585
|
-
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
|
|
586
861
|
httpcat --json transactions --user 0x1234... --offset 50
|
|
587
862
|
`)
|
|
588
863
|
.action(async (options, command) => {
|
|
@@ -602,9 +877,37 @@ Examples:
|
|
|
602
877
|
privateKey = await promptForPrivateKey();
|
|
603
878
|
}
|
|
604
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
|
+
}
|
|
605
902
|
const input = {};
|
|
606
|
-
|
|
903
|
+
// Use selected account's address by default, unless --user is explicitly provided
|
|
904
|
+
if (options.user) {
|
|
607
905
|
input.userAddress = options.user;
|
|
906
|
+
}
|
|
907
|
+
else {
|
|
908
|
+
// Default to selected account's address
|
|
909
|
+
input.userAddress = userAddress;
|
|
910
|
+
}
|
|
608
911
|
if (options.token)
|
|
609
912
|
input.tokenId = options.token;
|
|
610
913
|
if (options.type) {
|
|
@@ -646,9 +949,13 @@ Examples:
|
|
|
646
949
|
program
|
|
647
950
|
.command("positions")
|
|
648
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")
|
|
649
954
|
.addHelpText("after", `
|
|
650
955
|
Examples:
|
|
651
956
|
httpcat positions
|
|
957
|
+
httpcat positions --active
|
|
958
|
+
httpcat positions --graduated
|
|
652
959
|
httpcat --json positions
|
|
653
960
|
httpcat --private-key 0x... positions
|
|
654
961
|
`)
|
|
@@ -674,7 +981,9 @@ Examples:
|
|
|
674
981
|
const userAddress = account.address;
|
|
675
982
|
// Show which account is being used and verify it matches (if not quiet and not json)
|
|
676
983
|
if (!globalOpts.quiet && !globalOpts.json) {
|
|
677
|
-
const activeIndex = accountIndex !== undefined
|
|
984
|
+
const activeIndex = accountIndex !== undefined
|
|
985
|
+
? accountIndex
|
|
986
|
+
: config.getActiveAccountIndex();
|
|
678
987
|
const accounts = config.getAllAccounts();
|
|
679
988
|
const accountInfo = accounts.find((acc) => acc.index === activeIndex);
|
|
680
989
|
if (accountInfo) {
|
|
@@ -689,17 +998,33 @@ Examples:
|
|
|
689
998
|
console.log();
|
|
690
999
|
}
|
|
691
1000
|
}
|
|
692
|
-
|
|
1001
|
+
let result = await withLoading(() => getPositions(client, userAddress), {
|
|
693
1002
|
message: "Fetching positions...",
|
|
694
1003
|
json: globalOpts.json,
|
|
695
1004
|
quiet: globalOpts.quiet,
|
|
696
1005
|
spinner: "cat",
|
|
697
1006
|
});
|
|
1007
|
+
const filter = command.active
|
|
1008
|
+
? "active"
|
|
1009
|
+
: command.graduated
|
|
1010
|
+
? "graduated"
|
|
1011
|
+
: "all";
|
|
698
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
|
+
}
|
|
699
1024
|
outputJson("positions", result);
|
|
700
1025
|
}
|
|
701
1026
|
else if (!globalOpts.quiet) {
|
|
702
|
-
displayPositions(result);
|
|
1027
|
+
displayPositions(result, filter);
|
|
703
1028
|
}
|
|
704
1029
|
process.exit(0);
|
|
705
1030
|
}
|
|
@@ -758,15 +1083,15 @@ Examples:
|
|
|
758
1083
|
process.exit(getExitCode(error));
|
|
759
1084
|
}
|
|
760
1085
|
});
|
|
761
|
-
//
|
|
1086
|
+
// Balances command
|
|
762
1087
|
program
|
|
763
|
-
.command("
|
|
764
|
-
.description("Check wallet
|
|
1088
|
+
.command("balances")
|
|
1089
|
+
.description("Check wallet balances (ETH and USDC)")
|
|
765
1090
|
.addHelpText("after", `
|
|
766
1091
|
Examples:
|
|
767
|
-
httpcat
|
|
768
|
-
httpcat
|
|
769
|
-
httpcat --json
|
|
1092
|
+
httpcat balances
|
|
1093
|
+
httpcat balances --private-key 0x...
|
|
1094
|
+
httpcat --json balances
|
|
770
1095
|
`)
|
|
771
1096
|
.action(async (command) => {
|
|
772
1097
|
try {
|
|
@@ -785,13 +1110,13 @@ Examples:
|
|
|
785
1110
|
privateKey = undefined;
|
|
786
1111
|
}
|
|
787
1112
|
const result = await withLoading(() => checkBalance(privateKey), {
|
|
788
|
-
message: "Checking
|
|
1113
|
+
message: "Checking balances...",
|
|
789
1114
|
json: globalOpts.json,
|
|
790
1115
|
quiet: globalOpts.quiet,
|
|
791
1116
|
spinner: "cat",
|
|
792
1117
|
});
|
|
793
1118
|
if (globalOpts.json) {
|
|
794
|
-
outputJson("
|
|
1119
|
+
outputJson("balances", result);
|
|
795
1120
|
}
|
|
796
1121
|
else if (!globalOpts.quiet) {
|
|
797
1122
|
displayBalance(result);
|
|
@@ -801,7 +1126,7 @@ Examples:
|
|
|
801
1126
|
catch (error) {
|
|
802
1127
|
const globalOpts = command.parent?.opts() || {};
|
|
803
1128
|
if (globalOpts.json) {
|
|
804
|
-
outputError("
|
|
1129
|
+
outputError("balances", error, getExitCode(error));
|
|
805
1130
|
}
|
|
806
1131
|
else {
|
|
807
1132
|
handleError(error, globalOpts.verbose);
|
|
@@ -837,7 +1162,7 @@ Examples:
|
|
|
837
1162
|
outputJson("account", result);
|
|
838
1163
|
}
|
|
839
1164
|
else if (!globalOpts.quiet) {
|
|
840
|
-
displayAccountInfo(result);
|
|
1165
|
+
await displayAccountInfo(result);
|
|
841
1166
|
}
|
|
842
1167
|
process.exit(0);
|
|
843
1168
|
}
|
|
@@ -891,31 +1216,6 @@ accountCommand
|
|
|
891
1216
|
process.exit(getExitCode(error));
|
|
892
1217
|
}
|
|
893
1218
|
});
|
|
894
|
-
accountCommand
|
|
895
|
-
.command("add")
|
|
896
|
-
.description("Add a new seed-derived account")
|
|
897
|
-
.addHelpText("after", `
|
|
898
|
-
Examples:
|
|
899
|
-
httpcat account add # Add a new account from existing seed phrase
|
|
900
|
-
httpcat account add # If no seed phrase, prompts to generate/import one
|
|
901
|
-
`)
|
|
902
|
-
.action(async () => {
|
|
903
|
-
try {
|
|
904
|
-
const { addAccount } = await import("./commands/account.js");
|
|
905
|
-
await addAccount();
|
|
906
|
-
process.exit(0);
|
|
907
|
-
}
|
|
908
|
-
catch (error) {
|
|
909
|
-
const globalOpts = accountCommand.parent?.opts() || {};
|
|
910
|
-
if (globalOpts.json) {
|
|
911
|
-
outputError("account_add", error, getExitCode(error));
|
|
912
|
-
}
|
|
913
|
-
else {
|
|
914
|
-
handleError(error, globalOpts.verbose);
|
|
915
|
-
}
|
|
916
|
-
process.exit(getExitCode(error));
|
|
917
|
-
}
|
|
918
|
-
});
|
|
919
1219
|
accountCommand
|
|
920
1220
|
.command("add")
|
|
921
1221
|
.description("Add a new seed-derived account")
|
|
@@ -945,7 +1245,7 @@ program
|
|
|
945
1245
|
.command("chat")
|
|
946
1246
|
.description("Start streaming chat ($0.01 to join, $0.0001 per message, 10 min lease)")
|
|
947
1247
|
.argument("[token]", "Optional: Token symbol, name, or address for token-specific chat")
|
|
948
|
-
.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")
|
|
949
1249
|
.addHelpText("after", `
|
|
950
1250
|
Examples:
|
|
951
1251
|
httpcat chat # Join general chat
|
|
@@ -1019,6 +1319,14 @@ Configuration:
|
|
|
1019
1319
|
.action(async () => {
|
|
1020
1320
|
await runMcpServer();
|
|
1021
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
|
+
});
|
|
1022
1330
|
// Interactive shell (default when no command)
|
|
1023
1331
|
program.action(async (options) => {
|
|
1024
1332
|
try {
|