httpcat-cli 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (322) hide show
  1. package/.github/workflows/ci.yml +3 -0
  2. package/.github/workflows/rc-publish.yml +6 -0
  3. package/.github/workflows/release.yml +102 -0
  4. package/.github/workflows/sync-version.yml +31 -2
  5. package/README.md +1408 -109
  6. package/additions.txt +3 -0
  7. package/bun.lock +260 -25
  8. package/dist/agent/autonomous-trader.d.ts.map +1 -0
  9. package/dist/agent/autonomous-trader.js +362 -0
  10. package/dist/agent/autonomous-trader.js.map +1 -0
  11. package/dist/agent/ax-agent.d.ts.map +1 -1
  12. package/dist/agent/ax-agent.js +356 -18
  13. package/dist/agent/ax-agent.js.map +1 -1
  14. package/dist/agent/event-client.d.ts.map +1 -0
  15. package/dist/agent/event-client.js +82 -0
  16. package/dist/agent/event-client.js.map +1 -0
  17. package/dist/agent/log-stream.d.ts.map +1 -0
  18. package/dist/agent/log-stream.js +95 -0
  19. package/dist/agent/log-stream.js.map +1 -0
  20. package/dist/agent/memory/conversation-session.d.ts.map +1 -0
  21. package/dist/agent/memory/conversation-session.js +232 -0
  22. package/dist/agent/memory/conversation-session.js.map +1 -0
  23. package/dist/agent/memory/conversation-store.d.ts.map +1 -0
  24. package/dist/agent/memory/conversation-store.js +214 -0
  25. package/dist/agent/memory/conversation-store.js.map +1 -0
  26. package/dist/agent/memory/database-schema.d.ts.map +1 -0
  27. package/dist/agent/memory/database-schema.js +355 -0
  28. package/dist/agent/memory/database-schema.js.map +1 -0
  29. package/dist/agent/memory/decision-tracker.d.ts.map +1 -0
  30. package/dist/agent/memory/decision-tracker.js +274 -0
  31. package/dist/agent/memory/decision-tracker.js.map +1 -0
  32. package/dist/agent/memory/memory-manager.d.ts.map +1 -0
  33. package/dist/agent/memory/memory-manager.js +187 -0
  34. package/dist/agent/memory/memory-manager.js.map +1 -0
  35. package/dist/agent/memory/types.d.ts.map +1 -0
  36. package/dist/agent/memory/types.js +5 -0
  37. package/dist/agent/memory/types.js.map +1 -0
  38. package/dist/agent/message-formatter.d.ts.map +1 -0
  39. package/dist/agent/message-formatter.js +76 -0
  40. package/dist/agent/message-formatter.js.map +1 -0
  41. package/dist/agent/position-db.d.ts.map +1 -0
  42. package/dist/agent/position-db.js +154 -0
  43. package/dist/agent/position-db.js.map +1 -0
  44. package/dist/agent/simple-chat-ui-static.d.ts.map +1 -0
  45. package/dist/agent/simple-chat-ui-static.js +129 -0
  46. package/dist/agent/simple-chat-ui-static.js.map +1 -0
  47. package/dist/agent/simple-chat-ui.d.ts.map +1 -0
  48. package/dist/agent/simple-chat-ui.js +90 -0
  49. package/dist/agent/simple-chat-ui.js.map +1 -0
  50. package/dist/agent/tools.d.ts.map +1 -1
  51. package/dist/agent/tools.js +297 -4
  52. package/dist/agent/tools.js.map +1 -1
  53. package/dist/agent/ui.d.ts.map +1 -0
  54. package/dist/agent/ui.js +84 -0
  55. package/dist/agent/ui.js.map +1 -0
  56. package/dist/agent/unified-runtime.d.ts.map +1 -0
  57. package/dist/agent/unified-runtime.js +397 -0
  58. package/dist/agent/unified-runtime.js.map +1 -0
  59. package/dist/client.d.ts.map +1 -1
  60. package/dist/client.js +272 -21
  61. package/dist/client.js.map +1 -1
  62. package/dist/commands/account.d.ts.map +1 -1
  63. package/dist/commands/account.js +187 -33
  64. package/dist/commands/account.js.map +1 -1
  65. package/dist/commands/agent.d.ts.map +1 -0
  66. package/dist/commands/agent.js +125 -0
  67. package/dist/commands/agent.js.map +1 -0
  68. package/dist/commands/approve.d.ts.map +1 -0
  69. package/dist/commands/approve.js +505 -0
  70. package/dist/commands/approve.js.map +1 -0
  71. package/dist/commands/automation.d.ts.map +1 -0
  72. package/dist/commands/automation.js +346 -0
  73. package/dist/commands/automation.js.map +1 -0
  74. package/dist/commands/balances.d.ts.map +1 -1
  75. package/dist/commands/balances.js +226 -73
  76. package/dist/commands/balances.js.map +1 -1
  77. package/dist/commands/buy.d.ts.map +1 -1
  78. package/dist/commands/buy.js +149 -146
  79. package/dist/commands/buy.js.map +1 -1
  80. package/dist/commands/call.d.ts.map +1 -0
  81. package/dist/commands/call.js +51 -0
  82. package/dist/commands/call.js.map +1 -0
  83. package/dist/commands/cex.d.ts.map +1 -0
  84. package/dist/commands/cex.js +958 -0
  85. package/dist/commands/cex.js.map +1 -0
  86. package/dist/commands/chat.d.ts.map +1 -1
  87. package/dist/commands/chat.js +169 -411
  88. package/dist/commands/chat.js.map +1 -1
  89. package/dist/commands/claim.d.ts.map +1 -1
  90. package/dist/commands/claim.js +313 -29
  91. package/dist/commands/claim.js.map +1 -1
  92. package/dist/commands/create.d.ts.map +1 -1
  93. package/dist/commands/create.js +151 -43
  94. package/dist/commands/create.js.map +1 -1
  95. package/dist/commands/gasless-swap.d.ts.map +1 -0
  96. package/dist/commands/gasless-swap.js +232 -0
  97. package/dist/commands/gasless-swap.js.map +1 -0
  98. package/dist/commands/health.d.ts.map +1 -1
  99. package/dist/commands/health.js +63 -7
  100. package/dist/commands/health.js.map +1 -1
  101. package/dist/commands/info.d.ts.map +1 -1
  102. package/dist/commands/info.js +131 -47
  103. package/dist/commands/info.js.map +1 -1
  104. package/dist/commands/launchpad.d.ts.map +1 -0
  105. package/dist/commands/launchpad.js +708 -0
  106. package/dist/commands/launchpad.js.map +1 -0
  107. package/dist/commands/list.d.ts.map +1 -1
  108. package/dist/commands/list.js +57 -23
  109. package/dist/commands/list.js.map +1 -1
  110. package/dist/commands/market.d.ts.map +1 -0
  111. package/dist/commands/market.js +960 -0
  112. package/dist/commands/market.js.map +1 -0
  113. package/dist/commands/mcp-install.d.ts.map +1 -0
  114. package/dist/commands/mcp-install.js +387 -0
  115. package/dist/commands/mcp-install.js.map +1 -0
  116. package/dist/commands/opps.d.ts.map +1 -0
  117. package/dist/commands/opps.js +409 -0
  118. package/dist/commands/opps.js.map +1 -0
  119. package/dist/commands/perps.d.ts.map +1 -0
  120. package/dist/commands/perps.js +248 -0
  121. package/dist/commands/perps.js.map +1 -0
  122. package/dist/commands/portfolio.d.ts.map +1 -0
  123. package/dist/commands/portfolio.js +679 -0
  124. package/dist/commands/portfolio.js.map +1 -0
  125. package/dist/commands/positions.d.ts.map +1 -1
  126. package/dist/commands/positions.js +76 -47
  127. package/dist/commands/positions.js.map +1 -1
  128. package/dist/commands/predict.d.ts.map +1 -0
  129. package/dist/commands/predict.js +280 -0
  130. package/dist/commands/predict.js.map +1 -0
  131. package/dist/commands/predictions.d.ts.map +1 -0
  132. package/dist/commands/predictions.js +486 -0
  133. package/dist/commands/predictions.js.map +1 -0
  134. package/dist/commands/risk.d.ts.map +1 -0
  135. package/dist/commands/risk.js +225 -0
  136. package/dist/commands/risk.js.map +1 -0
  137. package/dist/commands/security.d.ts.map +1 -0
  138. package/dist/commands/security.js +244 -0
  139. package/dist/commands/security.js.map +1 -0
  140. package/dist/commands/sell.d.ts.map +1 -1
  141. package/dist/commands/sell.js +67 -34
  142. package/dist/commands/sell.js.map +1 -1
  143. package/dist/commands/send.d.ts.map +1 -0
  144. package/dist/commands/send.js +733 -0
  145. package/dist/commands/send.js.map +1 -0
  146. package/dist/commands/sign.d.ts.map +1 -0
  147. package/dist/commands/sign.js +1048 -0
  148. package/dist/commands/sign.js.map +1 -0
  149. package/dist/commands/swap.d.ts.map +1 -0
  150. package/dist/commands/swap.js +744 -0
  151. package/dist/commands/swap.js.map +1 -0
  152. package/dist/commands/system.d.ts.map +1 -0
  153. package/dist/commands/system.js +417 -0
  154. package/dist/commands/system.js.map +1 -0
  155. package/dist/commands/tools/index.d.ts.map +1 -0
  156. package/dist/commands/tools/index.js +2040 -0
  157. package/dist/commands/tools/index.js.map +1 -0
  158. package/dist/commands/trade.d.ts.map +1 -0
  159. package/dist/commands/trade.js +237 -0
  160. package/dist/commands/trade.js.map +1 -0
  161. package/dist/commands/transactions.d.ts.map +1 -1
  162. package/dist/commands/transactions.js +29 -17
  163. package/dist/commands/transactions.js.map +1 -1
  164. package/dist/commands/update.d.ts.map +1 -0
  165. package/dist/commands/update.js +429 -0
  166. package/dist/commands/update.js.map +1 -0
  167. package/dist/config.d.ts.map +1 -1
  168. package/dist/config.js +351 -40
  169. package/dist/config.js.map +1 -1
  170. package/dist/index.js +4524 -924
  171. package/dist/index.js.map +1 -1
  172. package/dist/interactive/art.d.ts.map +1 -1
  173. package/dist/interactive/art.js +33 -1
  174. package/dist/interactive/art.js.map +1 -1
  175. package/dist/interactive/shell.d.ts.map +1 -1
  176. package/dist/interactive/shell.js +467 -2652
  177. package/dist/interactive/shell.js.map +1 -1
  178. package/dist/mcp/context.d.ts.map +1 -0
  179. package/dist/mcp/context.js +211 -0
  180. package/dist/mcp/context.js.map +1 -0
  181. package/dist/mcp/onboarding.d.ts.map +1 -0
  182. package/dist/mcp/onboarding.js +266 -0
  183. package/dist/mcp/onboarding.js.map +1 -0
  184. package/dist/mcp/resources.d.ts.map +1 -0
  185. package/dist/mcp/resources.js +222 -0
  186. package/dist/mcp/resources.js.map +1 -0
  187. package/dist/mcp/server.d.ts.map +1 -1
  188. package/dist/mcp/server.js +51 -1
  189. package/dist/mcp/server.js.map +1 -1
  190. package/dist/mcp/tools.d.ts.map +1 -1
  191. package/dist/mcp/tools.js +4119 -169
  192. package/dist/mcp/tools.js.map +1 -1
  193. package/dist/mcp/types.d.ts.map +1 -1
  194. package/dist/types/agent-info.d.ts.map +1 -0
  195. package/dist/types/agent-info.js +11 -0
  196. package/dist/types/agent-info.js.map +1 -0
  197. package/dist/ui/components/ScrollableList.d.ts.map +1 -0
  198. package/dist/ui/components/ScrollableList.js +72 -0
  199. package/dist/ui/components/ScrollableList.js.map +1 -0
  200. package/dist/ui/components/ThemeProvider.d.ts.map +1 -0
  201. package/dist/ui/components/ThemeProvider.js +87 -0
  202. package/dist/ui/components/ThemeProvider.js.map +1 -0
  203. package/dist/ui/components/ThemedBox.d.ts.map +1 -0
  204. package/dist/ui/components/ThemedBox.js +24 -0
  205. package/dist/ui/components/ThemedBox.js.map +1 -0
  206. package/dist/ui/components/agent/ChatHeader.d.ts.map +1 -0
  207. package/dist/ui/components/agent/ChatHeader.js +39 -0
  208. package/dist/ui/components/agent/ChatHeader.js.map +1 -0
  209. package/dist/ui/components/agent/Header.d.ts.map +1 -0
  210. package/dist/ui/components/agent/Header.js +14 -0
  211. package/dist/ui/components/agent/Header.js.map +1 -0
  212. package/dist/ui/components/agent/Input.d.ts.map +1 -0
  213. package/dist/ui/components/agent/Input.js +23 -0
  214. package/dist/ui/components/agent/Input.js.map +1 -0
  215. package/dist/ui/components/agent/Output.d.ts.map +1 -0
  216. package/dist/ui/components/agent/Output.js +23 -0
  217. package/dist/ui/components/agent/Output.js.map +1 -0
  218. package/dist/ui/components/chat/TokenChatUI.d.ts.map +1 -0
  219. package/dist/ui/components/chat/TokenChatUI.js +133 -0
  220. package/dist/ui/components/chat/TokenChatUI.js.map +1 -0
  221. package/dist/ui/components/shell/ShellHeader.d.ts.map +1 -0
  222. package/dist/ui/components/shell/ShellHeader.js +31 -0
  223. package/dist/ui/components/shell/ShellHeader.js.map +1 -0
  224. package/dist/ui/components/shell/ShellInput.d.ts.map +1 -0
  225. package/dist/ui/components/shell/ShellInput.js +151 -0
  226. package/dist/ui/components/shell/ShellInput.js.map +1 -0
  227. package/dist/ui/components/shell/ShellOutput.d.ts.map +1 -0
  228. package/dist/ui/components/shell/ShellOutput.js +8 -0
  229. package/dist/ui/components/shell/ShellOutput.js.map +1 -0
  230. package/dist/ui/hooks/useChatWebSocket.d.ts.map +1 -0
  231. package/dist/ui/hooks/useChatWebSocket.js +76 -0
  232. package/dist/ui/hooks/useChatWebSocket.js.map +1 -0
  233. package/dist/ui/hooks/useCommandHistory.d.ts.map +1 -0
  234. package/dist/ui/hooks/useCommandHistory.js +70 -0
  235. package/dist/ui/hooks/useCommandHistory.js.map +1 -0
  236. package/dist/ui/hooks/useDebounce.d.ts.map +1 -0
  237. package/dist/ui/hooks/useDebounce.js +17 -0
  238. package/dist/ui/hooks/useDebounce.js.map +1 -0
  239. package/dist/ui/hooks/useLogStream.d.ts.map +1 -0
  240. package/dist/ui/hooks/useLogStream.js +20 -0
  241. package/dist/ui/hooks/useLogStream.js.map +1 -0
  242. package/dist/ui/hooks/useVirtualScroll.d.ts.map +1 -0
  243. package/dist/ui/hooks/useVirtualScroll.js +70 -0
  244. package/dist/ui/hooks/useVirtualScroll.js.map +1 -0
  245. package/dist/utils/admin.d.ts.map +1 -0
  246. package/dist/utils/admin.js +144 -0
  247. package/dist/utils/admin.js.map +1 -0
  248. package/dist/utils/autoSetup.d.ts.map +1 -0
  249. package/dist/utils/autoSetup.js +252 -0
  250. package/dist/utils/autoSetup.js.map +1 -0
  251. package/dist/utils/build-constants.d.ts.map +1 -0
  252. package/dist/utils/build-constants.js +10 -0
  253. package/dist/utils/build-constants.js.map +1 -0
  254. package/dist/utils/constants.d.ts.map +1 -1
  255. package/dist/utils/errors.d.ts.map +1 -1
  256. package/dist/utils/errors.js +10 -1
  257. package/dist/utils/errors.js.map +1 -1
  258. package/dist/utils/formatting.d.ts.map +1 -1
  259. package/dist/utils/formatting.js +46 -9
  260. package/dist/utils/formatting.js.map +1 -1
  261. package/dist/utils/llm-cli-config.d.ts.map +1 -0
  262. package/dist/utils/llm-cli-config.js +963 -0
  263. package/dist/utils/llm-cli-config.js.map +1 -0
  264. package/dist/utils/llm-cli-detector.d.ts.map +1 -0
  265. package/dist/utils/llm-cli-detector.js +202 -0
  266. package/dist/utils/llm-cli-detector.js.map +1 -0
  267. package/dist/utils/loading.d.ts.map +1 -1
  268. package/dist/utils/loading.js +25 -3
  269. package/dist/utils/loading.js.map +1 -1
  270. package/dist/utils/maintenance.d.ts.map +1 -0
  271. package/dist/utils/maintenance.js +17 -0
  272. package/dist/utils/maintenance.js.map +1 -0
  273. package/dist/utils/mcp-config.d.ts.map +1 -0
  274. package/dist/utils/mcp-config.js +77 -0
  275. package/dist/utils/mcp-config.js.map +1 -0
  276. package/dist/utils/privateKeyPrompt.d.ts.map +1 -1
  277. package/dist/utils/privateKeyPrompt.js +308 -129
  278. package/dist/utils/privateKeyPrompt.js.map +1 -1
  279. package/dist/utils/process-cleanup.d.ts.map +1 -0
  280. package/dist/utils/process-cleanup.js +136 -0
  281. package/dist/utils/process-cleanup.js.map +1 -0
  282. package/dist/utils/retry.d.ts.map +1 -0
  283. package/dist/utils/retry.js +56 -0
  284. package/dist/utils/retry.js.map +1 -0
  285. package/dist/utils/rpc-helpers.d.ts.map +1 -0
  286. package/dist/utils/rpc-helpers.js +70 -0
  287. package/dist/utils/rpc-helpers.js.map +1 -0
  288. package/dist/utils/rpc-transport.d.ts.map +1 -0
  289. package/dist/utils/rpc-transport.js +87 -0
  290. package/dist/utils/rpc-transport.js.map +1 -0
  291. package/dist/utils/shell-setup.d.ts.map +1 -0
  292. package/dist/utils/shell-setup.js +531 -0
  293. package/dist/utils/shell-setup.js.map +1 -0
  294. package/dist/utils/status.d.ts.map +1 -1
  295. package/dist/utils/status.js +34 -5
  296. package/dist/utils/status.js.map +1 -1
  297. package/dist/utils/token-resolver.d.ts.map +1 -1
  298. package/dist/utils/token-resolver.js +51 -8
  299. package/dist/utils/token-resolver.js.map +1 -1
  300. package/dist/utils/x402-caller.d.ts.map +1 -0
  301. package/dist/utils/x402-caller.js +17 -0
  302. package/dist/utils/x402-caller.js.map +1 -0
  303. package/docs/README.md +28 -0
  304. package/docs/agent/README.md +18 -0
  305. package/docs/api/README.md +41 -0
  306. package/docs/cli/README.md +42 -0
  307. package/docs/guides/README.md +26 -0
  308. package/docs/implementation/README.md +18 -0
  309. package/docs/planning/README.md +19 -0
  310. package/docs/testing/README.md +15 -0
  311. package/docs/ux/README.md +16 -0
  312. package/issues.txt +2 -0
  313. package/package.json +24 -9
  314. package/scripts/cat-spin.sh +417 -0
  315. package/scripts/deprecate-rc-versions.js +58 -0
  316. package/scripts/inject-build-constants.js +43 -0
  317. package/scripts/monitor-foobar.js +117 -0
  318. package/swap.logs +61 -0
  319. package/swapping.txt +108 -0
  320. package/test.txt +12 -0
  321. package/tests/fixtures/test-data.json +16 -0
  322. package/Screenshot 2025-12-21 at 8.56.02/342/200/257PM.png +0 -0
