perp-cli 0.3.3

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 (325) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +293 -0
  3. package/dist/__tests__/alert-logic.test.d.ts +1 -0
  4. package/dist/__tests__/alert-logic.test.js +107 -0
  5. package/dist/__tests__/arb-auto-3dex.test.d.ts +1 -0
  6. package/dist/__tests__/arb-auto-3dex.test.js +397 -0
  7. package/dist/__tests__/arb-history-stats.test.d.ts +1 -0
  8. package/dist/__tests__/arb-history-stats.test.js +176 -0
  9. package/dist/__tests__/arb-logic.test.d.ts +1 -0
  10. package/dist/__tests__/arb-logic.test.js +84 -0
  11. package/dist/__tests__/arb-manage.test.d.ts +1 -0
  12. package/dist/__tests__/arb-manage.test.js +253 -0
  13. package/dist/__tests__/arb-new-features.test.d.ts +1 -0
  14. package/dist/__tests__/arb-new-features.test.js +457 -0
  15. package/dist/__tests__/arb-sizing.test.d.ts +1 -0
  16. package/dist/__tests__/arb-sizing.test.js +48 -0
  17. package/dist/__tests__/arb-state.test.d.ts +1 -0
  18. package/dist/__tests__/arb-state.test.js +284 -0
  19. package/dist/__tests__/arb-userflow.test.d.ts +1 -0
  20. package/dist/__tests__/arb-userflow.test.js +945 -0
  21. package/dist/__tests__/arb-utils.test.d.ts +1 -0
  22. package/dist/__tests__/arb-utils.test.js +264 -0
  23. package/dist/__tests__/bot-conditions.test.d.ts +1 -0
  24. package/dist/__tests__/bot-conditions.test.js +341 -0
  25. package/dist/__tests__/client-id-tracker.test.d.ts +1 -0
  26. package/dist/__tests__/client-id-tracker.test.js +137 -0
  27. package/dist/__tests__/commands/new-atomic-commands.test.d.ts +1 -0
  28. package/dist/__tests__/commands/new-atomic-commands.test.js +502 -0
  29. package/dist/__tests__/commands/order-intent.test.d.ts +1 -0
  30. package/dist/__tests__/commands/order-intent.test.js +600 -0
  31. package/dist/__tests__/commands/trade-commands.test.d.ts +1 -0
  32. package/dist/__tests__/commands/trade-commands.test.js +821 -0
  33. package/dist/__tests__/config.test.d.ts +1 -0
  34. package/dist/__tests__/config.test.js +86 -0
  35. package/dist/__tests__/cross-chain-margin.test.d.ts +1 -0
  36. package/dist/__tests__/cross-chain-margin.test.js +287 -0
  37. package/dist/__tests__/dex-asset-map.test.d.ts +1 -0
  38. package/dist/__tests__/dex-asset-map.test.js +191 -0
  39. package/dist/__tests__/errors.test.d.ts +1 -0
  40. package/dist/__tests__/errors.test.js +110 -0
  41. package/dist/__tests__/event-stream.test.d.ts +1 -0
  42. package/dist/__tests__/event-stream.test.js +276 -0
  43. package/dist/__tests__/exchanges/interface.test.d.ts +1 -0
  44. package/dist/__tests__/exchanges/interface.test.js +132 -0
  45. package/dist/__tests__/exchanges/mock-adapter.d.ts +69 -0
  46. package/dist/__tests__/exchanges/mock-adapter.js +137 -0
  47. package/dist/__tests__/execution-log.test.d.ts +1 -0
  48. package/dist/__tests__/execution-log.test.js +106 -0
  49. package/dist/__tests__/funding-calc.test.d.ts +1 -0
  50. package/dist/__tests__/funding-calc.test.js +71 -0
  51. package/dist/__tests__/funding-history.test.d.ts +1 -0
  52. package/dist/__tests__/funding-history.test.js +343 -0
  53. package/dist/__tests__/funding-rates.test.d.ts +1 -0
  54. package/dist/__tests__/funding-rates.test.js +342 -0
  55. package/dist/__tests__/funding.test.d.ts +1 -0
  56. package/dist/__tests__/funding.test.js +173 -0
  57. package/dist/__tests__/gap-logic.test.d.ts +1 -0
  58. package/dist/__tests__/gap-logic.test.js +43 -0
  59. package/dist/__tests__/hip3-dex.test.d.ts +1 -0
  60. package/dist/__tests__/hip3-dex.test.js +234 -0
  61. package/dist/__tests__/integration/agent-features.integration.test.d.ts +1 -0
  62. package/dist/__tests__/integration/agent-features.integration.test.js +553 -0
  63. package/dist/__tests__/integration/atomic-commands.integration.test.d.ts +13 -0
  64. package/dist/__tests__/integration/atomic-commands.integration.test.js +246 -0
  65. package/dist/__tests__/integration/bridge-simulation.integration.test.d.ts +1 -0
  66. package/dist/__tests__/integration/bridge-simulation.integration.test.js +453 -0
  67. package/dist/__tests__/integration/bridge-strict.integration.test.d.ts +1 -0
  68. package/dist/__tests__/integration/bridge-strict.integration.test.js +812 -0
  69. package/dist/__tests__/integration/bridge.integration.test.d.ts +1 -0
  70. package/dist/__tests__/integration/bridge.integration.test.js +309 -0
  71. package/dist/__tests__/integration/cli-e2e.integration.test.d.ts +1 -0
  72. package/dist/__tests__/integration/cli-e2e.integration.test.js +202 -0
  73. package/dist/__tests__/integration/dex-arb.integration.test.d.ts +1 -0
  74. package/dist/__tests__/integration/dex-arb.integration.test.js +116 -0
  75. package/dist/__tests__/integration/envelope-consistency.integration.test.d.ts +13 -0
  76. package/dist/__tests__/integration/envelope-consistency.integration.test.js +205 -0
  77. package/dist/__tests__/integration/hip3-dex.integration.test.d.ts +1 -0
  78. package/dist/__tests__/integration/hip3-dex.integration.test.js +147 -0
  79. package/dist/__tests__/integration/hyperliquid.integration.test.d.ts +1 -0
  80. package/dist/__tests__/integration/hyperliquid.integration.test.js +79 -0
  81. package/dist/__tests__/integration/lighter.integration.test.d.ts +1 -0
  82. package/dist/__tests__/integration/lighter.integration.test.js +53 -0
  83. package/dist/__tests__/integration/new-commands-e2e.integration.test.d.ts +9 -0
  84. package/dist/__tests__/integration/new-commands-e2e.integration.test.js +236 -0
  85. package/dist/__tests__/integration/order-verification.integration.test.d.ts +1 -0
  86. package/dist/__tests__/integration/order-verification.integration.test.js +321 -0
  87. package/dist/__tests__/integration/pacifica.integration.test.d.ts +1 -0
  88. package/dist/__tests__/integration/pacifica.integration.test.js +75 -0
  89. package/dist/__tests__/integration/response-shapes.integration.test.d.ts +1 -0
  90. package/dist/__tests__/integration/response-shapes.integration.test.js +278 -0
  91. package/dist/__tests__/liquidity.test.d.ts +1 -0
  92. package/dist/__tests__/liquidity.test.js +225 -0
  93. package/dist/__tests__/plan-executor.test.d.ts +1 -0
  94. package/dist/__tests__/plan-executor.test.js +314 -0
  95. package/dist/__tests__/position-history.test.d.ts +1 -0
  96. package/dist/__tests__/position-history.test.js +367 -0
  97. package/dist/__tests__/retry.test.d.ts +1 -0
  98. package/dist/__tests__/retry.test.js +310 -0
  99. package/dist/__tests__/risk-assessment.test.d.ts +1 -0
  100. package/dist/__tests__/risk-assessment.test.js +145 -0
  101. package/dist/__tests__/security-adversarial.test.d.ts +1 -0
  102. package/dist/__tests__/security-adversarial.test.js +574 -0
  103. package/dist/__tests__/strategies.test.d.ts +1 -0
  104. package/dist/__tests__/strategies.test.js +539 -0
  105. package/dist/__tests__/trade-execution.test.d.ts +1 -0
  106. package/dist/__tests__/trade-execution.test.js +129 -0
  107. package/dist/__tests__/trade-validator.test.d.ts +1 -0
  108. package/dist/__tests__/trade-validator.test.js +655 -0
  109. package/dist/__tests__/utils.test.d.ts +1 -0
  110. package/dist/__tests__/utils.test.js +76 -0
  111. package/dist/api/public/hyperliquid.d.ts +18 -0
  112. package/dist/api/public/hyperliquid.js +82 -0
  113. package/dist/api/public/index.d.ts +8 -0
  114. package/dist/api/public/index.js +8 -0
  115. package/dist/api/public/lighter.d.ts +24 -0
  116. package/dist/api/public/lighter.js +100 -0
  117. package/dist/api/public/pacifica.d.ts +17 -0
  118. package/dist/api/public/pacifica.js +54 -0
  119. package/dist/api/public/urls.d.ts +12 -0
  120. package/dist/api/public/urls.js +33 -0
  121. package/dist/arb/history-stats.d.ts +44 -0
  122. package/dist/arb/history-stats.js +135 -0
  123. package/dist/arb/index.d.ts +4 -0
  124. package/dist/arb/index.js +4 -0
  125. package/dist/arb/sizing.d.ts +23 -0
  126. package/dist/arb/sizing.js +96 -0
  127. package/dist/arb/state.d.ts +51 -0
  128. package/dist/arb/state.js +112 -0
  129. package/dist/arb/utils.d.ts +81 -0
  130. package/dist/arb/utils.js +267 -0
  131. package/dist/arb-history-stats.d.ts +5 -0
  132. package/dist/arb-history-stats.js +5 -0
  133. package/dist/arb-sizing.d.ts +5 -0
  134. package/dist/arb-sizing.js +5 -0
  135. package/dist/arb-state.d.ts +5 -0
  136. package/dist/arb-state.js +5 -0
  137. package/dist/arb-utils.d.ts +5 -0
  138. package/dist/arb-utils.js +5 -0
  139. package/dist/bot/conditions.d.ts +32 -0
  140. package/dist/bot/conditions.js +141 -0
  141. package/dist/bot/config.d.ts +76 -0
  142. package/dist/bot/config.js +160 -0
  143. package/dist/bot/engine.d.ts +8 -0
  144. package/dist/bot/engine.js +519 -0
  145. package/dist/bot/presets.d.ts +11 -0
  146. package/dist/bot/presets.js +296 -0
  147. package/dist/bridge-engine.d.ts +133 -0
  148. package/dist/bridge-engine.js +1487 -0
  149. package/dist/cache.d.ts +25 -0
  150. package/dist/cache.js +99 -0
  151. package/dist/cli-spec.d.ts +50 -0
  152. package/dist/cli-spec.js +75 -0
  153. package/dist/client-id-tracker.d.ts +25 -0
  154. package/dist/client-id-tracker.js +76 -0
  155. package/dist/commands/account.d.ts +3 -0
  156. package/dist/commands/account.js +425 -0
  157. package/dist/commands/agent.d.ts +3 -0
  158. package/dist/commands/agent.js +386 -0
  159. package/dist/commands/alert.d.ts +2 -0
  160. package/dist/commands/alert.js +421 -0
  161. package/dist/commands/analytics.d.ts +3 -0
  162. package/dist/commands/analytics.js +311 -0
  163. package/dist/commands/arb/index.d.ts +3 -0
  164. package/dist/commands/arb/index.js +921 -0
  165. package/dist/commands/arb-auto.d.ts +54 -0
  166. package/dist/commands/arb-auto.js +1328 -0
  167. package/dist/commands/arb-manage.d.ts +5 -0
  168. package/dist/commands/arb-manage.js +5 -0
  169. package/dist/commands/arb.d.ts +2 -0
  170. package/dist/commands/arb.js +347 -0
  171. package/dist/commands/backtest.d.ts +2 -0
  172. package/dist/commands/backtest.js +327 -0
  173. package/dist/commands/bot.d.ts +3 -0
  174. package/dist/commands/bot.js +412 -0
  175. package/dist/commands/bridge.d.ts +2 -0
  176. package/dist/commands/bridge.js +396 -0
  177. package/dist/commands/dashboard.d.ts +3 -0
  178. package/dist/commands/dashboard.js +176 -0
  179. package/dist/commands/deposit.d.ts +4 -0
  180. package/dist/commands/deposit.js +573 -0
  181. package/dist/commands/dex.d.ts +3 -0
  182. package/dist/commands/dex.js +114 -0
  183. package/dist/commands/env.d.ts +2 -0
  184. package/dist/commands/env.js +136 -0
  185. package/dist/commands/funding.d.ts +2 -0
  186. package/dist/commands/funding.js +347 -0
  187. package/dist/commands/gap.d.ts +2 -0
  188. package/dist/commands/gap.js +305 -0
  189. package/dist/commands/health.d.ts +2 -0
  190. package/dist/commands/health.js +67 -0
  191. package/dist/commands/history.d.ts +2 -0
  192. package/dist/commands/history.js +235 -0
  193. package/dist/commands/init.d.ts +15 -0
  194. package/dist/commands/init.js +266 -0
  195. package/dist/commands/jobs.d.ts +2 -0
  196. package/dist/commands/jobs.js +133 -0
  197. package/dist/commands/manage.d.ts +4 -0
  198. package/dist/commands/manage.js +309 -0
  199. package/dist/commands/market.d.ts +3 -0
  200. package/dist/commands/market.js +225 -0
  201. package/dist/commands/plan.d.ts +3 -0
  202. package/dist/commands/plan.js +95 -0
  203. package/dist/commands/portfolio.d.ts +3 -0
  204. package/dist/commands/portfolio.js +169 -0
  205. package/dist/commands/rebalance.d.ts +3 -0
  206. package/dist/commands/rebalance.js +293 -0
  207. package/dist/commands/risk.d.ts +3 -0
  208. package/dist/commands/risk.js +169 -0
  209. package/dist/commands/run.d.ts +3 -0
  210. package/dist/commands/run.js +202 -0
  211. package/dist/commands/settings.d.ts +2 -0
  212. package/dist/commands/settings.js +102 -0
  213. package/dist/commands/stream.d.ts +5 -0
  214. package/dist/commands/stream.js +123 -0
  215. package/dist/commands/trade.d.ts +3 -0
  216. package/dist/commands/trade.js +1273 -0
  217. package/dist/commands/wallet.d.ts +14 -0
  218. package/dist/commands/wallet.js +602 -0
  219. package/dist/commands/withdraw.d.ts +3 -0
  220. package/dist/commands/withdraw.js +187 -0
  221. package/dist/config.d.ts +5 -0
  222. package/dist/config.js +68 -0
  223. package/dist/cross-chain-margin.d.ts +46 -0
  224. package/dist/cross-chain-margin.js +107 -0
  225. package/dist/dashboard/server.d.ts +80 -0
  226. package/dist/dashboard/server.js +340 -0
  227. package/dist/dashboard/ui.d.ts +4 -0
  228. package/dist/dashboard/ui.js +538 -0
  229. package/dist/dashboard/ws-feeds.d.ts +29 -0
  230. package/dist/dashboard/ws-feeds.js +660 -0
  231. package/dist/dex-asset-map.d.ts +80 -0
  232. package/dist/dex-asset-map.js +201 -0
  233. package/dist/errors.d.ts +109 -0
  234. package/dist/errors.js +84 -0
  235. package/dist/event-stream.d.ts +25 -0
  236. package/dist/event-stream.js +168 -0
  237. package/dist/exchanges/hyperliquid.d.ts +212 -0
  238. package/dist/exchanges/hyperliquid.js +931 -0
  239. package/dist/exchanges/interface.d.ts +95 -0
  240. package/dist/exchanges/interface.js +5 -0
  241. package/dist/exchanges/lighter.d.ts +159 -0
  242. package/dist/exchanges/lighter.js +793 -0
  243. package/dist/exchanges/pacifica.d.ts +51 -0
  244. package/dist/exchanges/pacifica.js +248 -0
  245. package/dist/execution-log.d.ts +36 -0
  246. package/dist/execution-log.js +102 -0
  247. package/dist/funding/history.d.ts +63 -0
  248. package/dist/funding/history.js +266 -0
  249. package/dist/funding/index.d.ts +3 -0
  250. package/dist/funding/index.js +3 -0
  251. package/dist/funding/normalize.d.ts +39 -0
  252. package/dist/funding/normalize.js +66 -0
  253. package/dist/funding/rates.d.ts +45 -0
  254. package/dist/funding/rates.js +172 -0
  255. package/dist/funding-history.d.ts +5 -0
  256. package/dist/funding-history.js +5 -0
  257. package/dist/funding-rates.d.ts +5 -0
  258. package/dist/funding-rates.js +5 -0
  259. package/dist/funding.d.ts +5 -0
  260. package/dist/funding.js +5 -0
  261. package/dist/index.d.ts +2 -0
  262. package/dist/index.js +458 -0
  263. package/dist/jobs.d.ts +37 -0
  264. package/dist/jobs.js +152 -0
  265. package/dist/liquidity.d.ts +34 -0
  266. package/dist/liquidity.js +100 -0
  267. package/dist/mcp-server.d.ts +9 -0
  268. package/dist/mcp-server.js +1206 -0
  269. package/dist/pacifica/client.d.ts +111 -0
  270. package/dist/pacifica/client.js +310 -0
  271. package/dist/pacifica/constants.d.ts +27 -0
  272. package/dist/pacifica/constants.js +47 -0
  273. package/dist/pacifica/deposit.d.ts +14 -0
  274. package/dist/pacifica/deposit.js +78 -0
  275. package/dist/pacifica/index.d.ts +6 -0
  276. package/dist/pacifica/index.js +11 -0
  277. package/dist/pacifica/signing.d.ts +49 -0
  278. package/dist/pacifica/signing.js +97 -0
  279. package/dist/pacifica/types/account.d.ts +42 -0
  280. package/dist/pacifica/types/account.js +1 -0
  281. package/dist/pacifica/types/index.d.ts +6 -0
  282. package/dist/pacifica/types/index.js +6 -0
  283. package/dist/pacifica/types/lake.d.ts +18 -0
  284. package/dist/pacifica/types/lake.js +1 -0
  285. package/dist/pacifica/types/market.d.ts +64 -0
  286. package/dist/pacifica/types/market.js +1 -0
  287. package/dist/pacifica/types/order.d.ts +92 -0
  288. package/dist/pacifica/types/order.js +1 -0
  289. package/dist/pacifica/types/position.d.ts +25 -0
  290. package/dist/pacifica/types/position.js +1 -0
  291. package/dist/pacifica/types/ws.d.ts +34 -0
  292. package/dist/pacifica/types/ws.js +41 -0
  293. package/dist/pacifica/ws-client.d.ts +42 -0
  294. package/dist/pacifica/ws-client.js +180 -0
  295. package/dist/plan-executor.d.ts +48 -0
  296. package/dist/plan-executor.js +280 -0
  297. package/dist/position-history.d.ts +68 -0
  298. package/dist/position-history.js +222 -0
  299. package/dist/rebalance.d.ts +64 -0
  300. package/dist/rebalance.js +142 -0
  301. package/dist/retry.d.ts +74 -0
  302. package/dist/retry.js +129 -0
  303. package/dist/risk.d.ts +48 -0
  304. package/dist/risk.js +156 -0
  305. package/dist/settings.d.ts +19 -0
  306. package/dist/settings.js +45 -0
  307. package/dist/shared-api.d.ts +5 -0
  308. package/dist/shared-api.js +5 -0
  309. package/dist/strategies/dca.d.ts +25 -0
  310. package/dist/strategies/dca.js +114 -0
  311. package/dist/strategies/funding-arb.d.ts +15 -0
  312. package/dist/strategies/funding-arb.js +281 -0
  313. package/dist/strategies/grid.d.ts +34 -0
  314. package/dist/strategies/grid.js +185 -0
  315. package/dist/strategies/trailing-stop.d.ts +17 -0
  316. package/dist/strategies/trailing-stop.js +121 -0
  317. package/dist/strategies/twap.d.ts +20 -0
  318. package/dist/strategies/twap.js +78 -0
  319. package/dist/trade-validator.d.ts +39 -0
  320. package/dist/trade-validator.js +154 -0
  321. package/dist/utils.d.ts +38 -0
  322. package/dist/utils.js +110 -0
  323. package/package.json +63 -0
  324. package/skills/perp-cli/SKILL.md +149 -0
  325. package/skills/perp-cli/references/commands.md +143 -0
