httpcat-cli 0.3.0 → 0.3.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/ci.yml +3 -0
- package/.github/workflows/rc-publish.yml +6 -0
- package/.github/workflows/release.yml +102 -0
- package/.github/workflows/sync-version.yml +31 -2
- package/README.md +1408 -109
- package/additions.txt +3 -0
- package/bun.lock +260 -25
- 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 +356 -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 +297 -4
- 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 +272 -21
- package/dist/client.js.map +1 -1
- package/dist/commands/account.d.ts.map +1 -1
- package/dist/commands/account.js +187 -33
- package/dist/commands/account.js.map +1 -1
- package/dist/commands/agent.d.ts.map +1 -0
- package/dist/commands/agent.js +125 -0
- package/dist/commands/agent.js.map +1 -0
- package/dist/commands/approve.d.ts.map +1 -0
- package/dist/commands/approve.js +505 -0
- package/dist/commands/approve.js.map +1 -0
- package/dist/commands/automation.d.ts.map +1 -0
- package/dist/commands/automation.js +346 -0
- package/dist/commands/automation.js.map +1 -0
- package/dist/commands/balances.d.ts.map +1 -1
- package/dist/commands/balances.js +226 -73
- package/dist/commands/balances.js.map +1 -1
- package/dist/commands/buy.d.ts.map +1 -1
- package/dist/commands/buy.js +149 -146
- package/dist/commands/buy.js.map +1 -1
- package/dist/commands/call.d.ts.map +1 -0
- package/dist/commands/call.js +51 -0
- package/dist/commands/call.js.map +1 -0
- package/dist/commands/cex.d.ts.map +1 -0
- package/dist/commands/cex.js +958 -0
- package/dist/commands/cex.js.map +1 -0
- package/dist/commands/chat.d.ts.map +1 -1
- package/dist/commands/chat.js +169 -411
- package/dist/commands/chat.js.map +1 -1
- package/dist/commands/claim.d.ts.map +1 -1
- package/dist/commands/claim.js +313 -29
- package/dist/commands/claim.js.map +1 -1
- package/dist/commands/create.d.ts.map +1 -1
- package/dist/commands/create.js +151 -43
- package/dist/commands/create.js.map +1 -1
- package/dist/commands/gasless-swap.d.ts.map +1 -0
- package/dist/commands/gasless-swap.js +232 -0
- package/dist/commands/gasless-swap.js.map +1 -0
- package/dist/commands/health.d.ts.map +1 -1
- package/dist/commands/health.js +63 -7
- package/dist/commands/health.js.map +1 -1
- package/dist/commands/info.d.ts.map +1 -1
- package/dist/commands/info.js +131 -47
- package/dist/commands/info.js.map +1 -1
- package/dist/commands/launchpad.d.ts.map +1 -0
- package/dist/commands/launchpad.js +708 -0
- package/dist/commands/launchpad.js.map +1 -0
- package/dist/commands/list.d.ts.map +1 -1
- package/dist/commands/list.js +57 -23
- package/dist/commands/list.js.map +1 -1
- package/dist/commands/market.d.ts.map +1 -0
- package/dist/commands/market.js +960 -0
- package/dist/commands/market.js.map +1 -0
- package/dist/commands/mcp-install.d.ts.map +1 -0
- package/dist/commands/mcp-install.js +387 -0
- package/dist/commands/mcp-install.js.map +1 -0
- package/dist/commands/opps.d.ts.map +1 -0
- package/dist/commands/opps.js +409 -0
- package/dist/commands/opps.js.map +1 -0
- package/dist/commands/perps.d.ts.map +1 -0
- package/dist/commands/perps.js +248 -0
- package/dist/commands/perps.js.map +1 -0
- package/dist/commands/portfolio.d.ts.map +1 -0
- package/dist/commands/portfolio.js +679 -0
- package/dist/commands/portfolio.js.map +1 -0
- 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/predict.d.ts.map +1 -0
- package/dist/commands/predict.js +280 -0
- package/dist/commands/predict.js.map +1 -0
- package/dist/commands/predictions.d.ts.map +1 -0
- package/dist/commands/predictions.js +486 -0
- package/dist/commands/predictions.js.map +1 -0
- package/dist/commands/risk.d.ts.map +1 -0
- package/dist/commands/risk.js +225 -0
- package/dist/commands/risk.js.map +1 -0
- package/dist/commands/security.d.ts.map +1 -0
- package/dist/commands/security.js +244 -0
- package/dist/commands/security.js.map +1 -0
- package/dist/commands/sell.d.ts.map +1 -1
- package/dist/commands/sell.js +67 -34
- package/dist/commands/sell.js.map +1 -1
- package/dist/commands/send.d.ts.map +1 -0
- package/dist/commands/send.js +733 -0
- package/dist/commands/send.js.map +1 -0
- package/dist/commands/sign.d.ts.map +1 -0
- package/dist/commands/sign.js +1048 -0
- package/dist/commands/sign.js.map +1 -0
- package/dist/commands/swap.d.ts.map +1 -0
- package/dist/commands/swap.js +744 -0
- package/dist/commands/swap.js.map +1 -0
- package/dist/commands/system.d.ts.map +1 -0
- package/dist/commands/system.js +417 -0
- package/dist/commands/system.js.map +1 -0
- package/dist/commands/tools/index.d.ts.map +1 -0
- package/dist/commands/tools/index.js +2040 -0
- package/dist/commands/tools/index.js.map +1 -0
- package/dist/commands/trade.d.ts.map +1 -0
- package/dist/commands/trade.js +237 -0
- package/dist/commands/trade.js.map +1 -0
- package/dist/commands/transactions.d.ts.map +1 -1
- package/dist/commands/transactions.js +29 -17
- package/dist/commands/transactions.js.map +1 -1
- package/dist/commands/update.d.ts.map +1 -0
- package/dist/commands/update.js +429 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +351 -40
- package/dist/config.js.map +1 -1
- package/dist/index.js +4524 -924
- package/dist/index.js.map +1 -1
- package/dist/interactive/art.d.ts.map +1 -1
- package/dist/interactive/art.js +33 -1
- package/dist/interactive/art.js.map +1 -1
- package/dist/interactive/shell.d.ts.map +1 -1
- package/dist/interactive/shell.js +467 -2652
- package/dist/interactive/shell.js.map +1 -1
- package/dist/mcp/context.d.ts.map +1 -0
- package/dist/mcp/context.js +211 -0
- package/dist/mcp/context.js.map +1 -0
- package/dist/mcp/onboarding.d.ts.map +1 -0
- package/dist/mcp/onboarding.js +266 -0
- package/dist/mcp/onboarding.js.map +1 -0
- package/dist/mcp/resources.d.ts.map +1 -0
- package/dist/mcp/resources.js +222 -0
- package/dist/mcp/resources.js.map +1 -0
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +51 -1
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +4119 -169
- 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 +151 -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/admin.d.ts.map +1 -0
- package/dist/utils/admin.js +144 -0
- package/dist/utils/admin.js.map +1 -0
- package/dist/utils/autoSetup.d.ts.map +1 -0
- package/dist/utils/autoSetup.js +252 -0
- package/dist/utils/autoSetup.js.map +1 -0
- package/dist/utils/build-constants.d.ts.map +1 -0
- package/dist/utils/build-constants.js +10 -0
- package/dist/utils/build-constants.js.map +1 -0
- package/dist/utils/constants.d.ts.map +1 -1
- package/dist/utils/errors.d.ts.map +1 -1
- package/dist/utils/errors.js +10 -1
- package/dist/utils/errors.js.map +1 -1
- package/dist/utils/formatting.d.ts.map +1 -1
- package/dist/utils/formatting.js +46 -9
- package/dist/utils/formatting.js.map +1 -1
- package/dist/utils/llm-cli-config.d.ts.map +1 -0
- package/dist/utils/llm-cli-config.js +963 -0
- package/dist/utils/llm-cli-config.js.map +1 -0
- package/dist/utils/llm-cli-detector.d.ts.map +1 -0
- package/dist/utils/llm-cli-detector.js +202 -0
- package/dist/utils/llm-cli-detector.js.map +1 -0
- package/dist/utils/loading.d.ts.map +1 -1
- package/dist/utils/loading.js +25 -3
- package/dist/utils/loading.js.map +1 -1
- package/dist/utils/maintenance.d.ts.map +1 -0
- package/dist/utils/maintenance.js +17 -0
- package/dist/utils/maintenance.js.map +1 -0
- package/dist/utils/mcp-config.d.ts.map +1 -0
- package/dist/utils/mcp-config.js +77 -0
- package/dist/utils/mcp-config.js.map +1 -0
- package/dist/utils/privateKeyPrompt.d.ts.map +1 -1
- package/dist/utils/privateKeyPrompt.js +308 -129
- package/dist/utils/privateKeyPrompt.js.map +1 -1
- package/dist/utils/process-cleanup.d.ts.map +1 -0
- package/dist/utils/process-cleanup.js +136 -0
- package/dist/utils/process-cleanup.js.map +1 -0
- package/dist/utils/retry.d.ts.map +1 -0
- package/dist/utils/retry.js +56 -0
- package/dist/utils/retry.js.map +1 -0
- package/dist/utils/rpc-helpers.d.ts.map +1 -0
- package/dist/utils/rpc-helpers.js +70 -0
- package/dist/utils/rpc-helpers.js.map +1 -0
- package/dist/utils/rpc-transport.d.ts.map +1 -0
- package/dist/utils/rpc-transport.js +87 -0
- package/dist/utils/rpc-transport.js.map +1 -0
- package/dist/utils/shell-setup.d.ts.map +1 -0
- package/dist/utils/shell-setup.js +531 -0
- package/dist/utils/shell-setup.js.map +1 -0
- package/dist/utils/status.d.ts.map +1 -1
- package/dist/utils/status.js +34 -5
- package/dist/utils/status.js.map +1 -1
- package/dist/utils/token-resolver.d.ts.map +1 -1
- package/dist/utils/token-resolver.js +51 -8
- package/dist/utils/token-resolver.js.map +1 -1
- package/dist/utils/x402-caller.d.ts.map +1 -0
- package/dist/utils/x402-caller.js +17 -0
- package/dist/utils/x402-caller.js.map +1 -0
- package/docs/README.md +28 -0
- package/docs/agent/README.md +18 -0
- package/docs/api/README.md +41 -0
- package/docs/cli/README.md +42 -0
- package/docs/guides/README.md +26 -0
- package/docs/implementation/README.md +18 -0
- package/docs/planning/README.md +19 -0
- package/docs/testing/README.md +15 -0
- package/docs/ux/README.md +16 -0
- package/issues.txt +2 -0
- package/package.json +24 -9
- package/scripts/cat-spin.sh +417 -0
- package/scripts/deprecate-rc-versions.js +58 -0
- package/scripts/inject-build-constants.js +43 -0
- package/scripts/monitor-foobar.js +117 -0
- package/swap.logs +61 -0
- package/swapping.txt +108 -0
- package/test.txt +12 -0
- package/tests/fixtures/test-data.json +16 -0
- package/Screenshot 2025-12-21 at 8.56.02/342/200/257PM.png +0 -0
|
@@ -1,2702 +1,517 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Interactive Shell - Ink version
|
|
4
|
+
*
|
|
5
|
+
* This is a React/Ink replacement for the neo-blessed shell.
|
|
6
|
+
* Key improvements:
|
|
7
|
+
* - No manual rendering needed (React handles it)
|
|
8
|
+
* - Cleaner state management
|
|
9
|
+
* - Better separation of concerns
|
|
10
|
+
* - Command history with hooks
|
|
11
|
+
* - Theme toggling with F1
|
|
12
|
+
*/
|
|
13
|
+
import { useState, useEffect } from "react";
|
|
14
|
+
import { render, Box, useInput, useApp, useStdout } from "ink";
|
|
15
|
+
import { ThemeProvider, useTheme, } from "../ui/components/ThemeProvider.js";
|
|
16
|
+
import { ChatHeader } from "../ui/components/agent/ChatHeader.js";
|
|
17
|
+
import { ShellOutput } from "../ui/components/shell/ShellOutput.js";
|
|
18
|
+
import { ShellInput } from "../ui/components/shell/ShellInput.js";
|
|
4
19
|
import { HttpcatClient } from "../client.js";
|
|
20
|
+
import chalk from "chalk";
|
|
5
21
|
import { config } from "../config.js";
|
|
6
|
-
import { printCat } from "./art.js";
|
|
7
|
-
import { validateAmount } from "../utils/validation.js";
|
|
8
|
-
// Import commands
|
|
9
|
-
import { createToken, processPhotoUrl, isFilePath, } from "../commands/create.js";
|
|
10
|
-
import { buyToken, TEST_AMOUNTS, PROD_AMOUNTS, } from "../commands/buy.js";
|
|
11
|
-
import { sellToken, parseTokenAmount } from "../commands/sell.js";
|
|
12
|
-
import { getTokenInfo } from "../commands/info.js";
|
|
13
|
-
import { listTokens } from "../commands/list.js";
|
|
14
|
-
import { formatCurrency, formatTokenAmount, formatAddress, } from "../utils/formatting.js";
|
|
15
|
-
import { getPositions } from "../commands/positions.js";
|
|
16
22
|
import { privateKeyToAccount } from "viem/accounts";
|
|
17
|
-
import {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
//
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const
|
|
34
|
-
if (
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
// Check TERM_PROGRAM for common terminals
|
|
43
|
-
const termProgram = process.env.TERM_PROGRAM?.toLowerCase();
|
|
44
|
-
if (termProgram) {
|
|
45
|
-
// These terminals often have dark backgrounds by default
|
|
46
|
-
if (["iterm2", "vscode", "hyper", "alacritty", "kitty"].includes(termProgram)) {
|
|
47
|
-
return "dark";
|
|
48
|
-
}
|
|
49
|
-
// These often have light backgrounds
|
|
50
|
-
if (["apple_terminal"].includes(termProgram)) {
|
|
51
|
-
return "light";
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
// Check for common dark terminal indicators
|
|
55
|
-
const term = process.env.TERM?.toLowerCase() || "";
|
|
56
|
-
if (term.includes("256") || term.includes("xterm")) {
|
|
57
|
-
// Most modern terminals default to dark
|
|
58
|
-
return "dark";
|
|
59
|
-
}
|
|
60
|
-
// Default to dark (safer assumption for modern terminals)
|
|
61
|
-
return "dark";
|
|
62
|
-
}
|
|
63
|
-
export async function startInteractiveShell(client, autoChatToken) {
|
|
64
|
-
// Auto-detect terminal background and set default theme
|
|
65
|
-
const detectedBg = detectTerminalBackground();
|
|
66
|
-
let currentTheme = detectedBg === "dark" ? "dark" : "win95";
|
|
67
|
-
// Helper function to get theme-appropriate cyan/blue color for blessed tags
|
|
68
|
-
// For dark theme, use lighter colors (light-cyan-fg) for better visibility on black
|
|
69
|
-
const getCyanColor = (theme) => theme === "dark" ? "light-cyan-fg" : "cyan-fg";
|
|
70
|
-
const getBlueColor = (theme) => theme === "dark" ? "light-blue-fg" : "blue-fg";
|
|
71
|
-
// Create blessed screen with optimized settings
|
|
72
|
-
const screen = blessed.screen({
|
|
73
|
-
smartCSR: true,
|
|
74
|
-
title: "httpcat Interactive Shell",
|
|
75
|
-
fullUnicode: true, // Support double-width/surrogate/combining chars (emojis)
|
|
76
|
-
fastCSR: false, // Disable fast CSR to prevent rendering issues
|
|
77
|
-
cursor: {
|
|
78
|
-
artificial: true,
|
|
79
|
-
shape: "line",
|
|
80
|
-
blink: true,
|
|
81
|
-
color: "green",
|
|
82
|
-
},
|
|
83
|
-
// Force Unicode support for emojis
|
|
84
|
-
forceUnicode: true,
|
|
85
|
-
});
|
|
86
|
-
const network = client.getNetwork();
|
|
87
|
-
// Theme colors - no backgrounds, just borders
|
|
88
|
-
const getThemeColors = (theme) => {
|
|
89
|
-
switch (theme) {
|
|
90
|
-
case "light":
|
|
91
|
-
return {
|
|
92
|
-
bg: "default", // Transparent/default
|
|
93
|
-
fg: "black",
|
|
94
|
-
border: "black",
|
|
95
|
-
inputBg: "default",
|
|
96
|
-
inputFg: "black", // Explicit black for visibility
|
|
97
|
-
inputFocusBg: "default",
|
|
98
|
-
inputFocusFg: "black",
|
|
99
|
-
};
|
|
100
|
-
case "win95":
|
|
101
|
-
return {
|
|
102
|
-
bg: "default",
|
|
103
|
-
fg: "black",
|
|
104
|
-
border: "black",
|
|
105
|
-
inputBg: "default",
|
|
106
|
-
inputFg: "black", // Explicit black for visibility
|
|
107
|
-
inputFocusBg: "default",
|
|
108
|
-
inputFocusFg: "black",
|
|
109
|
-
};
|
|
110
|
-
default: // dark
|
|
111
|
-
return {
|
|
112
|
-
bg: "default",
|
|
113
|
-
fg: "green",
|
|
114
|
-
border: "green",
|
|
115
|
-
inputBg: "default",
|
|
116
|
-
inputFg: "green", // Explicit green for visibility
|
|
117
|
-
inputFocusBg: "default",
|
|
118
|
-
inputFocusFg: "green",
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
};
|
|
122
|
-
let themeColors = getThemeColors(currentTheme);
|
|
123
|
-
// Create header box with thick borders, transparent background - more compact
|
|
124
|
-
const headerBox = blessed.box({
|
|
125
|
-
top: 0,
|
|
126
|
-
left: 0,
|
|
127
|
-
width: "100%",
|
|
128
|
-
height: 6, // Reduced to save vertical space
|
|
129
|
-
content: "",
|
|
130
|
-
tags: true,
|
|
131
|
-
style: {
|
|
132
|
-
fg: themeColors.fg,
|
|
133
|
-
bg: "default", // Transparent
|
|
134
|
-
bold: false,
|
|
135
|
-
border: {
|
|
136
|
-
fg: themeColors.border,
|
|
137
|
-
bold: true,
|
|
138
|
-
},
|
|
139
|
-
},
|
|
140
|
-
padding: {
|
|
141
|
-
left: 1,
|
|
142
|
-
right: 1,
|
|
143
|
-
top: 0,
|
|
144
|
-
bottom: 0,
|
|
145
|
-
},
|
|
146
|
-
border: {
|
|
147
|
-
type: "line",
|
|
148
|
-
fg: themeColors.border,
|
|
149
|
-
ch: "═", // Double line for thicker border
|
|
150
|
-
},
|
|
151
|
-
});
|
|
152
|
-
// Cat face variants for animation
|
|
153
|
-
const catFaces = [
|
|
154
|
-
{ name: "Sleepy", face: "[=^ -.- ^=]" },
|
|
155
|
-
{ name: "Smug", face: "[=^‿^=]" },
|
|
156
|
-
{ name: "Unhinged", face: "[=^◉_◉^=]" },
|
|
157
|
-
{ name: "Judgy", face: "[=^ಠ‿ಠ^=]" },
|
|
158
|
-
{ name: "Cute", face: "[=^。^=]" },
|
|
159
|
-
{ name: "Menacing", face: "[=^>_<^=]" },
|
|
160
|
-
{ name: "Loaf Mode", face: "[=^___^=]" },
|
|
161
|
-
{ name: "Cosmic", face: "[=^✧_✧^=]" },
|
|
162
|
-
];
|
|
163
|
-
let currentCatIndex = 0;
|
|
164
|
-
let catAnimationInterval = null;
|
|
165
|
-
// Helper function to build welcome content with account info - more compact
|
|
166
|
-
const buildWelcomeContent = async (theme, catFace) => {
|
|
167
|
-
const welcomeLines = [];
|
|
168
|
-
const colorTag = theme === "dark" ? "green-fg" : "black-fg";
|
|
169
|
-
const cyanColor = getCyanColor(theme);
|
|
170
|
-
// Use provided cat face or current one
|
|
171
|
-
const displayCatFace = catFace || catFaces[currentCatIndex].face;
|
|
172
|
-
// Cat face with breathing room, compact info below
|
|
173
|
-
welcomeLines.push(`{${colorTag}}${displayCatFace}{/${colorTag}}`);
|
|
174
|
-
welcomeLines.push(`{green-fg}🐱 Welcome to httpcat!{/green-fg} | {green-fg}🌐 {${cyanColor}}${network}{/${cyanColor}}{/green-fg}`);
|
|
175
|
-
// Get account info
|
|
176
|
-
let accountInfo = null;
|
|
177
|
-
try {
|
|
178
|
-
const accounts = config.getAllAccounts();
|
|
179
|
-
const activeIndex = config.getActiveAccountIndex();
|
|
180
|
-
const account = accounts.find((acc) => acc.index === activeIndex);
|
|
181
|
-
if (account) {
|
|
182
|
-
// Get balance info
|
|
23
|
+
import { checkMaintenanceMode } from "../utils/maintenance.js";
|
|
24
|
+
function Shell({ client, autoChatToken, onTransition }) {
|
|
25
|
+
const { exit } = useApp();
|
|
26
|
+
const { theme, setTheme } = useTheme();
|
|
27
|
+
const { stdout } = useStdout();
|
|
28
|
+
const [outputLines, setOutputLines] = useState([]);
|
|
29
|
+
const [isFirstCommand, setIsFirstCommand] = useState(true);
|
|
30
|
+
// Header state
|
|
31
|
+
const [network, setNetwork] = useState("Unknown");
|
|
32
|
+
const [account, setAccount] = useState();
|
|
33
|
+
const [ethBalance, setEthBalance] = useState("0.00 ETH");
|
|
34
|
+
const [usdcBalance, setUsdcBalance] = useState("$0.00");
|
|
35
|
+
// Initialize network and account info
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
const networkName = client.getNetwork();
|
|
38
|
+
setNetwork(networkName);
|
|
39
|
+
const privateKey = config.getPrivateKey();
|
|
40
|
+
if (privateKey) {
|
|
41
|
+
const accountInfo = privateKeyToAccount(privateKey);
|
|
42
|
+
setAccount(accountInfo.address);
|
|
43
|
+
// Fetch balances in background
|
|
44
|
+
(async () => {
|
|
183
45
|
try {
|
|
184
|
-
const
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
balance,
|
|
189
|
-
};
|
|
46
|
+
const { checkBalance } = await import("../commands/balances.js");
|
|
47
|
+
const balanceInfo = await checkBalance(privateKey, true);
|
|
48
|
+
setEthBalance(`${parseFloat(balanceInfo.ethFormatted).toFixed(4)} ETH`);
|
|
49
|
+
setUsdcBalance(`$${parseFloat(balanceInfo.usdcFormatted).toFixed(2)}`);
|
|
190
50
|
}
|
|
191
51
|
catch (error) {
|
|
192
|
-
//
|
|
193
|
-
accountInfo = { account };
|
|
52
|
+
// Silently fail
|
|
194
53
|
}
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
catch (error) {
|
|
198
|
-
// If account info fails, continue without it
|
|
54
|
+
})();
|
|
199
55
|
}
|
|
200
|
-
//
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
|
|
56
|
+
// Check for maintenance mode on initialization (with timeout to prevent hanging)
|
|
57
|
+
(async () => {
|
|
58
|
+
try {
|
|
59
|
+
// Add timeout to prevent hanging if API is slow
|
|
60
|
+
const timeoutPromise = new Promise((resolve) => {
|
|
61
|
+
setTimeout(() => resolve(false), 2000); // 2 second timeout
|
|
62
|
+
});
|
|
63
|
+
const checkPromise = checkMaintenanceMode(client);
|
|
64
|
+
const isMaintenance = await Promise.race([checkPromise, timeoutPromise]);
|
|
65
|
+
if (isMaintenance) {
|
|
66
|
+
log(chalk.yellow("⚠️ API is in maintenance mode"));
|
|
67
|
+
log(chalk.dim(" Most commands will be unavailable. Check status with: health"));
|
|
68
|
+
log("");
|
|
69
|
+
}
|
|
210
70
|
}
|
|
211
|
-
|
|
212
|
-
|
|
71
|
+
catch (error) {
|
|
72
|
+
// Silently fail - don't block shell startup
|
|
213
73
|
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
// Set initial header content
|
|
218
|
-
buildWelcomeContent(currentTheme, catFaces[currentCatIndex].face).then((content) => {
|
|
219
|
-
headerBox.setContent(content);
|
|
220
|
-
screen.render();
|
|
221
|
-
});
|
|
222
|
-
// Start cat face animation (cycle through every minute)
|
|
223
|
-
const startCatAnimation = () => {
|
|
224
|
-
if (catAnimationInterval) {
|
|
225
|
-
clearInterval(catAnimationInterval);
|
|
226
|
-
}
|
|
227
|
-
catAnimationInterval = setInterval(() => {
|
|
228
|
-
currentCatIndex = (currentCatIndex + 1) % catFaces.length;
|
|
229
|
-
buildWelcomeContent(currentTheme, catFaces[currentCatIndex].face).then((content) => {
|
|
230
|
-
headerBox.setContent(content);
|
|
231
|
-
screen.render();
|
|
232
|
-
});
|
|
233
|
-
}, 60000); // Change every minute (60 seconds)
|
|
234
|
-
};
|
|
235
|
-
// Start animation
|
|
236
|
-
startCatAnimation();
|
|
237
|
-
// Create output log box (scrollable) with thick borders, transparent background
|
|
238
|
-
const outputBox = blessed.log({
|
|
239
|
-
top: 6, // Adjusted to match new header height
|
|
240
|
-
left: 0,
|
|
241
|
-
width: "100%",
|
|
242
|
-
bottom: 4, // Leave space for input box at bottom
|
|
243
|
-
tags: true,
|
|
244
|
-
scrollable: true,
|
|
245
|
-
alwaysScroll: true,
|
|
246
|
-
scrollbar: {
|
|
247
|
-
ch: " ",
|
|
248
|
-
inverse: currentTheme !== "dark",
|
|
249
|
-
},
|
|
250
|
-
style: {
|
|
251
|
-
fg: themeColors.fg,
|
|
252
|
-
bg: "default", // Transparent
|
|
253
|
-
border: {
|
|
254
|
-
fg: themeColors.border,
|
|
255
|
-
bold: true,
|
|
256
|
-
},
|
|
257
|
-
},
|
|
258
|
-
padding: {
|
|
259
|
-
left: 0,
|
|
260
|
-
right: 1,
|
|
261
|
-
},
|
|
262
|
-
mouse: true, // Enable mouse scrolling
|
|
263
|
-
border: {
|
|
264
|
-
type: "line",
|
|
265
|
-
fg: themeColors.border,
|
|
266
|
-
ch: "═", // Double line for thicker border
|
|
267
|
-
},
|
|
268
|
-
});
|
|
269
|
-
// Create prompt label with bold font (appears larger) - positioned inside input box
|
|
270
|
-
const promptLabel = blessed.text({
|
|
271
|
-
bottom: 1,
|
|
272
|
-
left: 2,
|
|
273
|
-
width: 8, // Exactly "httpcat>" (8 characters)
|
|
274
|
-
height: 1,
|
|
275
|
-
content: "",
|
|
276
|
-
tags: true,
|
|
277
|
-
style: {
|
|
278
|
-
fg: themeColors.fg,
|
|
279
|
-
bg: "default", // Transparent
|
|
280
|
-
bold: true,
|
|
281
|
-
},
|
|
282
|
-
});
|
|
283
|
-
// Helper to update prompt label content
|
|
284
|
-
const updatePromptLabel = (theme) => {
|
|
285
|
-
const colorTag = theme === "dark" ? "green-fg" : "black-fg";
|
|
286
|
-
promptLabel.content = `{${colorTag}}{bold}httpcat>{/bold}{/${colorTag}}`;
|
|
287
|
-
};
|
|
288
|
-
updatePromptLabel(currentTheme);
|
|
289
|
-
// Create input box with visible cursor and stylish border
|
|
290
|
-
const inputBox = blessed.textbox({
|
|
291
|
-
bottom: 0,
|
|
292
|
-
left: 0,
|
|
293
|
-
width: "100%",
|
|
294
|
-
height: 3,
|
|
295
|
-
inputOnFocus: true,
|
|
296
|
-
keys: true,
|
|
297
|
-
vi: false, // Disabled to prevent double input issues in agent mode
|
|
298
|
-
secret: false,
|
|
299
|
-
tags: true,
|
|
300
|
-
alwaysScroll: false,
|
|
301
|
-
scrollable: false,
|
|
302
|
-
padding: {
|
|
303
|
-
left: 10, // Space for "httpcat>" prompt (8 chars) + 2 for spacing
|
|
304
|
-
right: 1,
|
|
305
|
-
top: 0,
|
|
306
|
-
bottom: 0,
|
|
307
|
-
},
|
|
308
|
-
cursor: {
|
|
309
|
-
artificial: true,
|
|
310
|
-
shape: "block", // Block cursor is more visible than line
|
|
311
|
-
blink: true,
|
|
312
|
-
color: currentTheme === "dark" ? "green" : "black",
|
|
313
|
-
},
|
|
314
|
-
style: {
|
|
315
|
-
fg: themeColors.fg,
|
|
316
|
-
bg: "default",
|
|
317
|
-
border: {
|
|
318
|
-
fg: themeColors.border,
|
|
319
|
-
bold: true,
|
|
320
|
-
},
|
|
321
|
-
focus: {
|
|
322
|
-
fg: themeColors.fg,
|
|
323
|
-
bg: "default",
|
|
324
|
-
border: {
|
|
325
|
-
fg: themeColors.border,
|
|
326
|
-
bold: true,
|
|
327
|
-
},
|
|
328
|
-
},
|
|
329
|
-
},
|
|
330
|
-
border: {
|
|
331
|
-
type: "line",
|
|
332
|
-
fg: themeColors.border,
|
|
333
|
-
ch: "─", // Single line border
|
|
334
|
-
},
|
|
335
|
-
});
|
|
336
|
-
// Helper to update theme
|
|
337
|
-
const updateTheme = (newTheme) => {
|
|
338
|
-
currentTheme = newTheme;
|
|
339
|
-
themeColors = getThemeColors(currentTheme);
|
|
340
|
-
// Update screen cursor color
|
|
341
|
-
screen.cursor.color =
|
|
342
|
-
currentTheme === "dark" ? "green" : "black";
|
|
343
|
-
// Update header content with new theme colors (keep current cat face)
|
|
344
|
-
buildWelcomeContent(currentTheme, catFaces[currentCatIndex].face).then((content) => {
|
|
345
|
-
headerBox.setContent(content);
|
|
346
|
-
screen.render();
|
|
347
|
-
});
|
|
348
|
-
// Update all widget styles
|
|
349
|
-
headerBox.style.fg = themeColors.fg;
|
|
350
|
-
headerBox.style.bg = "default"; // Transparent
|
|
351
|
-
headerBox.border = { type: "line", fg: themeColors.border, ch: "═" };
|
|
352
|
-
outputBox.style.fg = themeColors.fg;
|
|
353
|
-
outputBox.style.bg = "default"; // Transparent
|
|
354
|
-
outputBox.scrollbar.inverse = currentTheme !== "dark";
|
|
355
|
-
outputBox.border = { type: "line", fg: themeColors.border, ch: "═" };
|
|
356
|
-
updatePromptLabel(currentTheme);
|
|
357
|
-
promptLabel.style.fg = themeColors.fg;
|
|
358
|
-
promptLabel.style.bg = "default"; // Transparent
|
|
359
|
-
promptLabel.style.bold = true;
|
|
360
|
-
// Update input box cursor, style, and border
|
|
361
|
-
inputBox.style.fg = themeColors.fg;
|
|
362
|
-
inputBox.style.bg = "default";
|
|
363
|
-
inputBox.style.focus.fg = themeColors.fg;
|
|
364
|
-
inputBox.style.focus.bg = "default";
|
|
365
|
-
inputBox.style.border.fg = themeColors.border;
|
|
366
|
-
inputBox.style.focus.border.fg = themeColors.border;
|
|
367
|
-
inputBox.border = { type: "line", fg: themeColors.border, ch: "─" };
|
|
368
|
-
if (inputBox.cursor) {
|
|
369
|
-
inputBox.cursor.color =
|
|
370
|
-
currentTheme === "dark" ? "green" : "black";
|
|
371
|
-
}
|
|
372
|
-
screen.cursor.color =
|
|
373
|
-
currentTheme === "dark" ? "green" : "black";
|
|
374
|
-
screen.render();
|
|
375
|
-
};
|
|
376
|
-
// Helper to log output (define before use)
|
|
74
|
+
})();
|
|
75
|
+
}, [client]);
|
|
76
|
+
// Add a line to output
|
|
377
77
|
const log = (text) => {
|
|
378
|
-
|
|
379
|
-
outputBox.setScrollPerc(100);
|
|
380
|
-
screen.render();
|
|
78
|
+
setOutputLines((prev) => [...prev, text]);
|
|
381
79
|
};
|
|
382
|
-
// Helper to log multiple lines
|
|
383
80
|
const logLines = (lines) => {
|
|
384
|
-
|
|
385
|
-
outputBox.setScrollPerc(100);
|
|
386
|
-
screen.render();
|
|
81
|
+
setOutputLines((prev) => [...prev, ...lines]);
|
|
387
82
|
};
|
|
388
|
-
// Helper to log
|
|
389
|
-
const
|
|
390
|
-
|
|
391
|
-
const
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
const totalScrollable = Math.max(1, linesAfter - visibleHeight);
|
|
407
|
-
const scrollPerc = Math.max(0, Math.min(100, (newContentStartLine / totalScrollable) * 100));
|
|
408
|
-
try {
|
|
409
|
-
outputBox.setScrollPerc(scrollPerc);
|
|
410
|
-
}
|
|
411
|
-
catch (e) {
|
|
412
|
-
// Fallback if method doesn't exist
|
|
413
|
-
outputBox.setScrollPerc(0);
|
|
414
|
-
}
|
|
83
|
+
// Helper to redirect console.log output to shell output during command execution
|
|
84
|
+
const withConsoleRedirect = async (fn) => {
|
|
85
|
+
const originalLog = console.log;
|
|
86
|
+
const redirectOutput = (...args) => {
|
|
87
|
+
const message = args
|
|
88
|
+
.map((arg) => (typeof arg === "string" ? arg : JSON.stringify(arg)))
|
|
89
|
+
.join(" ");
|
|
90
|
+
// Split multi-line output into separate log entries
|
|
91
|
+
const lines = message.split("\n");
|
|
92
|
+
lines.forEach((line) => {
|
|
93
|
+
if (line) {
|
|
94
|
+
log(line);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
};
|
|
98
|
+
console.log = redirectOutput;
|
|
99
|
+
try {
|
|
100
|
+
return await fn();
|
|
415
101
|
}
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
outputBox.setScrollPerc(100);
|
|
102
|
+
finally {
|
|
103
|
+
console.log = originalLog;
|
|
419
104
|
}
|
|
420
|
-
// Re-enable auto-scroll
|
|
421
|
-
outputBox.alwaysScroll = originalAlwaysScroll;
|
|
422
|
-
screen.render();
|
|
423
105
|
};
|
|
424
|
-
//
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
log(chalk.
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
inputBox.key(["up"], () => {
|
|
450
|
-
if (commandHistory.length === 0) {
|
|
451
|
-
return;
|
|
452
|
-
}
|
|
453
|
-
// If we're not currently navigating history, save the current input
|
|
454
|
-
if (historyIndex === -1) {
|
|
455
|
-
currentInputBeforeHistory = inputBox.getValue();
|
|
456
|
-
historyIndex = commandHistory.length - 1; // Start at the most recent command
|
|
457
|
-
}
|
|
458
|
-
else {
|
|
459
|
-
// Move to previous command (earlier in history)
|
|
460
|
-
if (historyIndex > 0) {
|
|
461
|
-
historyIndex--;
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
// Set the input to the command at current history index
|
|
465
|
-
inputBox.setValue(commandHistory[historyIndex]);
|
|
466
|
-
screen.render();
|
|
467
|
-
});
|
|
468
|
-
// Handle down arrow - navigate to next command in history (or back to current input)
|
|
469
|
-
inputBox.key(["down"], () => {
|
|
470
|
-
if (commandHistory.length === 0 || historyIndex === -1) {
|
|
471
|
-
return;
|
|
472
|
-
}
|
|
473
|
-
// Move to next command (more recent in history)
|
|
474
|
-
if (historyIndex < commandHistory.length - 1) {
|
|
475
|
-
historyIndex++;
|
|
476
|
-
inputBox.setValue(commandHistory[historyIndex]);
|
|
477
|
-
}
|
|
478
|
-
else {
|
|
479
|
-
// We're at the most recent command, go back to the input that was there before
|
|
480
|
-
historyIndex = -1;
|
|
481
|
-
inputBox.setValue(currentInputBeforeHistory);
|
|
482
|
-
currentInputBeforeHistory = "";
|
|
483
|
-
}
|
|
484
|
-
screen.render();
|
|
485
|
-
});
|
|
486
|
-
// Handle left arrow - move cursor left within input
|
|
487
|
-
// Only intercept if we're not navigating history (blessed handles cursor movement automatically with vi: true)
|
|
488
|
-
inputBox.key(["left"], () => {
|
|
489
|
-
// Reset history navigation when user starts editing with arrow keys
|
|
490
|
-
if (historyIndex !== -1) {
|
|
491
|
-
historyIndex = -1;
|
|
492
|
-
currentInputBeforeHistory = "";
|
|
493
|
-
}
|
|
494
|
-
// Blessed will handle cursor movement automatically with vi: true
|
|
495
|
-
screen.render();
|
|
496
|
-
});
|
|
497
|
-
// Handle right arrow - move cursor right within input
|
|
498
|
-
inputBox.key(["right"], () => {
|
|
499
|
-
// Reset history navigation when user starts editing with arrow keys
|
|
500
|
-
if (historyIndex !== -1) {
|
|
501
|
-
historyIndex = -1;
|
|
502
|
-
currentInputBeforeHistory = "";
|
|
503
|
-
}
|
|
504
|
-
// Blessed will handle cursor movement automatically with vi: true
|
|
505
|
-
screen.render();
|
|
506
|
-
});
|
|
507
|
-
// Handle Home key - move cursor to beginning of line
|
|
508
|
-
inputBox.key(["home"], () => {
|
|
509
|
-
if (historyIndex === -1) {
|
|
510
|
-
// Blessed handles this automatically with vi: true
|
|
511
|
-
screen.render();
|
|
512
|
-
}
|
|
513
|
-
});
|
|
514
|
-
// Handle End key - move cursor to end of line
|
|
515
|
-
inputBox.key(["end"], () => {
|
|
516
|
-
if (historyIndex === -1) {
|
|
517
|
-
// Blessed handles this automatically with vi: true
|
|
518
|
-
screen.render();
|
|
106
|
+
// Welcome message
|
|
107
|
+
useEffect(() => {
|
|
108
|
+
log(chalk.green("╔════════════════════════════════════════════╗"));
|
|
109
|
+
log(chalk.green("║ httpcat Interactive Shell v0.3.0 ║"));
|
|
110
|
+
log(chalk.green("╚════════════════════════════════════════════╝"));
|
|
111
|
+
log("");
|
|
112
|
+
log(chalk.cyan("Welcome to the httpcat CLI!"));
|
|
113
|
+
log("Type " + chalk.yellow("help") + " for available commands");
|
|
114
|
+
log("Press " + chalk.yellow("F1") + " to toggle theme");
|
|
115
|
+
log("Press " + chalk.yellow("Ctrl+C") + " to exit");
|
|
116
|
+
log("");
|
|
117
|
+
}, []);
|
|
118
|
+
// Handle theme toggle and exit
|
|
119
|
+
useInput((input, key) => {
|
|
120
|
+
// F1 key for theme toggle (check via input)
|
|
121
|
+
if (input === "\x1bOP" || input === "q") {
|
|
122
|
+
// F1 sends ESC[OP sequence, or use 'q' as alternative
|
|
123
|
+
const themes = ["dark", "light", "win95"];
|
|
124
|
+
const currentIndex = themes.indexOf(theme);
|
|
125
|
+
const nextTheme = themes[(currentIndex + 1) % themes.length];
|
|
126
|
+
setTheme(nextTheme);
|
|
127
|
+
log(chalk.cyan(`Theme changed to: ${nextTheme}`));
|
|
128
|
+
}
|
|
129
|
+
else if (key.ctrl && input === "c") {
|
|
130
|
+
exit();
|
|
519
131
|
}
|
|
520
132
|
});
|
|
521
|
-
// Handle
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
commandHistory[commandHistory.length - 1] !== trimmed) {
|
|
537
|
-
commandHistory.push(trimmed);
|
|
538
|
-
// Limit history size to prevent memory issues (keep last 100 commands)
|
|
539
|
-
if (commandHistory.length > 100) {
|
|
540
|
-
commandHistory.shift();
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
// Log the command with prompt
|
|
544
|
-
log(`{green-fg}httpcat>{/green-fg} ${trimmed}`);
|
|
545
|
-
const [command, ...args] = trimmed.split(/\s+/);
|
|
133
|
+
// Handle command execution
|
|
134
|
+
const handleCommand = async (command) => {
|
|
135
|
+
// Clear welcome text on first command
|
|
136
|
+
if (isFirstCommand) {
|
|
137
|
+
setOutputLines([]);
|
|
138
|
+
setIsFirstCommand(false);
|
|
139
|
+
}
|
|
140
|
+
log(chalk.gray(`> ${command}`));
|
|
141
|
+
let parts = command.trim().split(/\s+/);
|
|
142
|
+
// Strip "httpcat" prefix if user includes it
|
|
143
|
+
if (parts[0].toLowerCase() === "httpcat" && parts.length > 1) {
|
|
144
|
+
parts = parts.slice(1);
|
|
145
|
+
}
|
|
146
|
+
const cmd = parts[0].toLowerCase();
|
|
147
|
+
const args = parts.slice(1);
|
|
546
148
|
try {
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
const isTestMode = network === "eip155:84532" || network === "eip155:11155111" || network.includes("sepolia");
|
|
651
|
-
const validAmounts = isTestMode ? TEST_AMOUNTS : PROD_AMOUNTS;
|
|
652
|
-
// Parse flags
|
|
653
|
-
const repeatCount = extractFlag(args, "--repeat")
|
|
654
|
-
? parseInt(extractFlag(args, "--repeat") || "1", 10)
|
|
655
|
-
: undefined;
|
|
656
|
-
const delayMs = extractFlag(args, "--delay")
|
|
657
|
-
? parseInt(extractFlag(args, "--delay") || "0", 10)
|
|
658
|
-
: 0;
|
|
659
|
-
// Try to validate and normalize the amount
|
|
660
|
-
let amount;
|
|
661
|
-
try {
|
|
662
|
-
amount = validateAmount(amountInput, validAmounts, isTestMode);
|
|
663
|
-
}
|
|
664
|
-
catch (error) {
|
|
665
|
-
// Validation failed - show error and return
|
|
666
|
-
log(chalk.yellow(`Invalid amount: ${amountInput}`));
|
|
667
|
-
log(chalk.dim(`Valid amounts: ${validAmounts.join(", ")}`));
|
|
668
|
-
return;
|
|
669
|
-
}
|
|
670
|
-
const privateKey = config.getPrivateKey();
|
|
671
|
-
// Handle repeat mode
|
|
672
|
-
if (repeatCount && repeatCount > 0) {
|
|
673
|
-
const results = [];
|
|
674
|
-
let totalSpent = 0;
|
|
675
|
-
let stoppedEarly = false;
|
|
676
|
-
let stopReason = "";
|
|
677
|
-
for (let i = 1; i <= repeatCount; i++) {
|
|
678
|
-
try {
|
|
679
|
-
// Retry logic: try up to 10 times with exponential backoff on 402 errors
|
|
680
|
-
// Client is recreated on each attempt to ensure fresh signature for new nonce
|
|
681
|
-
let result = null;
|
|
682
|
-
let lastError = null;
|
|
683
|
-
const maxRetries = 10;
|
|
684
|
-
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
685
|
-
try {
|
|
686
|
-
if (attempt === 1) {
|
|
687
|
-
log(chalk.blue(`Buy ${i}/${repeatCount}...`));
|
|
688
|
-
}
|
|
689
|
-
else {
|
|
690
|
-
log(chalk.yellow(` Retrying buy ${i}/${repeatCount} (attempt ${attempt}/${maxRetries})...`));
|
|
691
|
-
}
|
|
692
|
-
screen.render();
|
|
693
|
-
// Recreate client inside the operation to ensure fresh signature
|
|
694
|
-
// for each attempt (including retries). This fixes the issue where x402-fetch
|
|
695
|
-
// generates a new nonce but reuses the same signature, causing subsequent buys to fail
|
|
696
|
-
result = await (async () => {
|
|
697
|
-
// Recreate client for each attempt to ensure fresh signature for new nonce
|
|
698
|
-
const client = await HttpcatClient.create(privateKey);
|
|
699
|
-
return buyToken(client, identifier, amount, isTestMode, true, // silent=true to avoid console.log interference
|
|
700
|
-
privateKey);
|
|
701
|
-
})();
|
|
702
|
-
// Success! Break out of retry loop
|
|
703
|
-
break;
|
|
704
|
-
}
|
|
705
|
-
catch (error) {
|
|
706
|
-
lastError = error;
|
|
707
|
-
// Only retry on 402 errors (payment required)
|
|
708
|
-
const is402Error = error?.message?.includes("402") ||
|
|
709
|
-
error?.status === 402 ||
|
|
710
|
-
(typeof error === "string" && error.includes("402"));
|
|
711
|
-
if (is402Error && attempt < maxRetries) {
|
|
712
|
-
// Exponential backoff: 1s, 2s, 4s, 8s, 16s, 32s, 64s, 128s, 256s, 512s (capped at 10s)
|
|
713
|
-
const backoffMs = Math.min(Math.pow(2, attempt - 1) * 1000, 10000);
|
|
714
|
-
log(chalk.yellow(` ⚠️ Payment required (attempt ${attempt}/${maxRetries}), retrying in ${backoffMs / 1000}s...`));
|
|
715
|
-
screen.render();
|
|
716
|
-
await new Promise((resolve) => setTimeout(resolve, backoffMs));
|
|
717
|
-
continue; // Retry
|
|
718
|
-
}
|
|
719
|
-
else {
|
|
720
|
-
// Not a 402 error, or max retries reached - throw the error
|
|
721
|
-
throw error;
|
|
722
|
-
}
|
|
723
|
-
}
|
|
149
|
+
switch (cmd) {
|
|
150
|
+
case "help":
|
|
151
|
+
logLines([
|
|
152
|
+
"",
|
|
153
|
+
chalk.bold.cyan("Available Commands:"),
|
|
154
|
+
"",
|
|
155
|
+
chalk.bold.yellow("Token Operations:"),
|
|
156
|
+
chalk.yellow(" buy <token> <amt>") + " - Buy tokens",
|
|
157
|
+
chalk.yellow(" sell <token> <amt>") + " - Sell tokens",
|
|
158
|
+
chalk.yellow(" swap <in> <out> <amt>") + " - Swap tokens",
|
|
159
|
+
chalk.yellow(" create") +
|
|
160
|
+
" - Create a new token (use CLI)",
|
|
161
|
+
"",
|
|
162
|
+
chalk.bold.yellow("Token Information:"),
|
|
163
|
+
chalk.yellow(" info <token>") + " - Get token information",
|
|
164
|
+
chalk.yellow(" list") + " - List all tokens",
|
|
165
|
+
"",
|
|
166
|
+
chalk.bold.yellow("Portfolio:"),
|
|
167
|
+
chalk.yellow(" positions") + " - View your positions",
|
|
168
|
+
chalk.yellow(" balances") + " - Check wallet balances",
|
|
169
|
+
chalk.yellow(" transactions") +
|
|
170
|
+
" - View recent transactions",
|
|
171
|
+
"",
|
|
172
|
+
chalk.bold.yellow("Perps (Perpetual Futures):"),
|
|
173
|
+
chalk.yellow(" perps markets") +
|
|
174
|
+
" - Browse available markets (use CLI)",
|
|
175
|
+
chalk.yellow(" perps onboard") +
|
|
176
|
+
" - One-time venue setup (use CLI)",
|
|
177
|
+
chalk.yellow(" perps deposit") +
|
|
178
|
+
" - Deposit USDC to venues (use CLI)",
|
|
179
|
+
chalk.yellow(" perps trade") +
|
|
180
|
+
" - Open/close positions (use CLI)",
|
|
181
|
+
chalk.yellow(" perps positions") +
|
|
182
|
+
" - View open positions (use CLI)",
|
|
183
|
+
"",
|
|
184
|
+
chalk.bold.yellow("Predictions (Betting Markets):"),
|
|
185
|
+
chalk.yellow(" predictions markets") +
|
|
186
|
+
" - Browse markets (use CLI)",
|
|
187
|
+
chalk.yellow(" predictions search") +
|
|
188
|
+
" - Search markets (use CLI)",
|
|
189
|
+
chalk.yellow(" predictions bet") + " - Place a bet (use CLI)",
|
|
190
|
+
chalk.yellow(" predictions detect-arbs") +
|
|
191
|
+
" - Find arbitrage (use CLI)",
|
|
192
|
+
chalk.yellow(" predictions detect-insiders") +
|
|
193
|
+
" - Find traders (use CLI)",
|
|
194
|
+
"",
|
|
195
|
+
chalk.bold.yellow("Social & AI:"),
|
|
196
|
+
chalk.yellow(" chat") + " - Join general chat",
|
|
197
|
+
chalk.yellow(" chat <token>") + " - Join token chat",
|
|
198
|
+
"",
|
|
199
|
+
chalk.bold.yellow("Signing:"),
|
|
200
|
+
chalk.yellow(" sign") +
|
|
201
|
+
" - Enter interactive signing mode",
|
|
202
|
+
chalk.yellow(" sign message <text>") + " - Sign a message",
|
|
203
|
+
chalk.yellow(" sign file <path>") + " - Sign a file",
|
|
204
|
+
"",
|
|
205
|
+
chalk.bold.yellow("System:"),
|
|
206
|
+
chalk.yellow(" account") + " - View account info",
|
|
207
|
+
chalk.yellow(" health") + " - Check API health",
|
|
208
|
+
chalk.yellow(" clear") + " - Clear screen",
|
|
209
|
+
chalk.yellow(" exit") + " - Exit shell",
|
|
210
|
+
chalk.yellow(" help") + " - Show this help",
|
|
211
|
+
"",
|
|
212
|
+
chalk.bold.cyan("Keyboard Shortcuts:"),
|
|
213
|
+
"",
|
|
214
|
+
chalk.yellow(" /") + " + type - Autocomplete commands",
|
|
215
|
+
chalk.yellow(" Tab / Enter") + " - Select autocomplete",
|
|
216
|
+
chalk.yellow(" F1") + " - Toggle theme",
|
|
217
|
+
chalk.yellow(" Ctrl+C") + " - Exit",
|
|
218
|
+
chalk.yellow(" ↑/↓") +
|
|
219
|
+
" - Navigate command history",
|
|
220
|
+
"",
|
|
221
|
+
]);
|
|
222
|
+
break;
|
|
223
|
+
case "clear":
|
|
224
|
+
setOutputLines([]);
|
|
225
|
+
break;
|
|
226
|
+
case "exit":
|
|
227
|
+
case "quit":
|
|
228
|
+
exit();
|
|
229
|
+
break;
|
|
230
|
+
case "sign":
|
|
231
|
+
{
|
|
232
|
+
// Handle sign command - delegate to sign command's interactive mode
|
|
233
|
+
log(chalk.cyan("Entering interactive signing mode..."));
|
|
234
|
+
const { createSignCommand } = await import("../commands/sign.js");
|
|
235
|
+
const signCmd = createSignCommand();
|
|
236
|
+
// Execute sign command without subcommand to trigger interactive mode
|
|
237
|
+
const signArgs = args.length > 0 ? ["sign", ...args] : ["sign"];
|
|
238
|
+
// Note: This will trigger the interactive mode defined in sign.ts
|
|
239
|
+
log(chalk.yellow("Note: Use 'httpcat sign' in CLI for full signing features"));
|
|
240
|
+
log(chalk.gray("Available: sign message <text>, sign file <path>"));
|
|
241
|
+
if (args.length >= 2 && args[0] === "message") {
|
|
242
|
+
// Simple message signing
|
|
243
|
+
const message = args.slice(1).join(" ");
|
|
244
|
+
log(chalk.cyan(`Signing message: "${message}"...`));
|
|
245
|
+
// Would need to import and call sign functions directly
|
|
246
|
+
log(chalk.yellow('Use CLI: httpcat sign message "' + message + '"'));
|
|
247
|
+
}
|
|
248
|
+
else if (args.length >= 2 && args[0] === "file") {
|
|
249
|
+
const filePath = args[1];
|
|
250
|
+
log(chalk.cyan(`Signing file: ${filePath}...`));
|
|
251
|
+
log(chalk.yellow('Use CLI: httpcat sign file "' + filePath + '"'));
|
|
724
252
|
}
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
253
|
+
}
|
|
254
|
+
break;
|
|
255
|
+
case "health":
|
|
256
|
+
// Import and execute health command
|
|
257
|
+
log(chalk.cyan("Checking API health..."));
|
|
258
|
+
const { checkHealth, displayHealthStatus } = await import("../commands/health.js");
|
|
259
|
+
const healthData = await checkHealth(client);
|
|
260
|
+
await withConsoleRedirect(() => displayHealthStatus(healthData));
|
|
261
|
+
break;
|
|
262
|
+
case "balances":
|
|
263
|
+
{
|
|
264
|
+
log(chalk.cyan("Fetching balances..."));
|
|
265
|
+
const { checkBalance } = await import("../commands/balances.js");
|
|
266
|
+
const privateKey = config.getPrivateKey();
|
|
267
|
+
if (privateKey) {
|
|
268
|
+
await withConsoleRedirect(() => checkBalance(privateKey));
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
log(chalk.red("Error: Private key not configured"));
|
|
733
272
|
}
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
273
|
+
}
|
|
274
|
+
break;
|
|
275
|
+
case "list":
|
|
276
|
+
{
|
|
277
|
+
log(chalk.cyan("Fetching token list..."));
|
|
278
|
+
const { listTokens, displayTokenList } = await import("../commands/list.js");
|
|
279
|
+
const result = await listTokens(client, 1, 20, "mcap");
|
|
280
|
+
await withConsoleRedirect(() => displayTokenList(result, true));
|
|
281
|
+
}
|
|
282
|
+
break;
|
|
283
|
+
case "info":
|
|
284
|
+
if (args.length === 0) {
|
|
285
|
+
log(chalk.red("Error: Please provide a token address or symbol"));
|
|
286
|
+
log(chalk.gray("Usage: info <token>"));
|
|
287
|
+
break;
|
|
288
|
+
}
|
|
289
|
+
log(chalk.cyan(`Fetching info for ${args[0]}...`));
|
|
290
|
+
const { getTokenInfo, displayTokenInfo } = await import("../commands/info.js");
|
|
291
|
+
const info = await getTokenInfo(client, args[0], undefined, true); // silent=true
|
|
292
|
+
await withConsoleRedirect(() => displayTokenInfo(info, undefined, undefined, true, client));
|
|
293
|
+
break;
|
|
294
|
+
case "account":
|
|
295
|
+
log(chalk.cyan("Account information:"));
|
|
296
|
+
const { getAccountInfo, displayAccountInfo } = await import("../commands/account.js");
|
|
297
|
+
const accountData = await getAccountInfo();
|
|
298
|
+
await withConsoleRedirect(() => displayAccountInfo(accountData));
|
|
299
|
+
break;
|
|
300
|
+
case "buy":
|
|
301
|
+
{
|
|
302
|
+
if (args.length < 2) {
|
|
303
|
+
log(chalk.red("Error: Please provide token and amount"));
|
|
304
|
+
log(chalk.gray("Usage: buy <token> <amount>"));
|
|
743
305
|
break;
|
|
744
306
|
}
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
if (
|
|
749
|
-
|
|
750
|
-
const
|
|
751
|
-
const
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
log(chalk.dim(` Waiting for payment transaction confirmation...`));
|
|
759
|
-
screen.render();
|
|
760
|
-
try {
|
|
761
|
-
await publicClient.waitForTransactionReceipt({
|
|
762
|
-
hash: result.paymentTxHash,
|
|
763
|
-
});
|
|
764
|
-
log(chalk.dim(` ✅ Payment transaction confirmed`));
|
|
765
|
-
screen.render();
|
|
766
|
-
}
|
|
767
|
-
catch (txError) {
|
|
768
|
-
log(chalk.yellow(` ⚠️ Could not confirm payment transaction, proceeding...`));
|
|
769
|
-
screen.render();
|
|
770
|
-
}
|
|
771
|
-
}
|
|
772
|
-
// Wait for buy transaction (if present)
|
|
773
|
-
if (result.txHash) {
|
|
774
|
-
log(chalk.dim(` Waiting for buy transaction confirmation...`));
|
|
775
|
-
screen.render();
|
|
776
|
-
try {
|
|
777
|
-
await publicClient.waitForTransactionReceipt({
|
|
778
|
-
hash: result.txHash,
|
|
779
|
-
});
|
|
780
|
-
log(chalk.dim(` ✅ Buy transaction confirmed`));
|
|
781
|
-
screen.render();
|
|
782
|
-
}
|
|
783
|
-
catch (txError) {
|
|
784
|
-
log(chalk.yellow(` ⚠️ Could not confirm buy transaction, proceeding...`));
|
|
785
|
-
screen.render();
|
|
786
|
-
}
|
|
787
|
-
}
|
|
307
|
+
log(chalk.cyan(`Buying ${args[1]} USDC of ${args[0]}...`));
|
|
308
|
+
const { buyToken, displayBuyResult } = await import("../commands/buy.js");
|
|
309
|
+
const privateKey = config.getPrivateKey();
|
|
310
|
+
if (privateKey) {
|
|
311
|
+
// Create fresh client to ensure x402 payment headers are valid
|
|
312
|
+
const freshClient = await HttpcatClient.create(privateKey);
|
|
313
|
+
const network = freshClient.getNetwork();
|
|
314
|
+
const isTestMode = network === "eip155:84532" ||
|
|
315
|
+
network === "eip155:11155111" ||
|
|
316
|
+
network.includes("sepolia");
|
|
317
|
+
const result = await buyToken(freshClient, args[0], args[1], isTestMode, true, // silent=true to suppress status lines
|
|
318
|
+
privateKey);
|
|
319
|
+
await withConsoleRedirect(() => displayBuyResult(result, true));
|
|
788
320
|
}
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
// to process the transaction and update balance state
|
|
792
|
-
if (i < repeatCount) {
|
|
793
|
-
const MIN_DELAY_MS = 2000; // 2 seconds minimum for backend processing
|
|
794
|
-
const totalDelay = Math.max(delayMs, MIN_DELAY_MS);
|
|
795
|
-
if (totalDelay > 0) {
|
|
796
|
-
if (totalDelay === MIN_DELAY_MS && delayMs === 0) {
|
|
797
|
-
log(chalk.dim(` Waiting ${totalDelay / 1000}s for backend to process...`));
|
|
798
|
-
screen.render();
|
|
799
|
-
}
|
|
800
|
-
await new Promise((resolve) => setTimeout(resolve, totalDelay));
|
|
801
|
-
}
|
|
321
|
+
else {
|
|
322
|
+
log(chalk.red("Error: Private key not configured"));
|
|
802
323
|
}
|
|
803
324
|
}
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
log(chalk.
|
|
810
|
-
log("");
|
|
811
|
-
// Show current balance to help diagnose the issue
|
|
812
|
-
try {
|
|
813
|
-
const balance = await checkBalance(privateKey, true);
|
|
814
|
-
log(chalk.cyan("💰 Current Wallet Balances:"));
|
|
815
|
-
log(chalk.dim(` ETH: ${balance.ethFormatted}`));
|
|
816
|
-
log(chalk.dim(` USDC: ${balance.usdcFormatted}`));
|
|
817
|
-
if (balance.cat402Formatted) {
|
|
818
|
-
log(chalk.dim(` CAT: ${balance.cat402Formatted}`));
|
|
819
|
-
}
|
|
820
|
-
log("");
|
|
821
|
-
// Provide guidance based on what might be insufficient
|
|
822
|
-
const usdcBalance = parseFloat(balance.usdcFormatted.replace("$", "").replace(",", ""));
|
|
823
|
-
const ethBalance = parseFloat(balance.ethFormatted.replace(" ETH", "").replace(",", ""));
|
|
824
|
-
const buyAmount = parseFloat(amount);
|
|
825
|
-
log(chalk.yellow("💡 Possible issues:"));
|
|
826
|
-
if (usdcBalance < buyAmount) {
|
|
827
|
-
log(chalk.dim(` • Insufficient USDC for purchase: Need $${buyAmount.toFixed(6)}, have $${usdcBalance.toFixed(6)}`));
|
|
828
|
-
}
|
|
829
|
-
if (usdcBalance < 0.01) {
|
|
830
|
-
log(chalk.dim(` • Low USDC for API payments: x402 protocol requires USDC for API calls`));
|
|
831
|
-
}
|
|
832
|
-
if (ethBalance < 0.001) {
|
|
833
|
-
log(chalk.dim(` • Low ETH for gas: Need ETH to pay for transaction fees`));
|
|
834
|
-
}
|
|
835
|
-
log("");
|
|
836
|
-
log(chalk.dim(" Check your balance with: balances"));
|
|
837
|
-
}
|
|
838
|
-
catch (balanceError) {
|
|
839
|
-
// If we can't fetch balance, just show generic message
|
|
840
|
-
log(chalk.dim(" Check your balance with: balances"));
|
|
841
|
-
}
|
|
325
|
+
break;
|
|
326
|
+
case "sell":
|
|
327
|
+
{
|
|
328
|
+
if (args.length < 2) {
|
|
329
|
+
log(chalk.red("Error: Please provide token and amount"));
|
|
330
|
+
log(chalk.gray("Usage: sell <token> <amount>"));
|
|
842
331
|
break;
|
|
843
332
|
}
|
|
844
|
-
|
|
845
|
-
|
|
333
|
+
log(chalk.cyan(`Selling ${args[1]} tokens of ${args[0]}...`));
|
|
334
|
+
const { sellToken, displaySellResult } = await import("../commands/sell.js");
|
|
335
|
+
const privateKey = config.getPrivateKey();
|
|
336
|
+
if (privateKey) {
|
|
337
|
+
// Create fresh client to ensure x402 payment headers are valid
|
|
338
|
+
const freshClient = await HttpcatClient.create(privateKey);
|
|
339
|
+
const result = await sellToken(freshClient, args[0], args[1], true, // silent=true to suppress status lines
|
|
340
|
+
privateKey);
|
|
341
|
+
await withConsoleRedirect(() => displaySellResult(result, true));
|
|
342
|
+
}
|
|
343
|
+
else {
|
|
344
|
+
log(chalk.red("Error: Private key not configured"));
|
|
345
|
+
}
|
|
846
346
|
}
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
347
|
+
break;
|
|
348
|
+
case "create":
|
|
349
|
+
{
|
|
350
|
+
if (args.length < 2) {
|
|
351
|
+
log(chalk.red("Usage: create <name> <symbol>"));
|
|
352
|
+
log(chalk.gray('Example: create "My Token" "MTK"'));
|
|
353
|
+
break;
|
|
354
|
+
}
|
|
355
|
+
const name = args[0];
|
|
356
|
+
const symbol = args[1];
|
|
357
|
+
log(chalk.cyan(`Creating token ${name} (${symbol})...`));
|
|
358
|
+
const { createToken, displayCreateResult, processPhotoUrl } = await import("../commands/create.js");
|
|
359
|
+
const { randomUUID } = await import("crypto");
|
|
360
|
+
// Create fresh client for each create command (to avoid x402 header expiry)
|
|
361
|
+
const privateKey = config.getPrivateKey();
|
|
362
|
+
if (!privateKey) {
|
|
363
|
+
log(chalk.red("Error: Private key not configured"));
|
|
364
|
+
break;
|
|
365
|
+
}
|
|
366
|
+
const createClient = await HttpcatClient.create(privateKey);
|
|
367
|
+
// Generate default robohash URL
|
|
368
|
+
const uuid = randomUUID();
|
|
369
|
+
const photoUrl = `https://robohash.org/${uuid}?set=set4`;
|
|
370
|
+
const result = await withConsoleRedirect(() => createToken(createClient, {
|
|
371
|
+
name: name.trim(),
|
|
372
|
+
symbol: symbol.trim(),
|
|
373
|
+
photoUrl,
|
|
374
|
+
}));
|
|
375
|
+
await withConsoleRedirect(() => displayCreateResult(result, true));
|
|
863
376
|
}
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
const account = privateKeyToAccount(privateKey);
|
|
886
|
-
const userAddress = account.address;
|
|
887
|
-
log(chalk.blue("Checking token info..."));
|
|
888
|
-
screen.render();
|
|
889
|
-
const info = await getTokenInfo(client, identifier, userAddress, true); // silent=true to avoid console.log interference
|
|
890
|
-
if (!info.userPosition || info.userPosition.tokensOwned === "0") {
|
|
891
|
-
log(chalk.yellow("You do not own any of this token."));
|
|
892
|
-
return;
|
|
893
|
-
}
|
|
894
|
-
const tokenAmount = parseTokenAmount(amountInput, info.userPosition.tokensOwned);
|
|
895
|
-
log(chalk.blue("Selling tokens..."));
|
|
896
|
-
screen.render();
|
|
897
|
-
const result = await sellToken(client, identifier, tokenAmount, true, privateKey); // silent=true to avoid console.log interference
|
|
898
|
-
displaySellResultToLog(result, log, logLines);
|
|
899
|
-
break;
|
|
900
|
-
}
|
|
901
|
-
case "info": {
|
|
902
|
-
if (args.length < 1) {
|
|
903
|
-
log(chalk.red("Usage: info <address|name|symbol>"));
|
|
904
|
-
log(chalk.dim(' Examples: info 0x1234..., info "My Token", info MTK'));
|
|
905
|
-
return;
|
|
906
|
-
}
|
|
907
|
-
const [identifier] = args;
|
|
908
|
-
// Get user address from private key for balance checking
|
|
909
|
-
const privateKey = config.getPrivateKey();
|
|
910
|
-
const account = privateKeyToAccount(privateKey);
|
|
911
|
-
const userAddress = account.address;
|
|
912
|
-
log(chalk.blue("Fetching token info..."));
|
|
913
|
-
screen.render();
|
|
914
|
-
const info = await getTokenInfo(client, identifier, userAddress, true); // silent=true to avoid console.log interference
|
|
915
|
-
displayTokenInfoToLog(info, log, logLines);
|
|
916
|
-
break;
|
|
917
|
-
}
|
|
918
|
-
case "list": {
|
|
919
|
-
const page = parseInt(extractFlag(args, "--page") || "1");
|
|
920
|
-
const limit = parseInt(extractFlag(args, "--limit") || "20");
|
|
921
|
-
const sortBy = (extractFlag(args, "--sort") || "mcap");
|
|
922
|
-
log(chalk.blue("Fetching token list..."));
|
|
923
|
-
screen.render();
|
|
924
|
-
const result = await listTokens(client, page, limit, sortBy);
|
|
925
|
-
displayTokenListToLog(result, log, logLines, logLinesSmooth);
|
|
926
|
-
break;
|
|
927
|
-
}
|
|
928
|
-
case "positions": {
|
|
929
|
-
const privateKey = config.getPrivateKey();
|
|
930
|
-
const account = privateKeyToAccount(privateKey);
|
|
931
|
-
const userAddress = account.address;
|
|
932
|
-
// Parse flags
|
|
933
|
-
const activeOnly = args.includes("--active");
|
|
934
|
-
const graduatedOnly = args.includes("--graduated");
|
|
935
|
-
log(chalk.blue("Fetching positions..."));
|
|
936
|
-
screen.render();
|
|
937
|
-
const result = await getPositions(client, userAddress);
|
|
938
|
-
// Filter positions if flags are set
|
|
939
|
-
let filteredResult = result;
|
|
940
|
-
if (activeOnly || graduatedOnly) {
|
|
941
|
-
filteredResult = {
|
|
942
|
-
...result,
|
|
943
|
-
positions: result.positions.filter((p) => activeOnly
|
|
944
|
-
? p.token.status !== "graduated"
|
|
945
|
-
: p.token.status === "graduated"),
|
|
946
|
-
};
|
|
947
|
-
filteredResult.total = filteredResult.positions.length;
|
|
948
|
-
}
|
|
949
|
-
displayPositionsToLog(filteredResult, log, logLines);
|
|
950
|
-
break;
|
|
951
|
-
}
|
|
952
|
-
case "health": {
|
|
953
|
-
log(chalk.dim("Checking health..."));
|
|
954
|
-
screen.render();
|
|
955
|
-
const health = await checkHealth(client);
|
|
956
|
-
displayHealthStatusToLog(health, log, logLines);
|
|
957
|
-
break;
|
|
958
|
-
}
|
|
959
|
-
case "balances": {
|
|
960
|
-
try {
|
|
961
|
-
const privateKey = config.getPrivateKey();
|
|
962
|
-
log(chalk.blue("Checking balance..."));
|
|
963
|
-
screen.render();
|
|
964
|
-
const balance = await checkBalance(privateKey, true); // silent mode
|
|
965
|
-
displayBalanceToLog(balance, log, logLines);
|
|
966
|
-
}
|
|
967
|
-
catch (error) {
|
|
968
|
-
log(chalk.blue("Checking balance..."));
|
|
969
|
-
screen.render();
|
|
970
|
-
const balance = await checkBalance(undefined, true); // silent mode
|
|
971
|
-
displayBalanceToLog(balance, log, logLines);
|
|
972
|
-
}
|
|
973
|
-
break;
|
|
974
|
-
}
|
|
975
|
-
case "config": {
|
|
976
|
-
if (args[0] === "--show") {
|
|
977
|
-
displayConfigToLog(log, logLines);
|
|
978
|
-
}
|
|
979
|
-
else if (args[0] === "--set" && args[1]) {
|
|
980
|
-
const [key, value] = args[1].split("=");
|
|
981
|
-
config.set(key, value);
|
|
982
|
-
log(chalk.green(`${key} set to ${value}`));
|
|
983
|
-
}
|
|
984
|
-
else if (args[0] === "--reset") {
|
|
985
|
-
log(chalk.yellow("Config reset requires exiting the shell. Use 'httpcat config' from command line."));
|
|
986
|
-
}
|
|
987
|
-
else {
|
|
988
|
-
log(chalk.red("Usage: config [--show|--set key=value|--reset]"));
|
|
989
|
-
}
|
|
990
|
-
break;
|
|
991
|
-
}
|
|
992
|
-
case "network": {
|
|
993
|
-
log(chalk.yellow("Current network: ") + chalk.green(config.get("network")));
|
|
994
|
-
log(chalk.blue("Note: Only base-sepolia (testnet) is currently supported"));
|
|
995
|
-
break;
|
|
996
|
-
}
|
|
997
|
-
case "chat": {
|
|
998
|
-
const tokenIdentifier = args.length > 0 && !args[0].startsWith("--") ? args[0] : undefined;
|
|
999
|
-
log(chalk.yellow("💬 Entering chat mode..."));
|
|
1000
|
-
log(chalk.dim("Type /exit to return to shell, or /help for chat commands"));
|
|
1001
|
-
log("");
|
|
1002
|
-
// Start chat mode within the shell
|
|
1003
|
-
// Store original submit handler
|
|
1004
|
-
const originalHandlers = inputBox.listeners("submit");
|
|
1005
|
-
const originalSubmitHandler = originalHandlers[0];
|
|
1006
|
-
// Remove original handler temporarily
|
|
1007
|
-
inputBox.removeAllListeners("submit");
|
|
1008
|
-
await startChatInShell(client, tokenIdentifier, log, logLines, screen, inputBox, originalSubmitHandler, headerBox, buildWelcomeContent, currentTheme, catFaces, currentCatIndex);
|
|
1009
|
-
break;
|
|
1010
|
-
}
|
|
1011
|
-
case "exit":
|
|
1012
|
-
case "quit":
|
|
1013
|
-
screen.destroy();
|
|
1014
|
-
printCat("sleeping");
|
|
1015
|
-
console.log(chalk.cyan("Goodbye! 👋"));
|
|
1016
|
-
process.exit(0);
|
|
1017
|
-
break;
|
|
1018
|
-
case "clear":
|
|
1019
|
-
// Clear the output box
|
|
1020
|
-
screen.children[1].setContent("");
|
|
1021
|
-
screen.render();
|
|
1022
|
-
// Show playful message with ASCII cat
|
|
1023
|
-
const catArt = currentTheme === "dark"
|
|
1024
|
-
? `{green-fg} /\\_/\\{/green-fg}
|
|
1025
|
-
{green-fg} ( ^.^ ){/green-fg}
|
|
1026
|
-
{green-fg} > ^ <{/green-fg}`
|
|
1027
|
-
: `{black-fg} /\\_/\\{/black-fg}
|
|
1028
|
-
{black-fg} ( ^.^ ){/black-fg}
|
|
1029
|
-
{black-fg} > ^ <{/black-fg}`;
|
|
1030
|
-
log("");
|
|
1031
|
-
log(catArt);
|
|
1032
|
-
log(chalk.green.bold("Terminal cleared!"));
|
|
1033
|
-
log("");
|
|
1034
|
-
break;
|
|
1035
|
-
case "claim": {
|
|
1036
|
-
if (args.length < 1) {
|
|
1037
|
-
log(chalk.red("Usage: claim <address|name|symbol> [--execute] [--address <address>]"));
|
|
1038
|
-
log(chalk.dim(' Examples: claim MTK, claim "My Token" --execute'));
|
|
1039
|
-
return;
|
|
1040
|
-
}
|
|
1041
|
-
const [identifier] = args;
|
|
1042
|
-
const execute = args.includes("--execute");
|
|
1043
|
-
const callerAddress = extractFlag(args, "--address");
|
|
1044
|
-
if (execute) {
|
|
1045
|
-
const privateKey = config.getPrivateKey();
|
|
1046
|
-
const account = privateKeyToAccount(privateKey);
|
|
1047
|
-
const address = callerAddress || account.address;
|
|
1048
|
-
log(chalk.blue("Claiming fees..."));
|
|
1049
|
-
screen.render();
|
|
1050
|
-
const result = await claimFees(client, identifier, address, true); // silent=true to avoid console.log interference
|
|
1051
|
-
displayClaimResultToLog(result, log, logLines);
|
|
1052
|
-
}
|
|
1053
|
-
else {
|
|
1054
|
-
log(chalk.blue("Fetching fee information..."));
|
|
1055
|
-
screen.render();
|
|
1056
|
-
const result = await viewFees(client, identifier, true); // silent=true to avoid console.log interference
|
|
1057
|
-
displayFeesToLog(result, log, logLines);
|
|
1058
|
-
}
|
|
1059
|
-
break;
|
|
1060
|
-
}
|
|
1061
|
-
case "transactions": {
|
|
1062
|
-
const privateKey = config.getPrivateKey();
|
|
1063
|
-
const account = privateKeyToAccount(privateKey);
|
|
1064
|
-
const userAddress = account.address;
|
|
1065
|
-
const input = {};
|
|
1066
|
-
if (extractFlag(args, "--user")) {
|
|
1067
|
-
input.userAddress = extractFlag(args, "--user");
|
|
1068
|
-
}
|
|
1069
|
-
else {
|
|
1070
|
-
input.userAddress = userAddress;
|
|
1071
|
-
}
|
|
1072
|
-
if (extractFlag(args, "--token")) {
|
|
1073
|
-
input.tokenId = extractFlag(args, "--token");
|
|
1074
|
-
}
|
|
1075
|
-
if (extractFlag(args, "--type")) {
|
|
1076
|
-
const type = extractFlag(args, "--type");
|
|
1077
|
-
if (!["buy", "sell", "airdrop"].includes(type || "")) {
|
|
1078
|
-
log(chalk.red("Invalid type. Must be: buy, sell, or airdrop"));
|
|
1079
|
-
return;
|
|
1080
|
-
}
|
|
1081
|
-
input.type = type;
|
|
1082
|
-
}
|
|
1083
|
-
if (extractFlag(args, "--limit")) {
|
|
1084
|
-
input.limit = parseInt(extractFlag(args, "--limit") || "50", 10);
|
|
1085
|
-
}
|
|
1086
|
-
else {
|
|
1087
|
-
input.limit = 50;
|
|
1088
|
-
}
|
|
1089
|
-
if (extractFlag(args, "--offset")) {
|
|
1090
|
-
input.offset = parseInt(extractFlag(args, "--offset") || "0", 10);
|
|
1091
|
-
}
|
|
1092
|
-
else {
|
|
1093
|
-
input.offset = 0;
|
|
1094
|
-
}
|
|
1095
|
-
log(chalk.blue("Fetching transactions..."));
|
|
1096
|
-
screen.render();
|
|
1097
|
-
const result = await getTransactions(client, input);
|
|
1098
|
-
displayTransactionsToLog(result, log, logLines);
|
|
1099
|
-
break;
|
|
1100
|
-
}
|
|
1101
|
-
case "account": {
|
|
1102
|
-
if (args.length === 0) {
|
|
1103
|
-
// Show account info
|
|
1104
|
-
log(chalk.blue("Fetching account information..."));
|
|
1105
|
-
screen.render();
|
|
1106
|
-
const result = await getAccountInfo();
|
|
1107
|
-
displayAccountInfoToLog(result, log, logLines);
|
|
1108
|
-
}
|
|
1109
|
-
else if (args[0] === "list") {
|
|
1110
|
-
// For list, we need to capture console output or recreate the display
|
|
1111
|
-
const accounts = config.getAllAccounts();
|
|
1112
|
-
const activeIndex = config.getActiveAccountIndex();
|
|
1113
|
-
log("");
|
|
1114
|
-
log(chalk.green.bold("📋 All Accounts"));
|
|
1115
|
-
log("");
|
|
1116
|
-
if (accounts.length === 0) {
|
|
1117
|
-
log(chalk.yellow("No accounts configured."));
|
|
1118
|
-
log(chalk.blue('Run "httpcat config" to set up your wallet.'));
|
|
1119
|
-
return;
|
|
1120
|
-
}
|
|
1121
|
-
for (const account of accounts) {
|
|
1122
|
-
const isActive = account.index === activeIndex;
|
|
1123
|
-
const status = isActive
|
|
1124
|
-
? chalk.green("● Active")
|
|
1125
|
-
: chalk.blue("○ Inactive");
|
|
1126
|
-
const type = account.type === "custom" ? "Custom" : "Seed-Derived";
|
|
1127
|
-
const address = account.address.slice(0, 6) + "..." + account.address.slice(-4);
|
|
1128
|
-
log(` ${chalk.green(account.index.toString().padEnd(5))} ${chalk.blue(type.padEnd(12))} ${chalk.green(address.padEnd(15))} ${status}`);
|
|
1129
|
-
}
|
|
1130
|
-
log("");
|
|
1131
|
-
log(chalk.blue(`Active account: ${chalk.green(activeIndex.toString())}`));
|
|
1132
|
-
}
|
|
1133
|
-
else if (args[0] === "switch") {
|
|
1134
|
-
if (args.length < 2) {
|
|
1135
|
-
log(chalk.red("Usage: account switch <index>"));
|
|
1136
|
-
return;
|
|
1137
|
-
}
|
|
1138
|
-
const index = parseInt(args[1], 10);
|
|
1139
|
-
if (isNaN(index)) {
|
|
1140
|
-
log(chalk.red("Invalid account index"));
|
|
1141
|
-
return;
|
|
1142
|
-
}
|
|
1143
|
-
switchAccount(index);
|
|
1144
|
-
const accounts = config.getAllAccounts();
|
|
1145
|
-
const account = accounts.find((acc) => acc.index === index);
|
|
1146
|
-
log(chalk.green(`✅ Switched to account ${index}`));
|
|
1147
|
-
if (account) {
|
|
1148
|
-
log(chalk.blue(` Address: ${account.address.slice(0, 6)}...${account.address.slice(-4)}`));
|
|
1149
|
-
}
|
|
1150
|
-
// Refresh header with new account info
|
|
1151
|
-
buildWelcomeContent(currentTheme, catFaces[currentCatIndex].face).then((content) => {
|
|
1152
|
-
headerBox.setContent(content);
|
|
1153
|
-
screen.render();
|
|
1154
|
-
});
|
|
1155
|
-
}
|
|
1156
|
-
else if (args[0] === "add") {
|
|
1157
|
-
log(chalk.blue("Adding new account..."));
|
|
1158
|
-
screen.render();
|
|
1159
|
-
await addAccount();
|
|
1160
|
-
log(chalk.green("✅ Account added"));
|
|
1161
|
-
// Refresh header with new account info
|
|
1162
|
-
buildWelcomeContent(currentTheme, catFaces[currentCatIndex].face).then((content) => {
|
|
1163
|
-
headerBox.setContent(content);
|
|
1164
|
-
screen.render();
|
|
1165
|
-
});
|
|
1166
|
-
}
|
|
1167
|
-
else {
|
|
1168
|
-
log(chalk.red("Usage: account [list|switch <index>|add]"));
|
|
1169
|
-
}
|
|
1170
|
-
break;
|
|
1171
|
-
}
|
|
1172
|
-
case "env": {
|
|
1173
|
-
if (args.length === 0 || args[0] === "show") {
|
|
1174
|
-
const current = config.getCurrentEnvironment();
|
|
1175
|
-
if (!current) {
|
|
1176
|
-
log(chalk.yellow("No environment selected"));
|
|
1177
|
-
return;
|
|
1178
|
-
}
|
|
1179
|
-
const env = config.getEnvironmentConfig(current);
|
|
1180
|
-
if (!env) {
|
|
1181
|
-
log(chalk.yellow(`Environment "${current}" not found`));
|
|
1182
|
-
return;
|
|
1183
|
-
}
|
|
1184
|
-
log(chalk.cyan.bold(`Current Environment: ${chalk.green(current)}`));
|
|
1185
|
-
log(chalk.dim(`Agent URL: ${env.agentUrl}`));
|
|
1186
|
-
log(chalk.dim(`Network: ${env.network}`));
|
|
1187
|
-
}
|
|
1188
|
-
else if (args[0] === "list") {
|
|
1189
|
-
const envs = config.getEnvironments();
|
|
1190
|
-
const current = config.getCurrentEnvironment();
|
|
1191
|
-
log(chalk.cyan.bold("Available Environments:"));
|
|
1192
|
-
log("");
|
|
1193
|
-
for (const [name, env] of Object.entries(envs)) {
|
|
1194
|
-
const isCurrent = name === current;
|
|
1195
|
-
const prefix = isCurrent ? chalk.green("→ ") : " ";
|
|
1196
|
-
const nameDisplay = isCurrent
|
|
1197
|
-
? chalk.green.bold(name)
|
|
1198
|
-
: chalk.bold(name);
|
|
1199
|
-
log(`${prefix}${nameDisplay}`);
|
|
1200
|
-
log(chalk.dim(` Agent URL: ${env.agentUrl}`));
|
|
1201
|
-
log(chalk.dim(` Network: ${env.network}`));
|
|
1202
|
-
log("");
|
|
1203
|
-
}
|
|
1204
|
-
if (current) {
|
|
1205
|
-
log(chalk.dim(`Current environment: ${chalk.green(current)}`));
|
|
1206
|
-
}
|
|
1207
|
-
}
|
|
1208
|
-
else if (args[0] === "use") {
|
|
1209
|
-
if (args.length < 2) {
|
|
1210
|
-
log(chalk.red("Usage: env use <name>"));
|
|
1211
|
-
return;
|
|
1212
|
-
}
|
|
1213
|
-
config.setEnvironment(args[1]);
|
|
1214
|
-
const env = config.getEnvironmentConfig(args[1]);
|
|
1215
|
-
log(chalk.green(`✅ Switched to environment: ${args[1]}`));
|
|
1216
|
-
if (env) {
|
|
1217
|
-
log(chalk.dim(` Agent URL: ${env.agentUrl}`));
|
|
1218
|
-
log(chalk.dim(` Network: ${env.network}`));
|
|
1219
|
-
}
|
|
1220
|
-
}
|
|
1221
|
-
else if (args[0] === "add") {
|
|
1222
|
-
if (args.length < 3) {
|
|
1223
|
-
log(chalk.red("Usage: env add <name> <agentUrl> [--network <network>]"));
|
|
1224
|
-
return;
|
|
1225
|
-
}
|
|
1226
|
-
const network = extractFlag(args, "--network") || "eip155:84532";
|
|
1227
|
-
config.addEnvironment(args[1], args[2], network);
|
|
1228
|
-
log(chalk.green(`✅ Added environment: ${args[1]}`));
|
|
1229
|
-
log(chalk.dim(` Agent URL: ${args[2]}`));
|
|
1230
|
-
log(chalk.dim(` Network: ${network}`));
|
|
1231
|
-
}
|
|
1232
|
-
else if (args[0] === "update") {
|
|
1233
|
-
if (args.length < 3) {
|
|
1234
|
-
log(chalk.red("Usage: env update <name> <agentUrl> [--network <network>]"));
|
|
1235
|
-
return;
|
|
1236
|
-
}
|
|
1237
|
-
const network = extractFlag(args, "--network") || "eip155:84532";
|
|
1238
|
-
config.updateEnvironment(args[1], args[2], network);
|
|
1239
|
-
log(chalk.green(`✅ Updated environment: ${args[1]}`));
|
|
1240
|
-
log(chalk.dim(` Agent URL: ${args[2]}`));
|
|
1241
|
-
log(chalk.dim(` Network: ${network}`));
|
|
1242
|
-
}
|
|
1243
|
-
else {
|
|
1244
|
-
log(chalk.red("Usage: env [list|use <name>|show|add <name> <url>|update <name> <url>]"));
|
|
1245
|
-
}
|
|
1246
|
-
break;
|
|
1247
|
-
}
|
|
1248
|
-
case "agent":
|
|
1249
|
-
case "ai":
|
|
1250
|
-
case "cat": {
|
|
1251
|
-
log("");
|
|
1252
|
-
log(chalk.yellow("⚠️ Agent command is only available in CLI mode"));
|
|
1253
|
-
log(chalk.dim("Run 'httpcat cat' from the command line to start agent mode."));
|
|
1254
|
-
log("");
|
|
1255
|
-
break;
|
|
1256
|
-
}
|
|
1257
|
-
default:
|
|
1258
|
-
log(chalk.red(`Unknown command: ${command}`));
|
|
1259
|
-
log(chalk.dim('Type "help" for available commands'));
|
|
1260
|
-
}
|
|
1261
|
-
}
|
|
1262
|
-
function displayWelcomeMessage(log, logLines, outputBox, screen, theme = "dark") {
|
|
1263
|
-
// Helper to get theme-appropriate colors
|
|
1264
|
-
const getCyanColor = (t) => t === "dark" ? "light-cyan-fg" : "cyan-fg";
|
|
1265
|
-
const getBlueColor = (t) => t === "dark" ? "light-blue-fg" : "blue-fg";
|
|
1266
|
-
const cyanColor = getCyanColor(theme);
|
|
1267
|
-
const blueColor = getBlueColor(theme);
|
|
1268
|
-
// If we have outputBox, use blessed tags instead of chalk to avoid question marks
|
|
1269
|
-
// Otherwise fall back to the log function with chalk
|
|
1270
|
-
if (outputBox && screen) {
|
|
1271
|
-
const welcomeLines = [
|
|
1272
|
-
"",
|
|
1273
|
-
`{bold}{${cyanColor}}🐱 Welcome to httpcat!{/${cyanColor}}{/bold}`,
|
|
1274
|
-
"",
|
|
1275
|
-
"{yellow-fg}✨ Here are some commands to get you started:{/yellow-fg}",
|
|
1276
|
-
"",
|
|
1277
|
-
"{green-fg} 📋 list{/green-fg} - Browse all available tokens",
|
|
1278
|
-
"{green-fg} ℹ️ info <token>{/green-fg} - Get detailed info about a token",
|
|
1279
|
-
"{green-fg} 💰 buy <token> <amount>{/green-fg} - Buy tokens (e.g., buy MTK 0.10)",
|
|
1280
|
-
"{green-fg} 💼 positions{/green-fg} - View your portfolio",
|
|
1281
|
-
"{green-fg} 💵 balances{/green-fg} - Check your wallet balance",
|
|
1282
|
-
"",
|
|
1283
|
-
`{${blueColor}}💡 Type {bold}help{/bold} to see all available commands!{/${blueColor}}`,
|
|
1284
|
-
"",
|
|
1285
|
-
];
|
|
1286
|
-
// Log all welcome lines at once
|
|
1287
|
-
welcomeLines.forEach((line) => outputBox.log(line));
|
|
1288
|
-
// Scroll to top so welcome message is visible
|
|
1289
|
-
outputBox.setScrollPerc(0);
|
|
1290
|
-
screen.render();
|
|
1291
|
-
}
|
|
1292
|
-
else {
|
|
1293
|
-
// Fallback to regular log function if outputBox not available
|
|
1294
|
-
log("");
|
|
1295
|
-
log(chalk.cyan.bold("🐱 Welcome to httpcat!"));
|
|
1296
|
-
log("");
|
|
1297
|
-
log(chalk.yellow("✨ Here are some commands to get you started:"));
|
|
1298
|
-
log("");
|
|
1299
|
-
log(chalk.green(" 📋 list") +
|
|
1300
|
-
chalk.dim(" - Browse all available tokens"));
|
|
1301
|
-
log(chalk.green(" ℹ️ info <token>") +
|
|
1302
|
-
chalk.dim(" - Get detailed info about a token"));
|
|
1303
|
-
log(chalk.green(" 💰 buy <token> <amount>") +
|
|
1304
|
-
chalk.dim(" - Buy tokens (e.g., buy MTK 0.10)"));
|
|
1305
|
-
log(chalk.green(" 💼 positions") +
|
|
1306
|
-
chalk.dim(" - View your portfolio"));
|
|
1307
|
-
log(chalk.green(" 💵 balances") +
|
|
1308
|
-
chalk.dim(" - Check your wallet balance"));
|
|
1309
|
-
log("");
|
|
1310
|
-
log(chalk.blue("💡 Type ") +
|
|
1311
|
-
chalk.bold("help") +
|
|
1312
|
-
chalk.blue(" to see all available commands!"));
|
|
1313
|
-
log("");
|
|
1314
|
-
}
|
|
1315
|
-
}
|
|
1316
|
-
function displayHelp(log, logLines, outputBox, screen, theme = "dark") {
|
|
1317
|
-
// Helper to get theme-appropriate colors
|
|
1318
|
-
const getCyanColor = (t) => t === "dark" ? "light-cyan-fg" : "cyan-fg";
|
|
1319
|
-
const getBlueColor = (t) => t === "dark" ? "light-blue-fg" : "blue-fg";
|
|
1320
|
-
const cyanColor = getCyanColor(theme);
|
|
1321
|
-
const blueColor = getBlueColor(theme);
|
|
1322
|
-
// If we have outputBox, use blessed tags instead of chalk to avoid question marks
|
|
1323
|
-
// Otherwise fall back to the log function
|
|
1324
|
-
if (outputBox && screen) {
|
|
1325
|
-
// Get the line number where help will start (before logging)
|
|
1326
|
-
const linesBeforeHelp = outputBox.lines?.length || 0;
|
|
1327
|
-
// Collect all help lines using blessed tags
|
|
1328
|
-
const helpLines = [
|
|
1329
|
-
"",
|
|
1330
|
-
`{${cyanColor}}{bold}Available Commands:{/bold}{/${cyanColor}}`,
|
|
1331
|
-
"",
|
|
1332
|
-
// Token Operations
|
|
1333
|
-
`{${cyanColor}}{bold}Token Operations:{/bold}{/${cyanColor}}`,
|
|
1334
|
-
"{green-fg} create <name> <symbol>{/green-fg}",
|
|
1335
|
-
"{green-fg} buy <id> <amount> [--repeat N] [--delay MS]{/green-fg}",
|
|
1336
|
-
"{green-fg} sell <id> <amount|all>{/green-fg}",
|
|
1337
|
-
"{green-fg} claim <id> [--execute] [--address ADDR]{/green-fg}",
|
|
1338
|
-
"",
|
|
1339
|
-
// Token Information
|
|
1340
|
-
`{${cyanColor}}{bold}Token Information:{/bold}{/${cyanColor}}`,
|
|
1341
|
-
"{green-fg} info <id>{/green-fg}",
|
|
1342
|
-
"{green-fg} list [--sort mcap|created|name] [--page N] [--limit N]{/green-fg}",
|
|
1343
|
-
"",
|
|
1344
|
-
// Portfolio
|
|
1345
|
-
`{${cyanColor}}{bold}Portfolio:{/bold}{/${cyanColor}}`,
|
|
1346
|
-
"{green-fg} positions [--active|--graduated]{/green-fg}",
|
|
1347
|
-
"{green-fg} transactions [--user ADDR] [--token ID] [--type TYPE] [--limit N] [--offset N]{/green-fg}",
|
|
1348
|
-
"{green-fg} balances{/green-fg}",
|
|
1349
|
-
"",
|
|
1350
|
-
// Account Management
|
|
1351
|
-
`{${cyanColor}}{bold}Account Management:{/bold}{/${cyanColor}}`,
|
|
1352
|
-
"{green-fg} account [list|switch <index>|add]{/green-fg}",
|
|
1353
|
-
"{green-fg} env [list|use <name>|show|add|update]{/green-fg}",
|
|
1354
|
-
"",
|
|
1355
|
-
// Social
|
|
1356
|
-
`{${cyanColor}}{bold}Social:{/bold}{/${cyanColor}}`,
|
|
1357
|
-
"{green-fg} chat [token] [--input-format FORMAT]{/green-fg}",
|
|
1358
|
-
"",
|
|
1359
|
-
// Cat (agent and cat are synonyms)
|
|
1360
|
-
`{${cyanColor}}{bold}Cat (or 'agent'):{/bold}{/${cyanColor}}`,
|
|
1361
|
-
"{green-fg} agent <query>{/green-fg} - Ask the cat to do something (or use 'cat')",
|
|
1362
|
-
"{green-fg} agent --chat{/green-fg} - Enter interactive chat mode (or 'cat --chat')",
|
|
1363
|
-
"{green-fg} agent --setup{/green-fg} - Configure API key/provider (or 'cat --setup')",
|
|
1364
|
-
"",
|
|
1365
|
-
// System
|
|
1366
|
-
`{${cyanColor}}{bold}System:{/bold}{/${cyanColor}}`,
|
|
1367
|
-
"{green-fg} health{/green-fg}",
|
|
1368
|
-
"{green-fg} config [--show|--set|--reset]{/green-fg}",
|
|
1369
|
-
"{green-fg} network{/green-fg}",
|
|
1370
|
-
"",
|
|
1371
|
-
// Shell Commands
|
|
1372
|
-
`{${cyanColor}}{bold}Shell Commands:{/bold}{/${cyanColor}}`,
|
|
1373
|
-
"{green-fg} clear{/green-fg}",
|
|
1374
|
-
"{green-fg} help{/green-fg}",
|
|
1375
|
-
"{green-fg} exit{/green-fg}",
|
|
1376
|
-
"",
|
|
1377
|
-
`{${blueColor}}Tip: Run any command without arguments to see detailed help{/${blueColor}}`,
|
|
1378
|
-
];
|
|
1379
|
-
// Log all help lines at once
|
|
1380
|
-
helpLines.forEach((line) => outputBox.log(line));
|
|
1381
|
-
// Scroll to show "Available Commands" at the top of the visible area
|
|
1382
|
-
// Get total lines after logging
|
|
1383
|
-
const totalLines = outputBox.lines?.length || 0;
|
|
1384
|
-
const visibleHeight = outputBox.height || 20;
|
|
1385
|
-
if (totalLines > visibleHeight && linesBeforeHelp >= 0) {
|
|
1386
|
-
// Calculate which line "Available Commands" is on (after the empty line)
|
|
1387
|
-
const availableCommandsLine = linesBeforeHelp + 1;
|
|
1388
|
-
// Calculate scroll percentage to show that line at the top
|
|
1389
|
-
// Percentage = (line number / total lines) * 100
|
|
1390
|
-
// But we want the line to be at the top, so we need to account for visible height
|
|
1391
|
-
const scrollPerc = Math.max(0, Math.min(100, ((availableCommandsLine - 1) /
|
|
1392
|
-
Math.max(1, totalLines - visibleHeight)) *
|
|
1393
|
-
100));
|
|
1394
|
-
outputBox.setScrollPerc(scrollPerc);
|
|
1395
|
-
}
|
|
1396
|
-
else {
|
|
1397
|
-
// If all content fits in visible area or no previous content, scroll to top
|
|
1398
|
-
outputBox.setScrollPerc(0);
|
|
1399
|
-
}
|
|
1400
|
-
screen.render();
|
|
1401
|
-
}
|
|
1402
|
-
else {
|
|
1403
|
-
// Fallback to regular log function if outputBox not available
|
|
1404
|
-
log("");
|
|
1405
|
-
log(chalk.cyan.bold("Available Commands:"));
|
|
1406
|
-
log("");
|
|
1407
|
-
// Token Operations
|
|
1408
|
-
log(chalk.bold(chalk.cyan("Token Operations:")));
|
|
1409
|
-
log(chalk.green(" create <name> <symbol>"));
|
|
1410
|
-
log(chalk.green(" buy <id> <amount> [--repeat N] [--delay MS]"));
|
|
1411
|
-
log(chalk.green(" sell <id> <amount|all>"));
|
|
1412
|
-
log(chalk.green(" claim <id> [--execute] [--address ADDR]"));
|
|
1413
|
-
log("");
|
|
1414
|
-
// Token Information
|
|
1415
|
-
log(chalk.bold(chalk.cyan("Token Information:")));
|
|
1416
|
-
log(chalk.green(" info <id>"));
|
|
1417
|
-
log(chalk.green(" list [--sort mcap|created|name] [--page N] [--limit N]"));
|
|
1418
|
-
log("");
|
|
1419
|
-
// Portfolio
|
|
1420
|
-
log(chalk.bold(chalk.cyan("Portfolio:")));
|
|
1421
|
-
log(chalk.green(" positions [--active|--graduated]"));
|
|
1422
|
-
log(chalk.green(" transactions [--user ADDR] [--token ID] [--type TYPE] [--limit N] [--offset N]"));
|
|
1423
|
-
log(chalk.green(" balances"));
|
|
1424
|
-
log("");
|
|
1425
|
-
// Account Management
|
|
1426
|
-
log(chalk.bold(chalk.cyan("Account Management:")));
|
|
1427
|
-
log(chalk.green(" account [list|switch <index>|add]"));
|
|
1428
|
-
log(chalk.green(" env [list|use <name>|show|add|update]"));
|
|
1429
|
-
log("");
|
|
1430
|
-
// Social
|
|
1431
|
-
log(chalk.bold(chalk.cyan("Social:")));
|
|
1432
|
-
log(chalk.green(" chat [token] [--input-format FORMAT]"));
|
|
1433
|
-
log("");
|
|
1434
|
-
// Cat (agent and cat are synonyms)
|
|
1435
|
-
log(chalk.bold(chalk.cyan("Cat (or 'agent'):")));
|
|
1436
|
-
log(chalk.green(" agent <query>") +
|
|
1437
|
-
chalk.dim(" - Ask the cat to do something (or use 'cat')"));
|
|
1438
|
-
log(chalk.green(" agent --chat") +
|
|
1439
|
-
chalk.dim(" - Enter interactive chat mode (or 'cat --chat')"));
|
|
1440
|
-
log(chalk.green(" agent --setup") +
|
|
1441
|
-
chalk.dim(" - Configure API key/provider (or 'cat --setup')"));
|
|
1442
|
-
log("");
|
|
1443
|
-
// System
|
|
1444
|
-
log(chalk.bold(chalk.cyan("System:")));
|
|
1445
|
-
log(chalk.green(" health"));
|
|
1446
|
-
log(chalk.green(" config [--show|--set|--reset]"));
|
|
1447
|
-
log(chalk.green(" network"));
|
|
1448
|
-
log("");
|
|
1449
|
-
// Shell Commands
|
|
1450
|
-
log(chalk.bold(chalk.cyan("Shell Commands:")));
|
|
1451
|
-
log(chalk.green(" clear"));
|
|
1452
|
-
log(chalk.green(" help"));
|
|
1453
|
-
log(chalk.green(" exit"));
|
|
1454
|
-
log("");
|
|
1455
|
-
log(chalk.blue("Tip: Run any command without arguments to see detailed help"));
|
|
1456
|
-
}
|
|
1457
|
-
}
|
|
1458
|
-
function displayConfigToLog(log, logLines) {
|
|
1459
|
-
const cfg = config.getAll();
|
|
1460
|
-
log("");
|
|
1461
|
-
log(chalk.green.bold("Current Configuration:"));
|
|
1462
|
-
log("");
|
|
1463
|
-
log(chalk.blue("Network: ") + chalk.green(cfg.network));
|
|
1464
|
-
log(chalk.blue("Agent URL: ") + chalk.green(cfg.agentUrl));
|
|
1465
|
-
log(chalk.blue("Facilitator: ") + chalk.green(cfg.facilitatorUrl));
|
|
1466
|
-
log(chalk.blue("Max Payment: ") + chalk.green("$" + cfg.defaultMaxPayment));
|
|
1467
|
-
log(chalk.blue("ASCII Art: ") +
|
|
1468
|
-
chalk.green(cfg.preferences.enableAsciiArt ? "enabled" : "disabled"));
|
|
1469
|
-
log(chalk.blue("Private Key: ") +
|
|
1470
|
-
chalk.blue(cfg.privateKey ? "configured" : "not configured"));
|
|
1471
|
-
log("");
|
|
1472
|
-
log(chalk.blue(`Config file: ${config.getConfigPath()}`));
|
|
1473
|
-
}
|
|
1474
|
-
// Simplified display functions that log to blessed output
|
|
1475
|
-
function displayCreateResultToLog(result, log, logLines) {
|
|
1476
|
-
log(chalk.green("Token created successfully!"));
|
|
1477
|
-
log(chalk.blue("Name: ") + chalk.green(result.name));
|
|
1478
|
-
log(chalk.blue("Symbol: ") + chalk.green(result.symbol));
|
|
1479
|
-
if (result.tokenAddress) {
|
|
1480
|
-
log(chalk.blue("Address: ") + chalk.green(result.tokenAddress));
|
|
1481
|
-
}
|
|
1482
|
-
}
|
|
1483
|
-
function displayBuyResultToLog(result, log, logLines) {
|
|
1484
|
-
log(chalk.green("Purchase successful!"));
|
|
1485
|
-
log(chalk.blue("Tokens received: ") + chalk.green(result.tokensReceived));
|
|
1486
|
-
log(chalk.blue("Amount spent: ") + chalk.yellow(result.amountSpent));
|
|
1487
|
-
log(chalk.blue("New price: ") + chalk.green(result.newPrice));
|
|
1488
|
-
log(chalk.blue("Graduation: ") +
|
|
1489
|
-
chalk.magenta(result.graduationProgress.toFixed(2) + "%"));
|
|
1490
|
-
}
|
|
1491
|
-
function displaySellResultToLog(result, log, logLines) {
|
|
1492
|
-
log(chalk.green("Sale successful!"));
|
|
1493
|
-
log(chalk.blue("Tokens sold: ") +
|
|
1494
|
-
chalk.green(formatTokenAmount(result.tokensSold || "0")));
|
|
1495
|
-
log(chalk.blue("USDC received: ") +
|
|
1496
|
-
chalk.yellow(formatCurrency(result.usdcReceived || "0", 6)));
|
|
1497
|
-
if (result.fee) {
|
|
1498
|
-
log(chalk.blue("Fee (1%): ") + chalk.yellow(formatCurrency(result.fee, 6)));
|
|
1499
|
-
}
|
|
1500
|
-
if (result.newPrice) {
|
|
1501
|
-
log(chalk.blue("New price: ") + chalk.green(formatCurrency(result.newPrice)));
|
|
1502
|
-
}
|
|
1503
|
-
if (result.newMcap) {
|
|
1504
|
-
log(chalk.blue("New market cap: ") +
|
|
1505
|
-
chalk.yellow(formatCurrency(result.newMcap)));
|
|
1506
|
-
}
|
|
1507
|
-
if (result.graduationProgress !== undefined) {
|
|
1508
|
-
log(chalk.blue("Graduation: ") +
|
|
1509
|
-
chalk.magenta(result.graduationProgress.toFixed(2) + "%"));
|
|
1510
|
-
}
|
|
1511
|
-
if (result.txHash) {
|
|
1512
|
-
log(chalk.blue("Transaction: ") + chalk.green(result.txHash));
|
|
1513
|
-
}
|
|
1514
|
-
else if (result.usdcTransferTx) {
|
|
1515
|
-
log(chalk.blue("Transaction: ") + chalk.green(result.usdcTransferTx));
|
|
1516
|
-
}
|
|
1517
|
-
// Graduation progress bar
|
|
1518
|
-
if (result.graduationProgress !== undefined) {
|
|
1519
|
-
const barLength = 20;
|
|
1520
|
-
const filled = Math.floor((result.graduationProgress / 100) * barLength);
|
|
1521
|
-
const empty = barLength - filled;
|
|
1522
|
-
const bar = "🐱" + "=".repeat(filled) + ">" + " ".repeat(empty);
|
|
1523
|
-
log(chalk.yellow(`[${bar}] ${chalk.bold(result.graduationProgress.toFixed(2) + "%")} to moon!`));
|
|
1524
|
-
}
|
|
1525
|
-
// Display price impact warning for Uniswap swaps
|
|
1526
|
-
if (result.source === "uniswap_v2" && result.priceImpact !== undefined) {
|
|
1527
|
-
if (result.priceImpact < 0.5) {
|
|
1528
|
-
log(chalk.green(`Price Impact: ${result.priceImpact.toFixed(2)}%`));
|
|
1529
|
-
}
|
|
1530
|
-
else if (result.priceImpact < 1) {
|
|
1531
|
-
log(chalk.yellow(`Price Impact: ${result.priceImpact.toFixed(2)}%`));
|
|
1532
|
-
}
|
|
1533
|
-
else if (result.priceImpact < 3) {
|
|
1534
|
-
log(chalk.red(`⚠️ HIGH Price Impact: ${result.priceImpact.toFixed(2)}%`));
|
|
1535
|
-
}
|
|
1536
|
-
else {
|
|
1537
|
-
log(chalk.red(`🚨 VERY HIGH Price Impact: ${result.priceImpact.toFixed(2)}%`));
|
|
1538
|
-
}
|
|
1539
|
-
}
|
|
1540
|
-
// Display route info for Uniswap swaps
|
|
1541
|
-
if (result.source === "uniswap_v2" && result.route) {
|
|
1542
|
-
const feePercent = result.routeFee
|
|
1543
|
-
? (result.routeFee / 10000).toFixed(2)
|
|
1544
|
-
: "N/A";
|
|
1545
|
-
log(chalk.cyan(`Route: ${result.route} (${feePercent}% fee)`));
|
|
1546
|
-
}
|
|
1547
|
-
}
|
|
1548
|
-
function displayTokenInfoToLog(info, log, logLines) {
|
|
1549
|
-
log("");
|
|
1550
|
-
// Token header box
|
|
1551
|
-
const priceDisplay = info.status === "graduated"
|
|
1552
|
-
? chalk.dim("--")
|
|
1553
|
-
: info.price
|
|
1554
|
-
? formatCurrency(info.price)
|
|
1555
|
-
: chalk.dim("N/A");
|
|
1556
|
-
const mcapDisplay = info.status === "graduated"
|
|
1557
|
-
? chalk.dim("--")
|
|
1558
|
-
: info.mcap
|
|
1559
|
-
? formatCurrency(info.mcap)
|
|
1560
|
-
: chalk.dim("N/A");
|
|
1561
|
-
// Create box-like display
|
|
1562
|
-
log(chalk.green.bold(`📊 ${info.name} (${info.symbol})`));
|
|
1563
|
-
log(chalk.dim("─".repeat(60)));
|
|
1564
|
-
log(chalk.blue("Address: ") + chalk.green(formatAddress(info.address || "")));
|
|
1565
|
-
log(chalk.blue("Status: ") + chalk.green(info.status || "unknown"));
|
|
1566
|
-
log(chalk.blue("Price: ") + chalk.green(priceDisplay));
|
|
1567
|
-
log(chalk.blue("Market Cap: ") + chalk.yellow(mcapDisplay));
|
|
1568
|
-
log(chalk.blue("Total Supply: ") +
|
|
1569
|
-
chalk.green(formatTokenAmount(info.totalSupply || "0")));
|
|
1570
|
-
log(chalk.blue("Created: ") +
|
|
1571
|
-
chalk.green(new Date(info.createdAt || Date.now()).toLocaleString()));
|
|
1572
|
-
log("");
|
|
1573
|
-
// Graduation progress
|
|
1574
|
-
if (info.graduationProgress !== undefined) {
|
|
1575
|
-
const barLength = 20;
|
|
1576
|
-
const filled = Math.floor((info.graduationProgress / 100) * barLength);
|
|
1577
|
-
const empty = barLength - filled;
|
|
1578
|
-
const bar = "🐱" + "=".repeat(filled) + ">" + " ".repeat(empty);
|
|
1579
|
-
log(chalk.yellow(`[${bar}] ${chalk.bold(info.graduationProgress.toFixed(2) + "%")} to moon!`));
|
|
1580
|
-
log("");
|
|
1581
|
-
}
|
|
1582
|
-
// Position display
|
|
1583
|
-
if (info.userPosition && info.userPosition.tokensOwned !== "0") {
|
|
1584
|
-
log(chalk.green.bold("💼 Your Position"));
|
|
1585
|
-
log(chalk.dim("─".repeat(60)));
|
|
1586
|
-
log(chalk.blue("Tokens Owned: ") +
|
|
1587
|
-
chalk.green(formatTokenAmount(info.userPosition.tokensOwned)));
|
|
1588
|
-
log(chalk.blue("Invested: ") +
|
|
1589
|
-
chalk.green(formatCurrency(info.userPosition.usdcInvested || "0")));
|
|
1590
|
-
if (info.userPosition.currentValue) {
|
|
1591
|
-
log(chalk.blue("Current Value: ") +
|
|
1592
|
-
chalk.green(formatCurrency(info.userPosition.currentValue)));
|
|
1593
|
-
}
|
|
1594
|
-
if (info.userPosition.pnl) {
|
|
1595
|
-
const pnl = parseFloat(info.userPosition.pnl);
|
|
1596
|
-
const pnlColor = pnl >= 0 ? chalk.green : chalk.red;
|
|
1597
|
-
const pnlSign = pnl >= 0 ? "+" : "";
|
|
1598
|
-
const pnlIcon = pnl >= 0 ? "🟢" : "🔴";
|
|
1599
|
-
log(chalk.blue("P&L: ") +
|
|
1600
|
-
`${pnlIcon} ${pnlColor(pnlSign + formatCurrency(info.userPosition.pnl))}`);
|
|
1601
|
-
}
|
|
1602
|
-
log("");
|
|
1603
|
-
}
|
|
1604
|
-
}
|
|
1605
|
-
function displayTokenListToLog(result, log, logLines, logLinesSmooth) {
|
|
1606
|
-
// Collect all lines first for smooth scrolling
|
|
1607
|
-
const allLines = [];
|
|
1608
|
-
allLines.push("");
|
|
1609
|
-
allLines.push(chalk.green.bold(`Tokens (${result.total || result.tokens.length} total):`));
|
|
1610
|
-
if (result.page && result.pages) {
|
|
1611
|
-
allLines.push(chalk.dim(`Page ${result.page} of ${result.pages}`));
|
|
1612
|
-
}
|
|
1613
|
-
allLines.push("");
|
|
1614
|
-
if (result.tokens.length === 0) {
|
|
1615
|
-
allLines.push(chalk.yellow("No tokens found."));
|
|
1616
|
-
// Use smooth scrolling if available, otherwise regular logLines
|
|
1617
|
-
if (logLinesSmooth) {
|
|
1618
|
-
logLinesSmooth(allLines, false);
|
|
1619
|
-
}
|
|
1620
|
-
else {
|
|
1621
|
-
logLines(allLines);
|
|
1622
|
-
}
|
|
1623
|
-
return;
|
|
1624
|
-
}
|
|
1625
|
-
// Show header
|
|
1626
|
-
allLines.push(` ${chalk.cyan.bold("Name".padEnd(20))} ${chalk.cyan.bold("Symbol".padEnd(10))} ${chalk.cyan.bold("Market Cap".padEnd(15))} ${chalk.cyan.bold("Price".padEnd(12))} ${chalk.cyan.bold("Graduation".padEnd(12))} ${chalk.cyan.bold("Status")}`);
|
|
1627
|
-
allLines.push(chalk.dim(" " + "─".repeat(90)));
|
|
1628
|
-
// Show tokens
|
|
1629
|
-
for (const token of result.tokens) {
|
|
1630
|
-
const graduationIcon = token.status === "graduated"
|
|
1631
|
-
? "[G]"
|
|
1632
|
-
: token.graduationProgress >= 90
|
|
1633
|
-
? "[H]"
|
|
1634
|
-
: token.graduationProgress >= 50
|
|
1635
|
-
? "[M]"
|
|
1636
|
-
: "[L]";
|
|
1637
|
-
const mcapDisplay = token.status === "graduated"
|
|
1638
|
-
? chalk.dim("--")
|
|
1639
|
-
: formatCurrency(token.mcap || "0");
|
|
1640
|
-
const priceDisplay = token.status === "graduated"
|
|
1641
|
-
? chalk.dim("--")
|
|
1642
|
-
: formatCurrency(token.price || "0");
|
|
1643
|
-
const graduationDisplay = `${graduationIcon} ${(token.graduationProgress || 0).toFixed(1)}%`;
|
|
1644
|
-
const statusDisplay = token.status === "graduated"
|
|
1645
|
-
? chalk.green("✓ Graduated")
|
|
1646
|
-
: chalk.yellow("Active");
|
|
1647
|
-
allLines.push(` ${chalk.bold(token.name.padEnd(20))} ${chalk.green(token.symbol.padEnd(10))} ${mcapDisplay.padEnd(15)} ${priceDisplay.padEnd(12)} ${graduationDisplay.padEnd(12)} ${statusDisplay}`);
|
|
1648
|
-
}
|
|
1649
|
-
allLines.push("");
|
|
1650
|
-
if (result.page && result.pages && result.page < result.pages) {
|
|
1651
|
-
allLines.push(chalk.dim(`Use --page ${result.page + 1} to see more tokens`));
|
|
1652
|
-
allLines.push("");
|
|
1653
|
-
}
|
|
1654
|
-
// Show example commands
|
|
1655
|
-
if (result.tokens.length > 0) {
|
|
1656
|
-
const firstToken = result.tokens[0];
|
|
1657
|
-
allLines.push(chalk.dim("Example commands:"));
|
|
1658
|
-
allLines.push(chalk.dim(` buy ${firstToken.symbol}`));
|
|
1659
|
-
allLines.push(chalk.dim(` sell ${firstToken.symbol}`));
|
|
1660
|
-
allLines.push(chalk.dim(` info ${firstToken.symbol}`));
|
|
1661
|
-
allLines.push("");
|
|
1662
|
-
}
|
|
1663
|
-
// Use smooth scrolling if available (scrolls to show the list header), otherwise regular logLines
|
|
1664
|
-
if (logLinesSmooth) {
|
|
1665
|
-
logLinesSmooth(allLines, true); // scrollToTop=true to show the list header
|
|
1666
|
-
}
|
|
1667
|
-
else {
|
|
1668
|
-
logLines(allLines);
|
|
1669
|
-
}
|
|
1670
|
-
}
|
|
1671
|
-
function displayPositionsToLog(result, log, logLines) {
|
|
1672
|
-
if (!result.positions || result.positions.length === 0) {
|
|
1673
|
-
log(chalk.yellow("No positions found."));
|
|
1674
|
-
return;
|
|
1675
|
-
}
|
|
1676
|
-
log(chalk.green.bold(`Your Positions (${result.positions.length}):`));
|
|
1677
|
-
log("");
|
|
1678
|
-
for (const pos of result.positions) {
|
|
1679
|
-
const tokenName = pos.token?.name || "???";
|
|
1680
|
-
const tokenSymbol = pos.token?.symbol || "???";
|
|
1681
|
-
const tokensDisplay = formatTokenAmount(pos.tokensOwned || "0");
|
|
1682
|
-
const valueDisplay = pos.currentValue
|
|
1683
|
-
? formatCurrency(pos.currentValue)
|
|
1684
|
-
: "N/A";
|
|
1685
|
-
log(` ${chalk.green.bold(`${tokenName} (${tokenSymbol})`)} - ${chalk.green(`${tokensDisplay} tokens`)} @ ${chalk.yellow(valueDisplay)}`);
|
|
1686
|
-
}
|
|
1687
|
-
}
|
|
1688
|
-
function displayHealthStatusToLog(health, log, logLines) {
|
|
1689
|
-
const statusColor = health.status === "ok" || health.status === "healthy"
|
|
1690
|
-
? chalk.green
|
|
1691
|
-
: chalk.red;
|
|
1692
|
-
log(statusColor(`Agent Status: ${health.status}`));
|
|
1693
|
-
if (health.version) {
|
|
1694
|
-
log(chalk.blue("Version: ") + chalk.green(health.version));
|
|
1695
|
-
}
|
|
1696
|
-
if (health.uptime) {
|
|
1697
|
-
log(chalk.blue("Uptime: ") + chalk.green(health.uptime));
|
|
1698
|
-
}
|
|
1699
|
-
}
|
|
1700
|
-
function displayBalanceToLog(balance, log, logLines) {
|
|
1701
|
-
log("");
|
|
1702
|
-
log(chalk.green.bold("Wallet Balance:"));
|
|
1703
|
-
log(chalk.blue("Address: ") + chalk.green(balance.address));
|
|
1704
|
-
log(chalk.blue("ETH: ") +
|
|
1705
|
-
chalk.yellow(balance.ethFormatted || balance.ethBalance));
|
|
1706
|
-
log(chalk.blue("USDC: ") +
|
|
1707
|
-
chalk.green(balance.usdcFormatted || balance.usdcBalance));
|
|
1708
|
-
if (balance.cat402Formatted) {
|
|
1709
|
-
log(chalk.blue("CAT: ") + chalk.cyan(balance.cat402Formatted));
|
|
1710
|
-
}
|
|
1711
|
-
log("");
|
|
1712
|
-
// Show warnings if balances are low
|
|
1713
|
-
const ethNum = Number(balance.ethFormatted || balance.ethBalance);
|
|
1714
|
-
const usdcNum = Number((balance.usdcFormatted || balance.usdcBalance)
|
|
1715
|
-
.replace("$", "")
|
|
1716
|
-
.replace(",", ""));
|
|
1717
|
-
if (ethNum < 0.001 && usdcNum < 1) {
|
|
1718
|
-
log(chalk.yellow("⚠️ Low balances detected!"));
|
|
1719
|
-
log(chalk.dim(" You need Base Sepolia ETH for gas fees"));
|
|
1720
|
-
log(chalk.dim(" You need Base Sepolia USDC for trading"));
|
|
1721
|
-
log("");
|
|
1722
|
-
}
|
|
1723
|
-
else if (ethNum < 0.001) {
|
|
1724
|
-
log(chalk.yellow("⚠️ Low ETH balance - you may not have enough for gas fees"));
|
|
1725
|
-
log("");
|
|
1726
|
-
}
|
|
1727
|
-
else if (usdcNum < 1) {
|
|
1728
|
-
log(chalk.yellow("⚠️ Low USDC balance - you may not have enough for trades"));
|
|
1729
|
-
log("");
|
|
1730
|
-
}
|
|
1731
|
-
}
|
|
1732
|
-
function extractFlag(args, flag) {
|
|
1733
|
-
const index = args.findIndex((arg) => arg === flag);
|
|
1734
|
-
if (index >= 0 && index < args.length - 1) {
|
|
1735
|
-
return args[index + 1];
|
|
1736
|
-
}
|
|
1737
|
-
return undefined;
|
|
1738
|
-
}
|
|
1739
|
-
function displayFeesToLog(fees, log, logLines) {
|
|
1740
|
-
const hasFeesToken = BigInt(fees.feeToken || "0") > 0n;
|
|
1741
|
-
const hasFeesPaired = BigInt(fees.feePaired || "0") > 0n;
|
|
1742
|
-
const hasFees = hasFeesToken || hasFeesPaired;
|
|
1743
|
-
log("");
|
|
1744
|
-
log(chalk.green.bold(`💰 Accumulated Fees: ${fees.tokenName} (${fees.tokenSymbol})`));
|
|
1745
|
-
log(chalk.blue("━".repeat(50)));
|
|
1746
|
-
log(chalk.blue("Contract: ") + chalk.green(fees.tokenAddress));
|
|
1747
|
-
log(chalk.blue("LP Status: ") +
|
|
1748
|
-
chalk.green(fees.isLocked ? "🔒 Locked" : "❌ Not Locked"));
|
|
1749
|
-
if (typeof fees.v4TickLower === "number" &&
|
|
1750
|
-
typeof fees.v4TickUpper === "number" &&
|
|
1751
|
-
typeof fees.v4TickCurrent === "number" &&
|
|
1752
|
-
typeof fees.v4InRange === "boolean") {
|
|
1753
|
-
log(chalk.blue("V4 Range: ") +
|
|
1754
|
-
chalk.green(`[${fees.v4TickLower}, ${fees.v4TickUpper}) | Current: ${fees.v4TickCurrent} | In Range: ${fees.v4InRange ? "✅" : "❌"}`));
|
|
1755
|
-
}
|
|
1756
|
-
log("");
|
|
1757
|
-
log(chalk.green("Total Fees:"));
|
|
1758
|
-
log(chalk.blue(" Tokens: ") + chalk.green(hasFeesToken ? fees.feeToken : "0"));
|
|
1759
|
-
log(chalk.blue(" USDC: ") +
|
|
1760
|
-
chalk.green(hasFeesPaired ? fees.feePaired : "$0.00"));
|
|
1761
|
-
log("");
|
|
1762
|
-
log(chalk.green("Creator Share (80%):"));
|
|
1763
|
-
log(chalk.blue(" Tokens: ") + chalk.green(fees.creatorToken || "0"));
|
|
1764
|
-
log(chalk.blue(" USDC: ") + chalk.green(fees.creatorPaired || "$0.00"));
|
|
1765
|
-
log("");
|
|
1766
|
-
log(chalk.green("Platform Share (20%):"));
|
|
1767
|
-
log(chalk.blue(" Tokens: ") + chalk.green(fees.platformToken || "0"));
|
|
1768
|
-
log(chalk.blue(" USDC: ") + chalk.green(fees.platformPaired || "$0.00"));
|
|
1769
|
-
log(chalk.blue("━".repeat(50)));
|
|
1770
|
-
if (!hasFees) {
|
|
1771
|
-
if (fees.v4InRange === false) {
|
|
1772
|
-
log(chalk.yellow("\n⚠️ No fees accrued: position is out of range"));
|
|
1773
|
-
}
|
|
1774
|
-
else {
|
|
1775
|
-
log(chalk.yellow("\n⚠️ No fees accumulated yet. Trade volume needed."));
|
|
1776
|
-
}
|
|
1777
|
-
}
|
|
1778
|
-
else {
|
|
1779
|
-
log(chalk.blue("\n💡 Run with --execute to claim fees"));
|
|
1780
|
-
}
|
|
1781
|
-
}
|
|
1782
|
-
function displayClaimResultToLog(result, log, logLines) {
|
|
1783
|
-
log(chalk.green("✅ Fees claimed successfully!"));
|
|
1784
|
-
log(chalk.blue("Token: ") + chalk.green(result.tokenAddress));
|
|
1785
|
-
log(chalk.blue("Transaction: ") + chalk.green(result.txHash));
|
|
1786
|
-
log(chalk.blue("Claimed:"));
|
|
1787
|
-
log(chalk.blue(" Tokens: ") + chalk.green(result.creatorToken || "0"));
|
|
1788
|
-
log(chalk.blue(" USDC: ") + chalk.green(result.creatorPaired || "$0.00"));
|
|
1789
|
-
}
|
|
1790
|
-
function displayTransactionsToLog(result, log, logLines) {
|
|
1791
|
-
log(chalk.green.bold(`📜 Transactions (${result.total} total)`));
|
|
1792
|
-
log("");
|
|
1793
|
-
if (result.transactions.length === 0) {
|
|
1794
|
-
log(chalk.yellow("No transactions found."));
|
|
1795
|
-
return;
|
|
1796
|
-
}
|
|
1797
|
-
for (let i = 0; i < result.transactions.length; i++) {
|
|
1798
|
-
const tx = result.transactions[i];
|
|
1799
|
-
const typeIcon = tx.type === "buy" ? "🟢" : tx.type === "sell" ? "🔴" : "🎁";
|
|
1800
|
-
const statusIcon = tx.status === "success" ? "✅" : tx.status === "pending" ? "⏳" : "❌";
|
|
1801
|
-
log(chalk.green.bold(`${i + 1}. ${typeIcon} ${tx.type.toUpperCase()} ${statusIcon} ${tx.status.toUpperCase()}`));
|
|
1802
|
-
if (tx.token) {
|
|
1803
|
-
log(chalk.blue(` Token: ${tx.token.name} (${tx.token.symbol})`));
|
|
1804
|
-
}
|
|
1805
|
-
log(chalk.blue(` Amount: ${tx.amount}`));
|
|
1806
|
-
log(chalk.blue(` Fee: ${tx.fee}`));
|
|
1807
|
-
if (tx.txHash) {
|
|
1808
|
-
log(chalk.blue(` TX: ${tx.txHash}`));
|
|
1809
|
-
}
|
|
1810
|
-
log(chalk.blue(` Date: ${new Date(tx.createdAt).toLocaleString()}`));
|
|
1811
|
-
log("");
|
|
1812
|
-
}
|
|
1813
|
-
if (result.hasMore) {
|
|
1814
|
-
log(chalk.blue(`Showing ${result.offset + 1}-${result.offset + result.transactions.length} of ${result.total}`));
|
|
1815
|
-
log(chalk.blue(`Use --offset ${result.offset + result.limit} to see more`));
|
|
1816
|
-
}
|
|
1817
|
-
}
|
|
1818
|
-
function displayAccountInfoToLog(data, log, logLines) {
|
|
1819
|
-
const { account, balance } = data;
|
|
1820
|
-
log("");
|
|
1821
|
-
log(chalk.green.bold("👤 Account Information"));
|
|
1822
|
-
log(chalk.blue("=".repeat(50)));
|
|
1823
|
-
log("");
|
|
1824
|
-
log(chalk.blue("Account Index: ") + chalk.green(account.index.toString()));
|
|
1825
|
-
log(chalk.blue("Type: ") +
|
|
1826
|
-
chalk.green(account.type === "custom" ? "Custom" : "Seed-Derived"));
|
|
1827
|
-
log(chalk.blue("Address: ") + chalk.green(account.address));
|
|
1828
|
-
if (account.label) {
|
|
1829
|
-
log(chalk.blue("Label: ") + chalk.green(account.label));
|
|
1830
|
-
}
|
|
1831
|
-
log("");
|
|
1832
|
-
log(chalk.green.bold("💰 Balances"));
|
|
1833
|
-
log(chalk.blue("ETH: ") + chalk.yellow(balance.ethBalance));
|
|
1834
|
-
log(chalk.blue("USDC: ") + chalk.green(balance.usdcBalance));
|
|
1835
|
-
if (balance.cat402Formatted) {
|
|
1836
|
-
log(chalk.blue("CAT: ") + chalk.green(balance.cat402Formatted));
|
|
1837
|
-
}
|
|
1838
|
-
log("");
|
|
1839
|
-
if (data.positions && data.positions.positions.length > 0) {
|
|
1840
|
-
log(chalk.green.bold("💼 Positions"));
|
|
1841
|
-
log("");
|
|
1842
|
-
displayPositionsToLog(data.positions, log, logLines);
|
|
1843
|
-
}
|
|
1844
|
-
}
|
|
1845
|
-
// Helper function to format time remaining for lease
|
|
1846
|
-
function formatTimeRemaining(expiresAt) {
|
|
1847
|
-
const now = new Date();
|
|
1848
|
-
const diffMs = expiresAt.getTime() - now.getTime();
|
|
1849
|
-
if (diffMs <= 0) {
|
|
1850
|
-
return "expired";
|
|
1851
|
-
}
|
|
1852
|
-
const minutes = Math.floor(diffMs / 60000);
|
|
1853
|
-
const seconds = Math.floor((diffMs % 60000) / 1000);
|
|
1854
|
-
if (minutes > 0) {
|
|
1855
|
-
return `${minutes}m ${seconds}s`;
|
|
1856
|
-
}
|
|
1857
|
-
return `${seconds}s`;
|
|
1858
|
-
}
|
|
1859
|
-
// Helper function to build chat header content
|
|
1860
|
-
async function buildChatHeaderContent(theme, tokenInfo, tokenIdentifier, isConnected, leaseInfo, getCyanColor) {
|
|
1861
|
-
const colorTag = theme === "dark" ? "green-fg" : "black-fg";
|
|
1862
|
-
const cyanColor = getCyanColor(theme);
|
|
1863
|
-
const lines = [];
|
|
1864
|
-
// Title line
|
|
1865
|
-
if (tokenInfo) {
|
|
1866
|
-
lines.push(`{${colorTag}}💬 httpcat Chat: {${cyanColor}}${tokenInfo.name} (${tokenInfo.symbol}){/${cyanColor}}{/${colorTag}}`);
|
|
1867
|
-
}
|
|
1868
|
-
else if (tokenIdentifier) {
|
|
1869
|
-
// Fallback to identifier if token lookup failed
|
|
1870
|
-
const displayId = tokenIdentifier.length > 20
|
|
1871
|
-
? `${tokenIdentifier.slice(0, 10)}...${tokenIdentifier.slice(-6)}`
|
|
1872
|
-
: tokenIdentifier;
|
|
1873
|
-
lines.push(`{${colorTag}}💬 httpcat Chat: {${cyanColor}}${displayId}{/${cyanColor}}{/${colorTag}}`);
|
|
1874
|
-
}
|
|
1875
|
-
else {
|
|
1876
|
-
lines.push(`{${colorTag}}💬 httpcat Chat: General{/${colorTag}}`);
|
|
1877
|
-
}
|
|
1878
|
-
lines.push(`{${colorTag}}${"═".repeat(60)}{/${colorTag}}`);
|
|
1879
|
-
lines.push("");
|
|
1880
|
-
// Connection status
|
|
1881
|
-
if (isConnected) {
|
|
1882
|
-
lines.push(`{green-fg}✅ Connected to chat stream{/green-fg}`);
|
|
1883
|
-
lines.push("");
|
|
1884
|
-
}
|
|
1885
|
-
// Lease info
|
|
1886
|
-
if (leaseInfo) {
|
|
1887
|
-
const timeRemaining = formatTimeRemaining(leaseInfo.leaseExpiresAt);
|
|
1888
|
-
const isExpired = leaseInfo.leaseExpiresAt.getTime() <= Date.now();
|
|
1889
|
-
const timePrefix = isExpired ? "⚠️ " : "⏱️ ";
|
|
1890
|
-
lines.push(`{yellow-fg}${timePrefix}Lease expires in: ${timeRemaining}{/yellow-fg}`);
|
|
1891
|
-
}
|
|
1892
|
-
lines.push("");
|
|
1893
|
-
lines.push(`{${cyanColor}}💡 Type your message and press Enter to send{/${cyanColor}}`);
|
|
1894
|
-
lines.push(`{${cyanColor}}💡 Type /exit to return to shell{/${cyanColor}}`);
|
|
1895
|
-
lines.push(`{${cyanColor}}💡 Type /renew to renew your lease{/${cyanColor}}`);
|
|
1896
|
-
if (tokenIdentifier) {
|
|
1897
|
-
lines.push(`{${cyanColor}}💡 Type /buy <amount> to buy tokens{/${cyanColor}}`);
|
|
1898
|
-
lines.push(`{${cyanColor}}💡 Type /sell <amount> to sell tokens{/${cyanColor}}`);
|
|
1899
|
-
}
|
|
1900
|
-
return lines.join("\n");
|
|
1901
|
-
}
|
|
1902
|
-
// Simplified chat mode that works within the shell
|
|
1903
|
-
async function startChatInShell(client, tokenIdentifier, log, logLines, screen, inputBox, originalSubmitHandler, headerBox, buildWelcomeContent, currentTheme, catFaces, currentCatIndex) {
|
|
1904
|
-
const privateKey = config.getPrivateKey();
|
|
1905
|
-
const account = privateKeyToAccount(privateKey);
|
|
1906
|
-
const userAddress = account.address;
|
|
1907
|
-
let leaseInfo = null;
|
|
1908
|
-
let ws = null;
|
|
1909
|
-
let isInChatMode = true;
|
|
1910
|
-
let isProcessing = false; // Flag to prevent duplicate processing
|
|
1911
|
-
const displayedMessageIds = new Set();
|
|
1912
|
-
// Helper to format chat messages
|
|
1913
|
-
const formatChatMessage = (msg, isOwn = false) => {
|
|
1914
|
-
const date = new Date(msg.timestamp);
|
|
1915
|
-
const now = new Date();
|
|
1916
|
-
const diffMs = now.getTime() - date.getTime();
|
|
1917
|
-
const diffSec = Math.floor(diffMs / 1000);
|
|
1918
|
-
const diffMin = Math.floor(diffSec / 60);
|
|
1919
|
-
let timeStr = "just now";
|
|
1920
|
-
if (diffSec >= 60) {
|
|
1921
|
-
timeStr =
|
|
1922
|
-
diffMin < 60
|
|
1923
|
-
? `${diffMin}m ago`
|
|
1924
|
-
: date.toLocaleTimeString("en-US", {
|
|
1925
|
-
hour: "2-digit",
|
|
1926
|
-
minute: "2-digit",
|
|
1927
|
-
});
|
|
1928
|
-
}
|
|
1929
|
-
const authorColor = isOwn ? chalk.green : chalk.cyan;
|
|
1930
|
-
const authorShort = msg.authorShort || formatAddress(msg.author, 6);
|
|
1931
|
-
return `${chalk.dim(`[${timeStr}]`)} ${authorColor(authorShort)}: ${msg.message}`;
|
|
1932
|
-
};
|
|
1933
|
-
// Get token information if it's a token chat
|
|
1934
|
-
let tokenInfo = null;
|
|
1935
|
-
if (tokenIdentifier) {
|
|
1936
|
-
try {
|
|
1937
|
-
const info = await getTokenInfo(client, tokenIdentifier, userAddress, true);
|
|
1938
|
-
tokenInfo = { name: info.name, symbol: info.symbol };
|
|
1939
|
-
}
|
|
1940
|
-
catch (error) {
|
|
1941
|
-
// Token lookup failed - will use identifier in header
|
|
1942
|
-
tokenInfo = null;
|
|
1943
|
-
}
|
|
1944
|
-
}
|
|
1945
|
-
// Helper to get cyan color based on theme
|
|
1946
|
-
const getCyanColor = (theme) => theme === "dark" ? "light-cyan-fg" : "cyan-fg";
|
|
1947
|
-
// Helper to update header
|
|
1948
|
-
const updateChatHeader = async (isConnected = false) => {
|
|
1949
|
-
const content = await buildChatHeaderContent(currentTheme, tokenInfo, tokenIdentifier, isConnected, leaseInfo, getCyanColor);
|
|
1950
|
-
headerBox.setContent(content);
|
|
1951
|
-
screen.render();
|
|
1952
|
-
};
|
|
1953
|
-
// Update header to show chat mode
|
|
1954
|
-
await updateChatHeader(false);
|
|
1955
|
-
// Set up header update interval for lease countdown
|
|
1956
|
-
let headerUpdateInterval = null;
|
|
1957
|
-
try {
|
|
1958
|
-
// Join chat
|
|
1959
|
-
log(chalk.blue("Joining chat..."));
|
|
1960
|
-
screen.render();
|
|
1961
|
-
const joinResult = await joinChat(client, userAddress, tokenIdentifier, true);
|
|
1962
|
-
leaseInfo = {
|
|
1963
|
-
leaseId: joinResult.leaseId,
|
|
1964
|
-
leaseExpiresAt: new Date(joinResult.leaseExpiresAt),
|
|
1965
|
-
};
|
|
1966
|
-
const tokenAddress = joinResult.tokenAddress; // Store token address from response
|
|
1967
|
-
// Update header with lease info
|
|
1968
|
-
await updateChatHeader(false);
|
|
1969
|
-
// Display last messages
|
|
1970
|
-
if (joinResult.lastMessages.length > 0) {
|
|
1971
|
-
log(chalk.dim("─".repeat(60)));
|
|
1972
|
-
log(chalk.cyan.bold("Recent messages:"));
|
|
1973
|
-
for (const msg of joinResult.lastMessages) {
|
|
1974
|
-
displayedMessageIds.add(msg.messageId);
|
|
1975
|
-
const isOwn = msg.author.toLowerCase() === userAddress.toLowerCase();
|
|
1976
|
-
log(formatChatMessage(msg, isOwn));
|
|
1977
|
-
}
|
|
1978
|
-
log(chalk.dim("─".repeat(60)));
|
|
1979
|
-
}
|
|
1980
|
-
// Connect to WebSocket
|
|
1981
|
-
const agentUrl = client.getAgentUrl();
|
|
1982
|
-
const normalizedWsUrl = normalizeWebSocketUrl(joinResult.wsUrl, agentUrl);
|
|
1983
|
-
const wsUrlObj = new URL(normalizedWsUrl);
|
|
1984
|
-
wsUrlObj.searchParams.set("leaseId", joinResult.leaseId);
|
|
1985
|
-
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1986
|
-
ws = new WebSocket(wsUrlObj.toString());
|
|
1987
|
-
ws.on("open", () => {
|
|
1988
|
-
log(chalk.green("✅ Connected to chat stream"));
|
|
1989
|
-
updateChatHeader(true);
|
|
1990
|
-
screen.render();
|
|
1991
|
-
});
|
|
1992
|
-
ws.on("message", (data) => {
|
|
1993
|
-
try {
|
|
1994
|
-
const event = JSON.parse(data.toString());
|
|
1995
|
-
if (event.type === "message" && event.data) {
|
|
1996
|
-
const msg = {
|
|
1997
|
-
...event.data,
|
|
1998
|
-
authorShort: formatAddress(event.data.author, 6),
|
|
1999
|
-
};
|
|
2000
|
-
if (!displayedMessageIds.has(msg.messageId)) {
|
|
2001
|
-
displayedMessageIds.add(msg.messageId);
|
|
2002
|
-
const isOwn = msg.author.toLowerCase() === userAddress.toLowerCase();
|
|
2003
|
-
log(formatChatMessage(msg, isOwn));
|
|
2004
|
-
screen.render();
|
|
377
|
+
break;
|
|
378
|
+
case "swap":
|
|
379
|
+
{
|
|
380
|
+
if (args.length < 3) {
|
|
381
|
+
log(chalk.red("Error: Please provide tokenIn, tokenOut, and amount"));
|
|
382
|
+
log(chalk.gray("Usage: swap <tokenIn> <tokenOut> <amount>"));
|
|
383
|
+
break;
|
|
384
|
+
}
|
|
385
|
+
log(chalk.cyan(`Swapping ${args[2]} of ${args[0]} for ${args[1]}...`));
|
|
386
|
+
const { swap, displaySwapResult } = await import("../commands/swap.js");
|
|
387
|
+
const privateKey = config.getPrivateKey();
|
|
388
|
+
if (privateKey) {
|
|
389
|
+
// Create fresh client to ensure x402 payment headers are valid
|
|
390
|
+
const freshClient = await HttpcatClient.create(privateKey);
|
|
391
|
+
const result = await swap(freshClient, args[0], args[1], args[2], 50, true, // silent=true to suppress status lines
|
|
392
|
+
privateKey);
|
|
393
|
+
await withConsoleRedirect(() => displaySwapResult(result));
|
|
394
|
+
}
|
|
395
|
+
else {
|
|
396
|
+
log(chalk.red("Error: Private key not configured"));
|
|
397
|
+
}
|
|
2005
398
|
}
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
}
|
|
2017
|
-
});
|
|
2018
|
-
ws.on("error", (error) => {
|
|
2019
|
-
log(chalk.red(`❌ WebSocket error: ${error.message}`));
|
|
2020
|
-
screen.render();
|
|
2021
|
-
});
|
|
2022
|
-
ws.on("close", () => {
|
|
2023
|
-
if (isInChatMode) {
|
|
2024
|
-
log(chalk.yellow("⚠️ WebSocket connection closed"));
|
|
2025
|
-
updateChatHeader(false);
|
|
2026
|
-
screen.render();
|
|
2027
|
-
}
|
|
2028
|
-
});
|
|
2029
|
-
// Start header update interval
|
|
2030
|
-
headerUpdateInterval = setInterval(async () => {
|
|
2031
|
-
if (leaseInfo && isInChatMode) {
|
|
2032
|
-
await updateChatHeader(true);
|
|
2033
|
-
}
|
|
2034
|
-
}, 1000);
|
|
2035
|
-
// Wait for connection
|
|
2036
|
-
await new Promise((resolve, reject) => {
|
|
2037
|
-
const timeout = setTimeout(() => reject(new Error("Connection timeout")), 10000);
|
|
2038
|
-
ws.once("open", () => {
|
|
2039
|
-
clearTimeout(timeout);
|
|
2040
|
-
resolve();
|
|
2041
|
-
});
|
|
2042
|
-
ws.once("error", (err) => {
|
|
2043
|
-
clearTimeout(timeout);
|
|
2044
|
-
reject(err);
|
|
2045
|
-
});
|
|
2046
|
-
});
|
|
2047
|
-
// Set up chat input handler
|
|
2048
|
-
const chatInputHandler = async (value) => {
|
|
2049
|
-
const trimmed = value.trim();
|
|
2050
|
-
// Clear input immediately
|
|
2051
|
-
inputBox.clearValue();
|
|
2052
|
-
screen.render();
|
|
2053
|
-
if (!trimmed) {
|
|
2054
|
-
isProcessing = false;
|
|
2055
|
-
inputBox.focus();
|
|
2056
|
-
screen.render();
|
|
2057
|
-
return;
|
|
2058
|
-
}
|
|
2059
|
-
// Handle commands
|
|
2060
|
-
if (trimmed.startsWith("/")) {
|
|
2061
|
-
const [cmd, ...cmdArgs] = trimmed.split(" ");
|
|
2062
|
-
switch (cmd) {
|
|
2063
|
-
case "/exit":
|
|
2064
|
-
case "/quit":
|
|
2065
|
-
isInChatMode = false;
|
|
2066
|
-
if (headerUpdateInterval) {
|
|
2067
|
-
clearInterval(headerUpdateInterval);
|
|
2068
|
-
headerUpdateInterval = null;
|
|
399
|
+
break;
|
|
400
|
+
case "positions":
|
|
401
|
+
{
|
|
402
|
+
log(chalk.cyan("Fetching your positions..."));
|
|
403
|
+
const { getPositions, displayPositions } = await import("../commands/positions.js");
|
|
404
|
+
const privateKey = config.getPrivateKey();
|
|
405
|
+
if (privateKey) {
|
|
406
|
+
const account = privateKeyToAccount(privateKey);
|
|
407
|
+
const positions = await getPositions(client, account.address);
|
|
408
|
+
await withConsoleRedirect(() => displayPositions(positions, "all", true));
|
|
2069
409
|
}
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
ws = null;
|
|
410
|
+
else {
|
|
411
|
+
log(chalk.red("Error: Private key not configured"));
|
|
2073
412
|
}
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
log(chalk.blue("Renewing lease..."));
|
|
2089
|
-
screen.render();
|
|
2090
|
-
const renewal = await renewLease(client, userAddress, leaseInfo?.leaseId);
|
|
2091
|
-
leaseInfo = {
|
|
2092
|
-
leaseId: renewal.leaseId,
|
|
2093
|
-
leaseExpiresAt: new Date(renewal.leaseExpiresAt),
|
|
2094
|
-
};
|
|
2095
|
-
await updateChatHeader(true);
|
|
2096
|
-
log(chalk.green("✅ Lease renewed!"));
|
|
2097
|
-
screen.render();
|
|
413
|
+
}
|
|
414
|
+
break;
|
|
415
|
+
case "transactions":
|
|
416
|
+
case "txns":
|
|
417
|
+
{
|
|
418
|
+
log(chalk.cyan("Fetching recent transactions..."));
|
|
419
|
+
const { getTransactions, displayTransactions } = await import("../commands/transactions.js");
|
|
420
|
+
const privateKey = config.getPrivateKey();
|
|
421
|
+
if (privateKey) {
|
|
422
|
+
const account = privateKeyToAccount(privateKey);
|
|
423
|
+
const txns = await getTransactions(client, {
|
|
424
|
+
userAddress: account.address,
|
|
425
|
+
});
|
|
426
|
+
await withConsoleRedirect(() => displayTransactions(txns));
|
|
2098
427
|
}
|
|
2099
|
-
|
|
2100
|
-
log(chalk.red(
|
|
2101
|
-
screen.render();
|
|
428
|
+
else {
|
|
429
|
+
log(chalk.red("Error: Private key not configured"));
|
|
2102
430
|
}
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
if (!leaseInfo || leaseInfo.leaseExpiresAt.getTime() <= Date.now()) {
|
|
2125
|
-
log(chalk.yellow("⏱️ Your lease has expired. Type /renew to continue."));
|
|
2126
|
-
screen.render();
|
|
2127
|
-
isProcessing = false;
|
|
2128
|
-
inputBox.focus();
|
|
2129
|
-
screen.render();
|
|
2130
|
-
return;
|
|
2131
|
-
}
|
|
2132
|
-
try {
|
|
2133
|
-
// Send message - it will appear via WebSocket when received
|
|
2134
|
-
await sendChatMessage(client, trimmed, leaseInfo.leaseId, userAddress, tokenAddress);
|
|
2135
|
-
// Re-attach handler for next message
|
|
2136
|
-
inputBox.removeAllListeners("submit");
|
|
2137
|
-
inputBox.once("submit", chatInputHandler);
|
|
2138
|
-
isProcessing = false;
|
|
2139
|
-
inputBox.focus();
|
|
2140
|
-
screen.render();
|
|
2141
|
-
}
|
|
2142
|
-
catch (error) {
|
|
2143
|
-
log(chalk.red(`❌ Send failed: ${error instanceof Error ? error.message : String(error)}`));
|
|
2144
|
-
screen.render();
|
|
2145
|
-
// Re-attach handler for next message
|
|
2146
|
-
inputBox.removeAllListeners("submit");
|
|
2147
|
-
inputBox.once("submit", chatInputHandler);
|
|
2148
|
-
isProcessing = false;
|
|
2149
|
-
inputBox.focus();
|
|
2150
|
-
screen.render();
|
|
2151
|
-
}
|
|
2152
|
-
};
|
|
2153
|
-
// Temporarily replace input handler
|
|
2154
|
-
// Remove all listeners first to ensure clean state
|
|
2155
|
-
inputBox.removeAllListeners("submit");
|
|
2156
|
-
// Clear any existing input value to prevent state issues
|
|
2157
|
-
inputBox.clearValue();
|
|
2158
|
-
// Add the chat handler - only one handler should be active
|
|
2159
|
-
inputBox.once("submit", chatInputHandler);
|
|
2160
|
-
// Ensure input box has focus and is ready
|
|
2161
|
-
inputBox.focus();
|
|
2162
|
-
screen.render();
|
|
2163
|
-
}
|
|
2164
|
-
catch (error) {
|
|
2165
|
-
log(chalk.red(`❌ Chat error: ${error instanceof Error ? error.message : String(error)}`));
|
|
2166
|
-
if (headerUpdateInterval) {
|
|
2167
|
-
clearInterval(headerUpdateInterval);
|
|
2168
|
-
headerUpdateInterval = null;
|
|
2169
|
-
}
|
|
2170
|
-
if (ws)
|
|
2171
|
-
ws.close();
|
|
2172
|
-
// Restore original header on error
|
|
2173
|
-
const originalContent = await buildWelcomeContent(currentTheme, catFaces[currentCatIndex].face);
|
|
2174
|
-
headerBox.setContent(originalContent);
|
|
2175
|
-
// Restore original handler on error
|
|
2176
|
-
inputBox.removeAllListeners("submit");
|
|
2177
|
-
inputBox.on("submit", originalSubmitHandler);
|
|
2178
|
-
screen.render();
|
|
2179
|
-
inputBox.focus();
|
|
2180
|
-
screen.render();
|
|
2181
|
-
}
|
|
2182
|
-
}
|
|
2183
|
-
/**
|
|
2184
|
-
* Start agent interactive mode (standalone, called from CLI)
|
|
2185
|
-
* This looks identical to the regular shell but starts in agent chat mode
|
|
2186
|
-
*/
|
|
2187
|
-
export async function startAgentInteractiveMode(client) {
|
|
2188
|
-
// Auto-detect terminal background and set default theme
|
|
2189
|
-
const detectedBg = detectTerminalBackground();
|
|
2190
|
-
let currentTheme = detectedBg === "dark" ? "dark" : "win95";
|
|
2191
|
-
// Helper function to get theme-appropriate cyan/blue color for blessed tags
|
|
2192
|
-
// For dark theme, use lighter colors (light-cyan-fg) for better visibility on black
|
|
2193
|
-
const getCyanColor = (theme) => theme === "dark" ? "light-cyan-fg" : "cyan-fg";
|
|
2194
|
-
const getBlueColor = (theme) => theme === "dark" ? "light-blue-fg" : "blue-fg";
|
|
2195
|
-
// Create blessed screen with optimized settings
|
|
2196
|
-
const screen = blessed.screen({
|
|
2197
|
-
smartCSR: true,
|
|
2198
|
-
title: "httpcat Agent Mode",
|
|
2199
|
-
fullUnicode: true, // Support double-width/surrogate/combining chars (emojis)
|
|
2200
|
-
fastCSR: false, // Disable fast CSR to prevent rendering issues
|
|
2201
|
-
cursor: {
|
|
2202
|
-
artificial: true,
|
|
2203
|
-
shape: "line",
|
|
2204
|
-
blink: true,
|
|
2205
|
-
color: "green",
|
|
2206
|
-
},
|
|
2207
|
-
// Force Unicode support for emojis
|
|
2208
|
-
forceUnicode: true,
|
|
2209
|
-
});
|
|
2210
|
-
const network = client.getNetwork();
|
|
2211
|
-
// Theme colors - no backgrounds, just borders
|
|
2212
|
-
const getThemeColors = (theme) => {
|
|
2213
|
-
switch (theme) {
|
|
2214
|
-
case "light":
|
|
2215
|
-
return {
|
|
2216
|
-
bg: "default", // Transparent/default
|
|
2217
|
-
fg: "black",
|
|
2218
|
-
border: "black",
|
|
2219
|
-
inputBg: "default",
|
|
2220
|
-
inputFg: "black", // Explicit black for visibility
|
|
2221
|
-
inputFocusBg: "default",
|
|
2222
|
-
inputFocusFg: "black",
|
|
2223
|
-
};
|
|
2224
|
-
case "win95":
|
|
2225
|
-
return {
|
|
2226
|
-
bg: "default",
|
|
2227
|
-
fg: "black",
|
|
2228
|
-
border: "black",
|
|
2229
|
-
inputBg: "default",
|
|
2230
|
-
inputFg: "black", // Explicit black for visibility
|
|
2231
|
-
inputFocusBg: "default",
|
|
2232
|
-
inputFocusFg: "black",
|
|
2233
|
-
};
|
|
2234
|
-
default: // dark
|
|
2235
|
-
return {
|
|
2236
|
-
bg: "default",
|
|
2237
|
-
fg: "green",
|
|
2238
|
-
border: "green",
|
|
2239
|
-
inputBg: "default",
|
|
2240
|
-
inputFg: "green", // Explicit green for visibility
|
|
2241
|
-
inputFocusBg: "default",
|
|
2242
|
-
inputFocusFg: "green",
|
|
2243
|
-
};
|
|
2244
|
-
}
|
|
2245
|
-
};
|
|
2246
|
-
let themeColors = getThemeColors(currentTheme);
|
|
2247
|
-
// Create header box with thick borders, transparent background - more compact
|
|
2248
|
-
const headerBox = blessed.box({
|
|
2249
|
-
top: 0,
|
|
2250
|
-
left: 0,
|
|
2251
|
-
width: "100%",
|
|
2252
|
-
height: 6, // Reduced to save vertical space
|
|
2253
|
-
content: "",
|
|
2254
|
-
tags: true,
|
|
2255
|
-
style: {
|
|
2256
|
-
fg: themeColors.fg,
|
|
2257
|
-
bg: "default", // Transparent
|
|
2258
|
-
bold: false,
|
|
2259
|
-
border: {
|
|
2260
|
-
fg: themeColors.border,
|
|
2261
|
-
bold: true,
|
|
2262
|
-
},
|
|
2263
|
-
},
|
|
2264
|
-
padding: {
|
|
2265
|
-
left: 1,
|
|
2266
|
-
right: 1,
|
|
2267
|
-
top: 0,
|
|
2268
|
-
bottom: 0,
|
|
2269
|
-
},
|
|
2270
|
-
border: {
|
|
2271
|
-
type: "line",
|
|
2272
|
-
fg: themeColors.border,
|
|
2273
|
-
ch: "═", // Double line for thicker border
|
|
2274
|
-
},
|
|
2275
|
-
});
|
|
2276
|
-
// Cat face variants for animation
|
|
2277
|
-
const catFaces = [
|
|
2278
|
-
{ name: "Sleepy", face: "[=^ -.- ^=]" },
|
|
2279
|
-
{ name: "Smug", face: "[=^‿^=]" },
|
|
2280
|
-
{ name: "Unhinged", face: "[=^◉_◉^=]" },
|
|
2281
|
-
{ name: "Judgy", face: "[=^ಠ‿ಠ^=]" },
|
|
2282
|
-
{ name: "Cute", face: "[=^。^=]" },
|
|
2283
|
-
{ name: "Menacing", face: "[=^>_<^=]" },
|
|
2284
|
-
{ name: "Loaf Mode", face: "[=^___^=]" },
|
|
2285
|
-
{ name: "Cosmic", face: "[=^✧_✧^=]" },
|
|
2286
|
-
];
|
|
2287
|
-
let currentCatIndex = 0;
|
|
2288
|
-
let catAnimationInterval = null;
|
|
2289
|
-
// Helper function to build welcome content with account info - more compact
|
|
2290
|
-
const buildWelcomeContent = async (theme, catFace) => {
|
|
2291
|
-
const welcomeLines = [];
|
|
2292
|
-
const colorTag = theme === "dark" ? "green-fg" : "black-fg";
|
|
2293
|
-
const cyanColor = getCyanColor(theme);
|
|
2294
|
-
// Use provided cat face or current one
|
|
2295
|
-
const displayCatFace = catFace || catFaces[currentCatIndex].face;
|
|
2296
|
-
// Cat face with breathing room, compact info below
|
|
2297
|
-
welcomeLines.push(`{${colorTag}}${displayCatFace}{/${colorTag}}`);
|
|
2298
|
-
welcomeLines.push(`{green-fg}🐱 Welcome to httpcat!{/green-fg} | {green-fg}🌐 {${cyanColor}}${network}{/${cyanColor}}{/green-fg}`);
|
|
2299
|
-
// Get account info
|
|
2300
|
-
let accountInfo = null;
|
|
2301
|
-
try {
|
|
2302
|
-
const accounts = config.getAllAccounts();
|
|
2303
|
-
const activeIndex = config.getActiveAccountIndex();
|
|
2304
|
-
const account = accounts.find((acc) => acc.index === activeIndex);
|
|
2305
|
-
if (account) {
|
|
2306
|
-
// Get balance info
|
|
2307
|
-
try {
|
|
2308
|
-
const privateKey = config.getAccountPrivateKey(activeIndex);
|
|
2309
|
-
const balance = await checkBalance(privateKey, true); // silent mode
|
|
2310
|
-
accountInfo = {
|
|
2311
|
-
account,
|
|
2312
|
-
balance,
|
|
2313
|
-
};
|
|
2314
|
-
}
|
|
2315
|
-
catch (error) {
|
|
2316
|
-
// If balance check fails, just show account info without balance
|
|
2317
|
-
accountInfo = { account };
|
|
2318
|
-
}
|
|
431
|
+
}
|
|
432
|
+
break;
|
|
433
|
+
case "chat":
|
|
434
|
+
{
|
|
435
|
+
const tokenIdentifier = args[0];
|
|
436
|
+
if (tokenIdentifier) {
|
|
437
|
+
log(chalk.cyan(`Joining chat for token: ${tokenIdentifier}...`));
|
|
438
|
+
}
|
|
439
|
+
else {
|
|
440
|
+
log(chalk.cyan("Joining general chat..."));
|
|
441
|
+
}
|
|
442
|
+
// Trigger transition to chat
|
|
443
|
+
if (onTransition) {
|
|
444
|
+
onTransition({ action: "chat", token: tokenIdentifier });
|
|
445
|
+
}
|
|
446
|
+
exit(); // Exit shell to trigger the transition
|
|
447
|
+
}
|
|
448
|
+
break;
|
|
449
|
+
default:
|
|
450
|
+
log(chalk.red(`Unknown command: ${cmd}`));
|
|
451
|
+
log(chalk.gray('Type "help" for available commands'));
|
|
2319
452
|
}
|
|
2320
453
|
}
|
|
2321
454
|
catch (error) {
|
|
2322
|
-
|
|
2323
|
-
}
|
|
2324
|
-
// Compact account info - combined on fewer lines
|
|
2325
|
-
if (accountInfo) {
|
|
2326
|
-
const { account, balance } = accountInfo;
|
|
2327
|
-
const accountType = account.type === "custom" ? "Custom" : "Seed-Derived";
|
|
2328
|
-
const accountLabel = account.label ? ` (${account.label})` : "";
|
|
2329
|
-
if (balance) {
|
|
2330
|
-
const ethDisplay = balance.ethFormatted || balance.ethBalance || "0 ETH";
|
|
2331
|
-
const usdcDisplay = balance.usdcFormatted || balance.usdcBalance || "$0.00";
|
|
2332
|
-
// Combine account and balance on one line to save space
|
|
2333
|
-
welcomeLines.push(`{${cyanColor}}👤 Account #{green-fg}${account.index}{/green-fg} | {green-fg}${accountType}${accountLabel}{/green-fg} | 💰 {yellow-fg}${ethDisplay}{/yellow-fg} | {green-fg}${usdcDisplay}{/green-fg}{/${cyanColor}}`);
|
|
2334
|
-
}
|
|
2335
|
-
else {
|
|
2336
|
-
welcomeLines.push(`{${cyanColor}}👤 Account #{green-fg}${account.index}{/green-fg} | {green-fg}${accountType}${accountLabel}{/green-fg}{/${cyanColor}}`);
|
|
2337
|
-
}
|
|
2338
|
-
}
|
|
2339
|
-
return welcomeLines.join("\n");
|
|
2340
|
-
};
|
|
2341
|
-
// Set initial header content
|
|
2342
|
-
buildWelcomeContent(currentTheme, catFaces[currentCatIndex].face).then((content) => {
|
|
2343
|
-
headerBox.setContent(content);
|
|
2344
|
-
screen.render();
|
|
2345
|
-
});
|
|
2346
|
-
// Start cat face animation (cycle through every minute)
|
|
2347
|
-
const startCatAnimation = () => {
|
|
2348
|
-
if (catAnimationInterval) {
|
|
2349
|
-
clearInterval(catAnimationInterval);
|
|
2350
|
-
}
|
|
2351
|
-
catAnimationInterval = setInterval(() => {
|
|
2352
|
-
currentCatIndex = (currentCatIndex + 1) % catFaces.length;
|
|
2353
|
-
buildWelcomeContent(currentTheme, catFaces[currentCatIndex].face).then((content) => {
|
|
2354
|
-
headerBox.setContent(content);
|
|
2355
|
-
screen.render();
|
|
2356
|
-
});
|
|
2357
|
-
}, 60000); // Change every minute (60 seconds)
|
|
2358
|
-
};
|
|
2359
|
-
// Start animation
|
|
2360
|
-
startCatAnimation();
|
|
2361
|
-
// Create output log box (scrollable) with thick borders, transparent background
|
|
2362
|
-
const outputBox = blessed.log({
|
|
2363
|
-
top: 6, // Adjusted to match new header height
|
|
2364
|
-
left: 0,
|
|
2365
|
-
width: "100%",
|
|
2366
|
-
bottom: 4, // Leave space for input box at bottom
|
|
2367
|
-
tags: true,
|
|
2368
|
-
scrollable: true,
|
|
2369
|
-
alwaysScroll: true,
|
|
2370
|
-
scrollbar: {
|
|
2371
|
-
ch: " ",
|
|
2372
|
-
inverse: currentTheme !== "dark",
|
|
2373
|
-
},
|
|
2374
|
-
style: {
|
|
2375
|
-
fg: themeColors.fg,
|
|
2376
|
-
bg: "default", // Transparent
|
|
2377
|
-
border: {
|
|
2378
|
-
fg: themeColors.border,
|
|
2379
|
-
bold: true,
|
|
2380
|
-
},
|
|
2381
|
-
},
|
|
2382
|
-
padding: {
|
|
2383
|
-
left: 0,
|
|
2384
|
-
right: 1,
|
|
2385
|
-
},
|
|
2386
|
-
mouse: true, // Enable mouse scrolling
|
|
2387
|
-
border: {
|
|
2388
|
-
type: "line",
|
|
2389
|
-
fg: themeColors.border,
|
|
2390
|
-
ch: "═", // Double line for thicker border
|
|
2391
|
-
},
|
|
2392
|
-
});
|
|
2393
|
-
// Create prompt label with bold font (appears larger) - positioned inside input box
|
|
2394
|
-
const promptLabel = blessed.text({
|
|
2395
|
-
bottom: 1,
|
|
2396
|
-
left: 2,
|
|
2397
|
-
width: 8, // Exactly "httpcat>" (8 characters)
|
|
2398
|
-
height: 1,
|
|
2399
|
-
content: "",
|
|
2400
|
-
tags: true,
|
|
2401
|
-
style: {
|
|
2402
|
-
fg: themeColors.fg,
|
|
2403
|
-
bg: "default", // Transparent
|
|
2404
|
-
bold: true,
|
|
2405
|
-
},
|
|
2406
|
-
});
|
|
2407
|
-
// Helper to update prompt label content
|
|
2408
|
-
const updatePromptLabel = (theme) => {
|
|
2409
|
-
const colorTag = theme === "dark" ? "green-fg" : "black-fg";
|
|
2410
|
-
promptLabel.content = `{${colorTag}}{bold}httpcat>{/bold}{/${colorTag}}`;
|
|
2411
|
-
};
|
|
2412
|
-
updatePromptLabel(currentTheme);
|
|
2413
|
-
// Create input box with visible cursor and stylish border
|
|
2414
|
-
const inputBox = blessed.textbox({
|
|
2415
|
-
bottom: 0,
|
|
2416
|
-
left: 0,
|
|
2417
|
-
width: "100%",
|
|
2418
|
-
height: 3,
|
|
2419
|
-
inputOnFocus: true,
|
|
2420
|
-
keys: true,
|
|
2421
|
-
vi: false, // Disabled to prevent double input issues in agent mode
|
|
2422
|
-
secret: false,
|
|
2423
|
-
tags: true,
|
|
2424
|
-
alwaysScroll: false,
|
|
2425
|
-
scrollable: false,
|
|
2426
|
-
padding: {
|
|
2427
|
-
left: 10, // Space for "httpcat>" prompt (8 chars) + 2 for spacing
|
|
2428
|
-
right: 1,
|
|
2429
|
-
top: 0,
|
|
2430
|
-
bottom: 0,
|
|
2431
|
-
},
|
|
2432
|
-
cursor: {
|
|
2433
|
-
artificial: true,
|
|
2434
|
-
shape: "block", // Block cursor is more visible than line
|
|
2435
|
-
blink: true,
|
|
2436
|
-
color: currentTheme === "dark" ? "green" : "black",
|
|
2437
|
-
},
|
|
2438
|
-
style: {
|
|
2439
|
-
fg: themeColors.fg,
|
|
2440
|
-
bg: "default",
|
|
2441
|
-
border: {
|
|
2442
|
-
fg: themeColors.border,
|
|
2443
|
-
bold: true,
|
|
2444
|
-
},
|
|
2445
|
-
focus: {
|
|
2446
|
-
fg: themeColors.fg,
|
|
2447
|
-
bg: "default",
|
|
2448
|
-
border: {
|
|
2449
|
-
fg: themeColors.border,
|
|
2450
|
-
bold: true,
|
|
2451
|
-
},
|
|
2452
|
-
},
|
|
2453
|
-
},
|
|
2454
|
-
border: {
|
|
2455
|
-
type: "line",
|
|
2456
|
-
fg: themeColors.border,
|
|
2457
|
-
ch: "─", // Single line border
|
|
2458
|
-
},
|
|
2459
|
-
});
|
|
2460
|
-
// Helper to update theme
|
|
2461
|
-
const updateTheme = (newTheme) => {
|
|
2462
|
-
currentTheme = newTheme;
|
|
2463
|
-
themeColors = getThemeColors(currentTheme);
|
|
2464
|
-
// Update screen cursor color
|
|
2465
|
-
screen.cursor.color =
|
|
2466
|
-
currentTheme === "dark" ? "green" : "black";
|
|
2467
|
-
// Update header content with new theme colors (keep current cat face)
|
|
2468
|
-
buildWelcomeContent(currentTheme, catFaces[currentCatIndex].face).then((content) => {
|
|
2469
|
-
headerBox.setContent(content);
|
|
2470
|
-
screen.render();
|
|
2471
|
-
});
|
|
2472
|
-
// Update all widget styles
|
|
2473
|
-
headerBox.style.fg = themeColors.fg;
|
|
2474
|
-
headerBox.style.bg = "default"; // Transparent
|
|
2475
|
-
headerBox.border = { type: "line", fg: themeColors.border, ch: "═" };
|
|
2476
|
-
outputBox.style.fg = themeColors.fg;
|
|
2477
|
-
outputBox.style.bg = "default"; // Transparent
|
|
2478
|
-
outputBox.scrollbar.inverse = currentTheme !== "dark";
|
|
2479
|
-
outputBox.border = { type: "line", fg: themeColors.border, ch: "═" };
|
|
2480
|
-
updatePromptLabel(currentTheme);
|
|
2481
|
-
promptLabel.style.fg = themeColors.fg;
|
|
2482
|
-
promptLabel.style.bg = "default"; // Transparent
|
|
2483
|
-
promptLabel.style.bold = true;
|
|
2484
|
-
// Update input box cursor, style, and border
|
|
2485
|
-
inputBox.style.fg = themeColors.fg;
|
|
2486
|
-
inputBox.style.bg = "default";
|
|
2487
|
-
inputBox.style.focus.fg = themeColors.fg;
|
|
2488
|
-
inputBox.style.focus.bg = "default";
|
|
2489
|
-
inputBox.style.border.fg = themeColors.border;
|
|
2490
|
-
inputBox.style.focus.border.fg = themeColors.border;
|
|
2491
|
-
inputBox.border = { type: "line", fg: themeColors.border, ch: "─" };
|
|
2492
|
-
if (inputBox.cursor) {
|
|
2493
|
-
inputBox.cursor.color =
|
|
2494
|
-
currentTheme === "dark" ? "green" : "black";
|
|
2495
|
-
}
|
|
2496
|
-
screen.cursor.color =
|
|
2497
|
-
currentTheme === "dark" ? "green" : "black";
|
|
2498
|
-
screen.render();
|
|
2499
|
-
};
|
|
2500
|
-
// Helper to log output (define before use)
|
|
2501
|
-
const log = (text) => {
|
|
2502
|
-
outputBox.log(text);
|
|
2503
|
-
outputBox.setScrollPerc(100);
|
|
2504
|
-
screen.render();
|
|
2505
|
-
};
|
|
2506
|
-
// Helper to log multiple lines
|
|
2507
|
-
const logLines = (lines) => {
|
|
2508
|
-
lines.forEach((line) => outputBox.log(line));
|
|
2509
|
-
outputBox.setScrollPerc(100);
|
|
2510
|
-
screen.render();
|
|
2511
|
-
};
|
|
2512
|
-
// Wrap toggleTheme to also log
|
|
2513
|
-
const toggleThemeWithLog = () => {
|
|
2514
|
-
const themes = ["win95", "dark", "light"];
|
|
2515
|
-
const currentIndex = themes.indexOf(currentTheme);
|
|
2516
|
-
const nextTheme = themes[(currentIndex + 1) % themes.length];
|
|
2517
|
-
updateTheme(nextTheme);
|
|
2518
|
-
log(chalk.blue(`Theme switched to: ${nextTheme}`));
|
|
2519
|
-
};
|
|
2520
|
-
// Append all widgets in correct z-order (last appended is on top)
|
|
2521
|
-
screen.append(headerBox);
|
|
2522
|
-
screen.append(outputBox);
|
|
2523
|
-
screen.append(inputBox);
|
|
2524
|
-
screen.append(promptLabel); // Prompt label on top so it's always visible
|
|
2525
|
-
// Handle F1 for theme toggle
|
|
2526
|
-
screen.key(["f1"], () => {
|
|
2527
|
-
toggleThemeWithLog();
|
|
2528
|
-
});
|
|
2529
|
-
// Store toggle function for command handler
|
|
2530
|
-
screen.toggleTheme = toggleThemeWithLog;
|
|
2531
|
-
screen.updateTheme = updateTheme;
|
|
2532
|
-
// Handle Ctrl+C - two-stage: first clears input if text exists, second quits
|
|
2533
|
-
const handleCtrlC = () => {
|
|
2534
|
-
const currentValue = inputBox.getValue();
|
|
2535
|
-
// If there's text in the input, clear it instead of quitting
|
|
2536
|
-
if (currentValue && currentValue.trim().length > 0) {
|
|
2537
|
-
inputBox.clearValue();
|
|
2538
|
-
screen.render();
|
|
2539
|
-
return;
|
|
2540
|
-
}
|
|
2541
|
-
// No text in input, so quit
|
|
2542
|
-
if (catAnimationInterval) {
|
|
2543
|
-
clearInterval(catAnimationInterval);
|
|
2544
|
-
catAnimationInterval = null;
|
|
455
|
+
log(chalk.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
|
|
2545
456
|
}
|
|
2546
|
-
|
|
2547
|
-
printCat("sleeping");
|
|
2548
|
-
console.log(chalk.cyan("Goodbye! 👋"));
|
|
2549
|
-
process.exit(0);
|
|
457
|
+
log("");
|
|
2550
458
|
};
|
|
2551
|
-
//
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
//
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
//
|
|
2561
|
-
|
|
459
|
+
// Calculate terminal height and component heights
|
|
460
|
+
// Subtract 3: 1 for top status bar, 1 for bottom status bar, 1 for safety margin
|
|
461
|
+
const terminalHeight = Math.max(20, (stdout?.rows || 24) - 3);
|
|
462
|
+
// Component heights (including borders):
|
|
463
|
+
// - ChatHeader: height={6} + 2 border lines = 8 total
|
|
464
|
+
// - ShellInput: height={3} includes border = 3 total
|
|
465
|
+
// - ShellOutput: flexGrow fills remaining, but needs viewport height for scrolling
|
|
466
|
+
const headerTotalHeight = 8;
|
|
467
|
+
const inputTotalHeight = 3;
|
|
468
|
+
// Output viewport height = terminal - header - input - output border
|
|
469
|
+
const outputContentHeight = Math.max(8, // minimum reasonable height
|
|
470
|
+
terminalHeight - headerTotalHeight - inputTotalHeight - 2 // -2 for output's top+bottom border
|
|
471
|
+
);
|
|
472
|
+
return (_jsxs(Box, { flexDirection: "column", height: terminalHeight, width: "100%", overflow: "hidden", children: [_jsx(Box, { flexShrink: 0, width: "100%", children: _jsx(ChatHeader, { network: network, account: account, ethBalance: ethBalance, usdcBalance: usdcBalance, agentStatus: "Interactive Shell" }) }), _jsx(ShellOutput, { lines: outputLines, viewportHeight: outputContentHeight }), _jsx(Box, { flexShrink: 0, width: "100%", children: _jsx(ShellInput, { onSubmit: handleCommand }) })] }));
|
|
2562
473
|
}
|
|
2563
474
|
/**
|
|
2564
|
-
* Start
|
|
475
|
+
* Start the interactive shell
|
|
476
|
+
* Backwards-compatible with the blessed version
|
|
2565
477
|
*/
|
|
2566
|
-
async function
|
|
2567
|
-
|
|
2568
|
-
//
|
|
2569
|
-
const
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
// Create LLM and agent
|
|
2578
|
-
const llm = createLLM(agentConfig, apiKey);
|
|
2579
|
-
const agent = createHttpcatAgent(client, llm);
|
|
2580
|
-
// Helper to update header
|
|
2581
|
-
const updateAgentHeader = async () => {
|
|
2582
|
-
const colorTag = currentTheme === "dark" ? "green-fg" : "black-fg";
|
|
2583
|
-
const cyanColor = currentTheme === "dark" ? "light-cyan-fg" : "cyan-fg";
|
|
2584
|
-
const lines = [];
|
|
2585
|
-
lines.push(`{${colorTag}}🐱 Cat Chat Mode{/${colorTag}}`);
|
|
2586
|
-
lines.push(`{${colorTag}}${"═".repeat(60)}{/${colorTag}}`);
|
|
2587
|
-
lines.push("");
|
|
2588
|
-
lines.push(`{${cyanColor}}💡 Ask me to buy tokens, check balances, list tokens, and more!{/${cyanColor}}`);
|
|
2589
|
-
lines.push(`{${cyanColor}}💡 Type /exit to return to shell{/${cyanColor}}`);
|
|
2590
|
-
lines.push("");
|
|
2591
|
-
const content = lines.join("\n");
|
|
2592
|
-
headerBox.setContent(content);
|
|
2593
|
-
screen.render();
|
|
2594
|
-
};
|
|
2595
|
-
// Update header to show agent mode
|
|
2596
|
-
await updateAgentHeader();
|
|
2597
|
-
// Store original submit handler
|
|
2598
|
-
const originalHandlers = inputBox.listeners("submit");
|
|
2599
|
-
const originalSubmitHandler = originalHandlers[0];
|
|
2600
|
-
// Set up agent input handler (similar to chat mode)
|
|
2601
|
-
const agentInputHandler = async (value) => {
|
|
2602
|
-
// Guard against double processing
|
|
2603
|
-
if (isProcessing) {
|
|
2604
|
-
return;
|
|
2605
|
-
}
|
|
2606
|
-
isProcessing = true;
|
|
2607
|
-
const trimmed = value.trim();
|
|
2608
|
-
// Clear input immediately
|
|
2609
|
-
inputBox.clearValue();
|
|
2610
|
-
screen.render();
|
|
2611
|
-
if (!trimmed) {
|
|
2612
|
-
isProcessing = false;
|
|
2613
|
-
inputBox.focus();
|
|
2614
|
-
screen.render();
|
|
2615
|
-
return;
|
|
2616
|
-
}
|
|
2617
|
-
// Handle commands
|
|
2618
|
-
if (trimmed.startsWith("/")) {
|
|
2619
|
-
const [cmd] = trimmed.split(" ");
|
|
2620
|
-
switch (cmd) {
|
|
2621
|
-
case "/exit":
|
|
2622
|
-
case "/quit":
|
|
2623
|
-
screen.destroy();
|
|
2624
|
-
printCat("sleeping");
|
|
2625
|
-
console.log(chalk.cyan("Goodbye! 👋"));
|
|
2626
|
-
process.exit(0);
|
|
2627
|
-
return;
|
|
2628
|
-
case "/help":
|
|
2629
|
-
log(chalk.cyan("Cat commands: /exit, /quit, /help"));
|
|
2630
|
-
isProcessing = false;
|
|
2631
|
-
inputBox.focus();
|
|
2632
|
-
screen.render();
|
|
2633
|
-
return;
|
|
2634
|
-
default:
|
|
2635
|
-
log(chalk.yellow(`Unknown command: ${cmd}. Type /help for commands.`));
|
|
2636
|
-
isProcessing = false;
|
|
2637
|
-
inputBox.focus();
|
|
2638
|
-
screen.render();
|
|
2639
|
-
return;
|
|
2640
|
-
}
|
|
2641
|
-
}
|
|
2642
|
-
// Send to agent
|
|
478
|
+
export async function startInteractiveShell(client, autoChatToken) {
|
|
479
|
+
// Clear screen and reset cursor to top-left
|
|
480
|
+
process.stdout.write("\x1Bc"); // Full reset (clears screen and scrollback)
|
|
481
|
+
const transitionRef = { current: null };
|
|
482
|
+
const { waitUntilExit } = render(_jsx(ThemeProvider, { children: _jsx(Shell, { client: client, autoChatToken: autoChatToken, onTransition: (transition) => {
|
|
483
|
+
transitionRef.current = transition;
|
|
484
|
+
} }) }));
|
|
485
|
+
await waitUntilExit();
|
|
486
|
+
// If there's a pending transition, handle it
|
|
487
|
+
const transition = transitionRef.current;
|
|
488
|
+
if (transition && transition.action === "chat") {
|
|
2643
489
|
try {
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
490
|
+
const { startChatStream } = await import("../commands/chat.js");
|
|
491
|
+
// Give the terminal a moment to clear after shell exit
|
|
492
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
493
|
+
await startChatStream(client, false, // jsonMode
|
|
494
|
+
transition.token || undefined, "text", true // returnToShell
|
|
495
|
+
);
|
|
496
|
+
// Small delay before restarting shell
|
|
497
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
498
|
+
// Restart shell after chat exits
|
|
499
|
+
await startInteractiveShell(client);
|
|
2654
500
|
}
|
|
2655
501
|
catch (error) {
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
if (process.env.HTTPCAT_DEBUG) {
|
|
2660
|
-
log(chalk.dim(`Debug: Full error: ${JSON.stringify(error, null, 2)}`));
|
|
2661
|
-
if (errorStack) {
|
|
2662
|
-
log(chalk.dim(`Debug: Stack trace: ${errorStack}`));
|
|
2663
|
-
}
|
|
2664
|
-
}
|
|
2665
|
-
log(chalk.red(`❌ Agent error: ${errorMsg}`));
|
|
2666
|
-
// Check for authentication errors
|
|
2667
|
-
const isAuthError = errorMsg.includes("Authentication failed") ||
|
|
2668
|
-
errorMsg.includes("API key") ||
|
|
2669
|
-
errorMsg.includes("401") ||
|
|
2670
|
-
errorMsg.includes("403") ||
|
|
2671
|
-
errorMsg.includes("Unauthorized") ||
|
|
2672
|
-
errorMsg.includes("Invalid API key") ||
|
|
2673
|
-
errorMsg.includes("authentication") ||
|
|
2674
|
-
errorMsg.includes("Invalid API Key") ||
|
|
2675
|
-
errorStack.includes("401") ||
|
|
2676
|
-
errorStack.includes("403");
|
|
2677
|
-
if (isAuthError) {
|
|
2678
|
-
log("");
|
|
2679
|
-
log(chalk.yellow("🔑 Authentication Error Detected"));
|
|
2680
|
-
log(chalk.dim("Your API key may be invalid, expired, or incorrectly configured."));
|
|
2681
|
-
log(chalk.dim("Exit cat mode and run 'agent' (or 'cat') again to re-run the setup wizard."));
|
|
2682
|
-
}
|
|
2683
|
-
log("");
|
|
502
|
+
console.error("Chat error:", error);
|
|
503
|
+
// Restart shell even if chat fails
|
|
504
|
+
await startInteractiveShell(client);
|
|
2684
505
|
}
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
//
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
screen.render();
|
|
2696
|
-
// Show welcome message
|
|
2697
|
-
log(chalk.green("🐱 Cat chat mode activated!"));
|
|
2698
|
-
log(chalk.dim("Ask me anything about tokens, trading, or your portfolio."));
|
|
2699
|
-
log(chalk.dim("Type /exit to return to shell."));
|
|
2700
|
-
log("");
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
/**
|
|
509
|
+
* Start agent interactive mode
|
|
510
|
+
* Legacy function for backwards compatibility
|
|
511
|
+
*/
|
|
512
|
+
export async function startAgentInteractiveMode(client) {
|
|
513
|
+
// For now, just start the regular interactive shell
|
|
514
|
+
// This can be enhanced later with agent-specific features
|
|
515
|
+
await startInteractiveShell(client);
|
|
2701
516
|
}
|
|
2702
517
|
//# sourceMappingURL=shell.js.map
|