httpcat-cli 0.2.13 → 0.3.0-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/release.yml +96 -0
- package/.github/workflows/sync-version.yml +31 -2
- package/README.md +185 -62
- package/additions.txt +3 -0
- package/bun.lock +180 -1276
- package/dist/agent/autonomous-trader.d.ts.map +1 -0
- package/dist/agent/autonomous-trader.js +362 -0
- package/dist/agent/autonomous-trader.js.map +1 -0
- package/dist/agent/ax-agent.d.ts.map +1 -1
- package/dist/agent/ax-agent.js +293 -18
- package/dist/agent/ax-agent.js.map +1 -1
- package/dist/agent/event-client.d.ts.map +1 -0
- package/dist/agent/event-client.js +82 -0
- package/dist/agent/event-client.js.map +1 -0
- package/dist/agent/log-stream.d.ts.map +1 -0
- package/dist/agent/log-stream.js +95 -0
- package/dist/agent/log-stream.js.map +1 -0
- package/dist/agent/memory/conversation-session.d.ts.map +1 -0
- package/dist/agent/memory/conversation-session.js +232 -0
- package/dist/agent/memory/conversation-session.js.map +1 -0
- package/dist/agent/memory/conversation-store.d.ts.map +1 -0
- package/dist/agent/memory/conversation-store.js +214 -0
- package/dist/agent/memory/conversation-store.js.map +1 -0
- package/dist/agent/memory/database-schema.d.ts.map +1 -0
- package/dist/agent/memory/database-schema.js +355 -0
- package/dist/agent/memory/database-schema.js.map +1 -0
- package/dist/agent/memory/decision-tracker.d.ts.map +1 -0
- package/dist/agent/memory/decision-tracker.js +274 -0
- package/dist/agent/memory/decision-tracker.js.map +1 -0
- package/dist/agent/memory/memory-manager.d.ts.map +1 -0
- package/dist/agent/memory/memory-manager.js +187 -0
- package/dist/agent/memory/memory-manager.js.map +1 -0
- package/dist/agent/memory/types.d.ts.map +1 -0
- package/dist/agent/memory/types.js +5 -0
- package/dist/agent/memory/types.js.map +1 -0
- package/dist/agent/message-formatter.d.ts.map +1 -0
- package/dist/agent/message-formatter.js +76 -0
- package/dist/agent/message-formatter.js.map +1 -0
- package/dist/agent/position-db.d.ts.map +1 -0
- package/dist/agent/position-db.js +154 -0
- package/dist/agent/position-db.js.map +1 -0
- package/dist/agent/simple-chat-ui-static.d.ts.map +1 -0
- package/dist/agent/simple-chat-ui-static.js +129 -0
- package/dist/agent/simple-chat-ui-static.js.map +1 -0
- package/dist/agent/simple-chat-ui.d.ts.map +1 -0
- package/dist/agent/simple-chat-ui.js +90 -0
- package/dist/agent/simple-chat-ui.js.map +1 -0
- package/dist/agent/tools.d.ts.map +1 -1
- package/dist/agent/tools.js +274 -5
- package/dist/agent/tools.js.map +1 -1
- package/dist/agent/ui.d.ts.map +1 -0
- package/dist/agent/ui.js +84 -0
- package/dist/agent/ui.js.map +1 -0
- package/dist/agent/unified-runtime.d.ts.map +1 -0
- package/dist/agent/unified-runtime.js +397 -0
- package/dist/agent/unified-runtime.js.map +1 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +406 -46
- package/dist/client.js.map +1 -1
- package/dist/commands/account.d.ts.map +1 -1
- package/dist/commands/account.js +51 -22
- package/dist/commands/account.js.map +1 -1
- package/dist/commands/agent.d.ts.map +1 -0
- package/dist/commands/agent.js +258 -0
- package/dist/commands/agent.js.map +1 -0
- package/dist/commands/balances.d.ts.map +1 -1
- package/dist/commands/balances.js +122 -68
- package/dist/commands/balances.js.map +1 -1
- package/dist/commands/buy.d.ts.map +1 -1
- package/dist/commands/buy.js +73 -36
- package/dist/commands/buy.js.map +1 -1
- package/dist/commands/chat.d.ts.map +1 -1
- package/dist/commands/chat.js +200 -429
- package/dist/commands/chat.js.map +1 -1
- package/dist/commands/claim.d.ts.map +1 -1
- package/dist/commands/claim.js +84 -27
- package/dist/commands/claim.js.map +1 -1
- package/dist/commands/create.d.ts.map +1 -1
- package/dist/commands/create.js +65 -42
- package/dist/commands/create.js.map +1 -1
- package/dist/commands/health.d.ts.map +1 -1
- package/dist/commands/health.js +7 -5
- package/dist/commands/health.js.map +1 -1
- package/dist/commands/info.d.ts.map +1 -1
- package/dist/commands/info.js +125 -46
- package/dist/commands/info.js.map +1 -1
- package/dist/commands/list.d.ts.map +1 -1
- package/dist/commands/list.js +56 -22
- package/dist/commands/list.js.map +1 -1
- package/dist/commands/positions.d.ts.map +1 -1
- package/dist/commands/positions.js +76 -47
- package/dist/commands/positions.js.map +1 -1
- package/dist/commands/sell.d.ts.map +1 -1
- package/dist/commands/sell.js +62 -32
- package/dist/commands/sell.js.map +1 -1
- package/dist/commands/swap.d.ts.map +1 -0
- package/dist/commands/swap.js +392 -0
- package/dist/commands/swap.js.map +1 -0
- package/dist/commands/transactions.d.ts.map +1 -1
- package/dist/commands/transactions.js +28 -16
- package/dist/commands/transactions.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +132 -11
- package/dist/config.js.map +1 -1
- package/dist/index.js +453 -145
- package/dist/index.js.map +1 -1
- package/dist/interactive/art.d.ts.map +1 -1
- package/dist/interactive/art.js +38 -1
- package/dist/interactive/art.js.map +1 -1
- package/dist/interactive/shell.d.ts.map +1 -1
- package/dist/interactive/shell.js +398 -2259
- package/dist/interactive/shell.js.map +1 -1
- package/dist/mcp/chat-state.d.ts.map +1 -1
- package/dist/mcp/chat-state.js +2 -1
- package/dist/mcp/chat-state.js.map +1 -1
- package/dist/mcp/server.js +1 -1
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +108 -1
- package/dist/mcp/tools.js.map +1 -1
- package/dist/mcp/types.d.ts.map +1 -1
- package/dist/types/agent-info.d.ts.map +1 -0
- package/dist/types/agent-info.js +11 -0
- package/dist/types/agent-info.js.map +1 -0
- package/dist/ui/components/ScrollableList.d.ts.map +1 -0
- package/dist/ui/components/ScrollableList.js +72 -0
- package/dist/ui/components/ScrollableList.js.map +1 -0
- package/dist/ui/components/ThemeProvider.d.ts.map +1 -0
- package/dist/ui/components/ThemeProvider.js +87 -0
- package/dist/ui/components/ThemeProvider.js.map +1 -0
- package/dist/ui/components/ThemedBox.d.ts.map +1 -0
- package/dist/ui/components/ThemedBox.js +24 -0
- package/dist/ui/components/ThemedBox.js.map +1 -0
- package/dist/ui/components/agent/ChatHeader.d.ts.map +1 -0
- package/dist/ui/components/agent/ChatHeader.js +39 -0
- package/dist/ui/components/agent/ChatHeader.js.map +1 -0
- package/dist/ui/components/agent/Header.d.ts.map +1 -0
- package/dist/ui/components/agent/Header.js +14 -0
- package/dist/ui/components/agent/Header.js.map +1 -0
- package/dist/ui/components/agent/Input.d.ts.map +1 -0
- package/dist/ui/components/agent/Input.js +23 -0
- package/dist/ui/components/agent/Input.js.map +1 -0
- package/dist/ui/components/agent/Output.d.ts.map +1 -0
- package/dist/ui/components/agent/Output.js +23 -0
- package/dist/ui/components/agent/Output.js.map +1 -0
- package/dist/ui/components/chat/TokenChatUI.d.ts.map +1 -0
- package/dist/ui/components/chat/TokenChatUI.js +133 -0
- package/dist/ui/components/chat/TokenChatUI.js.map +1 -0
- package/dist/ui/components/shell/ShellHeader.d.ts.map +1 -0
- package/dist/ui/components/shell/ShellHeader.js +31 -0
- package/dist/ui/components/shell/ShellHeader.js.map +1 -0
- package/dist/ui/components/shell/ShellInput.d.ts.map +1 -0
- package/dist/ui/components/shell/ShellInput.js +147 -0
- package/dist/ui/components/shell/ShellInput.js.map +1 -0
- package/dist/ui/components/shell/ShellOutput.d.ts.map +1 -0
- package/dist/ui/components/shell/ShellOutput.js +8 -0
- package/dist/ui/components/shell/ShellOutput.js.map +1 -0
- package/dist/ui/hooks/useChatWebSocket.d.ts.map +1 -0
- package/dist/ui/hooks/useChatWebSocket.js +76 -0
- package/dist/ui/hooks/useChatWebSocket.js.map +1 -0
- package/dist/ui/hooks/useCommandHistory.d.ts.map +1 -0
- package/dist/ui/hooks/useCommandHistory.js +70 -0
- package/dist/ui/hooks/useCommandHistory.js.map +1 -0
- package/dist/ui/hooks/useDebounce.d.ts.map +1 -0
- package/dist/ui/hooks/useDebounce.js +17 -0
- package/dist/ui/hooks/useDebounce.js.map +1 -0
- package/dist/ui/hooks/useLogStream.d.ts.map +1 -0
- package/dist/ui/hooks/useLogStream.js +20 -0
- package/dist/ui/hooks/useLogStream.js.map +1 -0
- package/dist/ui/hooks/useVirtualScroll.d.ts.map +1 -0
- package/dist/ui/hooks/useVirtualScroll.js +70 -0
- package/dist/ui/hooks/useVirtualScroll.js.map +1 -0
- package/dist/utils/constants.d.ts.map +1 -1
- package/dist/utils/constants.js +44 -2
- package/dist/utils/constants.js.map +1 -1
- package/dist/utils/errors.d.ts.map +1 -1
- package/dist/utils/errors.js +3 -3
- package/dist/utils/errors.js.map +1 -1
- package/dist/utils/formatting.d.ts.map +1 -1
- package/dist/utils/formatting.js +37 -6
- package/dist/utils/formatting.js.map +1 -1
- package/dist/utils/loading.d.ts.map +1 -1
- package/dist/utils/loading.js +23 -2
- package/dist/utils/loading.js.map +1 -1
- package/dist/utils/privateKeyPrompt.d.ts.map +1 -1
- package/dist/utils/privateKeyPrompt.js +31 -7
- package/dist/utils/privateKeyPrompt.js.map +1 -1
- package/dist/utils/status.d.ts.map +1 -0
- package/dist/utils/status.js +96 -0
- package/dist/utils/status.js.map +1 -0
- package/dist/utils/token-resolver.d.ts.map +1 -1
- package/dist/utils/token-resolver.js +17 -7
- package/dist/utils/token-resolver.js.map +1 -1
- package/issues.txt +2 -0
- package/package.json +16 -6
- package/Screenshot 2025-12-21 at 8.56.02/342/200/257PM.png +0 -0
package/dist/index.js
CHANGED
|
@@ -1,8 +1,28 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
// Handle SIGINT (Ctrl+C) gracefully
|
|
3
|
+
let isExiting = false;
|
|
4
|
+
process.on("SIGINT", () => {
|
|
5
|
+
if (isExiting) {
|
|
6
|
+
// Force exit if already exiting
|
|
7
|
+
process.exit(130);
|
|
8
|
+
}
|
|
9
|
+
isExiting = true;
|
|
10
|
+
console.log("\n\nInterrupted by user. Exiting...");
|
|
11
|
+
process.exit(130);
|
|
12
|
+
});
|
|
13
|
+
process.on("SIGTERM", () => {
|
|
14
|
+
if (isExiting) {
|
|
15
|
+
process.exit(143);
|
|
16
|
+
}
|
|
17
|
+
isExiting = true;
|
|
18
|
+
console.log("\n\nTerminated. Exiting...");
|
|
19
|
+
process.exit(143);
|
|
20
|
+
});
|
|
2
21
|
import { Command } from "commander";
|
|
3
22
|
import chalk from "chalk";
|
|
4
23
|
import { privateKeyToAccount } from "viem/accounts";
|
|
5
24
|
import { createRequire } from "module";
|
|
25
|
+
import { randomUUID } from "crypto";
|
|
6
26
|
import { config } from "./config.js";
|
|
7
27
|
import { formatAddress, formatCurrency } from "./utils/formatting.js";
|
|
8
28
|
import { HttpcatClient, HttpcatError } from "./client.js";
|
|
@@ -12,7 +32,8 @@ import { runCatSpinAnimation } from "./interactive/cat-spin.js";
|
|
|
12
32
|
import { outputJson, outputError } from "./headless/json-output.js";
|
|
13
33
|
import { promptForPrivateKey } from "./utils/privateKeyPrompt.js";
|
|
14
34
|
import { withLoading } from "./utils/loading.js";
|
|
15
|
-
import {
|
|
35
|
+
import { writeStatusLine, clearStatusLine, formatRetryMessage, } from "./utils/status.js";
|
|
36
|
+
import { readFileSync, writeFileSync, existsSync, statSync, unlinkSync, readlinkSync, } from "fs";
|
|
16
37
|
import { fileURLToPath } from "url";
|
|
17
38
|
import { dirname, join } from "path";
|
|
18
39
|
import { tmpdir } from "os";
|
|
@@ -24,9 +45,10 @@ const VERSION = packageJson.version;
|
|
|
24
45
|
const require = createRequire(import.meta.url);
|
|
25
46
|
const { version: PACKAGE_VERSION } = require("../package.json");
|
|
26
47
|
// Import commands
|
|
27
|
-
import { createToken, displayCreateResult, processPhotoUrl, isFilePath } from "./commands/create.js";
|
|
48
|
+
import { createToken, displayCreateResult, processPhotoUrl, isFilePath, } from "./commands/create.js";
|
|
28
49
|
import { buyToken, displayBuyResult, displayBuyResultCompact, } from "./commands/buy.js";
|
|
29
50
|
import { sellToken, displaySellResult, parseTokenAmount, } from "./commands/sell.js";
|
|
51
|
+
import { swap, displaySwapResult } from "./commands/swap.js";
|
|
30
52
|
import { getTokenInfo, displayTokenInfo } from "./commands/info.js";
|
|
31
53
|
import { listTokens, displayTokenList } from "./commands/list.js";
|
|
32
54
|
import { getPositions, displayPositions } from "./commands/positions.js";
|
|
@@ -35,6 +57,7 @@ import { checkHealth, displayHealthStatus } from "./commands/health.js";
|
|
|
35
57
|
import { checkBalance, displayBalance } from "./commands/balances.js";
|
|
36
58
|
import { startChatStream } from "./commands/chat.js";
|
|
37
59
|
import { runMcpServer } from "./commands/mcp-server.js";
|
|
60
|
+
import { setupAgentCommands } from "./commands/agent.js";
|
|
38
61
|
import { getAccountInfo, displayAccountInfo, listAccounts, switchAccount, addAccount, } from "./commands/account.js";
|
|
39
62
|
import { viewFees, claimFees, displayFees, displayClaimResult, } from "./commands/claim.js";
|
|
40
63
|
// Check for --version --json before parsing
|
|
@@ -84,6 +107,11 @@ function isConfigured(cliPrivateKey) {
|
|
|
84
107
|
return true;
|
|
85
108
|
return config.isConfigured();
|
|
86
109
|
}
|
|
110
|
+
// Helper function to get global options properly
|
|
111
|
+
// IMPORTANT: Always use this instead of command.parent?.opts() to ensure global flags work
|
|
112
|
+
function getGlobalOpts() {
|
|
113
|
+
return program.opts();
|
|
114
|
+
}
|
|
87
115
|
// Helper to ensure wallet is unlocked
|
|
88
116
|
async function ensureWalletUnlocked() {
|
|
89
117
|
const password = config.getPassword();
|
|
@@ -194,7 +222,7 @@ envCommand
|
|
|
194
222
|
.description("Add a custom environment")
|
|
195
223
|
.argument("<name>", "Environment name")
|
|
196
224
|
.argument("<agentUrl>", "Agent URL (e.g., http://localhost:8787)")
|
|
197
|
-
.option("-n, --network <network>", "Network (default: base-sepolia)", "base-sepolia")
|
|
225
|
+
.option("-n, --network <network>", "Network: base-sepolia (eip155:84532) or base (eip155:8453) (default: base-sepolia)", "base-sepolia")
|
|
198
226
|
.action((name, agentUrl, options) => {
|
|
199
227
|
try {
|
|
200
228
|
config.addEnvironment(name, agentUrl, options.network);
|
|
@@ -215,7 +243,7 @@ envCommand
|
|
|
215
243
|
.description("Update an existing environment")
|
|
216
244
|
.argument("<name>", "Environment name")
|
|
217
245
|
.argument("<agentUrl>", "Agent URL (e.g., http://localhost:8787)")
|
|
218
|
-
.option("-n, --network <network>", "Network (default: base-sepolia)", "base-sepolia")
|
|
246
|
+
.option("-n, --network <network>", "Network: base-sepolia (eip155:84532) or base (eip155:8453) (default: base-sepolia)", "base-sepolia")
|
|
219
247
|
.action((name, agentUrl, options) => {
|
|
220
248
|
try {
|
|
221
249
|
config.updateEnvironment(name, agentUrl, options.network);
|
|
@@ -287,7 +315,20 @@ Examples:
|
|
|
287
315
|
`)
|
|
288
316
|
.action(async (name, symbol, options, command) => {
|
|
289
317
|
try {
|
|
290
|
-
|
|
318
|
+
// Fallback: get arguments from command object if not provided as parameters
|
|
319
|
+
// This handles cases where Commander.js doesn't parse arguments correctly
|
|
320
|
+
// In Commander.js v9+, use processedArgs; fallback to args for older versions
|
|
321
|
+
const args = command.processedArgs || command.args || [];
|
|
322
|
+
const actualName = name || args[0];
|
|
323
|
+
const actualSymbol = symbol || args[1];
|
|
324
|
+
// Validate required arguments
|
|
325
|
+
if (!actualName || typeof actualName !== "string") {
|
|
326
|
+
throw new Error("Token name is required. Usage: httpcat create <name> <symbol> [options]");
|
|
327
|
+
}
|
|
328
|
+
if (!actualSymbol || typeof actualSymbol !== "string") {
|
|
329
|
+
throw new Error("Token symbol is required. Usage: httpcat create <name> <symbol> [options]");
|
|
330
|
+
}
|
|
331
|
+
const globalOpts = getGlobalOpts();
|
|
291
332
|
const accountIndex = globalOpts.account;
|
|
292
333
|
// Ensure wallet is unlocked
|
|
293
334
|
await ensureWalletUnlocked();
|
|
@@ -302,20 +343,27 @@ Examples:
|
|
|
302
343
|
privateKey = await promptForPrivateKey();
|
|
303
344
|
}
|
|
304
345
|
const client = await HttpcatClient.create(privateKey);
|
|
346
|
+
// Generate default robohash URL if no photo provided
|
|
347
|
+
let photoToUse = options.photo;
|
|
348
|
+
if (!photoToUse) {
|
|
349
|
+
const uuid = randomUUID();
|
|
350
|
+
photoToUse = `https://robohash.org/${uuid}?set=set4`;
|
|
351
|
+
}
|
|
305
352
|
// Process photo if it's a file path (show loading state)
|
|
306
|
-
let processedPhotoUrl =
|
|
307
|
-
if (
|
|
308
|
-
processedPhotoUrl = await withLoading(async () => processPhotoUrl(
|
|
353
|
+
let processedPhotoUrl = photoToUse;
|
|
354
|
+
if (photoToUse && isFilePath(photoToUse)) {
|
|
355
|
+
processedPhotoUrl = await withLoading(async () => processPhotoUrl(photoToUse), {
|
|
309
356
|
message: "Uploading image...",
|
|
310
357
|
json: globalOpts.json,
|
|
311
358
|
quiet: globalOpts.quiet,
|
|
312
359
|
spinner: "cat",
|
|
360
|
+
clearOnSuccess: true,
|
|
313
361
|
});
|
|
314
362
|
}
|
|
315
363
|
// Create token (show loading state)
|
|
316
364
|
const result = await withLoading(() => createToken(client, {
|
|
317
|
-
name,
|
|
318
|
-
symbol,
|
|
365
|
+
name: actualName.trim(),
|
|
366
|
+
symbol: actualSymbol.trim(),
|
|
319
367
|
photoUrl: processedPhotoUrl,
|
|
320
368
|
websiteUrl: options.website,
|
|
321
369
|
}), {
|
|
@@ -323,6 +371,7 @@ Examples:
|
|
|
323
371
|
json: globalOpts.json,
|
|
324
372
|
quiet: globalOpts.quiet,
|
|
325
373
|
spinner: "cat",
|
|
374
|
+
clearOnSuccess: true,
|
|
326
375
|
});
|
|
327
376
|
if (globalOpts.json) {
|
|
328
377
|
outputJson("create_token", result);
|
|
@@ -333,7 +382,26 @@ Examples:
|
|
|
333
382
|
process.exit(0);
|
|
334
383
|
}
|
|
335
384
|
catch (error) {
|
|
336
|
-
const globalOpts =
|
|
385
|
+
const globalOpts = getGlobalOpts();
|
|
386
|
+
// Check if platform hasn't graduated - if so, provide a clearer error message
|
|
387
|
+
try {
|
|
388
|
+
const agentInfo = await config.getAgentInfo();
|
|
389
|
+
if (agentInfo.catGraduated !== true) {
|
|
390
|
+
const graduationError = new Error("Token creation is currently disabled. The platform token must graduate before new tokens can be created. Please try again later.");
|
|
391
|
+
if (globalOpts.json) {
|
|
392
|
+
outputError("create_token", graduationError, getExitCode(graduationError));
|
|
393
|
+
}
|
|
394
|
+
else {
|
|
395
|
+
handleError(graduationError, globalOpts.verbose);
|
|
396
|
+
}
|
|
397
|
+
process.exit(getExitCode(graduationError));
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
catch (infoError) {
|
|
402
|
+
// If we can't fetch agent info, fall through to normal error handling
|
|
403
|
+
}
|
|
404
|
+
// Handle other errors normally
|
|
337
405
|
if (globalOpts.json) {
|
|
338
406
|
outputError("create_token", error, getExitCode(error));
|
|
339
407
|
}
|
|
@@ -365,7 +433,7 @@ Examples:
|
|
|
365
433
|
`)
|
|
366
434
|
.action(async (tokenId, amount, options, command) => {
|
|
367
435
|
try {
|
|
368
|
-
const globalOpts =
|
|
436
|
+
const globalOpts = getGlobalOpts();
|
|
369
437
|
const accountIndex = globalOpts.account;
|
|
370
438
|
const repeatCount = options.repeat;
|
|
371
439
|
const delayMs = options.delay || 0;
|
|
@@ -382,15 +450,19 @@ Examples:
|
|
|
382
450
|
privateKey = await promptForPrivateKey();
|
|
383
451
|
}
|
|
384
452
|
const client = await HttpcatClient.create(privateKey);
|
|
385
|
-
const
|
|
453
|
+
const network = client.getNetwork();
|
|
454
|
+
const isTestMode = network === "eip155:84532" || network === "eip155:11155111" || network.includes("sepolia");
|
|
386
455
|
const silent = globalOpts.json || globalOpts.quiet;
|
|
387
456
|
// Handle percentage amounts for graduated tokens
|
|
388
457
|
let finalAmount = amount;
|
|
389
458
|
if (amount.endsWith("%")) {
|
|
390
|
-
if (!silent) {
|
|
391
|
-
|
|
459
|
+
if (!silent && !globalOpts.json && !globalOpts.quiet) {
|
|
460
|
+
writeStatusLine(chalk.dim("💰 Checking USDC balance for percentage buy..."));
|
|
392
461
|
}
|
|
393
462
|
const balance = await checkBalance(privateKey);
|
|
463
|
+
if (!silent && !globalOpts.json && !globalOpts.quiet) {
|
|
464
|
+
clearStatusLine();
|
|
465
|
+
}
|
|
394
466
|
const usdcBalance = parseFloat(balance.usdcFormatted.replace("$", "").replace(",", ""));
|
|
395
467
|
const percentage = parseFloat(amount.replace("%", ""));
|
|
396
468
|
if (isNaN(percentage) || percentage <= 0 || percentage > 100) {
|
|
@@ -413,18 +485,83 @@ Examples:
|
|
|
413
485
|
let stopReason = "";
|
|
414
486
|
for (let i = 1; i <= repeatCount; i++) {
|
|
415
487
|
try {
|
|
416
|
-
//
|
|
417
|
-
|
|
418
|
-
|
|
488
|
+
// Retry logic: try up to 10 times with exponential backoff on 402 errors
|
|
489
|
+
// Client is recreated on each attempt to ensure fresh signature for new nonce
|
|
490
|
+
let result = null;
|
|
491
|
+
let lastError = null;
|
|
492
|
+
const maxRetries = 10;
|
|
493
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
494
|
+
try {
|
|
495
|
+
// Show progress for non-JSON mode
|
|
496
|
+
if (!globalOpts.json && !globalOpts.quiet) {
|
|
497
|
+
if (attempt === 1) {
|
|
498
|
+
console.log(chalk.dim(`Buy ${i}/${repeatCount}...`));
|
|
499
|
+
}
|
|
500
|
+
else {
|
|
501
|
+
console.log(chalk.yellow(` Retrying buy ${i}/${repeatCount} (attempt ${attempt}/${maxRetries})...`));
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
// Recreate client inside the operation function to ensure fresh signature
|
|
505
|
+
// for each attempt (including retries). This fixes the issue where x402-fetch
|
|
506
|
+
// generates a new nonce but reuses the same signature, causing subsequent buys to fail
|
|
507
|
+
result = await withLoading(async () => {
|
|
508
|
+
// Recreate client for each attempt to ensure fresh signature for new nonce
|
|
509
|
+
const client = await HttpcatClient.create(privateKey);
|
|
510
|
+
return buyToken(client, tokenId, finalAmount, isTestMode, silent || i > 1, privateKey);
|
|
511
|
+
}, {
|
|
512
|
+
message: `Buying tokens (${i}/${repeatCount})...`,
|
|
513
|
+
json: globalOpts.json,
|
|
514
|
+
quiet: globalOpts.quiet || i > 1,
|
|
515
|
+
spinner: "cat",
|
|
516
|
+
clearOnSuccess: true,
|
|
517
|
+
});
|
|
518
|
+
// Success! Break out of retry loop
|
|
519
|
+
break;
|
|
520
|
+
}
|
|
521
|
+
catch (error) {
|
|
522
|
+
lastError = error;
|
|
523
|
+
// Only retry on 402 errors (payment required)
|
|
524
|
+
const is402Error = error instanceof HttpcatError && error.status === 402;
|
|
525
|
+
if (is402Error && attempt < maxRetries) {
|
|
526
|
+
// Exponential backoff: 1s, 2s, 4s, 8s, 16s, 32s, 64s, 128s, 256s, 512s (capped at 10s)
|
|
527
|
+
const backoffMs = Math.min(Math.pow(2, attempt - 1) * 1000, 10000);
|
|
528
|
+
const backoffSeconds = backoffMs / 1000;
|
|
529
|
+
if (!globalOpts.json && !globalOpts.quiet) {
|
|
530
|
+
// Write status on same line (overwrites previous)
|
|
531
|
+
const statusMessage = formatRetryMessage(attempt, maxRetries, backoffSeconds);
|
|
532
|
+
writeStatusLine(statusMessage);
|
|
533
|
+
}
|
|
534
|
+
await new Promise((resolve) => setTimeout(resolve, backoffMs));
|
|
535
|
+
continue; // Retry
|
|
536
|
+
}
|
|
537
|
+
else {
|
|
538
|
+
// Not a 402 error, or max retries reached - clear status and throw
|
|
539
|
+
if (!globalOpts.json && !globalOpts.quiet) {
|
|
540
|
+
clearStatusLine();
|
|
541
|
+
}
|
|
542
|
+
throw error;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
// If we exhausted retries, throw the last error
|
|
547
|
+
if (!result) {
|
|
548
|
+
// Clear status line before throwing
|
|
549
|
+
if (!globalOpts.json && !globalOpts.quiet) {
|
|
550
|
+
clearStatusLine();
|
|
551
|
+
}
|
|
552
|
+
if (lastError) {
|
|
553
|
+
throw lastError;
|
|
554
|
+
}
|
|
555
|
+
else {
|
|
556
|
+
throw new Error(`Failed to complete buy ${i}/${repeatCount} after ${maxRetries} attempts`);
|
|
557
|
+
}
|
|
419
558
|
}
|
|
420
|
-
const result = await withLoading(() => buyToken(client, tokenId, finalAmount, isTestMode, silent || i > 1, privateKey), {
|
|
421
|
-
message: `Buying tokens (${i}/${repeatCount})...`,
|
|
422
|
-
json: globalOpts.json,
|
|
423
|
-
quiet: globalOpts.quiet || i > 1,
|
|
424
|
-
spinner: "cat",
|
|
425
|
-
});
|
|
426
559
|
results.push(result);
|
|
427
560
|
totalSpent += parseFloat(result.amountSpent);
|
|
561
|
+
// Clear status line before showing result
|
|
562
|
+
if (!globalOpts.json && !globalOpts.quiet) {
|
|
563
|
+
clearStatusLine();
|
|
564
|
+
}
|
|
428
565
|
// Display compact result for each buy
|
|
429
566
|
if (globalOpts.json) {
|
|
430
567
|
// In JSON mode, output each result
|
|
@@ -443,58 +580,79 @@ Examples:
|
|
|
443
580
|
}
|
|
444
581
|
break;
|
|
445
582
|
}
|
|
446
|
-
//
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
583
|
+
// Wait for transaction confirmations if present
|
|
584
|
+
// This ensures both the buy transaction and payment transaction are confirmed
|
|
585
|
+
// before the next buy, preventing nonce/signature conflicts
|
|
586
|
+
if (i < repeatCount) {
|
|
587
|
+
const { createPublicClient, http } = await import("viem");
|
|
588
|
+
const { baseSepolia } = await import("viem/chains");
|
|
589
|
+
const publicClient = createPublicClient({
|
|
590
|
+
chain: baseSepolia,
|
|
591
|
+
transport: http(config.getRpcUrl()),
|
|
592
|
+
});
|
|
593
|
+
// Wait for payment transaction first (if present)
|
|
594
|
+
// This is critical to ensure the payment nonce is consumed before next request
|
|
595
|
+
if (result.paymentTxHash) {
|
|
596
|
+
if (!globalOpts.json && !globalOpts.quiet) {
|
|
597
|
+
console.log(chalk.dim(` Waiting for payment transaction confirmation...`));
|
|
598
|
+
}
|
|
461
599
|
try {
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
console.log(chalk.dim(` CAT: ${balance.cat402Formatted}`));
|
|
600
|
+
await publicClient.waitForTransactionReceipt({
|
|
601
|
+
hash: result.paymentTxHash,
|
|
602
|
+
});
|
|
603
|
+
if (!globalOpts.json && !globalOpts.quiet) {
|
|
604
|
+
console.log(chalk.dim(` ✅ Payment transaction confirmed`));
|
|
468
605
|
}
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
console.log(chalk.yellow("💡 Possible issues:"));
|
|
475
|
-
if (usdcBalance < buyAmount) {
|
|
476
|
-
console.log(chalk.dim(` • Insufficient USDC for purchase: Need $${buyAmount.toFixed(6)}, have $${usdcBalance.toFixed(6)}`));
|
|
477
|
-
}
|
|
478
|
-
if (usdcBalance < 0.01) {
|
|
479
|
-
console.log(chalk.dim(` • Low USDC for API payments: x402 protocol requires USDC for API calls`));
|
|
606
|
+
}
|
|
607
|
+
catch (txError) {
|
|
608
|
+
// If we can't wait for confirmation, log but continue
|
|
609
|
+
if (!globalOpts.json && !globalOpts.quiet) {
|
|
610
|
+
console.log(chalk.yellow(` ⚠️ Could not confirm payment transaction, proceeding...`));
|
|
480
611
|
}
|
|
481
|
-
|
|
482
|
-
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
// Wait for buy transaction (if present)
|
|
615
|
+
if (result.txHash) {
|
|
616
|
+
if (!globalOpts.json && !globalOpts.quiet) {
|
|
617
|
+
console.log(chalk.dim(` Waiting for buy transaction confirmation...`));
|
|
618
|
+
}
|
|
619
|
+
try {
|
|
620
|
+
await publicClient.waitForTransactionReceipt({
|
|
621
|
+
hash: result.txHash,
|
|
622
|
+
});
|
|
623
|
+
if (!globalOpts.json && !globalOpts.quiet) {
|
|
624
|
+
console.log(chalk.dim(` ✅ Buy transaction confirmed`));
|
|
483
625
|
}
|
|
484
|
-
console.log();
|
|
485
|
-
console.log(chalk.dim(" Check your balance with: httpcat balances"));
|
|
486
626
|
}
|
|
487
|
-
catch (
|
|
488
|
-
// If we can't
|
|
489
|
-
|
|
627
|
+
catch (txError) {
|
|
628
|
+
// If we can't wait for confirmation, log but continue
|
|
629
|
+
if (!globalOpts.json && !globalOpts.quiet) {
|
|
630
|
+
console.log(chalk.yellow(` ⚠️ Could not confirm buy transaction, proceeding...`));
|
|
631
|
+
}
|
|
490
632
|
}
|
|
491
633
|
}
|
|
492
|
-
break;
|
|
493
634
|
}
|
|
494
|
-
//
|
|
635
|
+
// Apply delay between iterations (except after the last one)
|
|
636
|
+
if (i < repeatCount && delayMs > 0) {
|
|
637
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
catch (error) {
|
|
641
|
+
// Clear status line before throwing
|
|
642
|
+
if (!globalOpts.json && !globalOpts.quiet) {
|
|
643
|
+
clearStatusLine();
|
|
644
|
+
}
|
|
645
|
+
// Don't catch 402 errors - x402-fetch handles payment automatically
|
|
646
|
+
// 402 is the normal payment flow on first call, not an error
|
|
647
|
+
// Let withLoading and x402-fetch handle payment retries
|
|
648
|
+
// Only re-throw to let outer handler deal with persistent errors
|
|
495
649
|
throw error;
|
|
496
650
|
}
|
|
497
651
|
}
|
|
652
|
+
// Clear status line before showing summary
|
|
653
|
+
if (!globalOpts.json && !globalOpts.quiet) {
|
|
654
|
+
clearStatusLine();
|
|
655
|
+
}
|
|
498
656
|
// Show final summary
|
|
499
657
|
if (!globalOpts.json && !globalOpts.quiet && results.length > 0) {
|
|
500
658
|
const lastResult = results[results.length - 1];
|
|
@@ -517,13 +675,60 @@ Examples:
|
|
|
517
675
|
process.exit(0);
|
|
518
676
|
}
|
|
519
677
|
else {
|
|
520
|
-
// Normal single buy execution
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
678
|
+
// Normal single buy execution with retry logic
|
|
679
|
+
let result = null;
|
|
680
|
+
let lastError = null;
|
|
681
|
+
let attempt = 0;
|
|
682
|
+
const maxRetries = null; // Retry indefinitely for single buys
|
|
683
|
+
while (!result) {
|
|
684
|
+
attempt++;
|
|
685
|
+
try {
|
|
686
|
+
// Show initial attempt message only on first try
|
|
687
|
+
if (attempt === 1 && !globalOpts.json && !globalOpts.quiet) {
|
|
688
|
+
writeStatusLine(chalk.dim("Buying tokens..."));
|
|
689
|
+
}
|
|
690
|
+
// Recreate client for each attempt to ensure fresh signature
|
|
691
|
+
const freshClient = await HttpcatClient.create(privateKey);
|
|
692
|
+
result = await buyToken(freshClient, tokenId, finalAmount, isTestMode, silent, privateKey);
|
|
693
|
+
// Success! Clear any status line and break
|
|
694
|
+
if (!globalOpts.json && !globalOpts.quiet) {
|
|
695
|
+
clearStatusLine();
|
|
696
|
+
}
|
|
697
|
+
break;
|
|
698
|
+
}
|
|
699
|
+
catch (error) {
|
|
700
|
+
lastError = error;
|
|
701
|
+
// Only retry on 402 errors (payment required)
|
|
702
|
+
const is402Error = error instanceof HttpcatError && error.status === 402;
|
|
703
|
+
if (is402Error) {
|
|
704
|
+
// Exponential backoff: 1s, 2s, 4s, 8s, 16s, 32s, 64s, 128s, 256s, 512s (capped at 10s)
|
|
705
|
+
const backoffMs = Math.min(Math.pow(2, attempt - 1) * 1000, 10000);
|
|
706
|
+
const backoffSeconds = backoffMs / 1000;
|
|
707
|
+
if (!globalOpts.json && !globalOpts.quiet) {
|
|
708
|
+
// Write status on same line (overwrites previous)
|
|
709
|
+
const statusMessage = formatRetryMessage(attempt, maxRetries, backoffSeconds);
|
|
710
|
+
writeStatusLine(statusMessage);
|
|
711
|
+
}
|
|
712
|
+
await new Promise((resolve) => setTimeout(resolve, backoffMs));
|
|
713
|
+
continue; // Retry
|
|
714
|
+
}
|
|
715
|
+
else {
|
|
716
|
+
// Not a 402 error - throw immediately
|
|
717
|
+
if (!globalOpts.json && !globalOpts.quiet) {
|
|
718
|
+
clearStatusLine();
|
|
719
|
+
}
|
|
720
|
+
throw error;
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
if (!result) {
|
|
725
|
+
if (lastError) {
|
|
726
|
+
throw lastError;
|
|
727
|
+
}
|
|
728
|
+
else {
|
|
729
|
+
throw new Error("Failed to complete buy");
|
|
730
|
+
}
|
|
731
|
+
}
|
|
527
732
|
if (globalOpts.json) {
|
|
528
733
|
outputJson("token_buy", result);
|
|
529
734
|
}
|
|
@@ -534,7 +739,11 @@ Examples:
|
|
|
534
739
|
}
|
|
535
740
|
}
|
|
536
741
|
catch (error) {
|
|
537
|
-
const globalOpts =
|
|
742
|
+
const globalOpts = getGlobalOpts();
|
|
743
|
+
// Clear status line before showing error
|
|
744
|
+
if (!globalOpts.json && !globalOpts.quiet) {
|
|
745
|
+
clearStatusLine();
|
|
746
|
+
}
|
|
538
747
|
if (globalOpts.json) {
|
|
539
748
|
outputError("token_buy", error, getExitCode(error));
|
|
540
749
|
}
|
|
@@ -559,15 +768,13 @@ Examples:
|
|
|
559
768
|
`)
|
|
560
769
|
.action(async (tokenId, amountInput, command) => {
|
|
561
770
|
try {
|
|
562
|
-
const globalOpts =
|
|
771
|
+
const globalOpts = getGlobalOpts();
|
|
563
772
|
const accountIndex = globalOpts.account;
|
|
564
773
|
// Ensure wallet is unlocked
|
|
565
774
|
await ensureWalletUnlocked();
|
|
566
|
-
|
|
567
|
-
const programOpts = program.opts();
|
|
568
|
-
let privateKey = getPrivateKey(programOpts.privateKey || globalOpts.privateKey, accountIndex);
|
|
775
|
+
let privateKey = getPrivateKey(globalOpts.privateKey, accountIndex);
|
|
569
776
|
// If not configured and not in JSON mode, prompt interactively
|
|
570
|
-
if (!isConfigured(
|
|
777
|
+
if (!isConfigured(globalOpts.privateKey)) {
|
|
571
778
|
if (globalOpts.json) {
|
|
572
779
|
console.error('❌ Not configured. Run "httpcat config" first or use --private-key flag.');
|
|
573
780
|
process.exit(2);
|
|
@@ -594,6 +801,7 @@ Examples:
|
|
|
594
801
|
json: globalOpts.json,
|
|
595
802
|
quiet: globalOpts.quiet,
|
|
596
803
|
spinner: "cat",
|
|
804
|
+
clearOnSuccess: true,
|
|
597
805
|
});
|
|
598
806
|
// Found in database! Continue with normal flow below
|
|
599
807
|
actualBalance = info.userPosition?.tokensOwned || "0";
|
|
@@ -602,14 +810,14 @@ Examples:
|
|
|
602
810
|
// Not in database - treat as external token
|
|
603
811
|
if (error.message?.includes("not found") ||
|
|
604
812
|
error.message?.includes("Invalid UUID")) {
|
|
605
|
-
if (!silent) {
|
|
606
|
-
|
|
813
|
+
if (!silent && !globalOpts.json && !globalOpts.quiet) {
|
|
814
|
+
writeStatusLine(chalk.dim("📊 Checking on-chain balance for external token..."));
|
|
607
815
|
}
|
|
608
816
|
const { createPublicClient, http, parseAbi } = await import("viem");
|
|
609
817
|
const { baseSepolia } = await import("viem/chains");
|
|
610
818
|
const publicClient = createPublicClient({
|
|
611
819
|
chain: baseSepolia,
|
|
612
|
-
transport: http(),
|
|
820
|
+
transport: http(config.getRpcUrl()),
|
|
613
821
|
});
|
|
614
822
|
const ERC20_ABI = parseAbi([
|
|
615
823
|
"function balanceOf(address owner) view returns (uint256)",
|
|
@@ -621,6 +829,9 @@ Examples:
|
|
|
621
829
|
args: [userAddress],
|
|
622
830
|
});
|
|
623
831
|
actualBalance = balance.toString();
|
|
832
|
+
if (!silent && !globalOpts.json && !globalOpts.quiet) {
|
|
833
|
+
clearStatusLine();
|
|
834
|
+
}
|
|
624
835
|
if (!silent) {
|
|
625
836
|
const { formatUnits } = await import("viem");
|
|
626
837
|
console.log(`💼 On-chain balance: ${formatUnits(balance, 18)} tokens`);
|
|
@@ -643,14 +854,14 @@ Examples:
|
|
|
643
854
|
actualBalance = info.userPosition?.tokensOwned || "0";
|
|
644
855
|
// For graduated tokens, check actual on-chain balance
|
|
645
856
|
if (info && info.status === "graduated") {
|
|
646
|
-
if (!silent) {
|
|
647
|
-
|
|
857
|
+
if (!silent && !globalOpts.json && !globalOpts.quiet) {
|
|
858
|
+
writeStatusLine(chalk.dim("📊 Checking on-chain balance for graduated token..."));
|
|
648
859
|
}
|
|
649
860
|
const { createPublicClient, http, parseAbi } = await import("viem");
|
|
650
861
|
const { baseSepolia } = await import("viem/chains");
|
|
651
862
|
const publicClient = createPublicClient({
|
|
652
863
|
chain: baseSepolia,
|
|
653
|
-
transport: http(),
|
|
864
|
+
transport: http(config.getRpcUrl()),
|
|
654
865
|
});
|
|
655
866
|
const ERC20_ABI = parseAbi([
|
|
656
867
|
"function balanceOf(address owner) view returns (uint256)",
|
|
@@ -662,6 +873,9 @@ Examples:
|
|
|
662
873
|
args: [userAddress],
|
|
663
874
|
});
|
|
664
875
|
actualBalance = balance.toString();
|
|
876
|
+
if (!silent && !globalOpts.json && !globalOpts.quiet) {
|
|
877
|
+
clearStatusLine();
|
|
878
|
+
}
|
|
665
879
|
if (!silent) {
|
|
666
880
|
const { formatUnits } = await import("viem");
|
|
667
881
|
console.log(`💼 On-chain balance: ${formatUnits(balance, 18)} tokens`);
|
|
@@ -677,6 +891,7 @@ Examples:
|
|
|
677
891
|
json: globalOpts.json,
|
|
678
892
|
quiet: globalOpts.quiet,
|
|
679
893
|
spinner: "cat",
|
|
894
|
+
clearOnSuccess: true,
|
|
680
895
|
});
|
|
681
896
|
if (globalOpts.json) {
|
|
682
897
|
outputJson("token_sell", result);
|
|
@@ -687,7 +902,7 @@ Examples:
|
|
|
687
902
|
process.exit(0);
|
|
688
903
|
}
|
|
689
904
|
catch (error) {
|
|
690
|
-
const globalOpts =
|
|
905
|
+
const globalOpts = getGlobalOpts();
|
|
691
906
|
if (globalOpts.json) {
|
|
692
907
|
outputError("token_sell", error, getExitCode(error));
|
|
693
908
|
}
|
|
@@ -697,6 +912,72 @@ Examples:
|
|
|
697
912
|
process.exit(getExitCode(error));
|
|
698
913
|
}
|
|
699
914
|
});
|
|
915
|
+
// Swap command (Pro - any token to any token via DEX aggregation)
|
|
916
|
+
program
|
|
917
|
+
.command("swap")
|
|
918
|
+
.description("Swap any token to any token ($0.10 - pro DEX aggregation)")
|
|
919
|
+
.argument("<tokenIn>", "Token to sell (address)")
|
|
920
|
+
.argument("<tokenOut>", "Token to buy (address)")
|
|
921
|
+
.argument("<amount>", "Amount to sell (in smallest unit, e.g., wei)")
|
|
922
|
+
.option("-s, --slippage <bps>", "Slippage tolerance in basis points (default: 50 = 0.5%)", "50")
|
|
923
|
+
.addHelpText("after", `
|
|
924
|
+
Examples:
|
|
925
|
+
# Swap 1 USDC for WETH
|
|
926
|
+
httpcat swap 0x036cbd53842c5426634e7929541ec2318f3dcf7e 0x4200000000000000000000000000000000000006 1000000
|
|
927
|
+
|
|
928
|
+
# Swap with 1% slippage
|
|
929
|
+
httpcat swap 0x036c... 0x4200... 1000000 --slippage 100
|
|
930
|
+
`)
|
|
931
|
+
.action(async (tokenIn, tokenOut, amount, options, command) => {
|
|
932
|
+
try {
|
|
933
|
+
const globalOpts = getGlobalOpts();
|
|
934
|
+
const accountIndex = globalOpts.account;
|
|
935
|
+
// Ensure wallet is unlocked
|
|
936
|
+
await ensureWalletUnlocked();
|
|
937
|
+
let privateKey = getPrivateKey(globalOpts.privateKey, accountIndex);
|
|
938
|
+
// If not configured and not in JSON mode, prompt interactively
|
|
939
|
+
if (!isConfigured(globalOpts.privateKey)) {
|
|
940
|
+
if (globalOpts.json) {
|
|
941
|
+
console.error('❌ Not configured. Run "httpcat config" first or use --private-key flag.');
|
|
942
|
+
process.exit(2);
|
|
943
|
+
}
|
|
944
|
+
privateKey = await promptForPrivateKey();
|
|
945
|
+
}
|
|
946
|
+
const client = await HttpcatClient.create(privateKey);
|
|
947
|
+
const silent = globalOpts.json || globalOpts.quiet;
|
|
948
|
+
const slippageBps = parseInt(options.slippage, 10) || 50;
|
|
949
|
+
if (!silent && !globalOpts.json && !globalOpts.quiet) {
|
|
950
|
+
writeStatusLine(chalk.dim("Swapping tokens..."));
|
|
951
|
+
}
|
|
952
|
+
const result = await withLoading(() => swap(client, tokenIn, tokenOut, amount, slippageBps, silent, privateKey), {
|
|
953
|
+
message: "Processing swap...",
|
|
954
|
+
json: globalOpts.json,
|
|
955
|
+
quiet: globalOpts.quiet,
|
|
956
|
+
spinner: "cat",
|
|
957
|
+
clearOnSuccess: true,
|
|
958
|
+
});
|
|
959
|
+
if (!silent && !globalOpts.json && !globalOpts.quiet) {
|
|
960
|
+
clearStatusLine();
|
|
961
|
+
}
|
|
962
|
+
if (globalOpts.json) {
|
|
963
|
+
console.log(JSON.stringify(result, null, 2));
|
|
964
|
+
}
|
|
965
|
+
else if (!globalOpts.quiet) {
|
|
966
|
+
displaySwapResult(result);
|
|
967
|
+
}
|
|
968
|
+
process.exit(0);
|
|
969
|
+
}
|
|
970
|
+
catch (error) {
|
|
971
|
+
const globalOpts = getGlobalOpts();
|
|
972
|
+
if (globalOpts.json) {
|
|
973
|
+
console.log(JSON.stringify({ error: error.message }, null, 2));
|
|
974
|
+
}
|
|
975
|
+
else if (!globalOpts.quiet) {
|
|
976
|
+
handleError(error, globalOpts.verbose);
|
|
977
|
+
}
|
|
978
|
+
process.exit(getExitCode(error));
|
|
979
|
+
}
|
|
980
|
+
});
|
|
700
981
|
// Claim command
|
|
701
982
|
program
|
|
702
983
|
.command("claim")
|
|
@@ -713,7 +994,7 @@ Examples:
|
|
|
713
994
|
`)
|
|
714
995
|
.action(async (identifier, options, command) => {
|
|
715
996
|
try {
|
|
716
|
-
const globalOpts =
|
|
997
|
+
const globalOpts = getGlobalOpts();
|
|
717
998
|
const accountIndex = globalOpts.account;
|
|
718
999
|
// Ensure wallet is unlocked
|
|
719
1000
|
await ensureWalletUnlocked();
|
|
@@ -739,6 +1020,7 @@ Examples:
|
|
|
739
1020
|
json: globalOpts.json,
|
|
740
1021
|
quiet: globalOpts.quiet,
|
|
741
1022
|
spinner: "cat",
|
|
1023
|
+
clearOnSuccess: true,
|
|
742
1024
|
});
|
|
743
1025
|
if (globalOpts.json) {
|
|
744
1026
|
outputJson("claim_fees", result);
|
|
@@ -754,6 +1036,7 @@ Examples:
|
|
|
754
1036
|
json: globalOpts.json,
|
|
755
1037
|
quiet: globalOpts.quiet,
|
|
756
1038
|
spinner: "cat",
|
|
1039
|
+
clearOnSuccess: true,
|
|
757
1040
|
});
|
|
758
1041
|
if (globalOpts.json) {
|
|
759
1042
|
outputJson("view_fees", result);
|
|
@@ -765,7 +1048,7 @@ Examples:
|
|
|
765
1048
|
process.exit(0);
|
|
766
1049
|
}
|
|
767
1050
|
catch (error) {
|
|
768
|
-
const globalOpts =
|
|
1051
|
+
const globalOpts = getGlobalOpts();
|
|
769
1052
|
if (globalOpts.json) {
|
|
770
1053
|
outputError("claim", error, getExitCode(error));
|
|
771
1054
|
}
|
|
@@ -789,7 +1072,7 @@ Examples:
|
|
|
789
1072
|
`)
|
|
790
1073
|
.action(async (tokenId, command) => {
|
|
791
1074
|
try {
|
|
792
|
-
const globalOpts =
|
|
1075
|
+
const globalOpts = getGlobalOpts();
|
|
793
1076
|
const accountIndex = globalOpts.account;
|
|
794
1077
|
// Ensure wallet is unlocked
|
|
795
1078
|
await ensureWalletUnlocked();
|
|
@@ -813,17 +1096,19 @@ Examples:
|
|
|
813
1096
|
json: globalOpts.json,
|
|
814
1097
|
quiet: globalOpts.quiet,
|
|
815
1098
|
spinner: "cat",
|
|
1099
|
+
clearOnSuccess: true,
|
|
816
1100
|
});
|
|
817
1101
|
if (globalOpts.json) {
|
|
818
1102
|
outputJson("token_info", result);
|
|
819
1103
|
}
|
|
820
1104
|
else if (!globalOpts.quiet) {
|
|
821
|
-
await
|
|
1105
|
+
const client = await HttpcatClient.create(privateKey);
|
|
1106
|
+
await displayTokenInfo(result, userAddress, client.getNetwork());
|
|
822
1107
|
}
|
|
823
1108
|
process.exit(0);
|
|
824
1109
|
}
|
|
825
1110
|
catch (error) {
|
|
826
|
-
const globalOpts =
|
|
1111
|
+
const globalOpts = getGlobalOpts();
|
|
827
1112
|
if (globalOpts.json) {
|
|
828
1113
|
outputError("token_info", error, getExitCode(error));
|
|
829
1114
|
}
|
|
@@ -849,7 +1134,7 @@ Examples:
|
|
|
849
1134
|
`)
|
|
850
1135
|
.action(async (options, command) => {
|
|
851
1136
|
try {
|
|
852
|
-
const globalOpts =
|
|
1137
|
+
const globalOpts = getGlobalOpts();
|
|
853
1138
|
const accountIndex = globalOpts.account;
|
|
854
1139
|
// Ensure wallet is unlocked
|
|
855
1140
|
await ensureWalletUnlocked();
|
|
@@ -869,6 +1154,7 @@ Examples:
|
|
|
869
1154
|
json: globalOpts.json,
|
|
870
1155
|
quiet: globalOpts.quiet,
|
|
871
1156
|
spinner: "cat",
|
|
1157
|
+
clearOnSuccess: true,
|
|
872
1158
|
});
|
|
873
1159
|
if (globalOpts.json) {
|
|
874
1160
|
outputJson("list_tokens", result);
|
|
@@ -879,7 +1165,7 @@ Examples:
|
|
|
879
1165
|
process.exit(0);
|
|
880
1166
|
}
|
|
881
1167
|
catch (error) {
|
|
882
|
-
const globalOpts =
|
|
1168
|
+
const globalOpts = getGlobalOpts();
|
|
883
1169
|
if (globalOpts.json) {
|
|
884
1170
|
outputError("list_tokens", error, getExitCode(error));
|
|
885
1171
|
}
|
|
@@ -908,7 +1194,7 @@ Examples:
|
|
|
908
1194
|
`)
|
|
909
1195
|
.action(async (options, command) => {
|
|
910
1196
|
try {
|
|
911
|
-
const globalOpts =
|
|
1197
|
+
const globalOpts = getGlobalOpts();
|
|
912
1198
|
const accountIndex = globalOpts.account;
|
|
913
1199
|
// Ensure wallet is unlocked
|
|
914
1200
|
await ensureWalletUnlocked();
|
|
@@ -971,6 +1257,7 @@ Examples:
|
|
|
971
1257
|
json: globalOpts.json,
|
|
972
1258
|
quiet: globalOpts.quiet,
|
|
973
1259
|
spinner: "cat",
|
|
1260
|
+
clearOnSuccess: true,
|
|
974
1261
|
});
|
|
975
1262
|
if (globalOpts.json) {
|
|
976
1263
|
outputJson("transactions", result);
|
|
@@ -981,7 +1268,7 @@ Examples:
|
|
|
981
1268
|
process.exit(0);
|
|
982
1269
|
}
|
|
983
1270
|
catch (error) {
|
|
984
|
-
const globalOpts =
|
|
1271
|
+
const globalOpts = getGlobalOpts();
|
|
985
1272
|
if (globalOpts.json) {
|
|
986
1273
|
outputError("transactions", error, getExitCode(error));
|
|
987
1274
|
}
|
|
@@ -1007,7 +1294,7 @@ Examples:
|
|
|
1007
1294
|
`)
|
|
1008
1295
|
.action(async (command) => {
|
|
1009
1296
|
try {
|
|
1010
|
-
const globalOpts =
|
|
1297
|
+
const globalOpts = getGlobalOpts();
|
|
1011
1298
|
const accountIndex = globalOpts.account;
|
|
1012
1299
|
// Ensure wallet is unlocked
|
|
1013
1300
|
await ensureWalletUnlocked();
|
|
@@ -1049,6 +1336,7 @@ Examples:
|
|
|
1049
1336
|
json: globalOpts.json,
|
|
1050
1337
|
quiet: globalOpts.quiet,
|
|
1051
1338
|
spinner: "cat",
|
|
1339
|
+
clearOnSuccess: true,
|
|
1052
1340
|
});
|
|
1053
1341
|
const filter = command.active
|
|
1054
1342
|
? "active"
|
|
@@ -1075,7 +1363,7 @@ Examples:
|
|
|
1075
1363
|
process.exit(0);
|
|
1076
1364
|
}
|
|
1077
1365
|
catch (error) {
|
|
1078
|
-
const globalOpts =
|
|
1366
|
+
const globalOpts = getGlobalOpts();
|
|
1079
1367
|
if (globalOpts.json) {
|
|
1080
1368
|
outputError("positions", error, getExitCode(error));
|
|
1081
1369
|
}
|
|
@@ -1097,7 +1385,7 @@ Examples:
|
|
|
1097
1385
|
`)
|
|
1098
1386
|
.action(async (command) => {
|
|
1099
1387
|
try {
|
|
1100
|
-
const globalOpts =
|
|
1388
|
+
const globalOpts = getGlobalOpts();
|
|
1101
1389
|
const privateKey = getPrivateKey(globalOpts.privateKey);
|
|
1102
1390
|
if (!isConfigured(globalOpts.privateKey)) {
|
|
1103
1391
|
console.error('❌ Not configured. Run "httpcat config" first or use --private-key flag.');
|
|
@@ -1109,6 +1397,7 @@ Examples:
|
|
|
1109
1397
|
json: globalOpts.json,
|
|
1110
1398
|
quiet: globalOpts.quiet,
|
|
1111
1399
|
spinner: "cat",
|
|
1400
|
+
clearOnSuccess: true,
|
|
1112
1401
|
});
|
|
1113
1402
|
if (globalOpts.json) {
|
|
1114
1403
|
outputJson("health", result);
|
|
@@ -1119,7 +1408,7 @@ Examples:
|
|
|
1119
1408
|
process.exit(result.status === "ok" ? 0 : 1);
|
|
1120
1409
|
}
|
|
1121
1410
|
catch (error) {
|
|
1122
|
-
const globalOpts =
|
|
1411
|
+
const globalOpts = getGlobalOpts();
|
|
1123
1412
|
if (globalOpts.json) {
|
|
1124
1413
|
outputError("health", error, getExitCode(error));
|
|
1125
1414
|
}
|
|
@@ -1141,14 +1430,15 @@ Examples:
|
|
|
1141
1430
|
`)
|
|
1142
1431
|
.action(async (command) => {
|
|
1143
1432
|
try {
|
|
1144
|
-
|
|
1145
|
-
const
|
|
1433
|
+
// Use program.opts() directly since command.parent?.opts() doesn't include global options
|
|
1434
|
+
const programOpts = program.opts();
|
|
1435
|
+
const accountIndex = programOpts.account;
|
|
1146
1436
|
// Ensure wallet is unlocked
|
|
1147
1437
|
await ensureWalletUnlocked();
|
|
1148
|
-
let privateKey = getPrivateKey(
|
|
1438
|
+
let privateKey = getPrivateKey(programOpts.privateKey, accountIndex);
|
|
1149
1439
|
// If not configured and not in JSON mode, prompt interactively
|
|
1150
|
-
if (!isConfigured(
|
|
1151
|
-
if (
|
|
1440
|
+
if (!isConfigured(programOpts.privateKey)) {
|
|
1441
|
+
if (programOpts.json) {
|
|
1152
1442
|
console.error('❌ Not configured. Run "httpcat config" first or use --private-key flag.');
|
|
1153
1443
|
process.exit(2);
|
|
1154
1444
|
}
|
|
@@ -1157,25 +1447,26 @@ Examples:
|
|
|
1157
1447
|
}
|
|
1158
1448
|
const result = await withLoading(() => checkBalance(privateKey), {
|
|
1159
1449
|
message: "Checking balances...",
|
|
1160
|
-
json:
|
|
1161
|
-
quiet:
|
|
1450
|
+
json: programOpts.json,
|
|
1451
|
+
quiet: programOpts.quiet,
|
|
1162
1452
|
spinner: "cat",
|
|
1453
|
+
clearOnSuccess: true,
|
|
1163
1454
|
});
|
|
1164
|
-
if (
|
|
1455
|
+
if (programOpts.json) {
|
|
1165
1456
|
outputJson("balances", result);
|
|
1166
1457
|
}
|
|
1167
|
-
else if (!
|
|
1458
|
+
else if (!programOpts.quiet) {
|
|
1168
1459
|
displayBalance(result);
|
|
1169
1460
|
}
|
|
1170
1461
|
process.exit(0);
|
|
1171
1462
|
}
|
|
1172
1463
|
catch (error) {
|
|
1173
|
-
const
|
|
1174
|
-
if (
|
|
1464
|
+
const programOpts = program.opts();
|
|
1465
|
+
if (programOpts.json) {
|
|
1175
1466
|
outputError("balances", error, getExitCode(error));
|
|
1176
1467
|
}
|
|
1177
1468
|
else {
|
|
1178
|
-
handleError(error,
|
|
1469
|
+
handleError(error, programOpts.verbose);
|
|
1179
1470
|
}
|
|
1180
1471
|
process.exit(getExitCode(error));
|
|
1181
1472
|
}
|
|
@@ -1203,6 +1494,7 @@ Examples:
|
|
|
1203
1494
|
json: globalOpts.json,
|
|
1204
1495
|
quiet: globalOpts.quiet,
|
|
1205
1496
|
spinner: "cat",
|
|
1497
|
+
clearOnSuccess: true,
|
|
1206
1498
|
});
|
|
1207
1499
|
if (globalOpts.json) {
|
|
1208
1500
|
outputJson("account", result);
|
|
@@ -1304,7 +1596,7 @@ Examples:
|
|
|
1304
1596
|
`)
|
|
1305
1597
|
.action(async (tokenIdentifier, options, command) => {
|
|
1306
1598
|
try {
|
|
1307
|
-
const globalOpts =
|
|
1599
|
+
const globalOpts = getGlobalOpts();
|
|
1308
1600
|
const chatOpts = options;
|
|
1309
1601
|
const accountIndex = globalOpts.account;
|
|
1310
1602
|
// Ensure wallet is unlocked
|
|
@@ -1324,15 +1616,18 @@ Examples:
|
|
|
1324
1616
|
if (globalOpts.json) {
|
|
1325
1617
|
const inputFormat = chatOpts.inputFormat || "text";
|
|
1326
1618
|
await startChatStream(client, true, // jsonMode
|
|
1327
|
-
tokenIdentifier || undefined, inputFormat
|
|
1619
|
+
tokenIdentifier || undefined, inputFormat, false // returnToShell
|
|
1620
|
+
);
|
|
1328
1621
|
}
|
|
1329
1622
|
else {
|
|
1330
|
-
// Interactive mode:
|
|
1331
|
-
await
|
|
1623
|
+
// Interactive mode: use Ink-based chat UI
|
|
1624
|
+
await startChatStream(client, false, // jsonMode
|
|
1625
|
+
tokenIdentifier || undefined, "text", false // returnToShell (not from shell, so exit on quit)
|
|
1626
|
+
);
|
|
1332
1627
|
}
|
|
1333
1628
|
}
|
|
1334
1629
|
catch (error) {
|
|
1335
|
-
const globalOpts =
|
|
1630
|
+
const globalOpts = getGlobalOpts();
|
|
1336
1631
|
if (globalOpts.json) {
|
|
1337
1632
|
outputError("chat", error, getExitCode(error));
|
|
1338
1633
|
}
|
|
@@ -1342,16 +1637,13 @@ Examples:
|
|
|
1342
1637
|
process.exit(getExitCode(error));
|
|
1343
1638
|
}
|
|
1344
1639
|
});
|
|
1345
|
-
//
|
|
1640
|
+
// Cat command - starts agent interactive mode
|
|
1346
1641
|
program
|
|
1347
|
-
.command("
|
|
1348
|
-
.
|
|
1349
|
-
.description("Configure AI agent for trading assistance")
|
|
1350
|
-
.option("-s, --setup", "Run setup wizard to configure API key")
|
|
1642
|
+
.command("cat")
|
|
1643
|
+
.description("Start interactive AI agent mode")
|
|
1351
1644
|
.addHelpText("after", `
|
|
1352
1645
|
Examples:
|
|
1353
|
-
httpcat
|
|
1354
|
-
httpcat cat --setup # Same, using 'cat' alias
|
|
1646
|
+
httpcat cat # Start interactive agent mode
|
|
1355
1647
|
|
|
1356
1648
|
The AI agent helps you:
|
|
1357
1649
|
• Trade tokens using natural language
|
|
@@ -1359,37 +1651,53 @@ The AI agent helps you:
|
|
|
1359
1651
|
• Get market information
|
|
1360
1652
|
• Execute commands hands-free
|
|
1361
1653
|
|
|
1362
|
-
|
|
1654
|
+
Type /exit to quit agent mode.
|
|
1363
1655
|
`)
|
|
1364
|
-
.action(async (options) => {
|
|
1656
|
+
.action(async (options, command) => {
|
|
1365
1657
|
try {
|
|
1366
|
-
const
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1658
|
+
const globalOpts = getGlobalOpts();
|
|
1659
|
+
const accountIndex = globalOpts.account;
|
|
1660
|
+
// Ensure wallet is unlocked
|
|
1661
|
+
await ensureWalletUnlocked();
|
|
1662
|
+
let privateKey = getPrivateKey(globalOpts.privateKey, accountIndex);
|
|
1663
|
+
// If not configured and not in JSON mode, prompt interactively
|
|
1664
|
+
if (!isConfigured(globalOpts.privateKey)) {
|
|
1665
|
+
if (globalOpts.json) {
|
|
1666
|
+
console.error('❌ Not configured. Run "httpcat config" first or use --private-key flag.');
|
|
1667
|
+
process.exit(2);
|
|
1668
|
+
}
|
|
1669
|
+
// Interactive prompt for private key
|
|
1670
|
+
privateKey = await promptForPrivateKey();
|
|
1370
1671
|
}
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1672
|
+
const client = await HttpcatClient.create(privateKey);
|
|
1673
|
+
// Check if agent is configured
|
|
1674
|
+
const agentConfig = config.getAIAgentConfig();
|
|
1675
|
+
if (!agentConfig) {
|
|
1375
1676
|
console.log();
|
|
1376
|
-
console.log(chalk.yellow("
|
|
1677
|
+
console.log(chalk.yellow("⚠️ Agent not configured"));
|
|
1678
|
+
console.log(chalk.dim("Run 'httpcat agent --setup' to configure the AI agent."));
|
|
1377
1679
|
console.log();
|
|
1378
|
-
|
|
1379
|
-
|
|
1680
|
+
process.exit(1);
|
|
1681
|
+
}
|
|
1682
|
+
const apiKey = config.getAIAgentApiKey();
|
|
1683
|
+
if (!apiKey) {
|
|
1380
1684
|
console.log();
|
|
1381
|
-
console.log(chalk.
|
|
1382
|
-
console.log(chalk.dim("
|
|
1383
|
-
console.log(chalk.dim(" Use the CLI setup above to configure your API keys for future use."));
|
|
1685
|
+
console.log(chalk.red("❌ Failed to get API key"));
|
|
1686
|
+
console.log(chalk.dim("Run 'httpcat agent --setup' to configure the API key."));
|
|
1384
1687
|
console.log();
|
|
1385
|
-
process.exit(
|
|
1688
|
+
process.exit(1);
|
|
1386
1689
|
}
|
|
1690
|
+
// Start agent interactive mode
|
|
1691
|
+
const { startAgentInteractiveMode } = await import("./interactive/shell.js");
|
|
1692
|
+
await startAgentInteractiveMode(client);
|
|
1387
1693
|
}
|
|
1388
1694
|
catch (error) {
|
|
1389
1695
|
handleError(error, program.opts().verbose);
|
|
1390
1696
|
process.exit(getExitCode(error));
|
|
1391
1697
|
}
|
|
1392
1698
|
});
|
|
1699
|
+
// Autonomous Trading Agent commands
|
|
1700
|
+
setupAgentCommands(program);
|
|
1393
1701
|
// MCP Server command
|
|
1394
1702
|
program
|
|
1395
1703
|
.command("mcp-server")
|
|
@@ -1436,12 +1744,12 @@ function displayGroupedHelp(program) {
|
|
|
1436
1744
|
console.log();
|
|
1437
1745
|
// Group commands
|
|
1438
1746
|
const commandGroups = {
|
|
1439
|
-
"Token Operations": ["create", "buy", "sell", "claim"],
|
|
1747
|
+
"Token Operations": ["create", "buy", "sell", "swap", "claim"],
|
|
1440
1748
|
"Token Information": ["info", "list"],
|
|
1441
|
-
|
|
1749
|
+
Portfolio: ["positions", "transactions", "balances"],
|
|
1442
1750
|
"Account Management": ["account", "env"],
|
|
1443
|
-
"AI & Social": ["agent", "chat"],
|
|
1444
|
-
|
|
1751
|
+
"AI & Social": ["agent", "cat", "chat"],
|
|
1752
|
+
System: ["health", "config", "mcp-server"],
|
|
1445
1753
|
};
|
|
1446
1754
|
// Display grouped commands
|
|
1447
1755
|
for (const [groupName, commandNames] of Object.entries(commandGroups)) {
|
|
@@ -1545,9 +1853,9 @@ function getTerminalSessionId() {
|
|
|
1545
1853
|
// Fall through to other methods (e.g., on macOS or if /proc doesn't exist)
|
|
1546
1854
|
}
|
|
1547
1855
|
// Fall back to environment variables or user/term combination
|
|
1548
|
-
return process.env.TERM_SESSION_ID ||
|
|
1856
|
+
return (process.env.TERM_SESSION_ID ||
|
|
1549
1857
|
process.env.SESSION_ID ||
|
|
1550
|
-
`${process.env.USER || "default"}-${process.env.TERM || "unknown"}
|
|
1858
|
+
`${process.env.USER || "default"}-${process.env.TERM || "unknown"}`);
|
|
1551
1859
|
}
|
|
1552
1860
|
// Helper function to check if animation has been shown in this terminal session
|
|
1553
1861
|
function hasAnimationBeenShown() {
|