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
package/dist/config.js CHANGED
@@ -4,25 +4,30 @@ import chalk from "chalk";
4
4
  import path from "path";
5
5
  import os from "os";
6
6
  import fs from "fs";
7
- import { decryptData, encryptData, seedPhraseToPrivateKey, getAddressFromPrivateKey } from "./utils/wallet.js";
7
+ import { decryptData, encryptData, seedPhraseToPrivateKey, getAddressFromPrivateKey, } from "./utils/wallet.js";
8
8
  const DEFAULT_ENVIRONMENTS = {
9
9
  local: {
10
10
  name: "local",
11
11
  agentUrl: "http://localhost:8787",
12
12
  network: "eip155:84532",
13
13
  },
14
- sepolia: {
15
- name: "sepolia",
16
- agentUrl: "https://agent.402.cat",
14
+ "base-sepolia": {
15
+ name: "base-sepolia",
16
+ agentUrl: "https://agent-test.402.cat",
17
17
  network: "eip155:84532",
18
18
  },
19
+ base: {
20
+ name: "base",
21
+ agentUrl: "https://agent-main.402.cat",
22
+ network: "eip155:8453",
23
+ },
19
24
  };
20
25
  const DEFAULT_CONFIG = {
21
- network: "eip155:84532",
22
- agentUrl: "https://agent.402.cat",
23
- facilitatorUrl: "https://facilitators.x402scan.com",
26
+ network: "eip155:8453",
27
+ agentUrl: "https://agent-main.402.cat",
28
+ facilitatorUrl: "https://facilitator.402.cat",
24
29
  defaultMaxPayment: "10.00",
25
- environment: "sepolia",
30
+ environment: "base",
26
31
  environments: DEFAULT_ENVIRONMENTS,
27
32
  preferences: {
28
33
  enableAsciiArt: true,
@@ -34,6 +39,8 @@ export class ConfigManager {
34
39
  store;
35
40
  session = null;
36
41
  configPath;
42
+ agentInfoCache = null;
43
+ AGENT_INFO_CACHE_TTL = 5 * 60 * 1000; // 5 minutes
37
44
  constructor() {
38
45
  // Explicitly define where we want the config file
39
46
  const configDir = path.join(os.homedir(), ".config", "httpcat");
@@ -70,8 +77,57 @@ export class ConfigManager {
70
77
  }
71
78
  }
72
79
  }
73
- // Initialize Conf WITHOUT cwd - let it use default location, then we'll redirect
80
+ // Validate and fix corrupted config files BEFORE Conf initialization
74
81
  // Conf's default on macOS is ~/Library/Preferences/httpcat-nodejs/config.json
82
+ const defaultConfPath = path.join(os.homedir(), "Library", "Preferences", "httpcat-nodejs", "config.json");
83
+ if (fs.existsSync(defaultConfPath)) {
84
+ try {
85
+ const content = fs.readFileSync(defaultConfPath, "utf-8").trim();
86
+ if (content) {
87
+ JSON.parse(content); // Validate JSON
88
+ }
89
+ else {
90
+ // Empty file - delete it so Conf uses defaults
91
+ fs.unlinkSync(defaultConfPath);
92
+ }
93
+ }
94
+ catch {
95
+ // Corrupted JSON - backup and delete it
96
+ try {
97
+ const backupPath = `${defaultConfPath}.backup.${Date.now()}`;
98
+ fs.copyFileSync(defaultConfPath, backupPath);
99
+ }
100
+ catch {
101
+ // Ignore backup failure
102
+ }
103
+ fs.unlinkSync(defaultConfPath);
104
+ }
105
+ }
106
+ // Also validate our custom config path before Conf tries to read it
107
+ if (fs.existsSync(this.configPath)) {
108
+ try {
109
+ const configContent = fs.readFileSync(this.configPath, "utf-8").trim();
110
+ if (configContent) {
111
+ JSON.parse(configContent); // Validate JSON
112
+ }
113
+ else {
114
+ // Empty file - initialize with defaults
115
+ fs.writeFileSync(this.configPath, JSON.stringify(DEFAULT_CONFIG, null, 2));
116
+ }
117
+ }
118
+ catch {
119
+ // Corrupted - backup and recreate
120
+ try {
121
+ const backupPath = `${this.configPath}.backup.${Date.now()}`;
122
+ fs.copyFileSync(this.configPath, backupPath);
123
+ }
124
+ catch {
125
+ // Ignore backup failure
126
+ }
127
+ fs.writeFileSync(this.configPath, JSON.stringify(DEFAULT_CONFIG, null, 2));
128
+ }
129
+ }
130
+ // Initialize Conf WITHOUT cwd - let it use default location, then we'll redirect
75
131
  // But we want ~/.config/httpcat/config.json
76
132
  // So we'll initialize Conf normally, then manually sync to our desired location
77
133
  this.store = new Conf({
@@ -81,8 +137,33 @@ export class ConfigManager {
81
137
  });
82
138
  // Always sync from correct location to Conf's location on init
83
139
  // This ensures Conf reads the right data
140
+ // If config file doesn't exist, create it with defaults
141
+ if (!fs.existsSync(this.configPath)) {
142
+ // Ensure directory exists
143
+ fs.mkdirSync(path.dirname(this.configPath), { recursive: true });
144
+ // Write defaults to config file
145
+ fs.writeFileSync(this.configPath, JSON.stringify(DEFAULT_CONFIG, null, 2));
146
+ }
84
147
  if (fs.existsSync(this.configPath)) {
85
148
  try {
149
+ // Validate JSON before copying
150
+ const configContent = fs.readFileSync(this.configPath, "utf-8").trim();
151
+ if (configContent) {
152
+ // Try to parse JSON to validate it
153
+ try {
154
+ JSON.parse(configContent);
155
+ }
156
+ catch (parseError) {
157
+ // Config file is corrupted - backup and recreate with defaults
158
+ const backupPath = `${this.configPath}.backup.${Date.now()}`;
159
+ fs.copyFileSync(this.configPath, backupPath);
160
+ fs.writeFileSync(this.configPath, JSON.stringify(DEFAULT_CONFIG, null, 2));
161
+ }
162
+ }
163
+ else {
164
+ // Empty file - initialize with defaults
165
+ fs.writeFileSync(this.configPath, JSON.stringify(DEFAULT_CONFIG, null, 2));
166
+ }
86
167
  // Ensure Conf's directory exists
87
168
  const confDir = path.dirname(this.store.path);
88
169
  fs.mkdirSync(confDir, { recursive: true });
@@ -96,13 +177,38 @@ export class ConfigManager {
96
177
  });
97
178
  }
98
179
  catch (error) {
99
- // Silently fail
180
+ // If copying fails, try to initialize Conf with defaults
181
+ try {
182
+ this.store = new Conf({
183
+ projectName: "httpcat",
184
+ defaults: DEFAULT_CONFIG,
185
+ });
186
+ }
187
+ catch (fallbackError) {
188
+ // Last resort: delete corrupted file and start fresh
189
+ if (fs.existsSync(this.configPath)) {
190
+ const backupPath = `${this.configPath}.backup.${Date.now()}`;
191
+ try {
192
+ fs.copyFileSync(this.configPath, backupPath);
193
+ }
194
+ catch {
195
+ // Ignore backup failure
196
+ }
197
+ fs.unlinkSync(this.configPath);
198
+ }
199
+ this.store = new Conf({
200
+ projectName: "httpcat",
201
+ defaults: DEFAULT_CONFIG,
202
+ });
203
+ }
100
204
  }
101
205
  }
102
206
  // Migrate old privateKey format on first access
103
207
  this.migrateLegacyPrivateKey();
104
208
  // Migrate network format from V1 to V2 (base-sepolia -> eip155:84532)
105
209
  this.migrateNetworkFormat();
210
+ // Migrate sepolia environment to base-sepolia
211
+ this.migrateSepoliaEnvironment();
106
212
  }
107
213
  /**
108
214
  * Migrate config from old location to new location (~/.config/httpcat)
@@ -122,7 +228,10 @@ export class ConfigManager {
122
228
  if (fs.existsSync(oldPath)) {
123
229
  const oldData = oldStore.store;
124
230
  const hasOldData = Object.keys(oldData).length > 0 &&
125
- (oldData.privateKey || oldData.accounts || oldData.encryptedPrivateKey || oldData.encryptedSeedPhrase);
231
+ (oldData.privateKey ||
232
+ oldData.accounts ||
233
+ oldData.encryptedPrivateKey ||
234
+ oldData.encryptedSeedPhrase);
126
235
  // Check if new config already exists
127
236
  const newConfigExists = fs.existsSync(newPath);
128
237
  if (hasOldData && !newConfigExists) {
@@ -159,7 +268,8 @@ export class ConfigManager {
159
268
  // Check if correct file is newer or Conf's file doesn't exist
160
269
  const needsSync = !fs.existsSync(this.store.path) ||
161
270
  (fs.existsSync(this.store.path) &&
162
- fs.statSync(this.configPath).mtimeMs > fs.statSync(this.store.path).mtimeMs);
271
+ fs.statSync(this.configPath).mtimeMs >
272
+ fs.statSync(this.store.path).mtimeMs);
163
273
  if (needsSync) {
164
274
  // Ensure Conf's directory exists
165
275
  const confDir = path.dirname(this.store.path);
@@ -200,7 +310,8 @@ export class ConfigManager {
200
310
  try {
201
311
  const needsSync = !fs.existsSync(this.store.path) ||
202
312
  (fs.existsSync(this.store.path) &&
203
- fs.statSync(this.configPath).mtimeMs > fs.statSync(this.store.path).mtimeMs);
313
+ fs.statSync(this.configPath).mtimeMs >
314
+ fs.statSync(this.store.path).mtimeMs);
204
315
  if (needsSync) {
205
316
  const confDir = path.dirname(this.store.path);
206
317
  fs.mkdirSync(confDir, { recursive: true });
@@ -225,6 +336,18 @@ export class ConfigManager {
225
336
  }
226
337
  clear() {
227
338
  this.store.clear();
339
+ // Sync the cleared state to the config file
340
+ if (this.store.path !== this.configPath && fs.existsSync(this.store.path)) {
341
+ try {
342
+ // Ensure directory exists
343
+ fs.mkdirSync(path.dirname(this.configPath), { recursive: true });
344
+ // Copy from Conf's location to correct location
345
+ fs.copyFileSync(this.store.path, this.configPath);
346
+ }
347
+ catch (error) {
348
+ // Silently fail sync - not critical
349
+ }
350
+ }
228
351
  }
229
352
  getConfigPath() {
230
353
  // Return the path we want to use (the correct one)
@@ -240,7 +363,9 @@ export class ConfigManager {
240
363
  if (configData.accounts && configData.accounts.length > 0) {
241
364
  return true;
242
365
  }
243
- if (configData.privateKey || configData.encryptedPrivateKey || configData.encryptedSeedPhrase) {
366
+ if (configData.privateKey ||
367
+ configData.encryptedPrivateKey ||
368
+ configData.encryptedSeedPhrase) {
244
369
  return true;
245
370
  }
246
371
  }
@@ -253,7 +378,8 @@ export class ConfigManager {
253
378
  try {
254
379
  const needsSync = !fs.existsSync(this.store.path) ||
255
380
  (fs.existsSync(this.store.path) &&
256
- fs.statSync(this.configPath).mtimeMs > fs.statSync(this.store.path).mtimeMs);
381
+ fs.statSync(this.configPath).mtimeMs >
382
+ fs.statSync(this.store.path).mtimeMs);
257
383
  if (needsSync) {
258
384
  const confDir = path.dirname(this.store.path);
259
385
  fs.mkdirSync(confDir, { recursive: true });
@@ -306,15 +432,38 @@ export class ConfigManager {
306
432
  }
307
433
  }
308
434
  }
435
+ /**
436
+ * Migrate sepolia environment to base-sepolia
437
+ */
438
+ migrateSepoliaEnvironment() {
439
+ const envs = this.get("environments");
440
+ if (envs && envs.sepolia) {
441
+ // Rename sepolia environment to base-sepolia
442
+ const updatedEnvs = {};
443
+ for (const [name, env] of Object.entries(envs)) {
444
+ const envProfile = env;
445
+ if (name === "sepolia") {
446
+ updatedEnvs["base-sepolia"] = { ...envProfile, name: "base-sepolia" };
447
+ }
448
+ else {
449
+ updatedEnvs[name] = envProfile;
450
+ }
451
+ }
452
+ this.set("environments", updatedEnvs);
453
+ }
454
+ // Update current environment if it's sepolia
455
+ const currentEnv = this.get("environment");
456
+ if (currentEnv === "sepolia") {
457
+ this.set("environment", "base-sepolia");
458
+ }
459
+ }
309
460
  /**
310
461
  * Convert network name to CAIP-2 format
311
462
  */
312
463
  networkToCAIP2(network) {
313
464
  const mapping = {
314
465
  "base-sepolia": "eip155:84532",
315
- "base": "eip155:8453",
316
- "ethereum": "eip155:1",
317
- "sepolia": "eip155:11155111",
466
+ base: "eip155:8453",
318
467
  };
319
468
  return mapping[network] || network; // Return as-is if already CAIP-2 or unknown
320
469
  }
@@ -325,8 +474,6 @@ export class ConfigManager {
325
474
  const mapping = {
326
475
  "eip155:84532": "base-sepolia",
327
476
  "eip155:8453": "base",
328
- "eip155:1": "ethereum",
329
- "eip155:11155111": "sepolia",
330
477
  };
331
478
  return mapping[caip2] || caip2; // Return as-is if not in mapping
332
479
  }
@@ -334,7 +481,9 @@ export class ConfigManager {
334
481
  * Check if network is a testnet
335
482
  */
336
483
  isTestnet(network) {
337
- return network === "eip155:84532" || network === "eip155:11155111" || network.includes("sepolia");
484
+ return (network === "eip155:84532" ||
485
+ network === "eip155:11155111" ||
486
+ network.includes("sepolia"));
338
487
  }
339
488
  /**
340
489
  * Migrate legacy privateKey to new account format
@@ -380,17 +529,30 @@ export class ConfigManager {
380
529
  console.log(chalk.cyan("=".repeat(80)));
381
530
  console.log();
382
531
  if (!options.skipPrivateKey) {
383
- // Use the nice wallet setup wizard instead of directly asking for private key
384
- const { promptForPrivateKey } = await import("./utils/privateKeyPrompt.js");
385
- await promptForPrivateKey();
386
- console.log();
532
+ // Check if import mode is enabled via environment variable
533
+ if (process.env.HTTPCAT_IMPORT_MODE === "true") {
534
+ // Use the nice wallet setup wizard instead of directly asking for private key
535
+ const { promptForPrivateKey } = await import("./utils/privateKeyPrompt.js");
536
+ await promptForPrivateKey();
537
+ console.log();
538
+ }
539
+ else {
540
+ // Auto-generate seed phrase for new users
541
+ const { autoSetupWallet } = await import("./utils/autoSetup.js");
542
+ await autoSetupWallet();
543
+ console.log();
544
+ }
387
545
  }
388
546
  // Network selection
389
- const network = "eip155:84532";
547
+ const network = "eip155:8453";
390
548
  this.set("network", network);
391
549
  console.log(chalk.green(`✅ Network set to: ${this.caip2ToNetwork(network)} (${network}) (testnet)\n`));
392
- // Agent URL
393
- const defaultUrl = "https://agent.402.cat";
550
+ // Agent URL - use correct default based on network
551
+ // base-sepolia (eip155:84532) -> testnet URL
552
+ // base (eip155:8453) -> mainnet URL
553
+ const defaultUrl = network === "eip155:8453"
554
+ ? "https://agent-main.402.cat"
555
+ : "https://agent-test.402.cat";
394
556
  const agentUrl = await this.prompt(`Agent URL [${defaultUrl}]: `, defaultUrl);
395
557
  this.set("agentUrl", agentUrl);
396
558
  console.log(chalk.green(`✅ Agent URL set to: ${agentUrl}\n`));
@@ -407,6 +569,68 @@ export class ConfigManager {
407
569
  console.log();
408
570
  console.log(chalk.dim(`Config saved to: ${this.getConfigPath()}`));
409
571
  console.log();
572
+ // Shell setup (completion and alias)
573
+ await this.promptShellSetup();
574
+ }
575
+ async promptShellSetup() {
576
+ try {
577
+ const { detectShell, installShellCompletion, installHcAlias } = await import("./utils/shell-setup.js");
578
+ const shell = detectShell();
579
+ if (shell === "unknown") {
580
+ console.log(chalk.yellow("⚠️ Could not detect shell type. Skipping shell setup."));
581
+ console.log();
582
+ return;
583
+ }
584
+ // Prompt for completion installation
585
+ const installCompletion = await this.prompt(`Install shell autocompletion for ${shell}? [Y/n]: `, "y");
586
+ if (installCompletion.toLowerCase() !== "n") {
587
+ console.log(chalk.cyan("Installing shell completion..."));
588
+ const success = installShellCompletion();
589
+ if (success) {
590
+ console.log(chalk.green(`✅ Shell completion installed for ${shell}!`));
591
+ console.log(chalk.dim(" Restart your terminal or run: source ~/." +
592
+ (shell === "zsh" ? "zshrc" : "bashrc")));
593
+ }
594
+ else {
595
+ console.log(chalk.yellow("⚠️ Failed to install shell completion. You can install it manually later with:"));
596
+ console.log(chalk.dim(` httpcat completion ${shell} >> ~/.${shell === "zsh" ? "zshrc" : "bashrc"}`));
597
+ }
598
+ console.log();
599
+ }
600
+ // Prompt for alias creation
601
+ const createAlias = await this.prompt("Create 'hc' alias for httpcat? [Y/n]: ", "y");
602
+ if (createAlias.toLowerCase() !== "n") {
603
+ console.log(chalk.cyan("Creating 'hc' alias..."));
604
+ const result = installHcAlias();
605
+ if (result.success) {
606
+ if (result.method === "symlink") {
607
+ console.log(chalk.green("✅ 'hc' alias created (symlink)!"));
608
+ console.log(chalk.dim(" You can now use 'hc' instead of 'httpcat' immediately."));
609
+ }
610
+ else {
611
+ console.log(chalk.green("✅ 'hc' alias created (shell alias)!"));
612
+ console.log(chalk.dim(" You can now use 'hc' instead of 'httpcat'. Restart your terminal or run:"));
613
+ if (shell === "zsh") {
614
+ console.log(chalk.dim(" source ~/.zshrc"));
615
+ }
616
+ else {
617
+ console.log(chalk.dim(" source ~/.bashrc"));
618
+ }
619
+ }
620
+ }
621
+ else {
622
+ console.log(chalk.yellow("⚠️ Failed to create 'hc' alias. You can create it manually with:"));
623
+ console.log(chalk.dim(` alias hc='httpcat'`));
624
+ console.log(chalk.dim(` Add this to your ~/.${shell === "zsh" ? "zshrc" : "bashrc"} file`));
625
+ }
626
+ console.log();
627
+ }
628
+ }
629
+ catch (error) {
630
+ // Non-fatal error - don't break the wizard
631
+ console.log(chalk.yellow("⚠️ Shell setup skipped due to error (non-fatal)"));
632
+ console.log();
633
+ }
410
634
  }
411
635
  prompt(question, defaultValue) {
412
636
  return new Promise((resolve) => {
@@ -448,18 +672,18 @@ export class ConfigManager {
448
672
  // Ensure session is valid
449
673
  this.ensureSessionValid();
450
674
  if (account.type === "custom") {
451
- // Account 0 - use encrypted private key
452
- const encrypted = this.get("encryptedPrivateKey");
675
+ // Custom account - get encrypted private key from account or fall back to global
676
+ const encrypted = account.encryptedPrivateKey || this.get("encryptedPrivateKey");
453
677
  if (!encrypted) {
454
- throw new Error("Account 0 private key not found");
678
+ throw new Error(`Private key not found for account ${accountIndex}`);
455
679
  }
456
680
  const password = this.get("password") || "";
457
681
  if (password) {
458
- // Decrypt using session
459
- if (!this.session?.decryptedPrivateKey) {
682
+ // Decrypt on-demand
683
+ if (!this.isSessionValid()) {
460
684
  throw new Error("Session expired. Please unlock your wallet.");
461
685
  }
462
- return this.session.decryptedPrivateKey;
686
+ return decryptData(encrypted, password);
463
687
  }
464
688
  else {
465
689
  // No password - stored as plain (but in encryptedPrivateKey field)
@@ -684,7 +908,22 @@ export class ConfigManager {
684
908
  this.set("environments", DEFAULT_ENVIRONMENTS);
685
909
  return DEFAULT_ENVIRONMENTS;
686
910
  }
687
- return envs;
911
+ // Merge in any missing default environments (but don't overwrite user-modified defaults)
912
+ let updated = false;
913
+ const mergedEnvs = { ...envs };
914
+ for (const [name, defaultEnv] of Object.entries(DEFAULT_ENVIRONMENTS)) {
915
+ if (!mergedEnvs[name]) {
916
+ // Add missing environment
917
+ mergedEnvs[name] = defaultEnv;
918
+ updated = true;
919
+ }
920
+ // Don't overwrite existing environments - user may have modified them
921
+ // Only add missing ones, never overwrite existing ones
922
+ }
923
+ if (updated) {
924
+ this.set("environments", mergedEnvs);
925
+ }
926
+ return mergedEnvs;
688
927
  }
689
928
  getCurrentEnvironment() {
690
929
  return this.get("environment");
@@ -704,8 +943,10 @@ export class ConfigManager {
704
943
  // Update agentUrl and network from environment
705
944
  this.set("agentUrl", env.agentUrl);
706
945
  this.set("network", env.network);
946
+ // Clear agent info cache since we switched environments
947
+ this.clearAgentInfoCache();
707
948
  }
708
- addEnvironment(name, agentUrl, network = "eip155:84532") {
949
+ addEnvironment(name, agentUrl, network = "eip155:8453") {
709
950
  const envs = this.getEnvironments();
710
951
  if (envs[name]) {
711
952
  throw new Error(`Environment "${name}" already exists. Use a different name.`);
@@ -717,7 +958,7 @@ export class ConfigManager {
717
958
  };
718
959
  this.set("environments", envs);
719
960
  }
720
- updateEnvironment(name, agentUrl, network = "eip155:84532") {
961
+ updateEnvironment(name, agentUrl, network = "eip155:8453") {
721
962
  const envs = this.getEnvironments();
722
963
  if (!envs[name]) {
723
964
  throw new Error(`Environment "${name}" does not exist. Use "httpcat env add" to create it.`);
@@ -752,6 +993,73 @@ export class ConfigManager {
752
993
  }
753
994
  return this.get("rpcUrl");
754
995
  }
996
+ getAlchemyApiKey() {
997
+ // Priority: Env var > Config
998
+ const envKey = process.env.HTTPCAT_ALCHEMY_API_KEY;
999
+ if (envKey) {
1000
+ return envKey;
1001
+ }
1002
+ return this.get("alchemyApiKey");
1003
+ }
1004
+ /**
1005
+ * Get public RPC URL for a given chain ID
1006
+ */
1007
+ getPublicRpcUrl(chainId) {
1008
+ switch (chainId) {
1009
+ case 84532: // Base Sepolia
1010
+ return "https://sepolia.base.org";
1011
+ case 8453: // Base Mainnet
1012
+ return "https://mainnet.base.org";
1013
+ default:
1014
+ throw new Error(`Unsupported chain ID for public RPC: ${chainId}`);
1015
+ }
1016
+ }
1017
+ /**
1018
+ * Get Alchemy RPC URL for a given chain ID and API key
1019
+ */
1020
+ getAlchemyRpcUrl(chainId, apiKey) {
1021
+ switch (chainId) {
1022
+ case 84532: // Base Sepolia
1023
+ return `https://base-sepolia.g.alchemy.com/v2/${apiKey}`;
1024
+ case 8453: // Base Mainnet
1025
+ return `https://base-mainnet.g.alchemy.com/v2/${apiKey}`;
1026
+ default:
1027
+ throw new Error(`Unsupported chain ID for Alchemy RPC: ${chainId}`);
1028
+ }
1029
+ }
1030
+ getPlatformTokenAddress() {
1031
+ // Get the CAT platform token address from environment variable
1032
+ return process.env.HTTPCAT_PLATFORM_TOKEN_ADDRESS;
1033
+ }
1034
+ /**
1035
+ * Fetch and cache agent info from the /info endpoint
1036
+ * Returns cached data if available and not expired
1037
+ */
1038
+ async getAgentInfo() {
1039
+ // Check if cached data is still valid
1040
+ if (this.agentInfoCache) {
1041
+ const age = Date.now() - this.agentInfoCache.timestamp;
1042
+ if (age < this.AGENT_INFO_CACHE_TTL) {
1043
+ return this.agentInfoCache.data;
1044
+ }
1045
+ }
1046
+ // Fetch fresh data
1047
+ const agentUrl = this.getAgentUrl();
1048
+ const { fetchAgentInfo } = await import("./types/agent-info.js");
1049
+ const data = await fetchAgentInfo(agentUrl);
1050
+ // Cache the data
1051
+ this.agentInfoCache = {
1052
+ data,
1053
+ timestamp: Date.now(),
1054
+ };
1055
+ return data;
1056
+ }
1057
+ /**
1058
+ * Clear the agent info cache (useful when switching environments)
1059
+ */
1060
+ clearAgentInfoCache() {
1061
+ this.agentInfoCache = null;
1062
+ }
755
1063
  /**
756
1064
  * AI Agent configuration methods
757
1065
  */
@@ -789,7 +1097,8 @@ export class ConfigManager {
789
1097
  // OpenAI keys can start with "sk-" (legacy) or "sk-proj-" (newer project keys)
790
1098
  // Newer keys are much longer, so we just check prefix and minimum length
791
1099
  const trimmed = apiKey.trim();
792
- return (trimmed.startsWith("sk-") || trimmed.startsWith("sk-proj-")) && trimmed.length >= 20;
1100
+ return ((trimmed.startsWith("sk-") || trimmed.startsWith("sk-proj-")) &&
1101
+ trimmed.length >= 20);
793
1102
  }
794
1103
  /**
795
1104
  * Validate Anthropic API key format
@@ -824,11 +1133,13 @@ export class ConfigManager {
824
1133
  apiKey = apiKey.trim();
825
1134
  // Validate API key format based on provider
826
1135
  if (apiKey && agentConfig.provider) {
827
- if (agentConfig.provider === "openai" && !this.validateOpenAIApiKey(apiKey)) {
1136
+ if (agentConfig.provider === "openai" &&
1137
+ !this.validateOpenAIApiKey(apiKey)) {
828
1138
  throw new Error("Invalid OpenAI API key format. OpenAI keys should start with 'sk-'. " +
829
1139
  "Please run 'agent --setup' (or 'cat --setup') to reconfigure your API key.");
830
1140
  }
831
- if (agentConfig.provider === "anthropic" && !this.validateAnthropicApiKey(apiKey)) {
1141
+ if (agentConfig.provider === "anthropic" &&
1142
+ !this.validateAnthropicApiKey(apiKey)) {
832
1143
  throw new Error("Invalid Anthropic API key format. Anthropic keys should start with 'sk-ant-'. " +
833
1144
  "Please run 'agent --setup' (or 'cat --setup') to reconfigure your API key.");
834
1145
  }