@@ -1,7 +1,6 @@
1
1
  import WebSocket from "ws";
2
2
  import chalk from "chalk";
3
- // @ts-ignore - neo-blessed doesn't have types, but @types/blessed provides compatible types
4
- import blessed from "neo-blessed";
3
+ import { createTokenChatUI } from "../ui/components/chat/TokenChatUI.js";
5
4
  import { formatAddress, formatTokenAmount, formatCurrency, } from "../utils/formatting.js";
6
5
  import { handleError } from "../utils/errors.js";
7
6
  import { config } from "../config.js";
@@ -10,9 +9,9 @@ import { privateKeyToAccount } from "viem/accounts";
10
9
  import { buyToken } from "./buy.js";
11
10
  import { sellToken, parseTokenAmount } from "./sell.js";
12
11
  import { getTokenInfo } from "./info.js";
13
- const CHAT_JOIN_ENTRYPOINT = "chat_join";
14
- const CHAT_MESSAGE_ENTRYPOINT = "chat_message";
15
- const CHAT_RENEW_LEASE_ENTRYPOINT = "chat_renew_lease";
12
+ const CHAT_JOIN_ENTRYPOINT = "chat/join";
13
+ const CHAT_MESSAGE_ENTRYPOINT = "chat/message";
14
+ const CHAT_RENEW_LEASE_ENTRYPOINT = "chat/renew-lease";
16
15
  const LEASE_DURATION_MS = 10 * 60 * 1000; // 10 minutes
