httpcat-cli 0.2.11 → 0.2.12-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/sync-version.yml +19 -3
- package/README.md +346 -13
- package/Screenshot 2025-12-21 at 8.56.02/342/200/257PM.png +0 -0
- package/bun.lock +8 -1
- package/cat-spin.sh +417 -0
- package/dist/agent/ax-agent.d.ts.map +1 -0
- package/dist/agent/ax-agent.js +459 -0
- package/dist/agent/ax-agent.js.map +1 -0
- package/dist/agent/llm-factory.d.ts.map +1 -0
- package/dist/agent/llm-factory.js +82 -0
- package/dist/agent/llm-factory.js.map +1 -0
- package/dist/agent/setup-wizard.d.ts.map +1 -0
- package/dist/agent/setup-wizard.js +114 -0
- package/dist/agent/setup-wizard.js.map +1 -0
- package/dist/agent/tools.d.ts.map +1 -0
- package/dist/agent/tools.js +312 -0
- package/dist/agent/tools.js.map +1 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +18 -0
- package/dist/client.js.map +1 -1
- package/dist/commands/balances.d.ts.map +1 -1
- package/dist/commands/balances.js +43 -41
- package/dist/commands/balances.js.map +1 -1
- package/dist/commands/chat.d.ts.map +1 -1
- package/dist/commands/chat.js +56 -46
- package/dist/commands/chat.js.map +1 -1
- package/dist/commands/create.d.ts.map +1 -1
- package/dist/commands/create.js +133 -5
- package/dist/commands/create.js.map +1 -1
- package/dist/commands/positions.d.ts.map +1 -1
- package/dist/commands/positions.js +51 -54
- package/dist/commands/positions.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +296 -20
- package/dist/config.js.map +1 -1
- package/dist/index.js +316 -15
- package/dist/index.js.map +1 -1
- package/dist/interactive/cat-spin.d.ts.map +1 -0
- package/dist/interactive/cat-spin.js +448 -0
- package/dist/interactive/cat-spin.js.map +1 -0
- package/dist/interactive/shell.d.ts.map +1 -1
- package/dist/interactive/shell.js +2001 -180
- package/dist/interactive/shell.js.map +1 -1
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +1 -6
- package/dist/mcp/tools.js.map +1 -1
- package/dist/mcp/types.d.ts.map +1 -1
- package/dist/utils/loading.d.ts.map +1 -1
- package/dist/utils/loading.js +30 -0
- package/dist/utils/loading.js.map +1 -1
- package/dist/utils/privateKeyPrompt.d.ts.map +1 -1
- package/dist/utils/privateKeyPrompt.js +13 -9
- package/dist/utils/privateKeyPrompt.js.map +1 -1
- package/dist/utils/token-resolver.d.ts.map +1 -1
- package/dist/utils/token-resolver.js +32 -0
- package/dist/utils/token-resolver.js.map +1 -1
- package/package.json +3 -1
|
@@ -5,74 +5,288 @@ import { config } from "../config.js";
|
|
|
5
5
|
import { printCat } from "./art.js";
|
|
6
6
|
import { validateAmount } from "../utils/validation.js";
|
|
7
7
|
// Import commands
|
|
8
|
-
import { createToken } from "../commands/create.js";
|
|
9
|
-
import { buyToken, TEST_AMOUNTS, PROD_AMOUNTS } from "../commands/buy.js";
|
|
8
|
+
import { createToken, processPhotoUrl, isFilePath, } from "../commands/create.js";
|
|
9
|
+
import { buyToken, TEST_AMOUNTS, PROD_AMOUNTS, } from "../commands/buy.js";
|
|
10
10
|
import { sellToken, parseTokenAmount } from "../commands/sell.js";
|
|
11
11
|
import { getTokenInfo } from "../commands/info.js";
|
|
12
12
|
import { listTokens } from "../commands/list.js";
|
|
13
|
+
import { formatCurrency, formatTokenAmount, formatAddress, } from "../utils/formatting.js";
|
|
13
14
|
import { getPositions } from "../commands/positions.js";
|
|
14
15
|
import { privateKeyToAccount } from "viem/accounts";
|
|
15
16
|
import { checkHealth } from "../commands/health.js";
|
|
16
|
-
import {
|
|
17
|
+
import { joinChat, sendChatMessage, renewLease, normalizeWebSocketUrl, } from "../commands/chat.js";
|
|
18
|
+
import WebSocket from "ws";
|
|
17
19
|
import { checkBalance } from "../commands/balances.js";
|
|
18
|
-
|
|
19
|
-
|
|
20
|
+
import { viewFees, claimFees } from "../commands/claim.js";
|
|
21
|
+
import { getTransactions } from "../commands/transactions.js";
|
|
22
|
+
import { getAccountInfo, switchAccount, addAccount, } from "../commands/account.js";
|
|
23
|
+
import { HttpcatError } from "../client.js";
|
|
24
|
+
import { createHttpcatAgent, chatWithAgent } from "../agent/ax-agent.js";
|
|
25
|
+
import { createLLM } from "../agent/llm-factory.js";
|
|
26
|
+
import { setupAIAgentWizard } from "../agent/setup-wizard.js";
|
|
27
|
+
// Detect terminal background color
|
|
28
|
+
function detectTerminalBackground() {
|
|
29
|
+
// Check COLORFGBG (format: "foreground;background")
|
|
30
|
+
// Background values: 0-7 are dark, 8-15 are light
|
|
31
|
+
const colorfgbg = process.env.COLORFGBG;
|
|
32
|
+
if (colorfgbg) {
|
|
33
|
+
const parts = colorfgbg.split(";");
|
|
34
|
+
if (parts.length >= 2) {
|
|
35
|
+
const bg = parseInt(parts[1], 10);
|
|
36
|
+
if (!isNaN(bg)) {
|
|
37
|
+
// 0-7 are dark backgrounds, 8-15 are light
|
|
38
|
+
return bg < 8 ? "dark" : "light";
|
|
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
|
|
20
72
|
const screen = blessed.screen({
|
|
21
73
|
smartCSR: true,
|
|
22
74
|
title: "httpcat Interactive Shell",
|
|
23
|
-
fullUnicode: true,
|
|
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,
|
|
24
85
|
});
|
|
25
86
|
const network = client.getNetwork();
|
|
26
|
-
//
|
|
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
|
|
27
124
|
const headerBox = blessed.box({
|
|
28
125
|
top: 0,
|
|
29
126
|
left: 0,
|
|
30
127
|
width: "100%",
|
|
31
|
-
height:
|
|
128
|
+
height: 6, // Reduced to save vertical space
|
|
32
129
|
content: "",
|
|
33
|
-
tags:
|
|
130
|
+
tags: true,
|
|
34
131
|
style: {
|
|
35
|
-
fg:
|
|
36
|
-
bg: "
|
|
132
|
+
fg: themeColors.fg,
|
|
133
|
+
bg: "default", // Transparent
|
|
134
|
+
bold: false,
|
|
135
|
+
border: {
|
|
136
|
+
fg: themeColors.border,
|
|
137
|
+
bold: true,
|
|
138
|
+
},
|
|
37
139
|
},
|
|
38
140
|
padding: {
|
|
39
141
|
left: 1,
|
|
40
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
|
|
41
150
|
},
|
|
42
151
|
});
|
|
43
|
-
//
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
|
183
|
+
try {
|
|
184
|
+
const privateKey = config.getAccountPrivateKey(activeIndex);
|
|
185
|
+
const balance = await checkBalance(privateKey, true); // silent mode
|
|
186
|
+
accountInfo = {
|
|
187
|
+
account,
|
|
188
|
+
balance,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
// If balance check fails, just show account info without balance
|
|
193
|
+
accountInfo = { account };
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
catch (error) {
|
|
198
|
+
// If account info fails, continue without it
|
|
199
|
+
}
|
|
200
|
+
// Compact account info - combined on fewer lines
|
|
201
|
+
if (accountInfo) {
|
|
202
|
+
const { account, balance } = accountInfo;
|
|
203
|
+
const accountType = account.type === "custom" ? "Custom" : "Seed-Derived";
|
|
204
|
+
const accountLabel = account.label ? ` (${account.label})` : "";
|
|
205
|
+
if (balance) {
|
|
206
|
+
const ethDisplay = balance.ethFormatted || balance.ethBalance || "0 ETH";
|
|
207
|
+
const usdcDisplay = balance.usdcFormatted || balance.usdcBalance || "$0.00";
|
|
208
|
+
// Combine account and balance on one line to save space
|
|
209
|
+
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}}`);
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
welcomeLines.push(`{${cyanColor}}👤 Account #{green-fg}${account.index}{/green-fg} | {green-fg}${accountType}${accountLabel}{/green-fg}{/${cyanColor}}`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return welcomeLines.join("\n");
|
|
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
|
|
54
238
|
const outputBox = blessed.log({
|
|
55
|
-
top:
|
|
239
|
+
top: 6, // Adjusted to match new header height
|
|
56
240
|
left: 0,
|
|
57
241
|
width: "100%",
|
|
58
|
-
|
|
242
|
+
bottom: 4, // Leave space for input box at bottom
|
|
59
243
|
tags: true,
|
|
60
244
|
scrollable: true,
|
|
61
245
|
alwaysScroll: true,
|
|
62
246
|
scrollbar: {
|
|
63
247
|
ch: " ",
|
|
64
|
-
inverse:
|
|
248
|
+
inverse: currentTheme !== "dark",
|
|
65
249
|
},
|
|
66
250
|
style: {
|
|
67
|
-
fg:
|
|
68
|
-
bg: "
|
|
251
|
+
fg: themeColors.fg,
|
|
252
|
+
bg: "default", // Transparent
|
|
253
|
+
border: {
|
|
254
|
+
fg: themeColors.border,
|
|
255
|
+
bold: true,
|
|
256
|
+
},
|
|
69
257
|
},
|
|
70
258
|
padding: {
|
|
71
|
-
left:
|
|
259
|
+
left: 0,
|
|
72
260
|
right: 1,
|
|
73
261
|
},
|
|
262
|
+
mouse: true, // Enable mouse scrolling
|
|
263
|
+
border: {
|
|
264
|
+
type: "line",
|
|
265
|
+
fg: themeColors.border,
|
|
266
|
+
ch: "═", // Double line for thicker border
|
|
267
|
+
},
|
|
74
268
|
});
|
|
75
|
-
// Create input box
|
|
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
|
|
76
290
|
const inputBox = blessed.textbox({
|
|
77
291
|
bottom: 0,
|
|
78
292
|
left: 0,
|
|
@@ -80,35 +294,86 @@ export async function startInteractiveShell(client) {
|
|
|
80
294
|
height: 3,
|
|
81
295
|
inputOnFocus: true,
|
|
82
296
|
keys: true,
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
bg: "blue",
|
|
89
|
-
},
|
|
90
|
-
},
|
|
297
|
+
vi: false, // Disabled to prevent double input issues in agent mode
|
|
298
|
+
secret: false,
|
|
299
|
+
tags: true,
|
|
300
|
+
alwaysScroll: false,
|
|
301
|
+
scrollable: false,
|
|
91
302
|
padding: {
|
|
92
|
-
left:
|
|
303
|
+
left: 10, // Space for "httpcat>" prompt (8 chars) + 2 for spacing
|
|
93
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",
|
|
94
313
|
},
|
|
95
|
-
});
|
|
96
|
-
// Create a prompt label
|
|
97
|
-
const promptLabel = blessed.text({
|
|
98
|
-
bottom: 1,
|
|
99
|
-
left: 1,
|
|
100
|
-
content: "httpcat> ",
|
|
101
314
|
style: {
|
|
102
|
-
fg:
|
|
103
|
-
bg: "
|
|
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
|
|
104
334
|
},
|
|
105
335
|
});
|
|
106
|
-
//
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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)
|
|
112
377
|
const log = (text) => {
|
|
113
378
|
outputBox.log(text);
|
|
114
379
|
outputBox.setScrollPerc(100);
|
|
@@ -120,19 +385,166 @@ export async function startInteractiveShell(client) {
|
|
|
120
385
|
outputBox.setScrollPerc(100);
|
|
121
386
|
screen.render();
|
|
122
387
|
};
|
|
388
|
+
// Helper to log multiple lines with smooth scrolling (for tables/lists)
|
|
389
|
+
const logLinesSmooth = async (lines, scrollToTop = false) => {
|
|
390
|
+
// Disable auto-scroll temporarily to batch output (prevents rapid scrolling)
|
|
391
|
+
const originalAlwaysScroll = outputBox.alwaysScroll;
|
|
392
|
+
outputBox.alwaysScroll = false;
|
|
393
|
+
// Get line count before adding new lines
|
|
394
|
+
const linesBefore = outputBox.lines?.length || 0;
|
|
395
|
+
// Add all lines at once (no rendering between lines = smooth, no rapid scrolling)
|
|
396
|
+
lines.forEach((line) => outputBox.log(line));
|
|
397
|
+
// Calculate scroll position after adding lines
|
|
398
|
+
const linesAfter = outputBox.lines?.length || 0;
|
|
399
|
+
const visibleHeight = outputBox.height || 20;
|
|
400
|
+
if (scrollToTop && linesAfter > visibleHeight) {
|
|
401
|
+
// Scroll to show the beginning of the new content
|
|
402
|
+
// Calculate which line the new content starts at
|
|
403
|
+
const newContentStartLine = linesBefore;
|
|
404
|
+
// Calculate percentage to show that line near the top
|
|
405
|
+
// We want to show the new content starting from the top of visible area
|
|
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
|
+
}
|
|
415
|
+
}
|
|
416
|
+
else {
|
|
417
|
+
// Scroll to bottom
|
|
418
|
+
outputBox.setScrollPerc(100);
|
|
419
|
+
}
|
|
420
|
+
// Re-enable auto-scroll
|
|
421
|
+
outputBox.alwaysScroll = originalAlwaysScroll;
|
|
422
|
+
screen.render();
|
|
423
|
+
};
|
|
424
|
+
// Wrap toggleTheme to also log
|
|
425
|
+
const toggleThemeWithLog = () => {
|
|
426
|
+
const themes = ["win95", "dark", "light"];
|
|
427
|
+
const currentIndex = themes.indexOf(currentTheme);
|
|
428
|
+
const nextTheme = themes[(currentIndex + 1) % themes.length];
|
|
429
|
+
updateTheme(nextTheme);
|
|
430
|
+
log(chalk.blue(`Theme switched to: ${nextTheme}`));
|
|
431
|
+
};
|
|
432
|
+
// Append all widgets in correct z-order (last appended is on top)
|
|
433
|
+
screen.append(headerBox);
|
|
434
|
+
screen.append(outputBox);
|
|
435
|
+
screen.append(inputBox);
|
|
436
|
+
screen.append(promptLabel); // Prompt label on top so it's always visible
|
|
437
|
+
// Handle F1 for theme toggle
|
|
438
|
+
screen.key(["f1"], () => {
|
|
439
|
+
toggleThemeWithLog();
|
|
440
|
+
});
|
|
441
|
+
// Store toggle function for command handler
|
|
442
|
+
screen.toggleTheme = toggleThemeWithLog;
|
|
443
|
+
screen.updateTheme = updateTheme;
|
|
444
|
+
// Command history
|
|
445
|
+
const commandHistory = [];
|
|
446
|
+
let historyIndex = -1; // -1 means not navigating history (showing current input)
|
|
447
|
+
let currentInputBeforeHistory = ""; // Store current input when starting to navigate history
|
|
448
|
+
// Handle up arrow - navigate to previous command in history
|
|
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();
|
|
519
|
+
}
|
|
520
|
+
});
|
|
123
521
|
// Handle input submission
|
|
124
522
|
inputBox.on("submit", async (value) => {
|
|
125
523
|
const trimmed = value.trim();
|
|
126
524
|
inputBox.clearValue();
|
|
525
|
+
screen.render(); // Clear input immediately
|
|
526
|
+
// Reset history navigation
|
|
527
|
+
historyIndex = -1;
|
|
528
|
+
currentInputBeforeHistory = "";
|
|
127
529
|
if (!trimmed) {
|
|
128
530
|
inputBox.focus();
|
|
129
531
|
screen.render();
|
|
130
532
|
return;
|
|
131
533
|
}
|
|
132
|
-
|
|
534
|
+
// Add to history (avoid duplicates - don't add if same as last command)
|
|
535
|
+
if (commandHistory.length === 0 ||
|
|
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}`);
|
|
133
545
|
const [command, ...args] = trimmed.split(/\s+/);
|
|
134
546
|
try {
|
|
135
|
-
await handleCommand(client, command.toLowerCase(), args, log, logLines, screen, inputBox);
|
|
547
|
+
await handleCommand(client, command.toLowerCase(), args, log, logLines, logLinesSmooth, screen, inputBox, currentTheme, buildWelcomeContent, headerBox, catFaces, currentCatIndex);
|
|
136
548
|
}
|
|
137
549
|
catch (error) {
|
|
138
550
|
log(chalk.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
|
|
@@ -141,43 +553,86 @@ export async function startInteractiveShell(client) {
|
|
|
141
553
|
inputBox.focus();
|
|
142
554
|
screen.render();
|
|
143
555
|
});
|
|
144
|
-
// Handle Ctrl+C
|
|
145
|
-
screen.key(["C-c"], () => {
|
|
146
|
-
screen.destroy();
|
|
147
|
-
printCat("sleeping");
|
|
148
|
-
console.log(chalk.cyan("Goodbye! 👋"));
|
|
149
|
-
process.exit(0);
|
|
150
|
-
});
|
|
151
556
|
// Handle escape to clear input
|
|
152
557
|
inputBox.key(["escape"], () => {
|
|
153
558
|
inputBox.clearValue();
|
|
154
559
|
screen.render();
|
|
155
560
|
});
|
|
561
|
+
// Handle Ctrl+C - two-stage: first clears input if text exists, second quits
|
|
562
|
+
const handleCtrlC = () => {
|
|
563
|
+
const currentValue = inputBox.getValue();
|
|
564
|
+
// If there's text in the input, clear it instead of quitting
|
|
565
|
+
if (currentValue && currentValue.trim().length > 0) {
|
|
566
|
+
inputBox.clearValue();
|
|
567
|
+
screen.render();
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
// No text in input, so quit
|
|
571
|
+
if (catAnimationInterval) {
|
|
572
|
+
clearInterval(catAnimationInterval);
|
|
573
|
+
catAnimationInterval = null;
|
|
574
|
+
}
|
|
575
|
+
screen.destroy();
|
|
576
|
+
printCat("sleeping");
|
|
577
|
+
console.log(chalk.cyan("Goodbye! 👋"));
|
|
578
|
+
process.exit(0);
|
|
579
|
+
};
|
|
580
|
+
// Handle Ctrl+C on screen level
|
|
581
|
+
screen.key(["C-c"], handleCtrlC);
|
|
582
|
+
// Handle Ctrl+C on input box level
|
|
583
|
+
inputBox.key(["C-c"], handleCtrlC);
|
|
156
584
|
// Focus input and render
|
|
157
585
|
inputBox.focus();
|
|
158
586
|
screen.render();
|
|
587
|
+
// Show welcome message with key commands on load
|
|
588
|
+
displayWelcomeMessage(log, logLines, outputBox, screen, currentTheme);
|
|
589
|
+
// Auto-enter chat mode if token identifier provided
|
|
590
|
+
if (autoChatToken !== undefined) {
|
|
591
|
+
// Store original submit handler
|
|
592
|
+
const originalHandlers = inputBox.listeners("submit");
|
|
593
|
+
const originalSubmitHandler = originalHandlers[0];
|
|
594
|
+
// Remove original handler temporarily
|
|
595
|
+
inputBox.removeAllListeners("submit");
|
|
596
|
+
// Enter chat mode automatically
|
|
597
|
+
await startChatInShell(client, autoChatToken, log, logLines, screen, inputBox, originalSubmitHandler, headerBox, buildWelcomeContent, currentTheme, catFaces, currentCatIndex);
|
|
598
|
+
}
|
|
159
599
|
}
|
|
160
|
-
async function handleCommand(client, command, args, log, logLines, screen, inputBox) {
|
|
600
|
+
async function handleCommand(client, command, args, log, logLines, logLinesSmooth, screen, inputBox, currentTheme, buildWelcomeContent, headerBox, catFaces, currentCatIndex) {
|
|
161
601
|
switch (command) {
|
|
162
602
|
case "help":
|
|
163
|
-
|
|
603
|
+
// Get outputBox from screen children (it's the second child after headerBox)
|
|
604
|
+
const outputBox = screen.children.find((child) => child.type === "log");
|
|
605
|
+
displayHelp(log, logLines, outputBox, screen, currentTheme);
|
|
164
606
|
break;
|
|
165
607
|
case "create": {
|
|
166
608
|
if (args.length < 2) {
|
|
167
|
-
log(chalk.red("Usage: create <name> <symbol> [--photo URL] [--
|
|
609
|
+
log(chalk.red("Usage: create <name> <symbol> [--photo URL|path] [--website URL]"));
|
|
168
610
|
return;
|
|
169
611
|
}
|
|
170
612
|
const [name, symbol] = args;
|
|
171
|
-
|
|
172
|
-
const bannerUrl = extractFlag(args, "--banner");
|
|
613
|
+
let photoUrl = extractFlag(args, "--photo");
|
|
173
614
|
const websiteUrl = extractFlag(args, "--website");
|
|
174
|
-
|
|
615
|
+
// Process photo if it's a file path (show loading state)
|
|
616
|
+
if (photoUrl && isFilePath(photoUrl)) {
|
|
617
|
+
log(chalk.blue("Uploading image..."));
|
|
618
|
+
screen.render();
|
|
619
|
+
try {
|
|
620
|
+
photoUrl = processPhotoUrl(photoUrl);
|
|
621
|
+
log(chalk.green("✓ Image uploaded"));
|
|
622
|
+
screen.render();
|
|
623
|
+
}
|
|
624
|
+
catch (error) {
|
|
625
|
+
log(chalk.red(`Failed to upload image: ${error instanceof Error ? error.message : String(error)}`));
|
|
626
|
+
screen.render();
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
log(chalk.blue("Creating token..."));
|
|
175
631
|
screen.render();
|
|
176
632
|
const result = await createToken(client, {
|
|
177
633
|
name,
|
|
178
634
|
symbol,
|
|
179
635
|
photoUrl,
|
|
180
|
-
bannerUrl,
|
|
181
636
|
websiteUrl,
|
|
182
637
|
});
|
|
183
638
|
displayCreateResultToLog(result, log, logLines);
|
|
@@ -185,14 +640,21 @@ async function handleCommand(client, command, args, log, logLines, screen, input
|
|
|
185
640
|
}
|
|
186
641
|
case "buy": {
|
|
187
642
|
if (args.length < 2) {
|
|
188
|
-
log(chalk.red("Usage: buy <address|name|symbol> <amount>"));
|
|
643
|
+
log(chalk.red("Usage: buy <address|name|symbol> <amount> [--repeat <count>] [--delay <ms>]"));
|
|
189
644
|
log(chalk.dim(" amount: 0.05, 0.10, or 0.20 (test mode) or 50, 100, 200 (production)"));
|
|
190
|
-
log(chalk.dim(' Examples: buy 0x1234... 0.20, buy "My Token" 0.10, buy MTK 0.05'));
|
|
645
|
+
log(chalk.dim(' Examples: buy 0x1234... 0.20, buy "My Token" 0.10, buy MTK 0.05 --repeat 10'));
|
|
191
646
|
return;
|
|
192
647
|
}
|
|
193
648
|
const [identifier, amountInput] = args;
|
|
194
649
|
const isTestMode = client.getNetwork().includes("sepolia");
|
|
195
650
|
const validAmounts = isTestMode ? TEST_AMOUNTS : PROD_AMOUNTS;
|
|
651
|
+
// Parse flags
|
|
652
|
+
const repeatCount = extractFlag(args, "--repeat")
|
|
653
|
+
? parseInt(extractFlag(args, "--repeat") || "1", 10)
|
|
654
|
+
: undefined;
|
|
655
|
+
const delayMs = extractFlag(args, "--delay")
|
|
656
|
+
? parseInt(extractFlag(args, "--delay") || "0", 10)
|
|
657
|
+
: 0;
|
|
196
658
|
// Try to validate and normalize the amount
|
|
197
659
|
let amount;
|
|
198
660
|
try {
|
|
@@ -204,10 +666,105 @@ async function handleCommand(client, command, args, log, logLines, screen, input
|
|
|
204
666
|
log(chalk.dim(`Valid amounts: ${validAmounts.join(", ")}`));
|
|
205
667
|
return;
|
|
206
668
|
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
669
|
+
const privateKey = config.getPrivateKey();
|
|
670
|
+
// Handle repeat mode
|
|
671
|
+
if (repeatCount && repeatCount > 0) {
|
|
672
|
+
const results = [];
|
|
673
|
+
let totalSpent = 0;
|
|
674
|
+
let stoppedEarly = false;
|
|
675
|
+
let stopReason = "";
|
|
676
|
+
for (let i = 1; i <= repeatCount; i++) {
|
|
677
|
+
try {
|
|
678
|
+
log(chalk.blue(`Buy ${i}/${repeatCount}...`));
|
|
679
|
+
screen.render();
|
|
680
|
+
const result = await buyToken(client, identifier, amount, isTestMode, true, // silent=true to avoid console.log interference
|
|
681
|
+
privateKey);
|
|
682
|
+
results.push(result);
|
|
683
|
+
totalSpent += parseFloat(result.amountSpent);
|
|
684
|
+
// Display compact result
|
|
685
|
+
displayBuyResultToLog(result, log, logLines);
|
|
686
|
+
// Check if token graduated
|
|
687
|
+
if (result.graduationReached) {
|
|
688
|
+
stoppedEarly = true;
|
|
689
|
+
stopReason = "Token graduated";
|
|
690
|
+
log(chalk.green("🎓 Token has graduated! Stopping buy loop."));
|
|
691
|
+
break;
|
|
692
|
+
}
|
|
693
|
+
// Apply delay between iterations (except after the last one)
|
|
694
|
+
if (i < repeatCount && delayMs > 0) {
|
|
695
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
catch (error) {
|
|
699
|
+
// Handle insufficient funds (402) gracefully
|
|
700
|
+
if (error instanceof HttpcatError && error.status === 402) {
|
|
701
|
+
stoppedEarly = true;
|
|
702
|
+
stopReason = "Insufficient funds";
|
|
703
|
+
log(chalk.yellow("💡 Insufficient funds. Stopping buy loop."));
|
|
704
|
+
log("");
|
|
705
|
+
// Show current balance to help diagnose the issue
|
|
706
|
+
try {
|
|
707
|
+
const balance = await checkBalance(privateKey, true);
|
|
708
|
+
log(chalk.cyan("💰 Current Wallet Balances:"));
|
|
709
|
+
log(chalk.dim(` ETH: ${balance.ethFormatted}`));
|
|
710
|
+
log(chalk.dim(` USDC: ${balance.usdcFormatted}`));
|
|
711
|
+
if (balance.cat402Formatted) {
|
|
712
|
+
log(chalk.dim(` CAT: ${balance.cat402Formatted}`));
|
|
713
|
+
}
|
|
714
|
+
log("");
|
|
715
|
+
// Provide guidance based on what might be insufficient
|
|
716
|
+
const usdcBalance = parseFloat(balance.usdcFormatted.replace("$", "").replace(",", ""));
|
|
717
|
+
const ethBalance = parseFloat(balance.ethFormatted.replace(" ETH", "").replace(",", ""));
|
|
718
|
+
const buyAmount = parseFloat(amount);
|
|
719
|
+
log(chalk.yellow("💡 Possible issues:"));
|
|
720
|
+
if (usdcBalance < buyAmount) {
|
|
721
|
+
log(chalk.dim(` • Insufficient USDC for purchase: Need $${buyAmount.toFixed(6)}, have $${usdcBalance.toFixed(6)}`));
|
|
722
|
+
}
|
|
723
|
+
if (usdcBalance < 0.01) {
|
|
724
|
+
log(chalk.dim(` • Low USDC for API payments: x402 protocol requires USDC for API calls`));
|
|
725
|
+
}
|
|
726
|
+
if (ethBalance < 0.001) {
|
|
727
|
+
log(chalk.dim(` • Low ETH for gas: Need ETH to pay for transaction fees`));
|
|
728
|
+
}
|
|
729
|
+
log("");
|
|
730
|
+
log(chalk.dim(" Check your balance with: balances"));
|
|
731
|
+
}
|
|
732
|
+
catch (balanceError) {
|
|
733
|
+
// If we can't fetch balance, just show generic message
|
|
734
|
+
log(chalk.dim(" Check your balance with: balances"));
|
|
735
|
+
}
|
|
736
|
+
break;
|
|
737
|
+
}
|
|
738
|
+
// For other errors, re-throw to be handled by outer catch
|
|
739
|
+
throw error;
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
// Show final summary
|
|
743
|
+
if (results.length > 0) {
|
|
744
|
+
const lastResult = results[results.length - 1];
|
|
745
|
+
const graduationStatus = lastResult.graduationReached
|
|
746
|
+
? "✅ GRADUATED!"
|
|
747
|
+
: `${(lastResult.graduationProgress || 0).toFixed(2)}%`;
|
|
748
|
+
log("");
|
|
749
|
+
log(chalk.cyan.bold("📊 Repeat Buy Summary"));
|
|
750
|
+
log(chalk.dim("─".repeat(50)));
|
|
751
|
+
log(`Total buys completed: ${chalk.bold(results.length.toString())}${repeatCount ? `/${repeatCount}` : ""}`);
|
|
752
|
+
log(`Total amount spent: ${chalk.bold(`$${totalSpent.toFixed(2)}`)}`);
|
|
753
|
+
log(`Final token price: ${chalk.bold(lastResult.newPrice)}`);
|
|
754
|
+
log(`Final graduation: ${chalk.bold(graduationStatus)}`);
|
|
755
|
+
if (stoppedEarly) {
|
|
756
|
+
log(`Stopped early: ${chalk.yellow(stopReason)}`);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
else {
|
|
761
|
+
// Normal single buy execution
|
|
762
|
+
log(chalk.blue("Buying tokens..."));
|
|
763
|
+
screen.render();
|
|
764
|
+
const result = await buyToken(client, identifier, amount, isTestMode, true, // silent=true to avoid console.log interference
|
|
765
|
+
privateKey);
|
|
766
|
+
displayBuyResultToLog(result, log, logLines);
|
|
767
|
+
}
|
|
211
768
|
break;
|
|
212
769
|
}
|
|
213
770
|
case "sell": {
|
|
@@ -217,17 +774,21 @@ async function handleCommand(client, command, args, log, logLines, screen, input
|
|
|
217
774
|
return;
|
|
218
775
|
}
|
|
219
776
|
const [identifier, amountInput] = args;
|
|
220
|
-
|
|
777
|
+
// Get user address from private key for position checking
|
|
778
|
+
const privateKey = config.getPrivateKey();
|
|
779
|
+
const account = privateKeyToAccount(privateKey);
|
|
780
|
+
const userAddress = account.address;
|
|
781
|
+
log(chalk.blue("Checking token info..."));
|
|
221
782
|
screen.render();
|
|
222
|
-
const info = await getTokenInfo(client, identifier);
|
|
783
|
+
const info = await getTokenInfo(client, identifier, userAddress, true); // silent=true to avoid console.log interference
|
|
223
784
|
if (!info.userPosition || info.userPosition.tokensOwned === "0") {
|
|
224
785
|
log(chalk.yellow("You do not own any of this token."));
|
|
225
786
|
return;
|
|
226
787
|
}
|
|
227
788
|
const tokenAmount = parseTokenAmount(amountInput, info.userPosition.tokensOwned);
|
|
228
|
-
log(chalk.
|
|
789
|
+
log(chalk.blue("Selling tokens..."));
|
|
229
790
|
screen.render();
|
|
230
|
-
const result = await sellToken(client, identifier, tokenAmount,
|
|
791
|
+
const result = await sellToken(client, identifier, tokenAmount, true, privateKey); // silent=true to avoid console.log interference
|
|
231
792
|
displaySellResultToLog(result, log, logLines);
|
|
232
793
|
break;
|
|
233
794
|
}
|
|
@@ -242,9 +803,9 @@ async function handleCommand(client, command, args, log, logLines, screen, input
|
|
|
242
803
|
const privateKey = config.getPrivateKey();
|
|
243
804
|
const account = privateKeyToAccount(privateKey);
|
|
244
805
|
const userAddress = account.address;
|
|
245
|
-
log(chalk.
|
|
806
|
+
log(chalk.blue("Fetching token info..."));
|
|
246
807
|
screen.render();
|
|
247
|
-
const info = await getTokenInfo(client, identifier, userAddress);
|
|
808
|
+
const info = await getTokenInfo(client, identifier, userAddress, true); // silent=true to avoid console.log interference
|
|
248
809
|
displayTokenInfoToLog(info, log, logLines);
|
|
249
810
|
break;
|
|
250
811
|
}
|
|
@@ -252,20 +813,34 @@ async function handleCommand(client, command, args, log, logLines, screen, input
|
|
|
252
813
|
const page = parseInt(extractFlag(args, "--page") || "1");
|
|
253
814
|
const limit = parseInt(extractFlag(args, "--limit") || "20");
|
|
254
815
|
const sortBy = (extractFlag(args, "--sort") || "mcap");
|
|
255
|
-
log(chalk.
|
|
816
|
+
log(chalk.blue("Fetching token list..."));
|
|
256
817
|
screen.render();
|
|
257
818
|
const result = await listTokens(client, page, limit, sortBy);
|
|
258
|
-
displayTokenListToLog(result, log, logLines);
|
|
819
|
+
displayTokenListToLog(result, log, logLines, logLinesSmooth);
|
|
259
820
|
break;
|
|
260
821
|
}
|
|
261
822
|
case "positions": {
|
|
262
823
|
const privateKey = config.getPrivateKey();
|
|
263
824
|
const account = privateKeyToAccount(privateKey);
|
|
264
825
|
const userAddress = account.address;
|
|
265
|
-
|
|
826
|
+
// Parse flags
|
|
827
|
+
const activeOnly = args.includes("--active");
|
|
828
|
+
const graduatedOnly = args.includes("--graduated");
|
|
829
|
+
log(chalk.blue("Fetching positions..."));
|
|
266
830
|
screen.render();
|
|
267
831
|
const result = await getPositions(client, userAddress);
|
|
268
|
-
|
|
832
|
+
// Filter positions if flags are set
|
|
833
|
+
let filteredResult = result;
|
|
834
|
+
if (activeOnly || graduatedOnly) {
|
|
835
|
+
filteredResult = {
|
|
836
|
+
...result,
|
|
837
|
+
positions: result.positions.filter((p) => activeOnly
|
|
838
|
+
? p.token.status !== "graduated"
|
|
839
|
+
: p.token.status === "graduated"),
|
|
840
|
+
};
|
|
841
|
+
filteredResult.total = filteredResult.positions.length;
|
|
842
|
+
}
|
|
843
|
+
displayPositionsToLog(filteredResult, log, logLines);
|
|
269
844
|
break;
|
|
270
845
|
}
|
|
271
846
|
case "health": {
|
|
@@ -278,15 +853,15 @@ async function handleCommand(client, command, args, log, logLines, screen, input
|
|
|
278
853
|
case "balances": {
|
|
279
854
|
try {
|
|
280
855
|
const privateKey = config.getPrivateKey();
|
|
281
|
-
log(chalk.
|
|
856
|
+
log(chalk.blue("Checking balance..."));
|
|
282
857
|
screen.render();
|
|
283
|
-
const balance = await checkBalance(privateKey);
|
|
858
|
+
const balance = await checkBalance(privateKey, true); // silent mode
|
|
284
859
|
displayBalanceToLog(balance, log, logLines);
|
|
285
860
|
}
|
|
286
861
|
catch (error) {
|
|
287
|
-
log(chalk.
|
|
862
|
+
log(chalk.blue("Checking balance..."));
|
|
288
863
|
screen.render();
|
|
289
|
-
const balance = await checkBalance();
|
|
864
|
+
const balance = await checkBalance(undefined, true); // silent mode
|
|
290
865
|
displayBalanceToLog(balance, log, logLines);
|
|
291
866
|
}
|
|
292
867
|
break;
|
|
@@ -309,17 +884,22 @@ async function handleCommand(client, command, args, log, logLines, screen, input
|
|
|
309
884
|
break;
|
|
310
885
|
}
|
|
311
886
|
case "network": {
|
|
312
|
-
log(chalk.yellow("Current network: ") + chalk.
|
|
313
|
-
log(chalk.
|
|
887
|
+
log(chalk.yellow("Current network: ") + chalk.green(config.get("network")));
|
|
888
|
+
log(chalk.blue("Note: Only base-sepolia (testnet) is currently supported"));
|
|
314
889
|
break;
|
|
315
890
|
}
|
|
316
891
|
case "chat": {
|
|
317
|
-
|
|
318
|
-
log(chalk.yellow("
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
892
|
+
const tokenIdentifier = args.length > 0 && !args[0].startsWith("--") ? args[0] : undefined;
|
|
893
|
+
log(chalk.yellow("💬 Entering chat mode..."));
|
|
894
|
+
log(chalk.dim("Type /exit to return to shell, or /help for chat commands"));
|
|
895
|
+
log("");
|
|
896
|
+
// Start chat mode within the shell
|
|
897
|
+
// Store original submit handler
|
|
898
|
+
const originalHandlers = inputBox.listeners("submit");
|
|
899
|
+
const originalSubmitHandler = originalHandlers[0];
|
|
900
|
+
// Remove original handler temporarily
|
|
901
|
+
inputBox.removeAllListeners("submit");
|
|
902
|
+
await startChatInShell(client, tokenIdentifier, log, logLines, screen, inputBox, originalSubmitHandler, headerBox, buildWelcomeContent, currentTheme, catFaces, currentCatIndex);
|
|
323
903
|
break;
|
|
324
904
|
}
|
|
325
905
|
case "exit":
|
|
@@ -333,115 +913,737 @@ async function handleCommand(client, command, args, log, logLines, screen, input
|
|
|
333
913
|
// Clear the output box
|
|
334
914
|
screen.children[1].setContent("");
|
|
335
915
|
screen.render();
|
|
916
|
+
// Show playful message with ASCII cat
|
|
917
|
+
const catArt = currentTheme === "dark"
|
|
918
|
+
? `{green-fg} /\\_/\\{/green-fg}
|
|
919
|
+
{green-fg} ( ^.^ ){/green-fg}
|
|
920
|
+
{green-fg} > ^ <{/green-fg}`
|
|
921
|
+
: `{black-fg} /\\_/\\{/black-fg}
|
|
922
|
+
{black-fg} ( ^.^ ){/black-fg}
|
|
923
|
+
{black-fg} > ^ <{/black-fg}`;
|
|
924
|
+
log("");
|
|
925
|
+
log(catArt);
|
|
926
|
+
log(chalk.green.bold("Terminal cleared!"));
|
|
927
|
+
log("");
|
|
336
928
|
break;
|
|
929
|
+
case "claim": {
|
|
930
|
+
if (args.length < 1) {
|
|
931
|
+
log(chalk.red("Usage: claim <address|name|symbol> [--execute] [--address <address>]"));
|
|
932
|
+
log(chalk.dim(' Examples: claim MTK, claim "My Token" --execute'));
|
|
933
|
+
return;
|
|
934
|
+
}
|
|
935
|
+
const [identifier] = args;
|
|
936
|
+
const execute = args.includes("--execute");
|
|
937
|
+
const callerAddress = extractFlag(args, "--address");
|
|
938
|
+
if (execute) {
|
|
939
|
+
const privateKey = config.getPrivateKey();
|
|
940
|
+
const account = privateKeyToAccount(privateKey);
|
|
941
|
+
const address = callerAddress || account.address;
|
|
942
|
+
log(chalk.blue("Claiming fees..."));
|
|
943
|
+
screen.render();
|
|
944
|
+
const result = await claimFees(client, identifier, address, true); // silent=true to avoid console.log interference
|
|
945
|
+
displayClaimResultToLog(result, log, logLines);
|
|
946
|
+
}
|
|
947
|
+
else {
|
|
948
|
+
log(chalk.blue("Fetching fee information..."));
|
|
949
|
+
screen.render();
|
|
950
|
+
const result = await viewFees(client, identifier, true); // silent=true to avoid console.log interference
|
|
951
|
+
displayFeesToLog(result, log, logLines);
|
|
952
|
+
}
|
|
953
|
+
break;
|
|
954
|
+
}
|
|
955
|
+
case "transactions": {
|
|
956
|
+
const privateKey = config.getPrivateKey();
|
|
957
|
+
const account = privateKeyToAccount(privateKey);
|
|
958
|
+
const userAddress = account.address;
|
|
959
|
+
const input = {};
|
|
960
|
+
if (extractFlag(args, "--user")) {
|
|
961
|
+
input.userAddress = extractFlag(args, "--user");
|
|
962
|
+
}
|
|
963
|
+
else {
|
|
964
|
+
input.userAddress = userAddress;
|
|
965
|
+
}
|
|
966
|
+
if (extractFlag(args, "--token")) {
|
|
967
|
+
input.tokenId = extractFlag(args, "--token");
|
|
968
|
+
}
|
|
969
|
+
if (extractFlag(args, "--type")) {
|
|
970
|
+
const type = extractFlag(args, "--type");
|
|
971
|
+
if (!["buy", "sell", "airdrop"].includes(type || "")) {
|
|
972
|
+
log(chalk.red("Invalid type. Must be: buy, sell, or airdrop"));
|
|
973
|
+
return;
|
|
974
|
+
}
|
|
975
|
+
input.type = type;
|
|
976
|
+
}
|
|
977
|
+
if (extractFlag(args, "--limit")) {
|
|
978
|
+
input.limit = parseInt(extractFlag(args, "--limit") || "50", 10);
|
|
979
|
+
}
|
|
980
|
+
else {
|
|
981
|
+
input.limit = 50;
|
|
982
|
+
}
|
|
983
|
+
if (extractFlag(args, "--offset")) {
|
|
984
|
+
input.offset = parseInt(extractFlag(args, "--offset") || "0", 10);
|
|
985
|
+
}
|
|
986
|
+
else {
|
|
987
|
+
input.offset = 0;
|
|
988
|
+
}
|
|
989
|
+
log(chalk.blue("Fetching transactions..."));
|
|
990
|
+
screen.render();
|
|
991
|
+
const result = await getTransactions(client, input);
|
|
992
|
+
displayTransactionsToLog(result, log, logLines);
|
|
993
|
+
break;
|
|
994
|
+
}
|
|
995
|
+
case "account": {
|
|
996
|
+
if (args.length === 0) {
|
|
997
|
+
// Show account info
|
|
998
|
+
log(chalk.blue("Fetching account information..."));
|
|
999
|
+
screen.render();
|
|
1000
|
+
const result = await getAccountInfo();
|
|
1001
|
+
displayAccountInfoToLog(result, log, logLines);
|
|
1002
|
+
}
|
|
1003
|
+
else if (args[0] === "list") {
|
|
1004
|
+
// For list, we need to capture console output or recreate the display
|
|
1005
|
+
const accounts = config.getAllAccounts();
|
|
1006
|
+
const activeIndex = config.getActiveAccountIndex();
|
|
1007
|
+
log("");
|
|
1008
|
+
log(chalk.green.bold("📋 All Accounts"));
|
|
1009
|
+
log("");
|
|
1010
|
+
if (accounts.length === 0) {
|
|
1011
|
+
log(chalk.yellow("No accounts configured."));
|
|
1012
|
+
log(chalk.blue('Run "httpcat config" to set up your wallet.'));
|
|
1013
|
+
return;
|
|
1014
|
+
}
|
|
1015
|
+
for (const account of accounts) {
|
|
1016
|
+
const isActive = account.index === activeIndex;
|
|
1017
|
+
const status = isActive
|
|
1018
|
+
? chalk.green("● Active")
|
|
1019
|
+
: chalk.blue("○ Inactive");
|
|
1020
|
+
const type = account.type === "custom" ? "Custom" : "Seed-Derived";
|
|
1021
|
+
const address = account.address.slice(0, 6) + "..." + account.address.slice(-4);
|
|
1022
|
+
log(` ${chalk.green(account.index.toString().padEnd(5))} ${chalk.blue(type.padEnd(12))} ${chalk.green(address.padEnd(15))} ${status}`);
|
|
1023
|
+
}
|
|
1024
|
+
log("");
|
|
1025
|
+
log(chalk.blue(`Active account: ${chalk.green(activeIndex.toString())}`));
|
|
1026
|
+
}
|
|
1027
|
+
else if (args[0] === "switch") {
|
|
1028
|
+
if (args.length < 2) {
|
|
1029
|
+
log(chalk.red("Usage: account switch <index>"));
|
|
1030
|
+
return;
|
|
1031
|
+
}
|
|
1032
|
+
const index = parseInt(args[1], 10);
|
|
1033
|
+
if (isNaN(index)) {
|
|
1034
|
+
log(chalk.red("Invalid account index"));
|
|
1035
|
+
return;
|
|
1036
|
+
}
|
|
1037
|
+
switchAccount(index);
|
|
1038
|
+
const accounts = config.getAllAccounts();
|
|
1039
|
+
const account = accounts.find((acc) => acc.index === index);
|
|
1040
|
+
log(chalk.green(`✅ Switched to account ${index}`));
|
|
1041
|
+
if (account) {
|
|
1042
|
+
log(chalk.blue(` Address: ${account.address.slice(0, 6)}...${account.address.slice(-4)}`));
|
|
1043
|
+
}
|
|
1044
|
+
// Refresh header with new account info
|
|
1045
|
+
buildWelcomeContent(currentTheme, catFaces[currentCatIndex].face).then((content) => {
|
|
1046
|
+
headerBox.setContent(content);
|
|
1047
|
+
screen.render();
|
|
1048
|
+
});
|
|
1049
|
+
}
|
|
1050
|
+
else if (args[0] === "add") {
|
|
1051
|
+
log(chalk.blue("Adding new account..."));
|
|
1052
|
+
screen.render();
|
|
1053
|
+
await addAccount();
|
|
1054
|
+
log(chalk.green("✅ Account added"));
|
|
1055
|
+
// Refresh header with new account info
|
|
1056
|
+
buildWelcomeContent(currentTheme, catFaces[currentCatIndex].face).then((content) => {
|
|
1057
|
+
headerBox.setContent(content);
|
|
1058
|
+
screen.render();
|
|
1059
|
+
});
|
|
1060
|
+
}
|
|
1061
|
+
else {
|
|
1062
|
+
log(chalk.red("Usage: account [list|switch <index>|add]"));
|
|
1063
|
+
}
|
|
1064
|
+
break;
|
|
1065
|
+
}
|
|
1066
|
+
case "env": {
|
|
1067
|
+
if (args.length === 0 || args[0] === "show") {
|
|
1068
|
+
const current = config.getCurrentEnvironment();
|
|
1069
|
+
if (!current) {
|
|
1070
|
+
log(chalk.yellow("No environment selected"));
|
|
1071
|
+
return;
|
|
1072
|
+
}
|
|
1073
|
+
const env = config.getEnvironmentConfig(current);
|
|
1074
|
+
if (!env) {
|
|
1075
|
+
log(chalk.yellow(`Environment "${current}" not found`));
|
|
1076
|
+
return;
|
|
1077
|
+
}
|
|
1078
|
+
log(chalk.cyan.bold(`Current Environment: ${chalk.green(current)}`));
|
|
1079
|
+
log(chalk.dim(`Agent URL: ${env.agentUrl}`));
|
|
1080
|
+
log(chalk.dim(`Network: ${env.network}`));
|
|
1081
|
+
}
|
|
1082
|
+
else if (args[0] === "list") {
|
|
1083
|
+
const envs = config.getEnvironments();
|
|
1084
|
+
const current = config.getCurrentEnvironment();
|
|
1085
|
+
log(chalk.cyan.bold("Available Environments:"));
|
|
1086
|
+
log("");
|
|
1087
|
+
for (const [name, env] of Object.entries(envs)) {
|
|
1088
|
+
const isCurrent = name === current;
|
|
1089
|
+
const prefix = isCurrent ? chalk.green("→ ") : " ";
|
|
1090
|
+
const nameDisplay = isCurrent
|
|
1091
|
+
? chalk.green.bold(name)
|
|
1092
|
+
: chalk.bold(name);
|
|
1093
|
+
log(`${prefix}${nameDisplay}`);
|
|
1094
|
+
log(chalk.dim(` Agent URL: ${env.agentUrl}`));
|
|
1095
|
+
log(chalk.dim(` Network: ${env.network}`));
|
|
1096
|
+
log("");
|
|
1097
|
+
}
|
|
1098
|
+
if (current) {
|
|
1099
|
+
log(chalk.dim(`Current environment: ${chalk.green(current)}`));
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
else if (args[0] === "use") {
|
|
1103
|
+
if (args.length < 2) {
|
|
1104
|
+
log(chalk.red("Usage: env use <name>"));
|
|
1105
|
+
return;
|
|
1106
|
+
}
|
|
1107
|
+
config.setEnvironment(args[1]);
|
|
1108
|
+
const env = config.getEnvironmentConfig(args[1]);
|
|
1109
|
+
log(chalk.green(`✅ Switched to environment: ${args[1]}`));
|
|
1110
|
+
if (env) {
|
|
1111
|
+
log(chalk.dim(` Agent URL: ${env.agentUrl}`));
|
|
1112
|
+
log(chalk.dim(` Network: ${env.network}`));
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
else if (args[0] === "add") {
|
|
1116
|
+
if (args.length < 3) {
|
|
1117
|
+
log(chalk.red("Usage: env add <name> <agentUrl> [--network <network>]"));
|
|
1118
|
+
return;
|
|
1119
|
+
}
|
|
1120
|
+
const network = extractFlag(args, "--network") || "base-sepolia";
|
|
1121
|
+
config.addEnvironment(args[1], args[2], network);
|
|
1122
|
+
log(chalk.green(`✅ Added environment: ${args[1]}`));
|
|
1123
|
+
log(chalk.dim(` Agent URL: ${args[2]}`));
|
|
1124
|
+
log(chalk.dim(` Network: ${network}`));
|
|
1125
|
+
}
|
|
1126
|
+
else if (args[0] === "update") {
|
|
1127
|
+
if (args.length < 3) {
|
|
1128
|
+
log(chalk.red("Usage: env update <name> <agentUrl> [--network <network>]"));
|
|
1129
|
+
return;
|
|
1130
|
+
}
|
|
1131
|
+
const network = extractFlag(args, "--network") || "base-sepolia";
|
|
1132
|
+
config.updateEnvironment(args[1], args[2], network);
|
|
1133
|
+
log(chalk.green(`✅ Updated environment: ${args[1]}`));
|
|
1134
|
+
log(chalk.dim(` Agent URL: ${args[2]}`));
|
|
1135
|
+
log(chalk.dim(` Network: ${network}`));
|
|
1136
|
+
}
|
|
1137
|
+
else {
|
|
1138
|
+
log(chalk.red("Usage: env [list|use <name>|show|add <name> <url>|update <name> <url>]"));
|
|
1139
|
+
}
|
|
1140
|
+
break;
|
|
1141
|
+
}
|
|
1142
|
+
case "agent":
|
|
1143
|
+
case "ai":
|
|
1144
|
+
case "cat": {
|
|
1145
|
+
// Check if --chat flag is provided (for future chat mode)
|
|
1146
|
+
if (args[0] === "--chat") {
|
|
1147
|
+
log(chalk.yellow("⚠️ Agent chat mode is currently disabled"));
|
|
1148
|
+
log(chalk.dim("Use 'agent <query>' or 'cat <query>' to ask the cat directly."));
|
|
1149
|
+
log("");
|
|
1150
|
+
break;
|
|
1151
|
+
}
|
|
1152
|
+
// Check if --setup flag is provided
|
|
1153
|
+
if (args[0] === "--setup") {
|
|
1154
|
+
try {
|
|
1155
|
+
await setupAIAgentWizard();
|
|
1156
|
+
log(chalk.green("✅ Agent configuration updated!"));
|
|
1157
|
+
log("");
|
|
1158
|
+
}
|
|
1159
|
+
catch (error) {
|
|
1160
|
+
log(chalk.red(`❌ Setup failed: ${error.message || String(error)}`));
|
|
1161
|
+
log("");
|
|
1162
|
+
}
|
|
1163
|
+
break;
|
|
1164
|
+
}
|
|
1165
|
+
// Get the query text (everything after the command)
|
|
1166
|
+
const query = args.join(" ").trim();
|
|
1167
|
+
if (!query) {
|
|
1168
|
+
log(chalk.yellow("Usage: agent <query>"));
|
|
1169
|
+
log(chalk.dim("Example: agent 'buy 0.1 USDC worth of WHALE'"));
|
|
1170
|
+
log(chalk.dim("Example: cat 'check my balance'"));
|
|
1171
|
+
log("");
|
|
1172
|
+
break;
|
|
1173
|
+
}
|
|
1174
|
+
// Check if agent is configured
|
|
1175
|
+
const agentConfig = config.getAIAgentConfig();
|
|
1176
|
+
if (!agentConfig) {
|
|
1177
|
+
log(chalk.yellow("⚠️ Agent not configured"));
|
|
1178
|
+
log(chalk.dim("Run 'agent --setup' to configure the AI agent."));
|
|
1179
|
+
log("");
|
|
1180
|
+
break;
|
|
1181
|
+
}
|
|
1182
|
+
const apiKey = config.getAIAgentApiKey();
|
|
1183
|
+
if (!apiKey) {
|
|
1184
|
+
log(chalk.red("❌ Failed to get API key"));
|
|
1185
|
+
log(chalk.dim("Run 'agent --setup' to configure the API key."));
|
|
1186
|
+
log("");
|
|
1187
|
+
break;
|
|
1188
|
+
}
|
|
1189
|
+
try {
|
|
1190
|
+
// Get account address for session ID
|
|
1191
|
+
const privateKey = config.getPrivateKey();
|
|
1192
|
+
const account = privateKeyToAccount(privateKey);
|
|
1193
|
+
const sessionId = account.address;
|
|
1194
|
+
// Show thinking indicator
|
|
1195
|
+
log(chalk.blue("🐱 Cat thinking..."));
|
|
1196
|
+
screen.render();
|
|
1197
|
+
// Create LLM and agent
|
|
1198
|
+
const llm = createLLM(agentConfig, apiKey);
|
|
1199
|
+
const agent = createHttpcatAgent(client, llm);
|
|
1200
|
+
// Chat with agent (pass session ID so it remembers)
|
|
1201
|
+
const response = await chatWithAgent(agent, llm, query, sessionId);
|
|
1202
|
+
// Display response
|
|
1203
|
+
log("");
|
|
1204
|
+
log(chalk.green("🐱 Cat:"));
|
|
1205
|
+
log(chalk.white(response));
|
|
1206
|
+
log("");
|
|
1207
|
+
}
|
|
1208
|
+
catch (error) {
|
|
1209
|
+
// Enhanced error logging
|
|
1210
|
+
if (process.env.HTTPCAT_DEBUG) {
|
|
1211
|
+
log(chalk.red(`❌ Error details: ${JSON.stringify(error, null, 2)}`));
|
|
1212
|
+
if (error.stack) {
|
|
1213
|
+
log(chalk.dim(error.stack));
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
// Check for authentication errors
|
|
1217
|
+
const errorMessage = error?.message || String(error);
|
|
1218
|
+
if (errorMessage.includes("Authentication failed") ||
|
|
1219
|
+
errorMessage.includes("API key") ||
|
|
1220
|
+
errorMessage.includes("401") ||
|
|
1221
|
+
errorMessage.includes("403") ||
|
|
1222
|
+
errorMessage.includes("Unauthorized") ||
|
|
1223
|
+
errorMessage.includes("Invalid API key")) {
|
|
1224
|
+
log(chalk.red("❌ Authentication failed"));
|
|
1225
|
+
log(chalk.dim("Your API key may be invalid or expired."));
|
|
1226
|
+
log(chalk.dim("Run 'agent --setup' to reconfigure."));
|
|
1227
|
+
}
|
|
1228
|
+
else {
|
|
1229
|
+
log(chalk.red(`❌ Error: ${errorMessage}`));
|
|
1230
|
+
}
|
|
1231
|
+
log("");
|
|
1232
|
+
}
|
|
1233
|
+
break;
|
|
1234
|
+
}
|
|
337
1235
|
default:
|
|
338
1236
|
log(chalk.red(`Unknown command: ${command}`));
|
|
339
1237
|
log(chalk.dim('Type "help" for available commands'));
|
|
340
1238
|
}
|
|
341
1239
|
}
|
|
342
|
-
function
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
const
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
"
|
|
356
|
-
"
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
1240
|
+
function displayWelcomeMessage(log, logLines, outputBox, screen, theme = "dark") {
|
|
1241
|
+
// Helper to get theme-appropriate colors
|
|
1242
|
+
const getCyanColor = (t) => t === "dark" ? "light-cyan-fg" : "cyan-fg";
|
|
1243
|
+
const getBlueColor = (t) => t === "dark" ? "light-blue-fg" : "blue-fg";
|
|
1244
|
+
const cyanColor = getCyanColor(theme);
|
|
1245
|
+
const blueColor = getBlueColor(theme);
|
|
1246
|
+
// If we have outputBox, use blessed tags instead of chalk to avoid question marks
|
|
1247
|
+
// Otherwise fall back to the log function with chalk
|
|
1248
|
+
if (outputBox && screen) {
|
|
1249
|
+
const welcomeLines = [
|
|
1250
|
+
"",
|
|
1251
|
+
`{bold}{${cyanColor}}🐱 Welcome to httpcat!{/${cyanColor}}{/bold}`,
|
|
1252
|
+
"",
|
|
1253
|
+
"{yellow-fg}✨ Here are some commands to get you started:{/yellow-fg}",
|
|
1254
|
+
"",
|
|
1255
|
+
"{green-fg} 📋 list{/green-fg} - Browse all available tokens",
|
|
1256
|
+
"{green-fg} ℹ️ info <token>{/green-fg} - Get detailed info about a token",
|
|
1257
|
+
"{green-fg} 💰 buy <token> <amount>{/green-fg} - Buy tokens (e.g., buy MTK 0.10)",
|
|
1258
|
+
"{green-fg} 💼 positions{/green-fg} - View your portfolio",
|
|
1259
|
+
"{green-fg} 💵 balances{/green-fg} - Check your wallet balance",
|
|
1260
|
+
"",
|
|
1261
|
+
`{${blueColor}}💡 Type {bold}help{/bold} to see all available commands!{/${blueColor}}`,
|
|
1262
|
+
"",
|
|
1263
|
+
];
|
|
1264
|
+
// Log all welcome lines at once
|
|
1265
|
+
welcomeLines.forEach((line) => outputBox.log(line));
|
|
1266
|
+
// Scroll to top so welcome message is visible
|
|
1267
|
+
outputBox.setScrollPerc(0);
|
|
1268
|
+
screen.render();
|
|
1269
|
+
}
|
|
1270
|
+
else {
|
|
1271
|
+
// Fallback to regular log function if outputBox not available
|
|
1272
|
+
log("");
|
|
1273
|
+
log(chalk.cyan.bold("🐱 Welcome to httpcat!"));
|
|
1274
|
+
log("");
|
|
1275
|
+
log(chalk.yellow("✨ Here are some commands to get you started:"));
|
|
1276
|
+
log("");
|
|
1277
|
+
log(chalk.green(" 📋 list") +
|
|
1278
|
+
chalk.dim(" - Browse all available tokens"));
|
|
1279
|
+
log(chalk.green(" ℹ️ info <token>") +
|
|
1280
|
+
chalk.dim(" - Get detailed info about a token"));
|
|
1281
|
+
log(chalk.green(" 💰 buy <token> <amount>") +
|
|
1282
|
+
chalk.dim(" - Buy tokens (e.g., buy MTK 0.10)"));
|
|
1283
|
+
log(chalk.green(" 💼 positions") +
|
|
1284
|
+
chalk.dim(" - View your portfolio"));
|
|
1285
|
+
log(chalk.green(" 💵 balances") +
|
|
1286
|
+
chalk.dim(" - Check your wallet balance"));
|
|
1287
|
+
log("");
|
|
1288
|
+
log(chalk.blue("💡 Type ") +
|
|
1289
|
+
chalk.bold("help") +
|
|
1290
|
+
chalk.blue(" to see all available commands!"));
|
|
1291
|
+
log("");
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
function displayHelp(log, logLines, outputBox, screen, theme = "dark") {
|
|
1295
|
+
// Helper to get theme-appropriate colors
|
|
1296
|
+
const getCyanColor = (t) => t === "dark" ? "light-cyan-fg" : "cyan-fg";
|
|
1297
|
+
const getBlueColor = (t) => t === "dark" ? "light-blue-fg" : "blue-fg";
|
|
1298
|
+
const cyanColor = getCyanColor(theme);
|
|
1299
|
+
const blueColor = getBlueColor(theme);
|
|
1300
|
+
// If we have outputBox, use blessed tags instead of chalk to avoid question marks
|
|
1301
|
+
// Otherwise fall back to the log function
|
|
1302
|
+
if (outputBox && screen) {
|
|
1303
|
+
// Get the line number where help will start (before logging)
|
|
1304
|
+
const linesBeforeHelp = outputBox.lines?.length || 0;
|
|
1305
|
+
// Collect all help lines using blessed tags
|
|
1306
|
+
const helpLines = [
|
|
1307
|
+
"",
|
|
1308
|
+
`{${cyanColor}}{bold}Available Commands:{/bold}{/${cyanColor}}`,
|
|
1309
|
+
"",
|
|
1310
|
+
// Token Operations
|
|
1311
|
+
`{${cyanColor}}{bold}Token Operations:{/bold}{/${cyanColor}}`,
|
|
1312
|
+
"{green-fg} create <name> <symbol>{/green-fg}",
|
|
1313
|
+
"{green-fg} buy <id> <amount> [--repeat N] [--delay MS]{/green-fg}",
|
|
1314
|
+
"{green-fg} sell <id> <amount|all>{/green-fg}",
|
|
1315
|
+
"{green-fg} claim <id> [--execute] [--address ADDR]{/green-fg}",
|
|
1316
|
+
"",
|
|
1317
|
+
// Token Information
|
|
1318
|
+
`{${cyanColor}}{bold}Token Information:{/bold}{/${cyanColor}}`,
|
|
1319
|
+
"{green-fg} info <id>{/green-fg}",
|
|
1320
|
+
"{green-fg} list [--sort mcap|created|name] [--page N] [--limit N]{/green-fg}",
|
|
1321
|
+
"",
|
|
1322
|
+
// Portfolio
|
|
1323
|
+
`{${cyanColor}}{bold}Portfolio:{/bold}{/${cyanColor}}`,
|
|
1324
|
+
"{green-fg} positions [--active|--graduated]{/green-fg}",
|
|
1325
|
+
"{green-fg} transactions [--user ADDR] [--token ID] [--type TYPE] [--limit N] [--offset N]{/green-fg}",
|
|
1326
|
+
"{green-fg} balances{/green-fg}",
|
|
1327
|
+
"",
|
|
1328
|
+
// Account Management
|
|
1329
|
+
`{${cyanColor}}{bold}Account Management:{/bold}{/${cyanColor}}`,
|
|
1330
|
+
"{green-fg} account [list|switch <index>|add]{/green-fg}",
|
|
1331
|
+
"{green-fg} env [list|use <name>|show|add|update]{/green-fg}",
|
|
1332
|
+
"",
|
|
1333
|
+
// Social
|
|
1334
|
+
`{${cyanColor}}{bold}Social:{/bold}{/${cyanColor}}`,
|
|
1335
|
+
"{green-fg} chat [token] [--input-format FORMAT]{/green-fg}",
|
|
1336
|
+
"",
|
|
1337
|
+
// Cat (agent and cat are synonyms)
|
|
1338
|
+
`{${cyanColor}}{bold}Cat (or 'agent'):{/bold}{/${cyanColor}}`,
|
|
1339
|
+
"{green-fg} agent <query>{/green-fg} - Ask the cat to do something (or use 'cat')",
|
|
1340
|
+
"{green-fg} agent --chat{/green-fg} - Enter interactive chat mode (or 'cat --chat')",
|
|
1341
|
+
"{green-fg} agent --setup{/green-fg} - Configure API key/provider (or 'cat --setup')",
|
|
1342
|
+
"",
|
|
1343
|
+
// System
|
|
1344
|
+
`{${cyanColor}}{bold}System:{/bold}{/${cyanColor}}`,
|
|
1345
|
+
"{green-fg} health{/green-fg}",
|
|
1346
|
+
"{green-fg} config [--show|--set|--reset]{/green-fg}",
|
|
1347
|
+
"{green-fg} network{/green-fg}",
|
|
1348
|
+
"",
|
|
1349
|
+
// Shell Commands
|
|
1350
|
+
`{${cyanColor}}{bold}Shell Commands:{/bold}{/${cyanColor}}`,
|
|
1351
|
+
"{green-fg} clear{/green-fg}",
|
|
1352
|
+
"{green-fg} help{/green-fg}",
|
|
1353
|
+
"{green-fg} exit{/green-fg}",
|
|
1354
|
+
"",
|
|
1355
|
+
`{${blueColor}}Tip: Run any command without arguments to see detailed help{/${blueColor}}`,
|
|
1356
|
+
];
|
|
1357
|
+
// Log all help lines at once
|
|
1358
|
+
helpLines.forEach((line) => outputBox.log(line));
|
|
1359
|
+
// Scroll to show "Available Commands" at the top of the visible area
|
|
1360
|
+
// Get total lines after logging
|
|
1361
|
+
const totalLines = outputBox.lines?.length || 0;
|
|
1362
|
+
const visibleHeight = outputBox.height || 20;
|
|
1363
|
+
if (totalLines > visibleHeight && linesBeforeHelp >= 0) {
|
|
1364
|
+
// Calculate which line "Available Commands" is on (after the empty line)
|
|
1365
|
+
const availableCommandsLine = linesBeforeHelp + 1;
|
|
1366
|
+
// Calculate scroll percentage to show that line at the top
|
|
1367
|
+
// Percentage = (line number / total lines) * 100
|
|
1368
|
+
// But we want the line to be at the top, so we need to account for visible height
|
|
1369
|
+
const scrollPerc = Math.max(0, Math.min(100, ((availableCommandsLine - 1) /
|
|
1370
|
+
Math.max(1, totalLines - visibleHeight)) *
|
|
1371
|
+
100));
|
|
1372
|
+
outputBox.setScrollPerc(scrollPerc);
|
|
1373
|
+
}
|
|
1374
|
+
else {
|
|
1375
|
+
// If all content fits in visible area or no previous content, scroll to top
|
|
1376
|
+
outputBox.setScrollPerc(0);
|
|
1377
|
+
}
|
|
1378
|
+
screen.render();
|
|
1379
|
+
}
|
|
1380
|
+
else {
|
|
1381
|
+
// Fallback to regular log function if outputBox not available
|
|
1382
|
+
log("");
|
|
1383
|
+
log(chalk.cyan.bold("Available Commands:"));
|
|
1384
|
+
log("");
|
|
1385
|
+
// Token Operations
|
|
1386
|
+
log(chalk.bold(chalk.cyan("Token Operations:")));
|
|
1387
|
+
log(chalk.green(" create <name> <symbol>"));
|
|
1388
|
+
log(chalk.green(" buy <id> <amount> [--repeat N] [--delay MS]"));
|
|
1389
|
+
log(chalk.green(" sell <id> <amount|all>"));
|
|
1390
|
+
log(chalk.green(" claim <id> [--execute] [--address ADDR]"));
|
|
1391
|
+
log("");
|
|
1392
|
+
// Token Information
|
|
1393
|
+
log(chalk.bold(chalk.cyan("Token Information:")));
|
|
1394
|
+
log(chalk.green(" info <id>"));
|
|
1395
|
+
log(chalk.green(" list [--sort mcap|created|name] [--page N] [--limit N]"));
|
|
1396
|
+
log("");
|
|
1397
|
+
// Portfolio
|
|
1398
|
+
log(chalk.bold(chalk.cyan("Portfolio:")));
|
|
1399
|
+
log(chalk.green(" positions [--active|--graduated]"));
|
|
1400
|
+
log(chalk.green(" transactions [--user ADDR] [--token ID] [--type TYPE] [--limit N] [--offset N]"));
|
|
1401
|
+
log(chalk.green(" balances"));
|
|
1402
|
+
log("");
|
|
1403
|
+
// Account Management
|
|
1404
|
+
log(chalk.bold(chalk.cyan("Account Management:")));
|
|
1405
|
+
log(chalk.green(" account [list|switch <index>|add]"));
|
|
1406
|
+
log(chalk.green(" env [list|use <name>|show|add|update]"));
|
|
1407
|
+
log("");
|
|
1408
|
+
// Social
|
|
1409
|
+
log(chalk.bold(chalk.cyan("Social:")));
|
|
1410
|
+
log(chalk.green(" chat [token] [--input-format FORMAT]"));
|
|
1411
|
+
log("");
|
|
1412
|
+
// Cat (agent and cat are synonyms)
|
|
1413
|
+
log(chalk.bold(chalk.cyan("Cat (or 'agent'):")));
|
|
1414
|
+
log(chalk.green(" agent <query>") +
|
|
1415
|
+
chalk.dim(" - Ask the cat to do something (or use 'cat')"));
|
|
1416
|
+
log(chalk.green(" agent --chat") +
|
|
1417
|
+
chalk.dim(" - Enter interactive chat mode (or 'cat --chat')"));
|
|
1418
|
+
log(chalk.green(" agent --setup") +
|
|
1419
|
+
chalk.dim(" - Configure API key/provider (or 'cat --setup')"));
|
|
1420
|
+
log("");
|
|
1421
|
+
// System
|
|
1422
|
+
log(chalk.bold(chalk.cyan("System:")));
|
|
1423
|
+
log(chalk.green(" health"));
|
|
1424
|
+
log(chalk.green(" config [--show|--set|--reset]"));
|
|
1425
|
+
log(chalk.green(" network"));
|
|
1426
|
+
log("");
|
|
1427
|
+
// Shell Commands
|
|
1428
|
+
log(chalk.bold(chalk.cyan("Shell Commands:")));
|
|
1429
|
+
log(chalk.green(" clear"));
|
|
1430
|
+
log(chalk.green(" help"));
|
|
1431
|
+
log(chalk.green(" exit"));
|
|
1432
|
+
log("");
|
|
1433
|
+
log(chalk.blue("Tip: Run any command without arguments to see detailed help"));
|
|
367
1434
|
}
|
|
368
|
-
log("");
|
|
369
|
-
log(chalk.dim("Examples:"));
|
|
370
|
-
log(chalk.dim(' create "My Token" "MTK"'));
|
|
371
|
-
log(chalk.dim(" buy 0x1234... 5"));
|
|
372
|
-
log(chalk.dim(' buy "My Token" 2'));
|
|
373
|
-
log(chalk.dim(" buy MTK 1"));
|
|
374
|
-
log(chalk.dim(" sell 0x1234... 50%"));
|
|
375
|
-
log(chalk.dim(' info "My Token"'));
|
|
376
|
-
log(chalk.dim(" list --sort mcap --limit 10"));
|
|
377
|
-
log(chalk.dim(" positions"));
|
|
378
1435
|
}
|
|
379
1436
|
function displayConfigToLog(log, logLines) {
|
|
380
1437
|
const cfg = config.getAll();
|
|
381
1438
|
log("");
|
|
382
|
-
log(chalk.
|
|
1439
|
+
log(chalk.green.bold("Current Configuration:"));
|
|
383
1440
|
log("");
|
|
384
|
-
log(chalk.
|
|
385
|
-
log(chalk.
|
|
386
|
-
log(chalk.
|
|
387
|
-
log(chalk.
|
|
388
|
-
log(chalk.
|
|
389
|
-
chalk.
|
|
390
|
-
log(chalk.
|
|
391
|
-
chalk.
|
|
1441
|
+
log(chalk.blue("Network: ") + chalk.green(cfg.network));
|
|
1442
|
+
log(chalk.blue("Agent URL: ") + chalk.green(cfg.agentUrl));
|
|
1443
|
+
log(chalk.blue("Facilitator: ") + chalk.green(cfg.facilitatorUrl));
|
|
1444
|
+
log(chalk.blue("Max Payment: ") + chalk.green("$" + cfg.defaultMaxPayment));
|
|
1445
|
+
log(chalk.blue("ASCII Art: ") +
|
|
1446
|
+
chalk.green(cfg.preferences.enableAsciiArt ? "enabled" : "disabled"));
|
|
1447
|
+
log(chalk.blue("Private Key: ") +
|
|
1448
|
+
chalk.blue(cfg.privateKey ? "configured" : "not configured"));
|
|
392
1449
|
log("");
|
|
393
|
-
log(chalk.
|
|
1450
|
+
log(chalk.blue(`Config file: ${config.getConfigPath()}`));
|
|
394
1451
|
}
|
|
395
1452
|
// Simplified display functions that log to blessed output
|
|
396
1453
|
function displayCreateResultToLog(result, log, logLines) {
|
|
397
1454
|
log(chalk.green("Token created successfully!"));
|
|
398
|
-
log(chalk.
|
|
399
|
-
log(chalk.
|
|
1455
|
+
log(chalk.blue("Name: ") + chalk.green(result.name));
|
|
1456
|
+
log(chalk.blue("Symbol: ") + chalk.green(result.symbol));
|
|
400
1457
|
if (result.tokenAddress) {
|
|
401
|
-
log(chalk.
|
|
1458
|
+
log(chalk.blue("Address: ") + chalk.green(result.tokenAddress));
|
|
402
1459
|
}
|
|
403
1460
|
}
|
|
404
1461
|
function displayBuyResultToLog(result, log, logLines) {
|
|
405
1462
|
log(chalk.green("Purchase successful!"));
|
|
406
|
-
log(chalk.
|
|
407
|
-
log(chalk.
|
|
408
|
-
log(chalk.
|
|
409
|
-
log(chalk.
|
|
1463
|
+
log(chalk.blue("Tokens received: ") + chalk.green(result.tokensReceived));
|
|
1464
|
+
log(chalk.blue("Amount spent: ") + chalk.yellow(result.amountSpent));
|
|
1465
|
+
log(chalk.blue("New price: ") + chalk.green(result.newPrice));
|
|
1466
|
+
log(chalk.blue("Graduation: ") +
|
|
410
1467
|
chalk.magenta(result.graduationProgress.toFixed(2) + "%"));
|
|
411
1468
|
}
|
|
412
1469
|
function displaySellResultToLog(result, log, logLines) {
|
|
413
1470
|
log(chalk.green("Sale successful!"));
|
|
414
|
-
log(chalk.
|
|
415
|
-
|
|
416
|
-
log(chalk.
|
|
417
|
-
|
|
418
|
-
|
|
1471
|
+
log(chalk.blue("Tokens sold: ") +
|
|
1472
|
+
chalk.green(formatTokenAmount(result.tokensSold || "0")));
|
|
1473
|
+
log(chalk.blue("USDC received: ") +
|
|
1474
|
+
chalk.yellow(formatCurrency(result.usdcReceived || "0", 6)));
|
|
1475
|
+
if (result.fee) {
|
|
1476
|
+
log(chalk.blue("Fee (1%): ") + chalk.yellow(formatCurrency(result.fee, 6)));
|
|
1477
|
+
}
|
|
1478
|
+
if (result.newPrice) {
|
|
1479
|
+
log(chalk.blue("New price: ") + chalk.green(formatCurrency(result.newPrice)));
|
|
1480
|
+
}
|
|
1481
|
+
if (result.newMcap) {
|
|
1482
|
+
log(chalk.blue("New market cap: ") +
|
|
1483
|
+
chalk.yellow(formatCurrency(result.newMcap)));
|
|
1484
|
+
}
|
|
1485
|
+
if (result.graduationProgress !== undefined) {
|
|
1486
|
+
log(chalk.blue("Graduation: ") +
|
|
1487
|
+
chalk.magenta(result.graduationProgress.toFixed(2) + "%"));
|
|
1488
|
+
}
|
|
1489
|
+
if (result.txHash) {
|
|
1490
|
+
log(chalk.blue("Transaction: ") + chalk.green(result.txHash));
|
|
1491
|
+
}
|
|
1492
|
+
else if (result.usdcTransferTx) {
|
|
1493
|
+
log(chalk.blue("Transaction: ") + chalk.green(result.usdcTransferTx));
|
|
1494
|
+
}
|
|
1495
|
+
// Graduation progress bar
|
|
1496
|
+
if (result.graduationProgress !== undefined) {
|
|
1497
|
+
const barLength = 20;
|
|
1498
|
+
const filled = Math.floor((result.graduationProgress / 100) * barLength);
|
|
1499
|
+
const empty = barLength - filled;
|
|
1500
|
+
const bar = "🐱" + "=".repeat(filled) + ">" + " ".repeat(empty);
|
|
1501
|
+
log(chalk.yellow(`[${bar}] ${chalk.bold(result.graduationProgress.toFixed(2) + "%")} to moon!`));
|
|
1502
|
+
}
|
|
1503
|
+
// Display price impact warning for Uniswap swaps
|
|
1504
|
+
if (result.source === "uniswap_v2" && result.priceImpact !== undefined) {
|
|
1505
|
+
if (result.priceImpact < 0.5) {
|
|
1506
|
+
log(chalk.green(`Price Impact: ${result.priceImpact.toFixed(2)}%`));
|
|
1507
|
+
}
|
|
1508
|
+
else if (result.priceImpact < 1) {
|
|
1509
|
+
log(chalk.yellow(`Price Impact: ${result.priceImpact.toFixed(2)}%`));
|
|
1510
|
+
}
|
|
1511
|
+
else if (result.priceImpact < 3) {
|
|
1512
|
+
log(chalk.red(`⚠️ HIGH Price Impact: ${result.priceImpact.toFixed(2)}%`));
|
|
1513
|
+
}
|
|
1514
|
+
else {
|
|
1515
|
+
log(chalk.red(`🚨 VERY HIGH Price Impact: ${result.priceImpact.toFixed(2)}%`));
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
// Display route info for Uniswap swaps
|
|
1519
|
+
if (result.source === "uniswap_v2" && result.route) {
|
|
1520
|
+
const feePercent = result.routeFee
|
|
1521
|
+
? (result.routeFee / 10000).toFixed(2)
|
|
1522
|
+
: "N/A";
|
|
1523
|
+
log(chalk.cyan(`Route: ${result.route} (${feePercent}% fee)`));
|
|
1524
|
+
}
|
|
419
1525
|
}
|
|
420
1526
|
function displayTokenInfoToLog(info, log, logLines) {
|
|
421
|
-
log(
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
1527
|
+
log("");
|
|
1528
|
+
// Token header box
|
|
1529
|
+
const priceDisplay = info.status === "graduated"
|
|
1530
|
+
? chalk.dim("--")
|
|
1531
|
+
: info.price
|
|
1532
|
+
? formatCurrency(info.price)
|
|
1533
|
+
: chalk.dim("N/A");
|
|
1534
|
+
const mcapDisplay = info.status === "graduated"
|
|
1535
|
+
? chalk.dim("--")
|
|
1536
|
+
: info.mcap
|
|
1537
|
+
? formatCurrency(info.mcap)
|
|
1538
|
+
: chalk.dim("N/A");
|
|
1539
|
+
// Create box-like display
|
|
1540
|
+
log(chalk.green.bold(`📊 ${info.name} (${info.symbol})`));
|
|
1541
|
+
log(chalk.dim("─".repeat(60)));
|
|
1542
|
+
log(chalk.blue("Address: ") + chalk.green(formatAddress(info.address || "")));
|
|
1543
|
+
log(chalk.blue("Status: ") + chalk.green(info.status || "unknown"));
|
|
1544
|
+
log(chalk.blue("Price: ") + chalk.green(priceDisplay));
|
|
1545
|
+
log(chalk.blue("Market Cap: ") + chalk.yellow(mcapDisplay));
|
|
1546
|
+
log(chalk.blue("Total Supply: ") +
|
|
1547
|
+
chalk.green(formatTokenAmount(info.totalSupply || "0")));
|
|
1548
|
+
log(chalk.blue("Created: ") +
|
|
1549
|
+
chalk.green(new Date(info.createdAt || Date.now()).toLocaleString()));
|
|
1550
|
+
log("");
|
|
1551
|
+
// Graduation progress
|
|
428
1552
|
if (info.graduationProgress !== undefined) {
|
|
429
|
-
|
|
430
|
-
|
|
1553
|
+
const barLength = 20;
|
|
1554
|
+
const filled = Math.floor((info.graduationProgress / 100) * barLength);
|
|
1555
|
+
const empty = barLength - filled;
|
|
1556
|
+
const bar = "🐱" + "=".repeat(filled) + ">" + " ".repeat(empty);
|
|
1557
|
+
log(chalk.yellow(`[${bar}] ${chalk.bold(info.graduationProgress.toFixed(2) + "%")} to moon!`));
|
|
1558
|
+
log("");
|
|
431
1559
|
}
|
|
1560
|
+
// Position display
|
|
432
1561
|
if (info.userPosition && info.userPosition.tokensOwned !== "0") {
|
|
433
|
-
log(chalk.
|
|
434
|
-
|
|
1562
|
+
log(chalk.green.bold("💼 Your Position"));
|
|
1563
|
+
log(chalk.dim("─".repeat(60)));
|
|
1564
|
+
log(chalk.blue("Tokens Owned: ") +
|
|
1565
|
+
chalk.green(formatTokenAmount(info.userPosition.tokensOwned)));
|
|
1566
|
+
log(chalk.blue("Invested: ") +
|
|
1567
|
+
chalk.green(formatCurrency(info.userPosition.usdcInvested || "0")));
|
|
1568
|
+
if (info.userPosition.currentValue) {
|
|
1569
|
+
log(chalk.blue("Current Value: ") +
|
|
1570
|
+
chalk.green(formatCurrency(info.userPosition.currentValue)));
|
|
1571
|
+
}
|
|
1572
|
+
if (info.userPosition.pnl) {
|
|
1573
|
+
const pnl = parseFloat(info.userPosition.pnl);
|
|
1574
|
+
const pnlColor = pnl >= 0 ? chalk.green : chalk.red;
|
|
1575
|
+
const pnlSign = pnl >= 0 ? "+" : "";
|
|
1576
|
+
const pnlIcon = pnl >= 0 ? "🟢" : "🔴";
|
|
1577
|
+
log(chalk.blue("P&L: ") +
|
|
1578
|
+
`${pnlIcon} ${pnlColor(pnlSign + formatCurrency(info.userPosition.pnl))}`);
|
|
1579
|
+
}
|
|
1580
|
+
log("");
|
|
435
1581
|
}
|
|
436
1582
|
}
|
|
437
|
-
function displayTokenListToLog(result, log, logLines) {
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
1583
|
+
function displayTokenListToLog(result, log, logLines, logLinesSmooth) {
|
|
1584
|
+
// Collect all lines first for smooth scrolling
|
|
1585
|
+
const allLines = [];
|
|
1586
|
+
allLines.push("");
|
|
1587
|
+
allLines.push(chalk.green.bold(`Tokens (${result.total || result.tokens.length} total):`));
|
|
1588
|
+
if (result.page && result.pages) {
|
|
1589
|
+
allLines.push(chalk.dim(`Page ${result.page} of ${result.pages}`));
|
|
1590
|
+
}
|
|
1591
|
+
allLines.push("");
|
|
1592
|
+
if (result.tokens.length === 0) {
|
|
1593
|
+
allLines.push(chalk.yellow("No tokens found."));
|
|
1594
|
+
// Use smooth scrolling if available, otherwise regular logLines
|
|
1595
|
+
if (logLinesSmooth) {
|
|
1596
|
+
logLinesSmooth(allLines, false);
|
|
1597
|
+
}
|
|
1598
|
+
else {
|
|
1599
|
+
logLines(allLines);
|
|
1600
|
+
}
|
|
1601
|
+
return;
|
|
442
1602
|
}
|
|
443
|
-
|
|
444
|
-
|
|
1603
|
+
// Show header
|
|
1604
|
+
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")}`);
|
|
1605
|
+
allLines.push(chalk.dim(" " + "─".repeat(90)));
|
|
1606
|
+
// Show tokens
|
|
1607
|
+
for (const token of result.tokens) {
|
|
1608
|
+
const graduationIcon = token.status === "graduated"
|
|
1609
|
+
? "[G]"
|
|
1610
|
+
: token.graduationProgress >= 90
|
|
1611
|
+
? "[H]"
|
|
1612
|
+
: token.graduationProgress >= 50
|
|
1613
|
+
? "[M]"
|
|
1614
|
+
: "[L]";
|
|
1615
|
+
const mcapDisplay = token.status === "graduated"
|
|
1616
|
+
? chalk.dim("--")
|
|
1617
|
+
: formatCurrency(token.mcap || "0");
|
|
1618
|
+
const priceDisplay = token.status === "graduated"
|
|
1619
|
+
? chalk.dim("--")
|
|
1620
|
+
: formatCurrency(token.price || "0");
|
|
1621
|
+
const graduationDisplay = `${graduationIcon} ${(token.graduationProgress || 0).toFixed(1)}%`;
|
|
1622
|
+
const statusDisplay = token.status === "graduated"
|
|
1623
|
+
? chalk.green("✓ Graduated")
|
|
1624
|
+
: chalk.yellow("Active");
|
|
1625
|
+
allLines.push(` ${chalk.bold(token.name.padEnd(20))} ${chalk.green(token.symbol.padEnd(10))} ${mcapDisplay.padEnd(15)} ${priceDisplay.padEnd(12)} ${graduationDisplay.padEnd(12)} ${statusDisplay}`);
|
|
1626
|
+
}
|
|
1627
|
+
allLines.push("");
|
|
1628
|
+
if (result.page && result.pages && result.page < result.pages) {
|
|
1629
|
+
allLines.push(chalk.dim(`Use --page ${result.page + 1} to see more tokens`));
|
|
1630
|
+
allLines.push("");
|
|
1631
|
+
}
|
|
1632
|
+
// Show example commands
|
|
1633
|
+
if (result.tokens.length > 0) {
|
|
1634
|
+
const firstToken = result.tokens[0];
|
|
1635
|
+
allLines.push(chalk.dim("Example commands:"));
|
|
1636
|
+
allLines.push(chalk.dim(` buy ${firstToken.symbol}`));
|
|
1637
|
+
allLines.push(chalk.dim(` sell ${firstToken.symbol}`));
|
|
1638
|
+
allLines.push(chalk.dim(` info ${firstToken.symbol}`));
|
|
1639
|
+
allLines.push("");
|
|
1640
|
+
}
|
|
1641
|
+
// Use smooth scrolling if available (scrolls to show the list header), otherwise regular logLines
|
|
1642
|
+
if (logLinesSmooth) {
|
|
1643
|
+
logLinesSmooth(allLines, true); // scrollToTop=true to show the list header
|
|
1644
|
+
}
|
|
1645
|
+
else {
|
|
1646
|
+
logLines(allLines);
|
|
445
1647
|
}
|
|
446
1648
|
}
|
|
447
1649
|
function displayPositionsToLog(result, log, logLines) {
|
|
@@ -449,27 +1651,61 @@ function displayPositionsToLog(result, log, logLines) {
|
|
|
449
1651
|
log(chalk.yellow("No positions found."));
|
|
450
1652
|
return;
|
|
451
1653
|
}
|
|
452
|
-
log(chalk.
|
|
1654
|
+
log(chalk.green.bold(`Your Positions (${result.positions.length}):`));
|
|
453
1655
|
log("");
|
|
454
1656
|
for (const pos of result.positions) {
|
|
455
|
-
|
|
1657
|
+
const tokenName = pos.token?.name || "???";
|
|
1658
|
+
const tokenSymbol = pos.token?.symbol || "???";
|
|
1659
|
+
const tokensDisplay = formatTokenAmount(pos.tokensOwned || "0");
|
|
1660
|
+
const valueDisplay = pos.currentValue
|
|
1661
|
+
? formatCurrency(pos.currentValue)
|
|
1662
|
+
: "N/A";
|
|
1663
|
+
log(` ${chalk.green.bold(`${tokenName} (${tokenSymbol})`)} - ${chalk.green(`${tokensDisplay} tokens`)} @ ${chalk.yellow(valueDisplay)}`);
|
|
456
1664
|
}
|
|
457
1665
|
}
|
|
458
1666
|
function displayHealthStatusToLog(health, log, logLines) {
|
|
459
|
-
const statusColor = health.status === "
|
|
1667
|
+
const statusColor = health.status === "ok" || health.status === "healthy"
|
|
1668
|
+
? chalk.green
|
|
1669
|
+
: chalk.red;
|
|
460
1670
|
log(statusColor(`Agent Status: ${health.status}`));
|
|
461
1671
|
if (health.version) {
|
|
462
|
-
log(chalk.
|
|
1672
|
+
log(chalk.blue("Version: ") + chalk.green(health.version));
|
|
463
1673
|
}
|
|
464
1674
|
if (health.uptime) {
|
|
465
|
-
log(chalk.
|
|
1675
|
+
log(chalk.blue("Uptime: ") + chalk.green(health.uptime));
|
|
466
1676
|
}
|
|
467
1677
|
}
|
|
468
1678
|
function displayBalanceToLog(balance, log, logLines) {
|
|
469
|
-
log(
|
|
470
|
-
log(chalk.
|
|
471
|
-
log(chalk.
|
|
472
|
-
log(chalk.
|
|
1679
|
+
log("");
|
|
1680
|
+
log(chalk.green.bold("Wallet Balance:"));
|
|
1681
|
+
log(chalk.blue("Address: ") + chalk.green(balance.address));
|
|
1682
|
+
log(chalk.blue("ETH: ") +
|
|
1683
|
+
chalk.yellow(balance.ethFormatted || balance.ethBalance));
|
|
1684
|
+
log(chalk.blue("USDC: ") +
|
|
1685
|
+
chalk.green(balance.usdcFormatted || balance.usdcBalance));
|
|
1686
|
+
if (balance.cat402Formatted) {
|
|
1687
|
+
log(chalk.blue("CAT: ") + chalk.cyan(balance.cat402Formatted));
|
|
1688
|
+
}
|
|
1689
|
+
log("");
|
|
1690
|
+
// Show warnings if balances are low
|
|
1691
|
+
const ethNum = Number(balance.ethFormatted || balance.ethBalance);
|
|
1692
|
+
const usdcNum = Number((balance.usdcFormatted || balance.usdcBalance)
|
|
1693
|
+
.replace("$", "")
|
|
1694
|
+
.replace(",", ""));
|
|
1695
|
+
if (ethNum < 0.001 && usdcNum < 1) {
|
|
1696
|
+
log(chalk.yellow("⚠️ Low balances detected!"));
|
|
1697
|
+
log(chalk.dim(" You need Base Sepolia ETH for gas fees"));
|
|
1698
|
+
log(chalk.dim(" You need Base Sepolia USDC for trading"));
|
|
1699
|
+
log("");
|
|
1700
|
+
}
|
|
1701
|
+
else if (ethNum < 0.001) {
|
|
1702
|
+
log(chalk.yellow("⚠️ Low ETH balance - you may not have enough for gas fees"));
|
|
1703
|
+
log("");
|
|
1704
|
+
}
|
|
1705
|
+
else if (usdcNum < 1) {
|
|
1706
|
+
log(chalk.yellow("⚠️ Low USDC balance - you may not have enough for trades"));
|
|
1707
|
+
log("");
|
|
1708
|
+
}
|
|
473
1709
|
}
|
|
474
1710
|
function extractFlag(args, flag) {
|
|
475
1711
|
const index = args.findIndex((arg) => arg === flag);
|
|
@@ -478,4 +1714,589 @@ function extractFlag(args, flag) {
|
|
|
478
1714
|
}
|
|
479
1715
|
return undefined;
|
|
480
1716
|
}
|
|
1717
|
+
function displayFeesToLog(fees, log, logLines) {
|
|
1718
|
+
const hasFeesToken = BigInt(fees.feeToken || "0") > 0n;
|
|
1719
|
+
const hasFeesPaired = BigInt(fees.feePaired || "0") > 0n;
|
|
1720
|
+
const hasFees = hasFeesToken || hasFeesPaired;
|
|
1721
|
+
log("");
|
|
1722
|
+
log(chalk.green.bold(`💰 Accumulated Fees: ${fees.tokenName} (${fees.tokenSymbol})`));
|
|
1723
|
+
log(chalk.blue("━".repeat(50)));
|
|
1724
|
+
log(chalk.blue("Contract: ") + chalk.green(fees.tokenAddress));
|
|
1725
|
+
log(chalk.blue("LP Status: ") +
|
|
1726
|
+
chalk.green(fees.isLocked ? "🔒 Locked" : "❌ Not Locked"));
|
|
1727
|
+
if (typeof fees.v4TickLower === "number" &&
|
|
1728
|
+
typeof fees.v4TickUpper === "number" &&
|
|
1729
|
+
typeof fees.v4TickCurrent === "number" &&
|
|
1730
|
+
typeof fees.v4InRange === "boolean") {
|
|
1731
|
+
log(chalk.blue("V4 Range: ") +
|
|
1732
|
+
chalk.green(`[${fees.v4TickLower}, ${fees.v4TickUpper}) | Current: ${fees.v4TickCurrent} | In Range: ${fees.v4InRange ? "✅" : "❌"}`));
|
|
1733
|
+
}
|
|
1734
|
+
log("");
|
|
1735
|
+
log(chalk.green("Total Fees:"));
|
|
1736
|
+
log(chalk.blue(" Tokens: ") + chalk.green(hasFeesToken ? fees.feeToken : "0"));
|
|
1737
|
+
log(chalk.blue(" USDC: ") +
|
|
1738
|
+
chalk.green(hasFeesPaired ? fees.feePaired : "$0.00"));
|
|
1739
|
+
log("");
|
|
1740
|
+
log(chalk.green("Creator Share (80%):"));
|
|
1741
|
+
log(chalk.blue(" Tokens: ") + chalk.green(fees.creatorToken || "0"));
|
|
1742
|
+
log(chalk.blue(" USDC: ") + chalk.green(fees.creatorPaired || "$0.00"));
|
|
1743
|
+
log("");
|
|
1744
|
+
log(chalk.green("Platform Share (20%):"));
|
|
1745
|
+
log(chalk.blue(" Tokens: ") + chalk.green(fees.platformToken || "0"));
|
|
1746
|
+
log(chalk.blue(" USDC: ") + chalk.green(fees.platformPaired || "$0.00"));
|
|
1747
|
+
log(chalk.blue("━".repeat(50)));
|
|
1748
|
+
if (!hasFees) {
|
|
1749
|
+
if (fees.v4InRange === false) {
|
|
1750
|
+
log(chalk.yellow("\n⚠️ No fees accrued: position is out of range"));
|
|
1751
|
+
}
|
|
1752
|
+
else {
|
|
1753
|
+
log(chalk.yellow("\n⚠️ No fees accumulated yet. Trade volume needed."));
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
else {
|
|
1757
|
+
log(chalk.blue("\n💡 Run with --execute to claim fees"));
|
|
1758
|
+
}
|
|
1759
|
+
}
|
|
1760
|
+
function displayClaimResultToLog(result, log, logLines) {
|
|
1761
|
+
log(chalk.green("✅ Fees claimed successfully!"));
|
|
1762
|
+
log(chalk.blue("Token: ") + chalk.green(result.tokenAddress));
|
|
1763
|
+
log(chalk.blue("Transaction: ") + chalk.green(result.txHash));
|
|
1764
|
+
log(chalk.blue("Claimed:"));
|
|
1765
|
+
log(chalk.blue(" Tokens: ") + chalk.green(result.creatorToken || "0"));
|
|
1766
|
+
log(chalk.blue(" USDC: ") + chalk.green(result.creatorPaired || "$0.00"));
|
|
1767
|
+
}
|
|
1768
|
+
function displayTransactionsToLog(result, log, logLines) {
|
|
1769
|
+
log(chalk.green.bold(`📜 Transactions (${result.total} total)`));
|
|
1770
|
+
log("");
|
|
1771
|
+
if (result.transactions.length === 0) {
|
|
1772
|
+
log(chalk.yellow("No transactions found."));
|
|
1773
|
+
return;
|
|
1774
|
+
}
|
|
1775
|
+
for (let i = 0; i < result.transactions.length; i++) {
|
|
1776
|
+
const tx = result.transactions[i];
|
|
1777
|
+
const typeIcon = tx.type === "buy" ? "🟢" : tx.type === "sell" ? "🔴" : "🎁";
|
|
1778
|
+
const statusIcon = tx.status === "success" ? "✅" : tx.status === "pending" ? "⏳" : "❌";
|
|
1779
|
+
log(chalk.green.bold(`${i + 1}. ${typeIcon} ${tx.type.toUpperCase()} ${statusIcon} ${tx.status.toUpperCase()}`));
|
|
1780
|
+
if (tx.token) {
|
|
1781
|
+
log(chalk.blue(` Token: ${tx.token.name} (${tx.token.symbol})`));
|
|
1782
|
+
}
|
|
1783
|
+
log(chalk.blue(` Amount: ${tx.amount}`));
|
|
1784
|
+
log(chalk.blue(` Fee: ${tx.fee}`));
|
|
1785
|
+
if (tx.txHash) {
|
|
1786
|
+
log(chalk.blue(` TX: ${tx.txHash}`));
|
|
1787
|
+
}
|
|
1788
|
+
log(chalk.blue(` Date: ${new Date(tx.createdAt).toLocaleString()}`));
|
|
1789
|
+
log("");
|
|
1790
|
+
}
|
|
1791
|
+
if (result.hasMore) {
|
|
1792
|
+
log(chalk.blue(`Showing ${result.offset + 1}-${result.offset + result.transactions.length} of ${result.total}`));
|
|
1793
|
+
log(chalk.blue(`Use --offset ${result.offset + result.limit} to see more`));
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
function displayAccountInfoToLog(data, log, logLines) {
|
|
1797
|
+
const { account, balance } = data;
|
|
1798
|
+
log("");
|
|
1799
|
+
log(chalk.green.bold("👤 Account Information"));
|
|
1800
|
+
log(chalk.blue("=".repeat(50)));
|
|
1801
|
+
log("");
|
|
1802
|
+
log(chalk.blue("Account Index: ") + chalk.green(account.index.toString()));
|
|
1803
|
+
log(chalk.blue("Type: ") +
|
|
1804
|
+
chalk.green(account.type === "custom" ? "Custom" : "Seed-Derived"));
|
|
1805
|
+
log(chalk.blue("Address: ") + chalk.green(account.address));
|
|
1806
|
+
if (account.label) {
|
|
1807
|
+
log(chalk.blue("Label: ") + chalk.green(account.label));
|
|
1808
|
+
}
|
|
1809
|
+
log("");
|
|
1810
|
+
log(chalk.green.bold("💰 Balances"));
|
|
1811
|
+
log(chalk.blue("ETH: ") + chalk.yellow(balance.ethBalance));
|
|
1812
|
+
log(chalk.blue("USDC: ") + chalk.green(balance.usdcBalance));
|
|
1813
|
+
if (balance.cat402Formatted) {
|
|
1814
|
+
log(chalk.blue("CAT: ") + chalk.green(balance.cat402Formatted));
|
|
1815
|
+
}
|
|
1816
|
+
log("");
|
|
1817
|
+
if (data.positions && data.positions.positions.length > 0) {
|
|
1818
|
+
log(chalk.green.bold("💼 Positions"));
|
|
1819
|
+
log("");
|
|
1820
|
+
displayPositionsToLog(data.positions, log, logLines);
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
// Helper function to format time remaining for lease
|
|
1824
|
+
function formatTimeRemaining(expiresAt) {
|
|
1825
|
+
const now = new Date();
|
|
1826
|
+
const diffMs = expiresAt.getTime() - now.getTime();
|
|
1827
|
+
if (diffMs <= 0) {
|
|
1828
|
+
return "expired";
|
|
1829
|
+
}
|
|
1830
|
+
const minutes = Math.floor(diffMs / 60000);
|
|
1831
|
+
const seconds = Math.floor((diffMs % 60000) / 1000);
|
|
1832
|
+
if (minutes > 0) {
|
|
1833
|
+
return `${minutes}m ${seconds}s`;
|
|
1834
|
+
}
|
|
1835
|
+
return `${seconds}s`;
|
|
1836
|
+
}
|
|
1837
|
+
// Helper function to build chat header content
|
|
1838
|
+
async function buildChatHeaderContent(theme, tokenInfo, tokenIdentifier, isConnected, leaseInfo, getCyanColor) {
|
|
1839
|
+
const colorTag = theme === "dark" ? "green-fg" : "black-fg";
|
|
1840
|
+
const cyanColor = getCyanColor(theme);
|
|
1841
|
+
const lines = [];
|
|
1842
|
+
// Title line
|
|
1843
|
+
if (tokenInfo) {
|
|
1844
|
+
lines.push(`{${colorTag}}💬 httpcat Chat: {${cyanColor}}${tokenInfo.name} (${tokenInfo.symbol}){/${cyanColor}}{/${colorTag}}`);
|
|
1845
|
+
}
|
|
1846
|
+
else if (tokenIdentifier) {
|
|
1847
|
+
// Fallback to identifier if token lookup failed
|
|
1848
|
+
const displayId = tokenIdentifier.length > 20
|
|
1849
|
+
? `${tokenIdentifier.slice(0, 10)}...${tokenIdentifier.slice(-6)}`
|
|
1850
|
+
: tokenIdentifier;
|
|
1851
|
+
lines.push(`{${colorTag}}💬 httpcat Chat: {${cyanColor}}${displayId}{/${cyanColor}}{/${colorTag}}`);
|
|
1852
|
+
}
|
|
1853
|
+
else {
|
|
1854
|
+
lines.push(`{${colorTag}}💬 httpcat Chat: General{/${colorTag}}`);
|
|
1855
|
+
}
|
|
1856
|
+
lines.push(`{${colorTag}}${"═".repeat(60)}{/${colorTag}}`);
|
|
1857
|
+
lines.push("");
|
|
1858
|
+
// Connection status
|
|
1859
|
+
if (isConnected) {
|
|
1860
|
+
lines.push(`{green-fg}✅ Connected to chat stream{/green-fg}`);
|
|
1861
|
+
lines.push("");
|
|
1862
|
+
}
|
|
1863
|
+
// Lease info
|
|
1864
|
+
if (leaseInfo) {
|
|
1865
|
+
const timeRemaining = formatTimeRemaining(leaseInfo.leaseExpiresAt);
|
|
1866
|
+
const isExpired = leaseInfo.leaseExpiresAt.getTime() <= Date.now();
|
|
1867
|
+
const timePrefix = isExpired ? "⚠️ " : "⏱️ ";
|
|
1868
|
+
lines.push(`{yellow-fg}${timePrefix}Lease expires in: ${timeRemaining}{/yellow-fg}`);
|
|
1869
|
+
}
|
|
1870
|
+
lines.push("");
|
|
1871
|
+
lines.push(`{${cyanColor}}💡 Type your message and press Enter to send{/${cyanColor}}`);
|
|
1872
|
+
lines.push(`{${cyanColor}}💡 Type /exit to return to shell{/${cyanColor}}`);
|
|
1873
|
+
lines.push(`{${cyanColor}}💡 Type /renew to renew your lease{/${cyanColor}}`);
|
|
1874
|
+
if (tokenIdentifier) {
|
|
1875
|
+
lines.push(`{${cyanColor}}💡 Type /buy <amount> to buy tokens{/${cyanColor}}`);
|
|
1876
|
+
lines.push(`{${cyanColor}}💡 Type /sell <amount> to sell tokens{/${cyanColor}}`);
|
|
1877
|
+
}
|
|
1878
|
+
return lines.join("\n");
|
|
1879
|
+
}
|
|
1880
|
+
// Simplified chat mode that works within the shell
|
|
1881
|
+
async function startChatInShell(client, tokenIdentifier, log, logLines, screen, inputBox, originalSubmitHandler, headerBox, buildWelcomeContent, currentTheme, catFaces, currentCatIndex) {
|
|
1882
|
+
const privateKey = config.getPrivateKey();
|
|
1883
|
+
const account = privateKeyToAccount(privateKey);
|
|
1884
|
+
const userAddress = account.address;
|
|
1885
|
+
let leaseInfo = null;
|
|
1886
|
+
let ws = null;
|
|
1887
|
+
let isInChatMode = true;
|
|
1888
|
+
let isProcessing = false; // Flag to prevent duplicate processing
|
|
1889
|
+
const displayedMessageIds = new Set();
|
|
1890
|
+
// Helper to format chat messages
|
|
1891
|
+
const formatChatMessage = (msg, isOwn = false) => {
|
|
1892
|
+
const date = new Date(msg.timestamp);
|
|
1893
|
+
const now = new Date();
|
|
1894
|
+
const diffMs = now.getTime() - date.getTime();
|
|
1895
|
+
const diffSec = Math.floor(diffMs / 1000);
|
|
1896
|
+
const diffMin = Math.floor(diffSec / 60);
|
|
1897
|
+
let timeStr = "just now";
|
|
1898
|
+
if (diffSec >= 60) {
|
|
1899
|
+
timeStr =
|
|
1900
|
+
diffMin < 60
|
|
1901
|
+
? `${diffMin}m ago`
|
|
1902
|
+
: date.toLocaleTimeString("en-US", {
|
|
1903
|
+
hour: "2-digit",
|
|
1904
|
+
minute: "2-digit",
|
|
1905
|
+
});
|
|
1906
|
+
}
|
|
1907
|
+
const authorColor = isOwn ? chalk.green : chalk.cyan;
|
|
1908
|
+
const authorShort = msg.authorShort || formatAddress(msg.author, 6);
|
|
1909
|
+
return `${chalk.dim(`[${timeStr}]`)} ${authorColor(authorShort)}: ${msg.message}`;
|
|
1910
|
+
};
|
|
1911
|
+
// Get token information if it's a token chat
|
|
1912
|
+
let tokenInfo = null;
|
|
1913
|
+
if (tokenIdentifier) {
|
|
1914
|
+
try {
|
|
1915
|
+
const info = await getTokenInfo(client, tokenIdentifier, userAddress, true);
|
|
1916
|
+
tokenInfo = { name: info.name, symbol: info.symbol };
|
|
1917
|
+
}
|
|
1918
|
+
catch (error) {
|
|
1919
|
+
// Token lookup failed - will use identifier in header
|
|
1920
|
+
tokenInfo = null;
|
|
1921
|
+
}
|
|
1922
|
+
}
|
|
1923
|
+
// Helper to get cyan color based on theme
|
|
1924
|
+
const getCyanColor = (theme) => theme === "dark" ? "light-cyan-fg" : "cyan-fg";
|
|
1925
|
+
// Helper to update header
|
|
1926
|
+
const updateChatHeader = async (isConnected = false) => {
|
|
1927
|
+
const content = await buildChatHeaderContent(currentTheme, tokenInfo, tokenIdentifier, isConnected, leaseInfo, getCyanColor);
|
|
1928
|
+
headerBox.setContent(content);
|
|
1929
|
+
screen.render();
|
|
1930
|
+
};
|
|
1931
|
+
// Update header to show chat mode
|
|
1932
|
+
await updateChatHeader(false);
|
|
1933
|
+
// Set up header update interval for lease countdown
|
|
1934
|
+
let headerUpdateInterval = null;
|
|
1935
|
+
try {
|
|
1936
|
+
// Join chat
|
|
1937
|
+
log(chalk.blue("Joining chat..."));
|
|
1938
|
+
screen.render();
|
|
1939
|
+
const joinResult = await joinChat(client, userAddress, tokenIdentifier, true);
|
|
1940
|
+
leaseInfo = {
|
|
1941
|
+
leaseId: joinResult.leaseId,
|
|
1942
|
+
leaseExpiresAt: new Date(joinResult.leaseExpiresAt),
|
|
1943
|
+
};
|
|
1944
|
+
// Update header with lease info
|
|
1945
|
+
await updateChatHeader(false);
|
|
1946
|
+
// Display last messages
|
|
1947
|
+
if (joinResult.lastMessages.length > 0) {
|
|
1948
|
+
log(chalk.dim("─".repeat(60)));
|
|
1949
|
+
log(chalk.cyan.bold("Recent messages:"));
|
|
1950
|
+
for (const msg of joinResult.lastMessages) {
|
|
1951
|
+
displayedMessageIds.add(msg.messageId);
|
|
1952
|
+
const isOwn = msg.author.toLowerCase() === userAddress.toLowerCase();
|
|
1953
|
+
log(formatChatMessage(msg, isOwn));
|
|
1954
|
+
}
|
|
1955
|
+
log(chalk.dim("─".repeat(60)));
|
|
1956
|
+
}
|
|
1957
|
+
// Connect to WebSocket
|
|
1958
|
+
const agentUrl = client.getAgentUrl();
|
|
1959
|
+
const normalizedWsUrl = normalizeWebSocketUrl(joinResult.wsUrl, agentUrl);
|
|
1960
|
+
const wsUrlObj = new URL(normalizedWsUrl);
|
|
1961
|
+
wsUrlObj.searchParams.set("leaseId", joinResult.leaseId);
|
|
1962
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1963
|
+
ws = new WebSocket(wsUrlObj.toString());
|
|
1964
|
+
ws.on("open", () => {
|
|
1965
|
+
log(chalk.green("✅ Connected to chat stream"));
|
|
1966
|
+
updateChatHeader(true);
|
|
1967
|
+
screen.render();
|
|
1968
|
+
});
|
|
1969
|
+
ws.on("message", (data) => {
|
|
1970
|
+
try {
|
|
1971
|
+
const event = JSON.parse(data.toString());
|
|
1972
|
+
if (event.type === "message" && event.data) {
|
|
1973
|
+
const msg = {
|
|
1974
|
+
...event.data,
|
|
1975
|
+
authorShort: formatAddress(event.data.author, 6),
|
|
1976
|
+
};
|
|
1977
|
+
if (!displayedMessageIds.has(msg.messageId)) {
|
|
1978
|
+
displayedMessageIds.add(msg.messageId);
|
|
1979
|
+
const isOwn = msg.author.toLowerCase() === userAddress.toLowerCase();
|
|
1980
|
+
log(formatChatMessage(msg, isOwn));
|
|
1981
|
+
screen.render();
|
|
1982
|
+
}
|
|
1983
|
+
}
|
|
1984
|
+
else if (event.type === "lease_expired") {
|
|
1985
|
+
log(chalk.yellow("⏱️ Your lease has expired. Type /renew to continue."));
|
|
1986
|
+
leaseInfo = null;
|
|
1987
|
+
updateChatHeader(true);
|
|
1988
|
+
screen.render();
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1991
|
+
catch (error) {
|
|
1992
|
+
// Ignore parse errors
|
|
1993
|
+
}
|
|
1994
|
+
});
|
|
1995
|
+
ws.on("error", (error) => {
|
|
1996
|
+
log(chalk.red(`❌ WebSocket error: ${error.message}`));
|
|
1997
|
+
screen.render();
|
|
1998
|
+
});
|
|
1999
|
+
ws.on("close", () => {
|
|
2000
|
+
if (isInChatMode) {
|
|
2001
|
+
log(chalk.yellow("⚠️ WebSocket connection closed"));
|
|
2002
|
+
updateChatHeader(false);
|
|
2003
|
+
screen.render();
|
|
2004
|
+
}
|
|
2005
|
+
});
|
|
2006
|
+
// Start header update interval
|
|
2007
|
+
headerUpdateInterval = setInterval(async () => {
|
|
2008
|
+
if (leaseInfo && isInChatMode) {
|
|
2009
|
+
await updateChatHeader(true);
|
|
2010
|
+
}
|
|
2011
|
+
}, 1000);
|
|
2012
|
+
// Wait for connection
|
|
2013
|
+
await new Promise((resolve, reject) => {
|
|
2014
|
+
const timeout = setTimeout(() => reject(new Error("Connection timeout")), 10000);
|
|
2015
|
+
ws.once("open", () => {
|
|
2016
|
+
clearTimeout(timeout);
|
|
2017
|
+
resolve();
|
|
2018
|
+
});
|
|
2019
|
+
ws.once("error", (err) => {
|
|
2020
|
+
clearTimeout(timeout);
|
|
2021
|
+
reject(err);
|
|
2022
|
+
});
|
|
2023
|
+
});
|
|
2024
|
+
// Set up chat input handler
|
|
2025
|
+
const chatInputHandler = async (value) => {
|
|
2026
|
+
const trimmed = value.trim();
|
|
2027
|
+
// Clear input immediately
|
|
2028
|
+
inputBox.clearValue();
|
|
2029
|
+
screen.render();
|
|
2030
|
+
if (!trimmed) {
|
|
2031
|
+
isProcessing = false;
|
|
2032
|
+
inputBox.focus();
|
|
2033
|
+
screen.render();
|
|
2034
|
+
return;
|
|
2035
|
+
}
|
|
2036
|
+
// Handle commands
|
|
2037
|
+
if (trimmed.startsWith("/")) {
|
|
2038
|
+
const [cmd, ...cmdArgs] = trimmed.split(" ");
|
|
2039
|
+
switch (cmd) {
|
|
2040
|
+
case "/exit":
|
|
2041
|
+
case "/quit":
|
|
2042
|
+
isInChatMode = false;
|
|
2043
|
+
if (headerUpdateInterval) {
|
|
2044
|
+
clearInterval(headerUpdateInterval);
|
|
2045
|
+
headerUpdateInterval = null;
|
|
2046
|
+
}
|
|
2047
|
+
if (ws) {
|
|
2048
|
+
ws.close();
|
|
2049
|
+
ws = null;
|
|
2050
|
+
}
|
|
2051
|
+
log(chalk.yellow("Exited chat mode. Back to shell."));
|
|
2052
|
+
log("");
|
|
2053
|
+
// Restore original header
|
|
2054
|
+
const originalContent = await buildWelcomeContent(currentTheme, catFaces[currentCatIndex].face);
|
|
2055
|
+
headerBox.setContent(originalContent);
|
|
2056
|
+
// Restore original input handler
|
|
2057
|
+
inputBox.removeAllListeners("submit");
|
|
2058
|
+
inputBox.on("submit", originalSubmitHandler);
|
|
2059
|
+
isProcessing = false;
|
|
2060
|
+
inputBox.focus();
|
|
2061
|
+
screen.render();
|
|
2062
|
+
return;
|
|
2063
|
+
case "/renew":
|
|
2064
|
+
try {
|
|
2065
|
+
log(chalk.blue("Renewing lease..."));
|
|
2066
|
+
screen.render();
|
|
2067
|
+
const renewal = await renewLease(client, userAddress, leaseInfo?.leaseId);
|
|
2068
|
+
leaseInfo = {
|
|
2069
|
+
leaseId: renewal.leaseId,
|
|
2070
|
+
leaseExpiresAt: new Date(renewal.leaseExpiresAt),
|
|
2071
|
+
};
|
|
2072
|
+
await updateChatHeader(true);
|
|
2073
|
+
log(chalk.green("✅ Lease renewed!"));
|
|
2074
|
+
screen.render();
|
|
2075
|
+
}
|
|
2076
|
+
catch (error) {
|
|
2077
|
+
log(chalk.red(`❌ Renew failed: ${error instanceof Error ? error.message : String(error)}`));
|
|
2078
|
+
screen.render();
|
|
2079
|
+
}
|
|
2080
|
+
isProcessing = false;
|
|
2081
|
+
inputBox.focus();
|
|
2082
|
+
screen.render();
|
|
2083
|
+
return;
|
|
2084
|
+
case "/help":
|
|
2085
|
+
log(chalk.cyan("Chat commands: /exit, /quit, /renew, /help"));
|
|
2086
|
+
screen.render();
|
|
2087
|
+
isProcessing = false;
|
|
2088
|
+
inputBox.focus();
|
|
2089
|
+
screen.render();
|
|
2090
|
+
return;
|
|
2091
|
+
default:
|
|
2092
|
+
log(chalk.yellow(`Unknown command: ${cmd}. Type /help for commands.`));
|
|
2093
|
+
screen.render();
|
|
2094
|
+
isProcessing = false;
|
|
2095
|
+
inputBox.focus();
|
|
2096
|
+
screen.render();
|
|
2097
|
+
return;
|
|
2098
|
+
}
|
|
2099
|
+
}
|
|
2100
|
+
// Send message
|
|
2101
|
+
if (!leaseInfo || leaseInfo.leaseExpiresAt.getTime() <= Date.now()) {
|
|
2102
|
+
log(chalk.yellow("⏱️ Your lease has expired. Type /renew to continue."));
|
|
2103
|
+
screen.render();
|
|
2104
|
+
isProcessing = false;
|
|
2105
|
+
inputBox.focus();
|
|
2106
|
+
screen.render();
|
|
2107
|
+
return;
|
|
2108
|
+
}
|
|
2109
|
+
try {
|
|
2110
|
+
// Send message - it will appear via WebSocket when received
|
|
2111
|
+
await sendChatMessage(client, trimmed, leaseInfo.leaseId, userAddress);
|
|
2112
|
+
// Re-attach handler for next message
|
|
2113
|
+
inputBox.removeAllListeners("submit");
|
|
2114
|
+
inputBox.once("submit", chatInputHandler);
|
|
2115
|
+
isProcessing = false;
|
|
2116
|
+
inputBox.focus();
|
|
2117
|
+
screen.render();
|
|
2118
|
+
}
|
|
2119
|
+
catch (error) {
|
|
2120
|
+
log(chalk.red(`❌ Send failed: ${error instanceof Error ? error.message : String(error)}`));
|
|
2121
|
+
screen.render();
|
|
2122
|
+
// Re-attach handler for next message
|
|
2123
|
+
inputBox.removeAllListeners("submit");
|
|
2124
|
+
inputBox.once("submit", chatInputHandler);
|
|
2125
|
+
isProcessing = false;
|
|
2126
|
+
inputBox.focus();
|
|
2127
|
+
screen.render();
|
|
2128
|
+
}
|
|
2129
|
+
};
|
|
2130
|
+
// Temporarily replace input handler
|
|
2131
|
+
// Remove all listeners first to ensure clean state
|
|
2132
|
+
inputBox.removeAllListeners("submit");
|
|
2133
|
+
// Clear any existing input value to prevent state issues
|
|
2134
|
+
inputBox.clearValue();
|
|
2135
|
+
// Add the chat handler - only one handler should be active
|
|
2136
|
+
inputBox.once("submit", chatInputHandler);
|
|
2137
|
+
// Ensure input box has focus and is ready
|
|
2138
|
+
inputBox.focus();
|
|
2139
|
+
screen.render();
|
|
2140
|
+
}
|
|
2141
|
+
catch (error) {
|
|
2142
|
+
log(chalk.red(`❌ Chat error: ${error instanceof Error ? error.message : String(error)}`));
|
|
2143
|
+
if (headerUpdateInterval) {
|
|
2144
|
+
clearInterval(headerUpdateInterval);
|
|
2145
|
+
headerUpdateInterval = null;
|
|
2146
|
+
}
|
|
2147
|
+
if (ws)
|
|
2148
|
+
ws.close();
|
|
2149
|
+
// Restore original header on error
|
|
2150
|
+
const originalContent = await buildWelcomeContent(currentTheme, catFaces[currentCatIndex].face);
|
|
2151
|
+
headerBox.setContent(originalContent);
|
|
2152
|
+
// Restore original handler on error
|
|
2153
|
+
inputBox.removeAllListeners("submit");
|
|
2154
|
+
inputBox.on("submit", originalSubmitHandler);
|
|
2155
|
+
screen.render();
|
|
2156
|
+
inputBox.focus();
|
|
2157
|
+
screen.render();
|
|
2158
|
+
}
|
|
2159
|
+
}
|
|
2160
|
+
/**
|
|
2161
|
+
* Start agent chat mode (similar to startChatInShell)
|
|
2162
|
+
*/
|
|
2163
|
+
async function startAgentChatMode(client, log, logLines, screen, inputBox, headerBox, buildWelcomeContent, currentTheme, catFaces, currentCatIndex) {
|
|
2164
|
+
let isProcessing = false;
|
|
2165
|
+
// Get agent config
|
|
2166
|
+
const agentConfig = config.getAIAgentConfig();
|
|
2167
|
+
if (!agentConfig) {
|
|
2168
|
+
throw new Error("Cat not configured");
|
|
2169
|
+
}
|
|
2170
|
+
const apiKey = config.getAIAgentApiKey();
|
|
2171
|
+
if (!apiKey) {
|
|
2172
|
+
throw new Error("Failed to get API key");
|
|
2173
|
+
}
|
|
2174
|
+
// Create LLM and agent
|
|
2175
|
+
const llm = createLLM(agentConfig, apiKey);
|
|
2176
|
+
const agent = createHttpcatAgent(client, llm);
|
|
2177
|
+
// Helper to update header
|
|
2178
|
+
const updateAgentHeader = async () => {
|
|
2179
|
+
const colorTag = currentTheme === "dark" ? "green-fg" : "black-fg";
|
|
2180
|
+
const cyanColor = currentTheme === "dark" ? "light-cyan-fg" : "cyan-fg";
|
|
2181
|
+
const lines = [];
|
|
2182
|
+
lines.push(`{${colorTag}}🐱 Cat Chat Mode{/${colorTag}}`);
|
|
2183
|
+
lines.push(`{${colorTag}}${"═".repeat(60)}{/${colorTag}}`);
|
|
2184
|
+
lines.push("");
|
|
2185
|
+
lines.push(`{${cyanColor}}💡 Ask me to buy tokens, check balances, list tokens, and more!{/${cyanColor}}`);
|
|
2186
|
+
lines.push(`{${cyanColor}}💡 Type /exit to return to shell{/${cyanColor}}`);
|
|
2187
|
+
lines.push("");
|
|
2188
|
+
const content = lines.join("\n");
|
|
2189
|
+
headerBox.setContent(content);
|
|
2190
|
+
screen.render();
|
|
2191
|
+
};
|
|
2192
|
+
// Update header to show agent mode
|
|
2193
|
+
await updateAgentHeader();
|
|
2194
|
+
// Store original submit handler
|
|
2195
|
+
const originalHandlers = inputBox.listeners("submit");
|
|
2196
|
+
const originalSubmitHandler = originalHandlers[0];
|
|
2197
|
+
// Set up agent input handler (similar to chat mode)
|
|
2198
|
+
const agentInputHandler = async (value) => {
|
|
2199
|
+
// Guard against double processing
|
|
2200
|
+
if (isProcessing) {
|
|
2201
|
+
return;
|
|
2202
|
+
}
|
|
2203
|
+
isProcessing = true;
|
|
2204
|
+
const trimmed = value.trim();
|
|
2205
|
+
// Clear input immediately
|
|
2206
|
+
inputBox.clearValue();
|
|
2207
|
+
screen.render();
|
|
2208
|
+
if (!trimmed) {
|
|
2209
|
+
isProcessing = false;
|
|
2210
|
+
inputBox.focus();
|
|
2211
|
+
screen.render();
|
|
2212
|
+
return;
|
|
2213
|
+
}
|
|
2214
|
+
// Handle commands
|
|
2215
|
+
if (trimmed.startsWith("/")) {
|
|
2216
|
+
const [cmd] = trimmed.split(" ");
|
|
2217
|
+
switch (cmd) {
|
|
2218
|
+
case "/exit":
|
|
2219
|
+
case "/quit":
|
|
2220
|
+
log(chalk.yellow("Exited cat mode. Back to shell."));
|
|
2221
|
+
log("");
|
|
2222
|
+
// Restore original header
|
|
2223
|
+
const originalContent = await buildWelcomeContent(currentTheme, catFaces[currentCatIndex].face);
|
|
2224
|
+
headerBox.setContent(originalContent);
|
|
2225
|
+
// Restore original input handler
|
|
2226
|
+
inputBox.removeAllListeners("submit");
|
|
2227
|
+
inputBox.on("submit", originalSubmitHandler);
|
|
2228
|
+
isProcessing = false;
|
|
2229
|
+
inputBox.focus();
|
|
2230
|
+
screen.render();
|
|
2231
|
+
return;
|
|
2232
|
+
case "/help":
|
|
2233
|
+
log(chalk.cyan("Cat commands: /exit, /quit, /help"));
|
|
2234
|
+
isProcessing = false;
|
|
2235
|
+
inputBox.focus();
|
|
2236
|
+
screen.render();
|
|
2237
|
+
return;
|
|
2238
|
+
default:
|
|
2239
|
+
log(chalk.yellow(`Unknown command: ${cmd}. Type /help for commands.`));
|
|
2240
|
+
isProcessing = false;
|
|
2241
|
+
inputBox.focus();
|
|
2242
|
+
screen.render();
|
|
2243
|
+
return;
|
|
2244
|
+
}
|
|
2245
|
+
}
|
|
2246
|
+
// Send to agent
|
|
2247
|
+
try {
|
|
2248
|
+
log(chalk.blue("🐱 Cat thinking..."));
|
|
2249
|
+
screen.render();
|
|
2250
|
+
const response = await chatWithAgent(agent, llm, trimmed);
|
|
2251
|
+
log(chalk.green("🐱 Cat:"));
|
|
2252
|
+
log(response);
|
|
2253
|
+
log("");
|
|
2254
|
+
}
|
|
2255
|
+
catch (error) {
|
|
2256
|
+
const errorMsg = error.message || String(error);
|
|
2257
|
+
const errorStack = error.stack || "";
|
|
2258
|
+
// Log full error details for debugging
|
|
2259
|
+
if (process.env.HTTPCAT_DEBUG) {
|
|
2260
|
+
log(chalk.dim(`Debug: Full error: ${JSON.stringify(error, null, 2)}`));
|
|
2261
|
+
if (errorStack) {
|
|
2262
|
+
log(chalk.dim(`Debug: Stack trace: ${errorStack}`));
|
|
2263
|
+
}
|
|
2264
|
+
}
|
|
2265
|
+
log(chalk.red(`❌ Agent error: ${errorMsg}`));
|
|
2266
|
+
// Check for authentication errors
|
|
2267
|
+
const isAuthError = errorMsg.includes("Authentication failed") ||
|
|
2268
|
+
errorMsg.includes("API key") ||
|
|
2269
|
+
errorMsg.includes("401") ||
|
|
2270
|
+
errorMsg.includes("403") ||
|
|
2271
|
+
errorMsg.includes("Unauthorized") ||
|
|
2272
|
+
errorMsg.includes("Invalid API key") ||
|
|
2273
|
+
errorMsg.includes("authentication") ||
|
|
2274
|
+
errorMsg.includes("Invalid API Key") ||
|
|
2275
|
+
errorStack.includes("401") ||
|
|
2276
|
+
errorStack.includes("403");
|
|
2277
|
+
if (isAuthError) {
|
|
2278
|
+
log("");
|
|
2279
|
+
log(chalk.yellow("🔑 Authentication Error Detected"));
|
|
2280
|
+
log(chalk.dim("Your API key may be invalid, expired, or incorrectly configured."));
|
|
2281
|
+
log(chalk.dim("Exit cat mode and run 'agent' (or 'cat') again to re-run the setup wizard."));
|
|
2282
|
+
}
|
|
2283
|
+
log("");
|
|
2284
|
+
}
|
|
2285
|
+
isProcessing = false;
|
|
2286
|
+
inputBox.focus();
|
|
2287
|
+
screen.render();
|
|
2288
|
+
};
|
|
2289
|
+
// Remove original handler and add agent handler
|
|
2290
|
+
inputBox.removeAllListeners("submit");
|
|
2291
|
+
inputBox.on("submit", agentInputHandler);
|
|
2292
|
+
// Clear and focus input
|
|
2293
|
+
inputBox.clearValue();
|
|
2294
|
+
inputBox.focus();
|
|
2295
|
+
screen.render();
|
|
2296
|
+
// Show welcome message
|
|
2297
|
+
log(chalk.green("🐱 Cat chat mode activated!"));
|
|
2298
|
+
log(chalk.dim("Ask me anything about tokens, trading, or your portfolio."));
|
|
2299
|
+
log(chalk.dim("Type /exit to return to shell."));
|
|
2300
|
+
log("");
|
|
2301
|
+
}
|
|
481
2302
|
//# sourceMappingURL=shell.js.map
|