@@ -0,0 +1,266 @@
1
+ import chalk from "chalk";
2
+ import { createInterface } from "readline";
3
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
4
+ import { resolve } from "path";
5
+ import { loadSettings, saveSettings } from "../settings.js";
6
+ const PERP_DIR = resolve(process.env.HOME || "~", ".perp");
7
+ const ENV_FILE = resolve(PERP_DIR, ".env");
8
+ // ── .env helpers ──────────────────────────────────────────────
9
+ export { ENV_FILE, loadEnvFile, setEnvVar };
10
+ function ensurePerpDir() {
11
+ if (!existsSync(PERP_DIR))
12
+ mkdirSync(PERP_DIR, { recursive: true, mode: 0o700 });
13
+ }
14
+ function loadEnvFile() {
15
+ if (!existsSync(ENV_FILE))
16
+ return {};
17
+ const lines = readFileSync(ENV_FILE, "utf-8").split("\n");
18
+ const env = {};
19
+ for (const line of lines) {
20
+ const trimmed = line.trim();
21
+ if (!trimmed || trimmed.startsWith("#"))
22
+ continue;
23
+ const eqIdx = trimmed.indexOf("=");
24
+ if (eqIdx > 0) {
25
+ env[trimmed.slice(0, eqIdx)] = trimmed.slice(eqIdx + 1);
26
+ }
27
+ }
28
+ return env;
29
+ }
30
+ function writeEnvFile(env) {
31
+ ensurePerpDir();
32
+ const lines = [
33
+ "# perp-cli configuration",
34
+ "# Generated by 'perp init' — edit freely",
35
+ "",
36
+ ];
37
+ for (const [key, value] of Object.entries(env)) {
38
+ lines.push(`${key}=${value}`);
39
+ }
40
+ lines.push("");
41
+ writeFileSync(ENV_FILE, lines.join("\n"), { mode: 0o600 });
42
+ }
43
+ function setEnvVar(key, value) {
44
+ ensurePerpDir();
45
+ const env = loadEnvFile();
46
+ env[key] = value;
47
+ writeEnvFile(env);
48
+ // Also set in current process
49
+ process.env[key] = value;
50
+ }
51
+ // ── Interactive helpers ───────────────────────────────────────
52
+ function ask(rl, question) {
53
+ return new Promise((resolve) => rl.question(question, (answer) => resolve(answer.trim())));
54
+ }
55
+ function askChoice(rl, question, choices) {
56
+ return new Promise((resolve) => {
57
+ const choiceStr = choices.map((c, i) => `${i + 1}) ${c}`).join(" ");
58
+ rl.question(`${question} [${choiceStr}]: `, (answer) => {
59
+ const trimmed = answer.trim();
60
+ const idx = parseInt(trimmed) - 1;
61
+ if (idx >= 0 && idx < choices.length)
62
+ return resolve(choices[idx]);
63
+ const match = choices.find((c) => c.toLowerCase().startsWith(trimmed.toLowerCase()));
64
+ resolve(match || choices[0]);
65
+ });
66
+ });
67
+ }
68
+ // ── Exchange → env var mapping ────────────────────────────────
69
+ export const EXCHANGE_ENV_MAP = {
70
+ pacifica: { envKey: "PACIFICA_PRIVATE_KEY", chain: "solana", label: "Pacifica (Solana)" },
71
+ hyperliquid: { envKey: "HL_PRIVATE_KEY", chain: "evm", label: "Hyperliquid (EVM)" },
72
+ lighter: { envKey: "LIGHTER_PRIVATE_KEY", chain: "evm", label: "Lighter (EVM)" },
73
+ };
74
+ // ── Validate keys ─────────────────────────────────────────────
75
+ export async function validateKey(chain, key) {
76
+ if (chain === "solana") {
77
+ try {
78
+ const { Keypair } = await import("@solana/web3.js");
79
+ const bs58 = (await import("bs58")).default;
80
+ try {
81
+ const kp = Keypair.fromSecretKey(bs58.decode(key));
82
+ return { valid: true, address: kp.publicKey.toBase58() };
83
+ }
84
+ catch {
85
+ const arr = JSON.parse(key);
86
+ const kp = Keypair.fromSecretKey(Uint8Array.from(arr));
87
+ return { valid: true, address: kp.publicKey.toBase58() };
88
+ }
89
+ }
90
+ catch {
91
+ return { valid: false, address: "" };
92
+ }
93
+ }
94
+ else {
95
+ try {
96
+ const { ethers } = await import("ethers");
97
+ const pk = key.startsWith("0x") ? key : `0x${key}`;
98
+ return { valid: true, address: new ethers.Wallet(pk).address };
99
+ }
100
+ catch {
101
+ return { valid: false, address: "" };
102
+ }
103
+ }
104
+ }
105
+ // ── Commands ──────────────────────────────────────────────────
106
+ export function registerInitCommand(program) {
107
+ // ── perp init (interactive) ──
108
+ program
109
+ .command("init")
110
+ .description("Interactive setup wizard — configure keys & default exchange")
111
+ .action(async () => {
112
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
113
+ try {
114
+ console.log(chalk.cyan.bold("\n perp-cli Setup Wizard\n"));
115
+ // Show current state
116
+ const env = loadEnvFile();
117
+ const configured = [];
118
+ for (const [exchange, info] of Object.entries(EXCHANGE_ENV_MAP)) {
119
+ if (env[info.envKey] || process.env[info.envKey]) {
120
+ configured.push(exchange);
121
+ }
122
+ }
123
+ if (configured.length > 0) {
124
+ console.log(chalk.white(" Already configured:"));
125
+ for (const ex of configured) {
126
+ const info = EXCHANGE_ENV_MAP[ex];
127
+ const key = env[info.envKey] || process.env[info.envKey] || "";
128
+ const masked = key.slice(0, 6) + "..." + key.slice(-4);
129
+ console.log(` ${chalk.cyan(ex.padEnd(14))} ${chalk.gray(masked)}`);
130
+ }
131
+ console.log();
132
+ const action = await askChoice(rl, " Add/update keys or exit?", ["update", "exit"]);
133
+ if (action === "exit") {
134
+ rl.close();
135
+ return;
136
+ }
137
+ }
138
+ // Select exchanges
139
+ console.log(chalk.white(" Exchanges:"));
140
+ console.log(` 1) ${chalk.cyan("Pacifica")} ${chalk.gray("— Solana perps (USDC on Solana)")}`);
141
+ console.log(` 2) ${chalk.cyan("Hyperliquid")} ${chalk.gray("— EVM perps (USDC on Arbitrum)")}`);
142
+ console.log(` 3) ${chalk.cyan("Lighter")} ${chalk.gray("— EVM perps (USDC on Ethereum/Arb)")}`);
143
+ console.log();
144
+ const exchangeChoice = await ask(rl, " Which exchange(s)? (1,2,3 or 'all'): ");
145
+ const selected = parseExchangeChoice(exchangeChoice);
146
+ if (selected.length === 0) {
147
+ console.log(chalk.red("\n No exchanges selected.\n"));
148
+ rl.close();
149
+ return;
150
+ }
151
+ // Collect keys per exchange
152
+ // EVM exchanges can share the same key
153
+ const evmExchanges = selected.filter((e) => EXCHANGE_ENV_MAP[e].chain === "evm");
154
+ const solExchanges = selected.filter((e) => EXCHANGE_ENV_MAP[e].chain === "solana");
155
+ const newEnv = { ...env };
156
+ if (evmExchanges.length > 0) {
157
+ console.log(chalk.cyan.bold("\n EVM Private Key") + chalk.gray(` (for ${evmExchanges.join(", ")})`));
158
+ // Check if they share one key
159
+ let sharedKey = "";
160
+ if (evmExchanges.length > 1) {
161
+ const share = await askChoice(rl, " Use same key for all EVM exchanges?", ["yes", "no"]);
162
+ if (share === "yes") {
163
+ sharedKey = await ask(rl, " EVM private key (0x...): ");
164
+ const { valid, address } = await validateKey("evm", sharedKey);
165
+ if (!valid) {
166
+ console.log(chalk.red(" Invalid EVM key."));
167
+ rl.close();
168
+ return;
169
+ }
170
+ const normalized = sharedKey.startsWith("0x") ? sharedKey : `0x${sharedKey}`;
171
+ console.log(` ${chalk.green("OK")} ${chalk.gray(address)}`);
172
+ for (const ex of evmExchanges) {
173
+ newEnv[EXCHANGE_ENV_MAP[ex].envKey] = normalized;
174
+ }
175
+ }
176
+ }
177
+ if (!sharedKey) {
178
+ for (const ex of evmExchanges) {
179
+ const info = EXCHANGE_ENV_MAP[ex];
180
+ const key = await ask(rl, ` ${info.label} private key (0x...): `);
181
+ if (!key) {
182
+ console.log(chalk.gray(" Skipped."));
183
+ continue;
184
+ }
185
+ const { valid, address } = await validateKey("evm", key);
186
+ if (!valid) {
187
+ console.log(chalk.red(" Invalid key, skipped."));
188
+ continue;
189
+ }
190
+ const normalized = key.startsWith("0x") ? key : `0x${key}`;
191
+ newEnv[info.envKey] = normalized;
192
+ console.log(` ${chalk.green("OK")} ${chalk.gray(address)}`);
193
+ }
194
+ }
195
+ }
196
+ if (solExchanges.length > 0) {
197
+ console.log(chalk.cyan.bold("\n Solana Private Key") + chalk.gray(" (for Pacifica)"));
198
+ const key = await ask(rl, " Solana private key (base58): ");
199
+ if (key) {
200
+ const { valid, address } = await validateKey("solana", key);
201
+ if (valid) {
202
+ newEnv.PACIFICA_PRIVATE_KEY = key;
203
+ console.log(` ${chalk.green("OK")} ${chalk.gray(address)}`);
204
+ }
205
+ else {
206
+ console.log(chalk.red(" Invalid Solana key, skipped."));
207
+ }
208
+ }
209
+ }
210
+ // Default exchange
211
+ const settings = loadSettings();
212
+ if (selected.length === 1) {
213
+ settings.defaultExchange = selected[0];
214
+ }
215
+ else if (!settings.defaultExchange) {
216
+ const defaultEx = await askChoice(rl, "\n Default exchange?", selected);
217
+ settings.defaultExchange = defaultEx;
218
+ }
219
+ saveSettings(settings);
220
+ // Write .env
221
+ writeEnvFile(newEnv);
222
+ // Summary
223
+ console.log(chalk.cyan.bold("\n Setup Complete!\n"));
224
+ console.log(` Config: ${chalk.gray("~/.perp/.env")}`);
225
+ console.log(` Default: ${chalk.cyan(settings.defaultExchange || selected[0])}`);
226
+ for (const ex of selected) {
227
+ const info = EXCHANGE_ENV_MAP[ex];
228
+ const key = newEnv[info.envKey];
229
+ if (key) {
230
+ console.log(` ${chalk.cyan(ex.padEnd(14))} ${chalk.green("configured")}`);
231
+ }
232
+ }
233
+ console.log(chalk.white.bold("\n Next steps:"));
234
+ console.log(` ${chalk.green("perp status")} account overview`);
235
+ if (selected.includes("hyperliquid")) {
236
+ console.log(` ${chalk.green("perp deposit hyperliquid <amt>")} deposit USDC`);
237
+ }
238
+ if (selected.includes("pacifica")) {
239
+ console.log(` ${chalk.green("perp deposit pacifica <amt>")} deposit USDC`);
240
+ }
241
+ console.log(` ${chalk.green("perp market list")} browse markets`);
242
+ console.log(` ${chalk.green("perp dashboard")} live monitoring\n`);
243
+ rl.close();
244
+ }
245
+ catch (err) {
246
+ rl.close();
247
+ throw err;
248
+ }
249
+ });
250
+ }
251
+ function parseExchangeChoice(input) {
252
+ const lower = input.toLowerCase().trim();
253
+ if (lower === "all" || lower === "1,2,3")
254
+ return ["pacifica", "hyperliquid", "lighter"];
255
+ const exchanges = [];
256
+ const parts = lower.split(/[,\s]+/);
257
+ for (const p of parts) {
258
+ if (p === "1" || p.startsWith("pac"))
259
+ exchanges.push("pacifica");
260
+ else if (p === "2" || p.startsWith("hyp") || p === "hl")
261
+ exchanges.push("hyperliquid");
262
+ else if (p === "3" || p.startsWith("lig") || p === "lt")
263
+ exchanges.push("lighter");
264
+ }
265
+ return [...new Set(exchanges)];
266
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function registerJobsCommands(program: Command, isJson: () => boolean): void;
@@ -0,0 +1,133 @@
1
+ import { listJobs, stopJob, removeJob, loadJob, logFile } from "../jobs.js";
2
+ import { printJson, jsonOk } from "../utils.js";
3
+ import chalk from "chalk";
4
+ import { existsSync, readFileSync } from "node:fs";
5
+ import { execSync } from "node:child_process";
6
+ export function registerJobsCommands(program, isJson) {
7
+ const jobs = program.command("jobs").description("Background job management (tmux)");
8
+ jobs
9
+ .command("list", { isDefault: true })
10
+ .description("List all jobs")
11
+ .action(() => {
12
+ const all = listJobs();
13
+ if (isJson())
14
+ return printJson(jsonOk(all));
15
+ if (all.length === 0) {
16
+ console.log(chalk.gray("\n No jobs.\n"));
17
+ return;
18
+ }
19
+ console.log(chalk.cyan.bold("\n Background Jobs\n"));
20
+ for (const j of all) {
21
+ const statusColor = j.status === "running" ? chalk.green : j.status === "error" ? chalk.red : chalk.gray;
22
+ const elapsed = j.status === "running"
23
+ ? ` (${formatElapsed(Date.now() - new Date(j.startedAt).getTime())})`
24
+ : "";
25
+ console.log(` ${chalk.white.bold(j.id)} ${statusColor(j.status.padEnd(8))} ` +
26
+ `${chalk.cyan(j.strategy.padEnd(14))} ` +
27
+ `${chalk.gray(j.exchange.padEnd(12))} ` +
28
+ `${chalk.gray(new Date(j.startedAt).toLocaleString())}${elapsed}`);
29
+ // Show result summary if available
30
+ if (j.result) {
31
+ const r = j.result;
32
+ if (r.pctComplete !== undefined) {
33
+ console.log(chalk.gray(` Progress: ${r.pctComplete}% | Filled: ${r.filled}/${(Number(r.filled) + Number(r.remaining)).toFixed(4)}`));
34
+ }
35
+ else if (r.activePositions !== undefined) {
36
+ console.log(chalk.gray(` Positions: ${r.activePositions} | Cycle: ${r.cycle}`));
37
+ }
38
+ }
39
+ }
40
+ console.log();
41
+ });
42
+ jobs
43
+ .command("stop <id>")
44
+ .description("Stop a running job")
45
+ .action((id) => {
46
+ const job = loadJob(id);
47
+ if (!job) {
48
+ console.log(chalk.red(`\n Job ${id} not found.\n`));
49
+ return;
50
+ }
51
+ stopJob(id);
52
+ if (isJson())
53
+ return printJson(jsonOk({ id, status: "stopped" }));
54
+ console.log(chalk.green(`\n Job ${id} stopped.\n`));
55
+ });
56
+ jobs
57
+ .command("logs <id>")
58
+ .description("Show logs for a job")
59
+ .option("-f, --follow", "Follow log output (tail -f)")
60
+ .option("-n, --lines <n>", "Number of lines to show", "50")
61
+ .action((id, opts) => {
62
+ const log = logFile(id);
63
+ if (!existsSync(log)) {
64
+ console.log(chalk.gray(`\n No logs for job ${id}.\n`));
65
+ return;
66
+ }
67
+ if (opts.follow) {
68
+ // Attach to tmux session if running, otherwise tail -f
69
+ const job = loadJob(id);
70
+ if (job?.status === "running") {
71
+ try {
72
+ execSync(`tmux attach-session -t ${job.tmuxSession}`, { stdio: "inherit" });
73
+ return;
74
+ }
75
+ catch { /* fall through to tail */ }
76
+ }
77
+ try {
78
+ execSync(`tail -f ${log}`, { stdio: "inherit" });
79
+ }
80
+ catch { /* ctrl-c */ }
81
+ }
82
+ else {
83
+ const content = readFileSync(log, "utf-8");
84
+ const lines = content.split("\n");
85
+ const n = parseInt(opts.lines);
86
+ const show = lines.slice(-n).join("\n");
87
+ console.log(show);
88
+ }
89
+ });
90
+ jobs
91
+ .command("remove <id>")
92
+ .description("Remove a job entry and its logs")
93
+ .action((id) => {
94
+ const job = loadJob(id);
95
+ if (!job) {
96
+ console.log(chalk.red(`\n Job ${id} not found.\n`));
97
+ return;
98
+ }
99
+ if (job.status === "running") {
100
+ stopJob(id);
101
+ }
102
+ removeJob(id);
103
+ if (isJson())
104
+ return printJson(jsonOk({ id, removed: true }));
105
+ console.log(chalk.green(`\n Job ${id} removed.\n`));
106
+ });
107
+ jobs
108
+ .command("clean")
109
+ .description("Remove all stopped/done jobs")
110
+ .action(() => {
111
+ const all = listJobs();
112
+ let removed = 0;
113
+ for (const j of all) {
114
+ if (j.status !== "running") {
115
+ removeJob(j.id);
116
+ removed++;
117
+ }
118
+ }
119
+ if (isJson())
120
+ return printJson(jsonOk({ removed }));
121
+ console.log(chalk.green(`\n Removed ${removed} finished jobs.\n`));
122
+ });
123
+ }
124
+ function formatElapsed(ms) {
125
+ const s = Math.floor(ms / 1000);
126
+ if (s < 60)
127
+ return `${s}s`;
128
+ const m = Math.floor(s / 60);
129
+ if (m < 60)
130
+ return `${m}m ${s % 60}s`;
131
+ const h = Math.floor(m / 60);
132
+ return `${h}h ${m % 60}m`;
133
+ }
@@ -0,0 +1,4 @@
1
+ import { Command } from "commander";
2
+ import type { ExchangeAdapter } from "../exchanges/interface.js";
3
+ import { PacificaAdapter } from "../exchanges/pacifica.js";
4
+ export declare function registerManageCommands(program: Command, getAdapter: () => Promise<ExchangeAdapter>, isJson: () => boolean, getPacificaAdapter: () => PacificaAdapter): void;