17
16
  /**
18
17
  * Normalize WebSocket URL based on agent URL
@@ -146,17 +145,16 @@ function formatMessageText(msg, isOwn = false) {
146
145
  const authorShort = msg.authorShort || formatAddress(msg.author, 6);
147
146
  return `${chalk.dim(`[${timeStr}]`)} ${authorColor(authorShort)}: ${messageColor(msg.message)}`;
148
147
  }
149
- function displayMessage(msg, isOwn = false, isPending = false, messageLogBox) {
148
+ function displayMessage(msg, isOwn = false, isPending = false, chatUI) {
150
149
  // Skip if we've already displayed this message
151
150
  if (displayedMessageIds.has(msg.messageId)) {
152
151
  return;
153
152
  }
154
153
  displayedMessageIds.add(msg.messageId);
155
154
  const messageText = formatMessageText(msg, isOwn);
156
- if (messageLogBox) {
157
- // Use blessed log box - automatically handles scrolling
158
- messageLogBox.log(messageText);
159
- messageLogBox.setScrollPerc(100); // Auto-scroll to bottom
155
+ if (chatUI) {
156
+ // Use Ink chat UI
157
+ chatUI.log(messageText);
160
158
  }
161
159
  else {
162
160
  // Simple console.log when no UI active (JSON mode)
@@ -207,56 +205,11 @@ function formatSellResultCompact(result) {
207
205
  `new price ${chalk.cyan(formatCurrency(result.newPrice))}, ` +
208
206
  `graduation ${chalk.magenta(graduationStatus)}`);
209
207
  }
210
- function updateHeaderBox(headerBox, leaseInfo, tokenName, isConnected = false) {
211
- const title = tokenName
212
- ? `💬 httpcat Chat: ${tokenName}`
213
- : "💬 httpcat Chat Stream";
214
- // Get terminal width, default to 80 if not available
215
- // Account for padding (left + right = 2)
216
- const terminalWidth = process.stdout.columns || 80;
217
- const boxWidth = headerBox.width;
218
- const availableWidth = typeof boxWidth === "number" ? boxWidth - 2 : terminalWidth - 2;
219
- const separatorWidth = Math.max(20, Math.min(availableWidth, terminalWidth - 2));
220
- // Use safe separator that won't overflow
221
- const separator = "═".repeat(separatorWidth);
222
- const lineSeparator = "─".repeat(separatorWidth);
223
- // Build content line by line - use plain text (blessed will handle wrapping)
224
- const lines = [];
225
- lines.push(title);
226
- lines.push(separator);
227
- lines.push("");
228
- if (isConnected) {
229
- lines.push("✅ Connected to chat stream");
230
- lines.push("");
231
- }
232
- lines.push("💰 Entry fee: $0.01 USDC (10 min lease)");
233
- lines.push("💬 Per message: $0.01 USDC");
234
- if (leaseInfo) {
235
- const timeRemaining = formatTimeRemaining(leaseInfo.leaseExpiresAt);
236
- const isExpired = leaseInfo.leaseExpiresAt.getTime() <= Date.now();
237
- const timePrefix = isExpired ? "⚠️ " : "⏱️ ";
238
- lines.push(`${timePrefix}Lease expires in: ${timeRemaining}`);
239
- }
240
- lines.push("");
241
- lines.push("💡 Type your message and press Enter to send");
242
- lines.push("💡 Type /exit or Ctrl+C to quit");
243
- lines.push("💡 Type /renew to renew your lease");
244
- if (tokenName) {
245
- lines.push("💡 Type /buy <amount> to buy tokens");
246
- lines.push("💡 Type /sell <amount> to sell tokens");
247
- }
248
- lines.push("");
249
- lines.push(lineSeparator);
250
- // Clear and set content to prevent overlapping
251
- headerBox.setContent(lines.join("\n"));
252
- }
253
- async function ensureLeaseValid(client, userAddress, leaseInfo, messageLogBox, screen) {
208
+ async function ensureLeaseValid(client, userAddress, leaseInfo, chatUI) {
254
209
  if (!leaseInfo || leaseInfo.leaseExpiresAt.getTime() <= Date.now()) {
255
210
  // Lease expired or doesn't exist, renew it
256
- if (messageLogBox) {
257
- messageLogBox.log(chalk.yellow("⏱️ Lease expired. Renewing..."));
258
- messageLogBox.setScrollPerc(100);
259
- screen?.render();
211
+ if (chatUI) {
212
+ chatUI.log(chalk.yellow("⏱️ Lease expired. Renewing..."));
260
213
  }
261
214
  else {
262
215
  clearLine();
@@ -270,26 +223,21 @@ async function ensureLeaseValid(client, userAddress, leaseInfo, messageLogBox, s
270
223
  }
271
224
  return leaseInfo;
272
225
  }
273
- export async function startChatStream(client, jsonMode = false, tokenIdentifier, inputFormat = "text") {
226
+ export async function startChatStream(client, jsonMode = false, tokenIdentifier, inputFormat = "text", returnToShell = false) {
274
227
  let leaseInfo = null;
275
228
  let userAddress;
276
229
  let tokenAddress; // Track token address from join response
277
230
  let ws = null;
278
231
  let wsUrl = null; // Store websocket URL for reconnection
279
232
  let leaseCheckInterval = null;
280
- let headerUpdateInterval = null;
281
233
  let isExiting = false;
282
234
  let isSending = false; // Track sending state across handlers
283
235
  let currentInput = ""; // Track current input across handlers
284
236
  let pendingMessages = new Map(); // messageId -> tempMessageId (shared between send and receive)
285
237
  let pulseIntervals = new Map(); // tempMessageId -> interval
286
238
  let isCleaningUp = false; // Flag to prevent intervals from writing during cleanup
287
- let messageCount = 0; // Track number of messages displayed (for positioning)
288
- // Blessed UI components (only used in non-JSON mode)
289
- let screen = null;
290
- let headerBox = null;
291
- let messageLogBox = null;
292
- let inputBox = null;
239
+ // Ink UI component (only used in non-JSON mode)
240
+ let chatUI = null;
293
241
  // Get userAddress from private key first (required for all chat operations)
294
242
  try {
295
243
  const privateKey = config.getPrivateKey();
@@ -308,6 +256,7 @@ export async function startChatStream(client, jsonMode = false, tokenIdentifier,
308
256
  throw new Error("Failed to get user address from private key. Please check your configuration.");
309
257
  }
310
258
  // Join chat
259
+ let joinResult;
311
260
  try {
312
261
  if (!jsonMode) {
313
262
  if (tokenIdentifier) {
@@ -317,7 +266,7 @@ export async function startChatStream(client, jsonMode = false, tokenIdentifier,
317
266
  console.log(chalk.dim("Joining general chat room..."));
318
267
  }
319
268
  }
320
- const joinResult = await joinChat(client, userAddress, tokenIdentifier, jsonMode);
269
+ joinResult = await joinChat(client, userAddress, tokenIdentifier, jsonMode);
321
270
  leaseInfo = {
322
271
  leaseId: joinResult.leaseId,
323
272
  leaseExpiresAt: new Date(joinResult.leaseExpiresAt),
@@ -353,10 +302,11 @@ export async function startChatStream(client, jsonMode = false, tokenIdentifier,
353
302
  // Helper function to attach websocket handlers
354
303
  const attachWebSocketHandlers = (websocket) => {
355
304
  websocket.on("open", () => {
356
- // Connection established - update header if using blessed UI
357
- if (!jsonMode && headerBox) {
358
- updateHeaderBox(headerBox, leaseInfo, tokenIdentifier, true);
359
- screen?.render();
305
+ // Connection established - update header if using Ink UI
306
+ if (!jsonMode && chatUI && leaseInfo) {
307
+ chatUI.updateHeader({
308
+ leaseExpiresAt: leaseInfo.leaseExpiresAt,
309
+ });
360
310
  }
361
311
  });
362
312
  websocket.on("message", async (data) => {
@@ -406,33 +356,19 @@ export async function startChatStream(client, jsonMode = false, tokenIdentifier,
406
356
  }
407
357
  // Small delay to ensure intervals have stopped
408
358
  await new Promise((resolve) => setTimeout(resolve, 20));
409
- // Stop pulsing and clear the grey pulsing input line
410
- if (inputBox) {
411
- // Clear input box
412
- inputBox.clearValue();
413
- screen?.render();
414
- }
415
359
  // Display the message (only if not already displayed)
416
- // In JSON mode, displayMessage should never be called, but add explicit check
417
360
  if (!displayedMessageIds.has(msg.messageId)) {
418
- displayMessage(msg, isOwn, false, messageLogBox || undefined);
361
+ displayMessage(msg, isOwn, false, chatUI || undefined);
419
362
  }
420
- // Re-enable input - clear the input line completely
363
+ // Re-enable input
421
364
  isSending = false;
422
365
  currentInput = "";
423
366
  isCleaningUp = false; // Clear cleanup flag
424
- if (inputBox && screen) {
425
- // Clear input box and refocus
426
- inputBox.clearValue();
427
- inputBox.focus();
428
- screen.render();
429
- }
430
367
  }
431
368
  else {
432
369
  // Not our message, just display it normally
433
- // In JSON mode, displayMessage should never be called, but add explicit check
434
370
  if (!displayedMessageIds.has(msg.messageId)) {
435
- displayMessage(msg, isOwn, false, messageLogBox || undefined);
371
+ displayMessage(msg, isOwn, false, chatUI || undefined);
436
372
  }
437
373
  }
438
374
  }
@@ -444,16 +380,15 @@ export async function startChatStream(client, jsonMode = false, tokenIdentifier,
444
380
  }));
445
381
  }
446
382
  else {
447
- if (messageLogBox) {
448
- messageLogBox.log(chalk.yellow("⏱️ Your lease has expired. Type /renew to continue chatting."));
449
- messageLogBox.setScrollPerc(100);
450
- screen?.render();
383
+ if (chatUI) {
384
+ chatUI.log(chalk.yellow("⏱️ Your lease has expired. Type /renew to continue chatting."));
451
385
  }
452
386
  }
453
387
  leaseInfo = null;
454
- if (headerBox) {
455
- updateHeaderBox(headerBox, null, tokenIdentifier, true);
456
- screen?.render();
388
+ if (chatUI) {
389
+ chatUI.updateHeader({
390
+ leaseExpiresAt: undefined,
391
+ });
457
392
  }
458
393
  }
459
394
  else if (event.type === "error") {
@@ -464,10 +399,8 @@ export async function startChatStream(client, jsonMode = false, tokenIdentifier,
464
399
  }));
465
400
  }
466
401
  else {
467
- if (messageLogBox) {
468
- messageLogBox.log(chalk.red(`❌ Error: ${event.error || "Unknown error"}`));
469
- messageLogBox.setScrollPerc(100);
470
- screen?.render();
402
+ if (chatUI) {
403
+ chatUI.log(chalk.red(`❌ Error: ${event.error || "Unknown error"}`));
471
404
  }
472
405
  }
473
406
  }
@@ -488,10 +421,8 @@ export async function startChatStream(client, jsonMode = false, tokenIdentifier,
488
421
  }
489
422
  else {
490
423
  const errorMsg = error.message || "Unknown WebSocket error";
491
- if (messageLogBox) {
492
- messageLogBox.log(chalk.red(`❌ WebSocket error: ${errorMsg}`));
493
- messageLogBox.setScrollPerc(100);
494
- screen?.render();
424
+ if (chatUI) {
425
+ chatUI.log(chalk.red(`❌ WebSocket error: ${errorMsg}`));
495
426
  }
496
427
  }
497
428
  });
@@ -518,9 +449,10 @@ export async function startChatStream(client, jsonMode = false, tokenIdentifier,
518
449
  message = `⚠️ Lease validation failed on server. Your lease appears valid. Type /renew to get a fresh lease.`;
519
450
  }
520
451
  leaseInfo = null; // Mark lease as invalid
521
- if (headerBox) {
522
- updateHeaderBox(headerBox, null, tokenIdentifier, false);
523
- screen?.render();
452
+ if (chatUI) {
453
+ chatUI.updateHeader({
454
+ leaseExpiresAt: undefined,
455
+ });
524
456
  }
525
457
  }
526
458
  else if (code === 1006) {
@@ -529,10 +461,8 @@ export async function startChatStream(client, jsonMode = false, tokenIdentifier,
529
461
  else {
530
462
  message = `⚠️ WebSocket connection closed (code: ${code}): ${reasonStr}`;
531
463
  }
532
- if (messageLogBox) {
533
- messageLogBox.log(chalk.yellow(message));
534
- messageLogBox.setScrollPerc(100);
535
- screen?.render();
464
+ if (chatUI) {
465
+ chatUI.log(chalk.yellow(message));
536
466
  }
537
467
  }
538
468
  });
@@ -549,12 +479,9 @@ export async function startChatStream(client, jsonMode = false, tokenIdentifier,
549
479
  if (retryCount > 0) {
550
480
  // Exponential backoff: 500ms, 1000ms, 2000ms
551
481
  const backoffDelay = Math.min(500 * Math.pow(2, retryCount - 1), 2000);
482
+ // Note: UI not created yet, so can't log to it
552
483
  if (!jsonMode) {
553
- if (messageLogBox) {
554
- messageLogBox.log(chalk.yellow(`🔄 Retrying WebSocket connection (attempt ${retryCount + 1}/${maxRetries})...`));
555
- messageLogBox.setScrollPerc(100);
556
- screen?.render();
557
- }
484
+ console.log(chalk.yellow(`🔄 Retrying WebSocket connection (attempt ${retryCount + 1}/${maxRetries})...`));
558
485
  }
559
486
  await new Promise((resolve) => setTimeout(resolve, backoffDelay));
560
487
  }
@@ -610,10 +537,8 @@ export async function startChatStream(client, jsonMode = false, tokenIdentifier,
610
537
  });
611
538
  connected = true;
612
539
  // Connection established successfully
613
- if (!jsonMode && messageLogBox && retryCount > 0) {
614
- messageLogBox.log(chalk.green("✅ WebSocket connected successfully"));
615
- messageLogBox.setScrollPerc(100);
616
- screen?.render();
540
+ if (!jsonMode && retryCount > 0) {
541
+ console.log(chalk.green("✅ WebSocket connected successfully"));
617
542
  }
618
543
  }
619
544
  catch (error) {
@@ -632,12 +557,8 @@ export async function startChatStream(client, jsonMode = false, tokenIdentifier,
632
557
  }));
633
558
  }
634
559
  else {
635
- if (messageLogBox) {
636
- messageLogBox.log(chalk.red(`❌ ${finalError}`));
637
- messageLogBox.log(chalk.yellow("💡 Try typing /renew to get a fresh lease"));
638
- messageLogBox.setScrollPerc(100);
639
- screen?.render();
640
- }
560
+ console.log(chalk.red(`❌ ${finalError}`));
561
+ console.log(chalk.yellow("💡 Try typing /renew to get a fresh lease"));
641
562
  }
642
563
  throw new Error(finalError);
643
564
  }
@@ -656,135 +577,52 @@ export async function startChatStream(client, jsonMode = false, tokenIdentifier,
656
577
  message: "Your lease has expired. Use /renew to continue chatting.",
657
578
  }));
658
579
  }
659
- else if (messageLogBox) {
660
- messageLogBox.log(chalk.yellow("⏱️ Your lease has expired. Type /renew to continue chatting."));
661
- messageLogBox.setScrollPerc(100);
662
- screen?.render();
580
+ else if (chatUI) {
581
+ chatUI.log(chalk.yellow("⏱️ Your lease has expired. Type /renew to continue chatting."));
663
582
  }
664
583
  leaseInfo = null;
665
- if (headerBox) {
666
- updateHeaderBox(headerBox, null, tokenIdentifier, true);
667
- screen?.render();
584
+ if (chatUI) {
585
+ chatUI.updateHeader({
586
+ leaseExpiresAt: undefined,
587
+ });
668
588
  }
669
589
  }
670
590
  }, 30000);
671
- // Set up blessed UI (only in interactive mode)
591
+ // Set up Ink UI (only in interactive mode)
672
592
  if (!jsonMode) {
673
- // Check if terminal supports blessed
593
+ // Check if terminal supports TTY
674
594
  if (!process.stdout.isTTY) {
675
595
  throw new Error("Chat requires an interactive terminal (TTY). Use --json mode for non-interactive usage.");
676
596
  }
677
- // Create blessed screen and widgets
678
- screen = blessed.screen({
679
- smartCSR: true,
680
- title: "httpcat Chat",
681
- fullUnicode: true,
682
- fastCSR: false, // Disable fast CSR to avoid rendering issues
683
- });
684
- // Calculate header height (approximately 10 lines)
685
- const headerHeight = 10;
686
- const inputHeight = 3;
687
- // Create header box (fixed at top)
688
- headerBox = blessed.box({
689
- top: 0,
690
- left: 0,
691
- width: "100%",
692
- height: headerHeight,
693
- content: "",
694
- tags: false, // Disable tags to avoid rendering issues
695
- wrap: true,
696
- scrollable: false,
697
- alwaysScroll: false,
698
- padding: {
699
- left: 1,
700
- right: 1,
701
- top: 0,
702
- bottom: 0,
703
- },
704
- style: {
705
- fg: "white",
706
- bg: "black",
707
- },
708
- });
709
- // Create message log box (scrollable, middle section)
710
- messageLogBox = blessed.log({
711
- top: headerHeight,
712
- left: 0,
713
- width: "100%",
714
- height: `100%-${headerHeight + inputHeight}`,
715
- tags: true,
716
- scrollable: true,
717
- alwaysScroll: true,
718
- scrollbar: {
719
- ch: " ",
720
- inverse: true,
721
- },
722
- style: {
723
- fg: "white",
724
- bg: "black",
725
- },
726
- });
727
- // Create input box (fixed at bottom)
728
- inputBox = blessed.textbox({
729
- bottom: 0,
730
- left: 0,
731
- width: "100%",
732
- height: inputHeight,
733
- content: "",
734
- inputOnFocus: true,
735
- tags: true,
736
- keys: true,
737
- style: {
738
- fg: "cyan",
739
- bg: "black",
740
- focus: {
741
- fg: "white",
742
- bg: "blue",
743
- },
744
- },
745
- });
746
- // Append widgets to screen
747
- screen.append(headerBox);
748
- screen.append(messageLogBox);
749
- screen.append(inputBox);
750
- // Initial render
751
- screen.render();
752
- // Initial header update
753
- updateHeaderBox(headerBox, leaseInfo, tokenIdentifier, false);
754
- screen.render();
755
- // Display last messages
756
- if (joinResult.lastMessages.length > 0) {
757
- const sortedMessages = [...joinResult.lastMessages].sort((a, b) => {
758
- const timeA = new Date(a.timestamp).getTime();
759
- const timeB = new Date(b.timestamp).getTime();
760
- return timeA - timeB; // Oldest first
761
- });
762
- sortedMessages.forEach((msg) => {
763
- displayedMessageIds.add(msg.messageId);
764
- const isOwn = msg.author === userAddress;
765
- // This is inside !jsonMode block, but add explicit check for safety
766
- displayMessage(msg, isOwn, false, messageLogBox || undefined);
767
- });
597
+ // Get token info for header
598
+ let tokenSymbol;
599
+ if (tokenIdentifier) {
600
+ try {
601
+ const tokenInfo = await getTokenInfo(client, tokenIdentifier, userAddress, true);
602
+ tokenSymbol = tokenInfo.symbol;
603
+ }
604
+ catch {
605
+ // Use identifier if we can't get symbol
606
+ tokenSymbol = tokenIdentifier;
607
+ }
768
608
  }
769
- // Handle terminal resize
770
- screen.on("resize", () => {
771
- screen?.render();
609
+ // Format initial messages
610
+ const initialMessages = joinResult.lastMessages
611
+ .sort((a, b) => {
612
+ const timeA = new Date(a.timestamp).getTime();
613
+ const timeB = new Date(b.timestamp).getTime();
614
+ return timeA - timeB; // Oldest first
615
+ })
616
+ .map((msg) => {
617
+ displayedMessageIds.add(msg.messageId);
618
+ const isOwn = msg.author === userAddress;
619
+ return formatMessageText(msg, isOwn);
772
620
  });
773
- // Update header with lease countdown every second
774
- headerUpdateInterval = setInterval(() => {
775
- if (leaseInfo && headerBox && screen && !isExiting) {
776
- updateHeaderBox(headerBox, leaseInfo, tokenIdentifier, true);
777
- screen.render();
778
- }
779
- }, 1000);
780
- // Handle input submission
781
- inputBox.on("submit", async (value) => {
782
- const trimmed = value.trim();
621
+ // Message handler callback
622
+ const handleMessage = async (text) => {
623
+ const trimmed = text.trim();
783
624
  // Ignore input while sending
784
625
  if (isSending || !trimmed) {
785
- inputBox?.clearValue();
786
- inputBox?.focus();
787
- screen?.render();
788
626
  return;
789
627
  }
790
628
  // Handle commands
@@ -796,11 +634,10 @@ export async function startChatStream(client, jsonMode = false, tokenIdentifier,
796
634
  isExiting = true;
797
635
  if (leaseCheckInterval)
798
636
  clearInterval(leaseCheckInterval);
799
- if (headerUpdateInterval)
800
- clearInterval(headerUpdateInterval);
801
637
  if (ws)
802
638
  ws.close();
803
- screen?.destroy();
639
+ if (chatUI)
640
+ await chatUI.destroy();
804
641
  console.log();
805
642
  printCat("sleeping");
806
643
  console.log(chalk.cyan("Chat disconnected. Goodbye! 👋"));
@@ -808,22 +645,21 @@ export async function startChatStream(client, jsonMode = false, tokenIdentifier,
808
645
  return;
809
646
  case "/renew": {
810
647
  try {
811
- inputBox?.setValue("⏱️ Renewing lease...");
812
- screen?.render();
648
+ if (chatUI) {
649
+ chatUI.log(chalk.yellow("⏱️ Renewing lease..."));
650
+ }
813
651
  const renewal = await renewLease(client, userAddress, leaseInfo?.leaseId);
814
652
  leaseInfo = {
815
653
  leaseId: renewal.leaseId,
816
654
  leaseExpiresAt: new Date(renewal.leaseExpiresAt),
817
655
  };
818
- // Update header first
819
- if (headerBox) {
820
- updateHeaderBox(headerBox, leaseInfo, tokenIdentifier, false);
821
- }
822
- if (messageLogBox) {
823
- messageLogBox.log(chalk.green(`✅ Lease renewed! Expires in ${formatTimeRemaining(leaseInfo.leaseExpiresAt)}`));
824
- messageLogBox.log(chalk.yellow("🔄 Reconnecting to chat stream..."));
825
- messageLogBox.setScrollPerc(100);
826
- screen?.render();
656
+ // Update header
657
+ if (chatUI) {
658
+ chatUI.updateHeader({
659
+ leaseExpiresAt: leaseInfo.leaseExpiresAt,
660
+ });
661
+ chatUI.log(chalk.green(`✅ Lease renewed! Expires in ${formatTimeRemaining(leaseInfo.leaseExpiresAt)}`));
662
+ chatUI.log(chalk.yellow("🔄 Reconnecting to chat stream..."));
827
663
  }
828
664
  // Close old connection properly
829
665
  if (ws) {
@@ -837,9 +673,6 @@ export async function startChatStream(client, jsonMode = false, tokenIdentifier,
837
673
  ws = null;
838
674
  }
839
675
  // Wait for database transaction to commit and old connection to fully close
840
- // The server validates the lease in the open handler, so we need to ensure
841
- // the database update is visible before connecting
842
- // Increased delay to 2 seconds for better database consistency
843
676
  await new Promise((resolve) => setTimeout(resolve, 2000));
844
677
  // Reconnect with new lease - with retry logic
845
678
  if (wsUrl) {
@@ -857,10 +690,8 @@ export async function startChatStream(client, jsonMode = false, tokenIdentifier,
857
690
  try {
858
691
  if (retryCount > 0) {
859
692
  const backoffDelay = Math.min(1000 * Math.pow(2, retryCount - 1), 5000);
860
- if (messageLogBox) {
861
- messageLogBox.log(chalk.yellow(`🔄 Retry ${retryCount}/${maxRetries} in ${backoffDelay}ms...`));
862
- messageLogBox.setScrollPerc(100);
863
- screen?.render();
693
+ if (chatUI) {
694
+ chatUI.log(chalk.yellow(`🔄 Retry ${retryCount}/${maxRetries} in ${backoffDelay}ms...`));
864
695
  }
865
696
  await new Promise((resolve) => setTimeout(resolve, backoffDelay));
866
697
  }
@@ -916,14 +747,12 @@ export async function startChatStream(client, jsonMode = false, tokenIdentifier,
916
747
  });
917
748
  connected = true;
918
749
  // Connection established
919
- if (headerBox) {
920
- updateHeaderBox(headerBox, leaseInfo, tokenIdentifier, true);
921
- }
922
- if (messageLogBox) {
923
- messageLogBox.log(chalk.green("✅ Reconnected to chat stream"));
924
- messageLogBox.setScrollPerc(100);
750
+ if (chatUI) {
751
+ chatUI.updateHeader({
752
+ leaseExpiresAt: leaseInfo?.leaseExpiresAt,
753
+ });
754
+ chatUI.log(chalk.green("✅ Reconnected to chat stream"));
925
755
  }
926
- screen?.render();
927
756
  }
928
757
  catch (error) {
929
758
  retryCount++;
@@ -933,114 +762,77 @@ export async function startChatStream(client, jsonMode = false, tokenIdentifier,
933
762
  throw new Error(`Failed to reconnect after ${maxRetries} attempts: ${errorMsg}`);
934
763
  }
935
764
  // Log retry attempt
936
- if (messageLogBox) {
937
- messageLogBox.log(chalk.yellow(`⚠️ Connection attempt ${retryCount} failed: ${errorMsg}`));
938
- messageLogBox.setScrollPerc(100);
939
- screen?.render();
765
+ if (chatUI) {
766
+ chatUI.log(chalk.yellow(`⚠️ Connection attempt ${retryCount} failed: ${errorMsg}`));
940
767
  }
941
768
  }
942
769
  }
943
770
  }
944
- inputBox?.clearValue();
945
- inputBox?.focus();
946
- screen?.render();
947
771
  }
948
772
  catch (error) {
949
773
  const errorMsg = error instanceof Error ? error.message : String(error);
950
- if (messageLogBox) {
951
- messageLogBox.log(chalk.red(`❌ ${errorMsg}`));
952
- messageLogBox.setScrollPerc(100);
774
+ if (chatUI) {
775
+ chatUI.log(chalk.red(`❌ ${errorMsg}`));
953
776
  }
954
- inputBox?.clearValue();
955
- inputBox?.focus();
956
- screen?.render();
957
777
  }
958
778
  return;
959
779
  }
960
780
  case "/buy": {
961
781
  // Only allow in token-specific chats
962
782
  if (!tokenIdentifier) {
963
- if (messageLogBox) {
964
- messageLogBox.log(chalk.yellow("⚠️ /buy command only works in token-specific chats"));
965
- messageLogBox.setScrollPerc(100);
966
- screen?.render();
783
+ if (chatUI) {
784
+ chatUI.log(chalk.yellow("⚠️ /buy command only works in token-specific chats"));
967
785
  }
968
- inputBox?.clearValue();
969
- inputBox?.focus();
970
- screen?.render();
971
786
  return;
972
787
  }
973
788
  try {
974
789
  const parts = trimmed.split(" ");
975
790
  if (parts.length < 2) {
976
- if (messageLogBox) {
977
- messageLogBox.log(chalk.yellow("⚠️ Usage: /buy <amount> (e.g., /buy 0.05)"));
978
- messageLogBox.setScrollPerc(100);
979
- screen?.render();
791
+ if (chatUI) {
792
+ chatUI.log(chalk.yellow("⚠️ Usage: /buy <amount> (e.g., /buy 0.05)"));
980
793
  }
981
- inputBox?.clearValue();
982
- inputBox?.focus();
983
- screen?.render();
984
794
  return;
985
795
  }
986
796
  const amount = parts[1];
987
- inputBox?.setValue(`💰 Buying tokens...`);
988
- screen?.render();
797
+ if (chatUI) {
798
+ chatUI.log(chalk.yellow("💰 Buying tokens..."));
799
+ }
989
800
  const network = client.getNetwork();
990
801
  const isTestMode = network === "eip155:84532" || network === "eip155:11155111" || network.includes("sepolia");
991
802
  const result = await buyToken(client, tokenIdentifier, amount, isTestMode, true // silent mode
992
803
  );
993
- if (messageLogBox) {
994
- messageLogBox.log(formatBuyResultCompact(result));
995
- messageLogBox.setScrollPerc(100);
996
- screen?.render();
804
+ if (chatUI) {
805
+ chatUI.log(formatBuyResultCompact(result));
997
806
  }
998
- inputBox?.clearValue();
999
- inputBox?.focus();
1000
- screen?.render();
1001
807
  }
1002
808
  catch (error) {
1003
809
  const errorMsg = error instanceof Error ? error.message : String(error);
1004
- if (messageLogBox) {
1005
- messageLogBox.log(chalk.red(`❌ Buy failed: ${errorMsg}`));
1006
- messageLogBox.setScrollPerc(100);
1007
- screen?.render();
810
+ if (chatUI) {
811
+ chatUI.log(chalk.red(`❌ Buy failed: ${errorMsg}`));
1008
812
  }
1009
- inputBox?.clearValue();
1010
- inputBox?.focus();
1011
- screen?.render();
1012
813
  }
1013
814
  return;
1014
815
  }
1015
816
  case "/sell": {
1016
817
  // Only allow in token-specific chats
1017
818
  if (!tokenIdentifier) {
1018
- if (messageLogBox) {
1019
- messageLogBox.log(chalk.yellow("⚠️ /sell command only works in token-specific chats"));
1020
- messageLogBox.setScrollPerc(100);
1021
- screen?.render();
819
+ if (chatUI) {
820
+ chatUI.log(chalk.yellow("⚠️ /sell command only works in token-specific chats"));
1022
821
  }
1023
- inputBox?.clearValue();
1024
- inputBox?.focus();
1025
- screen?.render();
1026
822
  return;
1027
823
  }
1028
824
  try {
1029
825
  const parts = trimmed.split(" ");
1030
826
  if (parts.length < 2) {
1031
- if (messageLogBox) {
1032
- messageLogBox.log(chalk.yellow("⚠️ Usage: /sell <amount|all|percentage> (e.g., /sell 0.05, /sell all, /sell 50%)"));
1033
- messageLogBox.setScrollPerc(100);
1034
- screen?.render();
827
+ if (chatUI) {
828
+ chatUI.log(chalk.yellow("⚠️ Usage: /sell <amount|all|percentage> (e.g., /sell 0.05, /sell all, /sell 50%)"));
1035
829
  }
1036
- inputBox?.clearValue();
1037
- inputBox?.focus();
1038
- screen?.render();
1039
830
  return;
1040
831
  }
1041
832
  const amountStr = parts[1];
1042
- inputBox?.setValue(`💸 Selling tokens...`);
1043
- screen?.render();
833
+ if (chatUI) {
834
+ chatUI.log(chalk.yellow("💸 Selling tokens..."));
835
+ }
1044
836
  // Get token info to check balance
1045
837
  if (!userAddress) {
1046
838
  throw new Error("User address not available");
@@ -1055,50 +847,30 @@ export async function startChatStream(client, jsonMode = false, tokenIdentifier,
1055
847
  const tokenAmount = parseTokenAmount(amountStr, tokenInfo.userPosition.tokensOwned);
1056
848
  const result = await sellToken(client, tokenIdentifier, tokenAmount, true // silent mode
1057
849
  );
1058
- if (messageLogBox) {
1059
- messageLogBox.log(formatSellResultCompact(result));
1060
- messageLogBox.setScrollPerc(100);
1061
- screen?.render();
850
+ if (chatUI) {
851
+ chatUI.log(formatSellResultCompact(result));
1062
852
  }
1063
- inputBox?.clearValue();
1064
- inputBox?.focus();
1065
- screen?.render();
1066
853
  }
1067
854
  catch (error) {
1068
855
  const errorMsg = error instanceof Error ? error.message : String(error);
1069
- if (messageLogBox) {
1070
- messageLogBox.log(chalk.red(`❌ Sell failed: ${errorMsg}`));
1071
- messageLogBox.setScrollPerc(100);
1072
- screen?.render();
856
+ if (chatUI) {
857
+ chatUI.log(chalk.red(`❌ Sell failed: ${errorMsg}`));
1073
858
  }
1074
- inputBox?.clearValue();
1075
- inputBox?.focus();
1076
- screen?.render();
1077
859
  }
1078
860
  return;
1079
861
  }
1080
862
  case "/help":
1081
- if (messageLogBox) {
863
+ if (chatUI) {
1082
864
  const helpText = tokenIdentifier
1083
865
  ? chalk.dim("Commands: /exit, /quit, /renew, /buy, /sell, /help")
1084
866
  : chalk.dim("Commands: /exit, /quit, /renew, /help");
1085
- messageLogBox.log(helpText);
1086
- messageLogBox.setScrollPerc(100);
1087
- screen?.render();
867
+ chatUI.log(helpText);
1088
868
  }
1089
- inputBox?.clearValue();
1090
- inputBox?.focus();
1091
- screen?.render();
1092
869
  return;
1093
870
  default:
1094
- if (messageLogBox) {
1095
- messageLogBox.log(chalk.yellow(`Unknown command: ${cmd}. Type /help for commands.`));
1096
- messageLogBox.setScrollPerc(100);
1097
- screen?.render();
871
+ if (chatUI) {
872
+ chatUI.log(chalk.yellow(`Unknown command: ${cmd}. Type /help for commands.`));
1098
873
  }
1099
- inputBox?.clearValue();
1100
- inputBox?.focus();
1101
- screen?.render();
1102
874
  return;
1103
875
  }
1104
876
  }
@@ -1114,53 +886,42 @@ export async function startChatStream(client, jsonMode = false, tokenIdentifier,
1114
886
  ? "closed"
1115
887
  : "unknown"
1116
888
  : "not initialized";
1117
- if (messageLogBox) {
1118
- messageLogBox.log(chalk.red(`❌ WebSocket is not connected (state: ${stateMsg}). Please wait for connection or type /renew.`));
1119
- messageLogBox.setScrollPerc(100);
1120
- screen?.render();
889
+ if (chatUI) {
890
+ chatUI.log(chalk.red(`❌ WebSocket is not connected (state: ${stateMsg}). Please wait for connection or type /renew.`));
1121
891
  }
1122
- inputBox?.clearValue();
1123
- inputBox?.focus();
1124
- screen?.render();
1125
892
  return;
1126
893
  }
1127
894
  // Check if lease is valid
1128
895
  if (!leaseInfo || leaseInfo.leaseExpiresAt.getTime() <= Date.now()) {
1129
- if (messageLogBox) {
1130
- messageLogBox.log(chalk.yellow("⏱️ Your lease has expired. Type /renew to continue chatting."));
1131
- messageLogBox.setScrollPerc(100);
1132
- screen?.render();
896
+ if (chatUI) {
897
+ chatUI.log(chalk.yellow("⏱️ Your lease has expired. Type /renew to continue chatting."));
1133
898
  }
1134
- inputBox?.clearValue();
1135
- inputBox?.focus();
1136
- screen?.render();
1137
899
  return;
1138
900
  }
1139
901
  // Send message
1140
902
  isSending = true;
1141
903
  currentInput = trimmed;
1142
- // Show pulsing animation in input box
904
+ // Show pulsing animation
1143
905
  let pulseCount = 0;
1144
906
  let pulseInterval = null;
1145
907
  const tempMessageId = `pending-${Date.now()}-${Math.random()}`;
1146
908
  const updatePulse = () => {
1147
- if (!inputBox || isCleaningUp)
909
+ if (isCleaningUp)
1148
910
  return;
1149
911
  pulseCount++;
1150
912
  const pulseChar = pulseCount % 2 === 0 ? "●" : "○";
1151
- inputBox.setValue(`${trimmed} ${pulseChar}`);
1152
- screen?.render();
913
+ // Note: Ink doesn't support directly updating input, so we just track state
1153
914
  };
1154
915
  // Initial pulse
1155
- updatePulse();
1156
916
  pulseInterval = setInterval(updatePulse, 500);
1157
917
  pulseIntervals.set(tempMessageId, pulseInterval);
1158
918
  try {
1159
919
  // Ensure lease is valid before sending
1160
- leaseInfo = await ensureLeaseValid(client, userAddress, leaseInfo, messageLogBox || undefined, screen || undefined);
1161
- if (headerBox) {
1162
- updateHeaderBox(headerBox, leaseInfo, tokenIdentifier, true);
1163
- screen?.render();
920
+ leaseInfo = await ensureLeaseValid(client, userAddress, leaseInfo, chatUI || undefined);
921
+ if (chatUI) {
922
+ chatUI.updateHeader({
923
+ leaseExpiresAt: leaseInfo.leaseExpiresAt,
924
+ });
1164
925
  }
1165
926
  // Double-check websocket is still connected after lease renewal
1166
927
  // WebSocket.OPEN = 1
@@ -1168,7 +929,6 @@ export async function startChatStream(client, jsonMode = false, tokenIdentifier,
1168
929
  throw new Error("WebSocket connection lost. Please wait for reconnection.");
1169
930
  }
1170
931
  const result = await sendChatMessage(client, trimmed, leaseInfo.leaseId, userAddress, tokenAddress);
1171
- userAddress = result.author;
1172
932
  // Track this message
1173
933
  pendingMessages.set(result.messageId, tempMessageId);
1174
934
  pendingMessages.set(`text:${trimmed}`, tempMessageId);
@@ -1181,38 +941,46 @@ export async function startChatStream(client, jsonMode = false, tokenIdentifier,
1181
941
  pulseIntervals.delete(tempMessageId);
1182
942
  }
1183
943
  const errorMsg = error instanceof Error ? error.message : String(error);
1184
- if (messageLogBox) {
1185
- messageLogBox.log(chalk.red(`❌ ${errorMsg}`));
1186
- messageLogBox.setScrollPerc(100);
944
+ if (chatUI) {
945
+ chatUI.log(chalk.red(`❌ ${errorMsg}`));
1187
946
  }
1188
- inputBox?.clearValue();
1189
- inputBox?.focus();
1190
947
  isSending = false;
1191
948
  currentInput = "";
1192
- screen?.render();
1193
949
  }
1194
- });
1195
- // Handle Ctrl+C
1196
- screen.key(["C-c"], () => {
950
+ };
951
+ // Exit handler
952
+ const handleExit = async () => {
1197
953
  isExiting = true;
1198
954
  isSending = false;
1199
955
  pulseIntervals.forEach((interval) => clearInterval(interval));
1200
956
  pulseIntervals.clear();
1201
957
  if (leaseCheckInterval)
1202
958
  clearInterval(leaseCheckInterval);
1203
- if (headerUpdateInterval)
1204
- clearInterval(headerUpdateInterval);
1205
959
  if (ws)
1206
960
  ws.close();
1207
- screen?.destroy();
961
+ if (chatUI)
962
+ await chatUI.destroy();
1208
963
  console.log();
1209
964
  printCat("sleeping");
1210
- console.log(chalk.cyan("Chat disconnected. Goodbye! 👋"));
1211
- process.exit(0);
1212
- });
1213
- // Focus input box and render
1214
- inputBox.focus();
1215
- screen.render();
965
+ if (returnToShell) {
966
+ console.log(chalk.cyan("Returning to shell..."));
967
+ // Don't exit - return to shell
968
+ }
969
+ else {
970
+ console.log(chalk.cyan("Chat disconnected. Goodbye! 👋"));
971
+ process.exit(0);
972
+ }
973
+ };
974
+ // Create Ink UI
975
+ chatUI = createTokenChatUI(handleMessage, handleExit, {
976
+ network: client.getNetwork(),
977
+ account: userAddress,
978
+ tokenSymbol,
979
+ tokenAddress,
980
+ leaseExpiresAt: leaseInfo.leaseExpiresAt,
981
+ }, initialMessages);
982
+ // Wait for the chat UI to exit (blocks until user exits)
983
+ await chatUI.waitUntilExit();
1216
984
  }
1217
985
  else {
1218
986
  // JSON mode: read messages from stdin and output JSON to stdout
@@ -1272,8 +1040,6 @@ export async function startChatStream(client, jsonMode = false, tokenIdentifier,
1272
1040
  isExiting = true;
1273
1041
  if (leaseCheckInterval)
1274
1042
  clearInterval(leaseCheckInterval);
1275
- if (headerUpdateInterval)
1276
- clearInterval(headerUpdateInterval);
1277
1043
  if (ws)
1278
1044
  ws.close();
1279
1045
  console.log(JSON.stringify({ type: "exiting" }));
@@ -1430,7 +1196,7 @@ export async function startChatStream(client, jsonMode = false, tokenIdentifier,
1430
1196
  // Send message
1431
1197
  isSending = true;
1432
1198
  try {
1433
- leaseInfo = await ensureLeaseValid(client, userAddress, leaseInfo, undefined, undefined);
1199
+ leaseInfo = await ensureLeaseValid(client, userAddress, leaseInfo, undefined);
1434
1200
  if (!ws || ws.readyState !== 1) {
1435
1201
  throw new Error("WebSocket connection lost. Please wait for reconnection.");
1436
1202
  }
@@ -1462,11 +1228,8 @@ export async function startChatStream(client, jsonMode = false, tokenIdentifier,
1462
1228
  isExiting = true;
1463
1229
  if (leaseCheckInterval)
1464
1230
  clearInterval(leaseCheckInterval);
1465
- if (headerUpdateInterval)
1466
- clearInterval(headerUpdateInterval);
1467
1231
  if (ws)
1468
1232
  ws.close();
1469
- // Don't try to use readline in JSON mode - it should never exist
1470
1233
  process.exit(0);
1471
1234
  });
1472
1235
  process.stdin.on("error", (error) => {
@@ -1482,11 +1245,8 @@ export async function startChatStream(client, jsonMode = false, tokenIdentifier,
1482
1245
  isExiting = true;
1483
1246
  if (leaseCheckInterval)
1484
1247
  clearInterval(leaseCheckInterval);
1485
- if (headerUpdateInterval)
1486
- clearInterval(headerUpdateInterval);
1487
1248
  if (ws)
1488
1249
  ws.close();
1489
- // Don't try to use readline in JSON mode - it should never exist
1490
1250
  console.log(JSON.stringify({ type: "exiting" }));
1491
1251
  process.exit(0);
1492
1252
  });
@@ -1497,8 +1257,6 @@ export async function startChatStream(client, jsonMode = false, tokenIdentifier,
1497
1257
  try {
1498
1258
  if (leaseCheckInterval)
1499
1259
  clearInterval(leaseCheckInterval);
1500
- if (headerUpdateInterval)
1501
- clearInterval(headerUpdateInterval);
1502
1260
  pulseIntervals.forEach((interval) => clearInterval(interval));
1503
1261
  pulseIntervals.clear();
1504
1262
  if (ws !== null) {
@@ -1514,8 +1272,8 @@ export async function startChatStream(client, jsonMode = false, tokenIdentifier,
1514
1272
  // Ignore cleanup errors
1515
1273
  }
1516
1274
  }
1517
- if (screen) {
1518
- screen.destroy();
1275
+ if (chatUI) {
1276
+ await chatUI.destroy();
1519
1277
  }
1520
1278
  }
1521
1279
  catch (cleanupError) {