perp-cli 0.12.17 → 0.14.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 (363) hide show
  1. package/README.md +247 -27
  2. package/dist/_stderr-filter.js +0 -1
  3. package/dist/agent-wallet/lighter-keystore.d.ts +11 -0
  4. package/dist/agent-wallet/lighter-keystore.js +92 -10
  5. package/dist/agent-wallet/passphrase.d.ts +1 -1
  6. package/dist/agent-wallet/passphrase.js +16 -3
  7. package/dist/agent-wallet/store.d.ts +37 -4
  8. package/dist/agent-wallet/store.js +132 -0
  9. package/dist/agent-wallet/venue-keystore.d.ts +14 -0
  10. package/dist/agent-wallet/venue-keystore.js +121 -0
  11. package/dist/api/public/hyperliquid.js +4 -4
  12. package/dist/api/public/lighter.js +5 -5
  13. package/dist/api/public/pacifica.d.ts +0 -1
  14. package/dist/api/public/pacifica.js +4 -13
  15. package/dist/api/public/urls.d.ts +9 -1
  16. package/dist/api/public/urls.js +35 -13
  17. package/dist/arb/file-lock.d.ts +11 -0
  18. package/dist/arb/file-lock.js +74 -0
  19. package/dist/arb/index.d.ts +2 -2
  20. package/dist/arb/index.js +2 -2
  21. package/dist/arb/pending-entry.d.ts +32 -0
  22. package/dist/arb/pending-entry.js +77 -0
  23. package/dist/arb/sizing.d.ts +6 -13
  24. package/dist/arb/sizing.js +12 -51
  25. package/dist/arb/state.d.ts +45 -12
  26. package/dist/arb/state.js +214 -42
  27. package/dist/arb/utils.d.ts +20 -6
  28. package/dist/arb/utils.js +43 -39
  29. package/dist/bot/conditions.d.ts +4 -4
  30. package/dist/bot/conditions.js +11 -6
  31. package/dist/bot/config.d.ts +17 -1
  32. package/dist/bot/config.js +48 -12
  33. package/dist/bot/engine.d.ts +61 -2
  34. package/dist/bot/engine.js +610 -144
  35. package/dist/bot/fill-size.d.ts +7 -0
  36. package/dist/bot/fill-size.js +18 -0
  37. package/dist/bot/index.d.ts +1 -1
  38. package/dist/bot/index.js +1 -1
  39. package/dist/bot/perp-order.d.ts +99 -0
  40. package/dist/bot/perp-order.js +299 -0
  41. package/dist/bot/presets.d.ts +7 -0
  42. package/dist/bot/presets.js +17 -0
  43. package/dist/bot/quarantine.d.ts +75 -0
  44. package/dist/bot/quarantine.js +190 -0
  45. package/dist/bot/quote-rules.d.ts +23 -0
  46. package/dist/bot/quote-rules.js +69 -0
  47. package/dist/bot/strategies/avellaneda-mm.d.ts +32 -2
  48. package/dist/bot/strategies/avellaneda-mm.js +72 -70
  49. package/dist/bot/strategies/basis-arb.js +2 -0
  50. package/dist/bot/strategies/dca-strategy.d.ts +2 -0
  51. package/dist/bot/strategies/dca-strategy.js +5 -0
  52. package/dist/bot/strategies/funding-arb-strategy.d.ts +43 -3
  53. package/dist/bot/strategies/funding-arb-strategy.js +571 -380
  54. package/dist/bot/strategies/funding-arb-utils.d.ts +48 -23
  55. package/dist/bot/strategies/funding-arb-utils.js +159 -195
  56. package/dist/bot/strategies/funding-arb-v2.d.ts +32 -0
  57. package/dist/bot/strategies/funding-arb-v2.js +258 -47
  58. package/dist/bot/strategies/funding-auto.js +215 -194
  59. package/dist/bot/strategies/spot-perp-arb-strategy.d.ts +14 -0
  60. package/dist/bot/strategies/spot-perp-arb-strategy.js +217 -50
  61. package/dist/bot/strategies/spot-perp-executor.d.ts +73 -19
  62. package/dist/bot/strategies/spot-perp-executor.js +319 -120
  63. package/dist/bot/strategies/spot-perp-state.d.ts +43 -0
  64. package/dist/bot/strategies/spot-perp-state.js +70 -0
  65. package/dist/bot/strategy-types.d.ts +6 -0
  66. package/dist/bot/tui/BotDashboard.d.ts +7 -2
  67. package/dist/bot/tui/BotDashboard.js +6 -3
  68. package/dist/bridge-engine.d.ts +120 -19
  69. package/dist/bridge-engine.js +710 -422
  70. package/dist/bridge-signers.d.ts +54 -0
  71. package/dist/bridge-signers.js +161 -0
  72. package/dist/cache.d.ts +4 -1
  73. package/dist/cache.js +11 -0
  74. package/dist/cli-flags.d.ts +50 -0
  75. package/dist/cli-flags.js +104 -0
  76. package/dist/cli-program.d.ts +35 -0
  77. package/dist/cli-program.js +303 -0
  78. package/dist/cli-spec.js +1 -1
  79. package/dist/client-id-tracker.js +11 -2
  80. package/dist/commands/account.d.ts +7 -1
  81. package/dist/commands/account.js +70 -92
  82. package/dist/commands/agent-import.d.ts +114 -0
  83. package/dist/commands/agent-import.js +794 -0
  84. package/dist/commands/agent.d.ts +232 -2
  85. package/dist/commands/agent.js +1858 -955
  86. package/dist/commands/alerts.d.ts +1 -0
  87. package/dist/commands/alerts.js +31 -28
  88. package/dist/commands/arb/index.d.ts +60 -1
  89. package/dist/commands/arb/index.js +381 -384
  90. package/dist/commands/arb-auto.d.ts +132 -4
  91. package/dist/commands/arb-auto.js +1474 -967
  92. package/dist/commands/arb.d.ts +3 -1
  93. package/dist/commands/arb.js +62 -30
  94. package/dist/commands/bot.js +127 -47
  95. package/dist/commands/bridge.d.ts +2 -1
  96. package/dist/commands/bridge.js +339 -192
  97. package/dist/commands/builder.d.ts +55 -0
  98. package/dist/commands/builder.js +387 -0
  99. package/dist/commands/funds-onchain.d.ts +32 -0
  100. package/dist/commands/funds-onchain.js +248 -0
  101. package/dist/commands/funds.d.ts +25 -0
  102. package/dist/commands/funds.js +925 -245
  103. package/dist/commands/history.js +102 -54
  104. package/dist/commands/init.d.ts +107 -15
  105. package/dist/commands/init.js +1116 -249
  106. package/dist/commands/jobs.d.ts +2 -1
  107. package/dist/commands/jobs.js +77 -15
  108. package/dist/commands/manage.js +136 -91
  109. package/dist/commands/market.d.ts +4 -1
  110. package/dist/commands/market.js +35 -32
  111. package/dist/commands/outcome.d.ts +10 -0
  112. package/dist/commands/outcome.js +396 -0
  113. package/dist/commands/plan.js +52 -12
  114. package/dist/commands/portfolio.d.ts +1 -0
  115. package/dist/commands/portfolio.js +60 -72
  116. package/dist/commands/rebalance.d.ts +2 -1
  117. package/dist/commands/rebalance.js +33 -43
  118. package/dist/commands/risk.js +46 -19
  119. package/dist/commands/settings.d.ts +1 -0
  120. package/dist/commands/settings.js +246 -51
  121. package/dist/commands/trade.d.ts +50 -0
  122. package/dist/commands/trade.js +1943 -695
  123. package/dist/commands/wallet.d.ts +30 -1
  124. package/dist/commands/wallet.js +357 -459
  125. package/dist/config.d.ts +60 -9
  126. package/dist/config.js +252 -82
  127. package/dist/constants.d.ts +29 -4
  128. package/dist/constants.js +62 -20
  129. package/dist/dashboard/server.d.ts +2 -0
  130. package/dist/dashboard/server.js +9 -6
  131. package/dist/dashboard/ui.js +6 -4
  132. package/dist/dashboard/ws-feeds.js +20 -8
  133. package/dist/dex-asset-map.js +5 -3
  134. package/dist/errors.d.ts +122 -0
  135. package/dist/errors.js +194 -0
  136. package/dist/event-stream.js +75 -19
  137. package/dist/evm-master-tx.d.ts +59 -0
  138. package/dist/evm-master-tx.js +154 -0
  139. package/dist/exchanges/adapter-network.d.ts +10 -0
  140. package/dist/exchanges/adapter-network.js +10 -0
  141. package/dist/exchanges/arcus/adapter.d.ts +196 -0
  142. package/dist/exchanges/arcus/adapter.js +1092 -0
  143. package/dist/exchanges/arcus/agent.d.ts +55 -0
  144. package/dist/exchanges/arcus/agent.js +188 -0
  145. package/dist/exchanges/arcus/client.d.ts +63 -0
  146. package/dist/exchanges/arcus/client.js +285 -0
  147. package/dist/exchanges/arcus/public.d.ts +65 -0
  148. package/dist/exchanges/arcus/public.js +138 -0
  149. package/dist/exchanges/arcus/signing.d.ts +176 -0
  150. package/dist/exchanges/arcus/signing.js +288 -0
  151. package/dist/exchanges/arcus/spot-chain.d.ts +60 -0
  152. package/dist/exchanges/arcus/spot-chain.js +241 -0
  153. package/dist/exchanges/arcus/spot-router.d.ts +177 -0
  154. package/dist/exchanges/arcus/spot-router.js +183 -0
  155. package/dist/exchanges/arcus/spot.d.ts +109 -0
  156. package/dist/exchanges/arcus/spot.js +645 -0
  157. package/dist/exchanges/aster-funds.d.ts +127 -0
  158. package/dist/exchanges/aster-funds.js +252 -0
  159. package/dist/exchanges/aster-nonce.d.ts +2 -0
  160. package/dist/exchanges/aster-nonce.js +107 -0
  161. package/dist/exchanges/aster-spot.d.ts +117 -0
  162. package/dist/exchanges/aster-spot.js +686 -0
  163. package/dist/exchanges/aster-typed-data.d.ts +133 -7
  164. package/dist/exchanges/aster-typed-data.js +121 -7
  165. package/dist/exchanges/aster.d.ts +240 -6
  166. package/dist/exchanges/aster.js +1139 -195
  167. package/dist/exchanges/builder-fee.d.ts +254 -0
  168. package/dist/exchanges/builder-fee.js +421 -0
  169. package/dist/exchanges/canonical-symbols.d.ts +32 -0
  170. package/dist/exchanges/canonical-symbols.js +96 -0
  171. package/dist/exchanges/capabilities.d.ts +7 -1
  172. package/dist/exchanges/capabilities.js +7 -0
  173. package/dist/exchanges/hl-signing.d.ts +165 -0
  174. package/dist/exchanges/hl-signing.js +158 -0
  175. package/dist/exchanges/hyperliquid-outcome.d.ts +211 -0
  176. package/dist/exchanges/hyperliquid-outcome.js +646 -0
  177. package/dist/exchanges/hyperliquid-spot.d.ts +44 -5
  178. package/dist/exchanges/hyperliquid-spot.js +308 -117
  179. package/dist/exchanges/hyperliquid.d.ts +462 -57
  180. package/dist/exchanges/hyperliquid.js +1575 -449
  181. package/dist/exchanges/index.d.ts +6 -0
  182. package/dist/exchanges/index.js +4 -0
  183. package/dist/exchanges/interface.d.ts +63 -10
  184. package/dist/exchanges/interface.js +18 -3
  185. package/dist/exchanges/ioc-outcome.d.ts +44 -0
  186. package/dist/exchanges/ioc-outcome.js +57 -0
  187. package/dist/exchanges/legacy-trading-v2.d.ts +39 -0
  188. package/dist/exchanges/legacy-trading-v2.js +940 -0
  189. package/dist/exchanges/lighter-public.d.ts +43 -0
  190. package/dist/exchanges/lighter-public.js +276 -0
  191. package/dist/exchanges/lighter-rh-deposit.d.ts +62 -0
  192. package/dist/exchanges/lighter-rh-deposit.js +185 -0
  193. package/dist/exchanges/lighter-signer.d.ts +123 -0
  194. package/dist/exchanges/lighter-signer.js +215 -0
  195. package/dist/exchanges/lighter-spot.d.ts +91 -13
  196. package/dist/exchanges/lighter-spot.js +595 -233
  197. package/dist/exchanges/lighter.d.ts +611 -39
  198. package/dist/exchanges/lighter.js +1890 -649
  199. package/dist/exchanges/ondo/adapter.d.ts +152 -0
  200. package/dist/exchanges/ondo/adapter.js +811 -0
  201. package/dist/exchanges/ondo/agent.d.ts +31 -0
  202. package/dist/exchanges/ondo/agent.js +101 -0
  203. package/dist/exchanges/ondo/client.d.ts +60 -0
  204. package/dist/exchanges/ondo/client.js +297 -0
  205. package/dist/exchanges/ondo/funds.d.ts +95 -0
  206. package/dist/exchanges/ondo/funds.js +198 -0
  207. package/dist/exchanges/ondo/public.d.ts +95 -0
  208. package/dist/exchanges/ondo/public.js +214 -0
  209. package/dist/exchanges/ondo/spot.d.ts +127 -0
  210. package/dist/exchanges/ondo/spot.js +392 -0
  211. package/dist/exchanges/outcome-interface.d.ts +140 -0
  212. package/dist/exchanges/outcome-interface.js +16 -0
  213. package/dist/exchanges/pacifica-spot.d.ts +83 -0
  214. package/dist/exchanges/pacifica-spot.js +324 -0
  215. package/dist/exchanges/pacifica-typed-data.d.ts +30 -13
  216. package/dist/exchanges/pacifica-typed-data.js +57 -9
  217. package/dist/exchanges/pacifica.d.ts +293 -4
  218. package/dist/exchanges/pacifica.js +1044 -90
  219. package/dist/exchanges/precision.d.ts +47 -0
  220. package/dist/exchanges/precision.js +209 -0
  221. package/dist/exchanges/qfex/adapter.d.ts +341 -0
  222. package/dist/exchanges/qfex/adapter.js +1386 -0
  223. package/dist/exchanges/qfex/agent.d.ts +58 -0
  224. package/dist/exchanges/qfex/agent.js +188 -0
  225. package/dist/exchanges/qfex/client.d.ts +54 -0
  226. package/dist/exchanges/qfex/client.js +138 -0
  227. package/dist/exchanges/qfex/errors.d.ts +70 -0
  228. package/dist/exchanges/qfex/errors.js +236 -0
  229. package/dist/exchanges/qfex/public.d.ts +122 -0
  230. package/dist/exchanges/qfex/public.js +268 -0
  231. package/dist/exchanges/qfex/signing.d.ts +160 -0
  232. package/dist/exchanges/qfex/signing.js +283 -0
  233. package/dist/exchanges/qfex/ws-transport.d.ts +294 -0
  234. package/dist/exchanges/qfex/ws-transport.js +798 -0
  235. package/dist/exchanges/rate-limiter.d.ts +12 -0
  236. package/dist/exchanges/rate-limiter.js +64 -0
  237. package/dist/exchanges/registry.d.ts +14 -2
  238. package/dist/exchanges/registry.js +20 -32
  239. package/dist/exchanges/slippage.d.ts +37 -0
  240. package/dist/exchanges/slippage.js +70 -0
  241. package/dist/exchanges/spot-factory.d.ts +166 -0
  242. package/dist/exchanges/spot-factory.js +431 -0
  243. package/dist/exchanges/spot-interface.d.ts +98 -4
  244. package/dist/exchanges/spot-interface.js +27 -1
  245. package/dist/exchanges/spot-result.d.ts +79 -0
  246. package/dist/exchanges/spot-result.js +273 -0
  247. package/dist/exchanges/trading-v2.d.ts +117 -0
  248. package/dist/exchanges/trading-v2.js +301 -0
  249. package/dist/exchanges/venue-types.d.ts +231 -0
  250. package/dist/exchanges/venue-types.js +87 -0
  251. package/dist/exchanges/venue.d.ts +211 -0
  252. package/dist/exchanges/venue.js +132 -0
  253. package/dist/exchanges/venues/arcus.d.ts +8 -0
  254. package/dist/exchanges/venues/arcus.js +109 -0
  255. package/dist/exchanges/venues/aster.d.ts +49 -0
  256. package/dist/exchanges/venues/aster.js +176 -0
  257. package/dist/exchanges/venues/create-helpers.d.ts +18 -0
  258. package/dist/exchanges/venues/create-helpers.js +199 -0
  259. package/dist/exchanges/venues/hyperliquid.d.ts +23 -0
  260. package/dist/exchanges/venues/hyperliquid.js +126 -0
  261. package/dist/exchanges/venues/index.d.ts +16 -0
  262. package/dist/exchanges/venues/index.js +26 -0
  263. package/dist/exchanges/venues/lighter-family.d.ts +92 -0
  264. package/dist/exchanges/venues/lighter-family.js +166 -0
  265. package/dist/exchanges/venues/lighter-rh.d.ts +7 -0
  266. package/dist/exchanges/venues/lighter-rh.js +58 -0
  267. package/dist/exchanges/venues/lighter.d.ts +6 -0
  268. package/dist/exchanges/venues/lighter.js +55 -0
  269. package/dist/exchanges/venues/ondo.d.ts +29 -0
  270. package/dist/exchanges/venues/ondo.js +136 -0
  271. package/dist/exchanges/venues/pacifica.d.ts +8 -0
  272. package/dist/exchanges/venues/pacifica.js +95 -0
  273. package/dist/exchanges/venues/qfex.d.ts +39 -0
  274. package/dist/exchanges/venues/qfex.js +140 -0
  275. package/dist/execution-log.js +10 -2
  276. package/dist/funding/legacy-public.d.ts +31 -0
  277. package/dist/funding/legacy-public.js +369 -0
  278. package/dist/funding/normalize.d.ts +28 -16
  279. package/dist/funding/normalize.js +61 -29
  280. package/dist/funding/rates.d.ts +46 -11
  281. package/dist/funding/rates.js +228 -277
  282. package/dist/guardrail/perp-guardrail.js +0 -0
  283. package/dist/index.js +156 -684
  284. package/dist/jobs.d.ts +84 -6
  285. package/dist/jobs.js +239 -41
  286. package/dist/landing.d.ts +7 -2
  287. package/dist/landing.js +19 -3
  288. package/dist/mcp-server.d.ts +14 -4
  289. package/dist/mcp-server.js +821 -403
  290. package/dist/mcp-trade-guard.d.ts +16 -0
  291. package/dist/mcp-trade-guard.js +59 -0
  292. package/dist/onchain-balances.d.ts +40 -0
  293. package/dist/onchain-balances.js +173 -0
  294. package/dist/pacifica/client.d.ts +41 -10
  295. package/dist/pacifica/client.js +167 -63
  296. package/dist/pacifica/constants.d.ts +0 -1
  297. package/dist/pacifica/constants.js +0 -5
  298. package/dist/pacifica/deposit.d.ts +30 -3
  299. package/dist/pacifica/deposit.js +119 -2
  300. package/dist/pacifica/faucet.d.ts +29 -0
  301. package/dist/pacifica/faucet.js +100 -0
  302. package/dist/pacifica/sign-tx.d.ts +15 -0
  303. package/dist/pacifica/sign-tx.js +41 -0
  304. package/dist/pacifica/types/account.d.ts +28 -4
  305. package/dist/pacifica/types/lake.d.ts +20 -5
  306. package/dist/pacifica/types/lake.js +4 -0
  307. package/dist/pacifica/types/market.d.ts +5 -0
  308. package/dist/pacifica/types/order.d.ts +4 -1
  309. package/dist/pacifica/types/position.d.ts +9 -1
  310. package/dist/plan-executor.d.ts +10 -5
  311. package/dist/plan-executor.js +165 -54
  312. package/dist/position-history.js +13 -0
  313. package/dist/rebalance.d.ts +26 -2
  314. package/dist/rebalance.js +86 -7
  315. package/dist/relay-swap.d.ts +232 -0
  316. package/dist/relay-swap.js +630 -0
  317. package/dist/risk.js +5 -1
  318. package/dist/secret-env.d.ts +15 -0
  319. package/dist/secret-env.js +36 -0
  320. package/dist/settings.d.ts +169 -6
  321. package/dist/settings.js +219 -38
  322. package/dist/signer/ows-evm.d.ts +13 -0
  323. package/dist/signer/ows-evm.js +22 -0
  324. package/dist/smart-order.d.ts +9 -0
  325. package/dist/smart-order.js +53 -8
  326. package/dist/strategies/dca.d.ts +6 -2
  327. package/dist/strategies/dca.js +66 -26
  328. package/dist/strategies/funding-arb.d.ts +11 -1
  329. package/dist/strategies/funding-arb.js +202 -89
  330. package/dist/strategies/grid.d.ts +31 -0
  331. package/dist/strategies/grid.js +89 -11
  332. package/dist/strategies/pending-stop.d.ts +8 -0
  333. package/dist/strategies/pending-stop.js +12 -0
  334. package/dist/strategies/split-order.d.ts +45 -17
  335. package/dist/strategies/split-order.js +157 -91
  336. package/dist/strategies/trailing-stop.d.ts +2 -1
  337. package/dist/strategies/trailing-stop.js +52 -6
  338. package/dist/strategies/twap.d.ts +6 -1
  339. package/dist/strategies/twap.js +39 -11
  340. package/dist/trade-validator.d.ts +22 -0
  341. package/dist/trade-validator.js +119 -15
  342. package/dist/utils/numeric.d.ts +46 -0
  343. package/dist/utils/numeric.js +79 -0
  344. package/dist/utils.d.ts +19 -0
  345. package/dist/utils.js +96 -19
  346. package/dist/withdraw-gate.d.ts +49 -0
  347. package/dist/withdraw-gate.js +131 -0
  348. package/package.json +15 -6
  349. package/skills/perp-cli/SKILL.md +159 -55
  350. package/skills/perp-cli/references/agent-operations.md +112 -58
  351. package/skills/perp-cli/references/commands.md +269 -73
  352. package/skills/perp-cli/references/strategies.md +34 -31
  353. package/skills/perp-cli/scripts/arb-monitor.sh +10 -5
  354. package/skills/perp-cli/scripts/funding-analysis.sh +14 -9
  355. package/skills/perp-cli/scripts/preflight.sh +21 -14
  356. package/skills/perp-cli/scripts/spot-perp-scan.sh +12 -7
  357. package/skills/perp-cli/scripts/validate-arb.sh +31 -15
  358. package/vendor/lighter-signer/SHA256SUMS +2 -0
  359. package/vendor/lighter-signer/VERSION +8 -0
  360. package/vendor/lighter-signer/lighter-signer.wasm +0 -0
  361. package/vendor/lighter-signer/wasm_exec.js +575 -0
  362. package/dist/bot/strategies/funding-arb-v2-strategy.d.ts +0 -42
  363. package/dist/bot/strategies/funding-arb-v2-strategy.js +0 -864
package/dist/jobs.d.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import type { Command } from "commander";
2
+ import type { Network } from "./exchanges/venue-types.js";
1
3
  export interface JobEntry {
2
4
  id: string;
3
5
  strategy: string;
@@ -9,24 +11,86 @@ export interface JobEntry {
9
11
  status: "running" | "stopped" | "done" | "error";
10
12
  result?: Record<string, unknown>;
11
13
  }
14
+ export declare function assertTmuxSessionName(name: string): string;
12
15
  export declare function logFile(id: string): string;
16
+ /** Kill a tmux session by (validated) name, without a shell. */
17
+ export declare function killTmuxSession(name: string): void;
13
18
  export declare function saveJob(job: JobEntry): void;
14
19
  export declare function loadJob(id: string): JobEntry | null;
15
20
  export declare function listJobs(): JobEntry[];
16
21
  /**
17
- * Start a job in a tmux background session.
18
- * Returns the job entry.
22
+ * Root flags that pick WHICH account / market a command uses. A background child
23
+ * must run with the same ones as its parent (otherwise it silently trades another
24
+ * wallet, skips the agent, or another HIP-3 dex). Only non-secret selectors are
25
+ * forwarded; the child resolves the credentials themselves (OWS vault / agent keystores).
26
+ */
27
+ export interface JobRootSelectors {
28
+ /** --ows <name> */
29
+ ows: string | null;
30
+ /** -w, --wallet <name> */
31
+ wallet: string | null;
32
+ /** --no-agent */
33
+ noAgent: boolean;
34
+ /** --dex <name> (Hyperliquid HIP-3) */
35
+ dex: string | null;
36
+ /** --no-builder (no builder fee on this run's orders) */
37
+ noBuilder: boolean;
38
+ }
39
+ /**
40
+ * The parent's root selectors, read from the root program. Throws INVALID_PARAMS when the
41
+ * parent was given key material on argv (-k / --passphrase / --ows-key): it cannot be handed
42
+ * to the child (argv and env are visible in the process list), and the child would otherwise
43
+ * sign with a different account or fail on the passphrase.
19
44
  */
20
- export declare function startJob(opts: {
45
+ export declare function jobSelectorsOf(cmd: Command): JobRootSelectors;
46
+ /** Options for a background job: `cliArgs` is the child's full command (path + args), e.g. ["strategy", "grid", "BTC"]. */
47
+ export interface StartJobOpts {
21
48
  strategy: string;
22
49
  exchange: string;
23
50
  params: Record<string, unknown>;
51
+ /** Root `-e` for the child (omit for multi-venue commands). */
52
+ venue?: string;
53
+ /** The parent's validated `-n` — the child always runs on the same network. */
54
+ network: Network;
55
+ /** The parent's root `--dry-run` — inherited by the child. */
56
+ dryRun: boolean;
57
+ /** The parent's account / market selectors (jobSelectorsOf) — inherited by the child. */
58
+ selectors: JobRootSelectors;
24
59
  cliArgs: string[];
25
- }): JobEntry;
60
+ }
26
61
  /**
27
- * Stop a running job.
62
+ * The child's argv after `node <cli>`: root flags first (`-n`, `--dry-run`, `-e`,
63
+ * `--ows`, `-w`, `--no-agent`, `--dex`, `--no-builder`), then the real command path + args, then
64
+ * `--job-id`. Every target command must exist in buildProgram() and declare
65
+ * `--job-id` (regression-tested).
28
66
  */
29
- export declare function stopJob(id: string): boolean;
67
+ export declare function jobArgv(opts: StartJobOpts, id: string): string[];
68
+ /**
69
+ * Start a job in a tmux background session.
70
+ * Returns the job entry.
71
+ */
72
+ export declare function startJob(opts: StartJobOpts): JobEntry;
73
+ /** Outcome of stopJob: how the job ended. */
74
+ export interface StopJobResult {
75
+ /** "notRunning": the session was already gone; "graceful": the job exited after SIGINT within the timeout; "forced": it did not — the session was killed (no cleanup ran). */
76
+ outcome: "notRunning" | "graceful" | "forced";
77
+ /** Why a graceful stop could not be attempted / finish (null when graceful / not running). */
78
+ note: string | null;
79
+ waitedMs: number;
80
+ }
81
+ /**
82
+ * Default wait for a job to finish its own cleanup after SIGINT. A daemon that receives SIGINT
83
+ * mid-entry finishes the verified leg sequence (or its rollback) first — that alone can take
84
+ * longer than 30s on a slow venue — so the default leaves room for one full verified pair.
85
+ */
86
+ export declare const JOB_STOP_TIMEOUT_MS = 90000;
87
+ /**
88
+ * Stop a running job GRACEFULLY: SIGINT to the job's node process (every bot handles it: cancel
89
+ * its orders, save state), then wait up to `timeoutMs` for the job to exit; only if it is still
90
+ * running, kill the tmux session ("forced" — the job's own cleanup did not run). Never a silent
91
+ * kill: the outcome says which happened.
92
+ */
93
+ export declare function stopJob(id: string, timeoutMs?: number): Promise<StopJobResult | null>;
30
94
  /**
31
95
  * Update job state file (called from within the running job).
32
96
  */
@@ -35,3 +99,17 @@ export declare function updateJobState(id: string, data: Partial<JobEntry>): voi
35
99
  * Remove a job entry.
36
100
  */
37
101
  export declare function removeJob(id: string): boolean;
102
+ /**
103
+ * The tmux shell command for a child: `env -u <secret>... node cli <argv>`.
104
+ * `env -u` removes secrets the tmux server's global environment may carry (a server started
105
+ * earlier from another shell). Only variable NAMES of secrets ever appear on the command line.
106
+ */
107
+ export declare function childShellCommand(nodeCmd: string, cliPath: string, argv: string[], log: string | null, env: NodeJS.ProcessEnv): string;
108
+ /**
109
+ * Launch `node <cli> <argv>` in a detached tmux session. The child never receives key
110
+ * material: every secret var is stripped from the tmux client's env (a fresh tmux server
111
+ * inherits it) and from the session command (`env -u`). `needsCredentials`: the child
112
+ * signs, so a shell-only secret env var (one the child cannot re-read from ~/.perp/.env)
113
+ * is refused with INVALID_PARAMS instead of silently switching the child's account.
114
+ */
115
+ export declare function launchTmuxChild(session: string, argv: string[], log: string | null, needsCredentials: boolean): void;
package/dist/jobs.js CHANGED
@@ -1,13 +1,37 @@
1
- import { execSync } from "node:child_process";
2
- import { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync } from "node:fs";
1
+ import { execFileSync, execSync } from "node:child_process";
2
+ import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync } from "node:fs";
3
3
  import { join } from "node:path";
4
4
  import { homedir } from "node:os";
5
5
  import { randomBytes } from "node:crypto";
6
+ import { parse as parseDotenv } from "dotenv";
7
+ import { PerpError } from "./errors.js";
8
+ import { QFEX_AUTOMATION_VERIFIED, QFEX_REDUCE_ONLY_WIRE, qfexAutomationGate, qfexReduceOnlyGate } from "./exchanges/qfex/signing.js";
9
+ import { startRefusal } from "./exchanges/trading-v2.js";
10
+ import { scrubSecretEnv, secretEnvNamesIn } from "./secret-env.js";
6
11
  const JOBS_DIR = join(homedir(), ".perp", "jobs");
7
12
  const LOGS_DIR = join(homedir(), ".perp", "logs");
8
13
  function ensureDirs() {
9
- mkdirSync(JOBS_DIR, { recursive: true });
10
- mkdirSync(LOGS_DIR, { recursive: true });
14
+ mkdirSync(JOBS_DIR, { recursive: true, mode: 0o700 });
15
+ mkdirSync(LOGS_DIR, { recursive: true, mode: 0o700 });
16
+ }
17
+ /**
18
+ * tmux session names we create: "perp-<hex id>" (jobs) / "perp-alerts-<base36>" (alerts).
19
+ * A name read back from a job file is validated before it reaches tmux (review L6) — tmux is
20
+ * also invoked without a shell (execFileSync), so a tampered name can never run a command.
21
+ */
22
+ const TMUX_SESSION_RE = /^perp-[A-Za-z0-9_-]{1,64}$/;
23
+ export function assertTmuxSessionName(name) {
24
+ if (!TMUX_SESSION_RE.test(name)) {
25
+ throw new PerpError("INVALID_PARAMS", `Invalid tmux session name ${JSON.stringify(name)}`, {
26
+ remediation: "Job files under ~/.perp/jobs must hold a perp-<id> session name; remove the corrupted job: perp background remove <id>",
27
+ });
28
+ }
29
+ return name;
30
+ }
31
+ /** Write a job/log file owner-only (0600) — they hold strategy params and order logs. */
32
+ function writePrivate(path, data, flag) {
33
+ writeFileSync(path, data, { mode: 0o600, flag });
34
+ chmodSync(path, 0o600);
11
35
  }
12
36
  function jobFile(id) {
13
37
  return join(JOBS_DIR, `${id}.json`);
@@ -20,7 +44,7 @@ function genId() {
20
44
  }
21
45
  function hasTmux() {
22
46
  try {
23
- execSync("which tmux", { stdio: "ignore" });
47
+ execSync("which tmux", { stdio: "ignore", env: scrubSecretEnv(process.env) });
24
48
  return true;
25
49
  }
26
50
  catch {
@@ -28,17 +52,22 @@ function hasTmux() {
28
52
  }
29
53
  }
30
54
  function tmuxSessionExists(name) {
55
+ const session = assertTmuxSessionName(name);
31
56
  try {
32
- execSync(`tmux has-session -t ${name} 2>/dev/null`, { stdio: "ignore" });
57
+ execFileSync("tmux", ["has-session", "-t", session], { stdio: "ignore", env: scrubSecretEnv(process.env) });
33
58
  return true;
34
59
  }
35
60
  catch {
36
61
  return false;
37
62
  }
38
63
  }
64
+ /** Kill a tmux session by (validated) name, without a shell. */
65
+ export function killTmuxSession(name) {
66
+ execFileSync("tmux", ["kill-session", "-t", assertTmuxSessionName(name)], { stdio: "ignore", env: scrubSecretEnv(process.env) });
67
+ }
39
68
  export function saveJob(job) {
40
69
  ensureDirs();
41
- writeFileSync(jobFile(job.id), JSON.stringify(job, null, 2));
70
+ writePrivate(jobFile(job.id), JSON.stringify(job, null, 2), "w");
42
71
  }
43
72
  export function loadJob(id) {
44
73
  const path = jobFile(id);
@@ -60,11 +89,83 @@ export function listJobs() {
60
89
  });
61
90
  }
62
91
  import { readdirSync } from "node:fs";
92
+ const SECRET_HANDOFF_REMEDIATION = "Background jobs never receive key material. Import the master key into the OWS vault (perp wallet set <exchange>) " +
93
+ "and select it with --ows/-w, and register an agent (perp wallet agent approve <venue>) — agent keys sign without the " +
94
+ "master passphrase. Keys in ~/.perp/.env are re-read by the job itself.";
95
+ /**
96
+ * The parent's root selectors, read from the root program. Throws INVALID_PARAMS when the
97
+ * parent was given key material on argv (-k / --passphrase / --ows-key): it cannot be handed
98
+ * to the child (argv and env are visible in the process list), and the child would otherwise
99
+ * sign with a different account or fail on the passphrase.
100
+ */
101
+ export function jobSelectorsOf(cmd) {
102
+ let root = cmd;
103
+ while (root.parent)
104
+ root = root.parent;
105
+ const o = root.opts();
106
+ const secretFlags = [
107
+ ...(o.privateKey !== undefined ? ["-k/--private-key"] : []),
108
+ ...(o.passphrase !== undefined ? ["--passphrase"] : []),
109
+ ...(o.owsKey !== undefined ? ["--ows-key"] : []),
110
+ ];
111
+ if (secretFlags.length > 0) {
112
+ throw new PerpError("INVALID_PARAMS", `--background cannot hand ${secretFlags.join(", ")} to the background job`, {
113
+ remediation: `Drop ${secretFlags.join(", ")}. ${SECRET_HANDOFF_REMEDIATION}`,
114
+ });
115
+ }
116
+ return {
117
+ ows: o.ows ?? null,
118
+ wallet: o.wallet ?? null,
119
+ // Commander's --no-agent sets opts.agent === false.
120
+ noAgent: o.agent === false,
121
+ dex: o.dex ?? null,
122
+ // Commander's --no-builder sets opts.builder === false.
123
+ noBuilder: o.builder === false,
124
+ };
125
+ }
126
+ /**
127
+ * The child's argv after `node <cli>`: root flags first (`-n`, `--dry-run`, `-e`,
128
+ * `--ows`, `-w`, `--no-agent`, `--dex`, `--no-builder`), then the real command path + args, then
129
+ * `--job-id`. Every target command must exist in buildProgram() and declare
130
+ * `--job-id` (regression-tested).
131
+ */
132
+ export function jobArgv(opts, id) {
133
+ const sel = opts.selectors;
134
+ return [
135
+ "-n", opts.network,
136
+ ...(opts.dryRun ? ["--dry-run"] : []),
137
+ ...(opts.venue !== undefined ? ["-e", opts.venue] : []),
138
+ ...(sel.ows !== null ? ["--ows", sel.ows] : []),
139
+ ...(sel.wallet !== null ? ["-w", sel.wallet] : []),
140
+ ...(sel.noAgent ? ["--no-agent"] : []),
141
+ ...(sel.dex !== null ? ["--dex", sel.dex] : []),
142
+ ...(sel.noBuilder ? ["--no-builder"] : []),
143
+ ...opts.cliArgs,
144
+ "--job-id", id,
145
+ ];
146
+ }
147
+ const shQuote = (a) => `'${a.replace(/'/g, "'\\''")}'`;
63
148
  /**
64
149
  * Start a job in a tmux background session.
65
150
  * Returns the job entry.
66
151
  */
67
152
  export function startJob(opts) {
153
+ // A background job trades unattended: refuse to start one on QFEX while its reduce-only
154
+ // (closing) orders are gated — it could open a position it cannot close — or while automation
155
+ // is unverified there (no stop orders: no protective exit). Dry runs send nothing.
156
+ if (!opts.dryRun) {
157
+ for (const v of new Set([opts.venue, opts.exchange])) {
158
+ if (v === undefined || v.toLowerCase() !== "qfex")
159
+ continue;
160
+ const what = `Background job "${opts.strategy}"`;
161
+ const reduceOnly = qfexReduceOnlyGate(QFEX_REDUCE_ONLY_WIRE);
162
+ if (reduceOnly !== null)
163
+ throw startRefusal(what, "it could open a position it cannot close", reduceOnly);
164
+ const automation = qfexAutomationGate(QFEX_AUTOMATION_VERIFIED);
165
+ if (automation !== null)
166
+ throw startRefusal(what, "it would trade unattended", automation);
167
+ }
168
+ }
68
169
  if (!hasTmux()) {
69
170
  throw new Error("tmux is required for background jobs. Install with: brew install tmux");
70
171
  }
@@ -72,14 +173,8 @@ export function startJob(opts) {
72
173
  const id = genId();
73
174
  const session = `perp-${id}`;
74
175
  const log = logFile(id);
75
- // Build the command — re-invoke ourselves with `run` subcommand
76
- const nodeCmd = process.argv[0];
77
- const cliPath = process.argv[1];
78
- const envVars = buildEnvString();
79
- const args = opts.cliArgs.join(" ");
80
- const cmd = `${envVars} ${nodeCmd} ${cliPath} run ${opts.strategy} ${args} --job-id ${id} 2>&1 | tee -a ${log}`;
81
- // Create tmux session
82
- execSync(`tmux new-session -d -s ${session} '${cmd.replace(/'/g, "'\\''")}'`);
176
+ // Re-invoke ourselves with the real command (jobArgv) — no key material on argv or env.
177
+ launchTmuxChild(session, jobArgv(opts, id), log, true);
83
178
  const job = {
84
179
  id,
85
180
  strategy: opts.strategy,
@@ -93,21 +188,95 @@ export function startJob(opts) {
93
188
  return job;
94
189
  }
95
190
  /**
96
- * Stop a running job.
191
+ * Default wait for a job to finish its own cleanup after SIGINT. A daemon that receives SIGINT
192
+ * mid-entry finishes the verified leg sequence (or its rollback) first — that alone can take
193
+ * longer than 30s on a slow venue — so the default leaves room for one full verified pair.
194
+ */
195
+ export const JOB_STOP_TIMEOUT_MS = 90_000;
196
+ /**
197
+ * The pid of the job's node process inside the tmux pane: the pane runs
198
+ * `sh -c "env … node <cli> … | tee -a <log>"`, so it is the pane shell's child that is not `tee`
199
+ * (`env` execs node, keeping its pid). null when it cannot be identified.
97
200
  */
98
- export function stopJob(id) {
201
+ function jobNodePid(session) {
202
+ const env = scrubSecretEnv(process.env);
203
+ let panePid;
204
+ try {
205
+ panePid = Number(execFileSync("tmux", ["list-panes", "-t", assertTmuxSessionName(session), "-F", "#{pane_pid}"], { env, encoding: "utf8" }).trim().split("\n")[0]);
206
+ }
207
+ catch {
208
+ return null;
209
+ }
210
+ if (!Number.isInteger(panePid) || panePid <= 0)
211
+ return null;
212
+ let rows;
213
+ try {
214
+ rows = execFileSync("ps", ["-o", "pid=,ppid=,comm="], { env, encoding: "utf8" });
215
+ }
216
+ catch {
217
+ return null;
218
+ }
219
+ const kids = rows.split("\n").map(l => l.trim().split(/\s+/)).filter(c => c.length >= 3 && Number(c[1]) === panePid);
220
+ const node = kids.filter(c => !/(^|\/)tee$/.test(c[2]));
221
+ if (node.length !== 1)
222
+ return null;
223
+ const pid = Number(node[0][0]);
224
+ return Number.isInteger(pid) && pid > 0 ? pid : null;
225
+ }
226
+ /**
227
+ * Stop a running job GRACEFULLY: SIGINT to the job's node process (every bot handles it: cancel
228
+ * its orders, save state), then wait up to `timeoutMs` for the job to exit; only if it is still
229
+ * running, kill the tmux session ("forced" — the job's own cleanup did not run). Never a silent
230
+ * kill: the outcome says which happened.
231
+ */
232
+ export async function stopJob(id, timeoutMs = JOB_STOP_TIMEOUT_MS) {
99
233
  const job = loadJob(id);
100
234
  if (!job)
101
- return false;
102
- if (tmuxSessionExists(job.tmuxSession)) {
103
- try {
104
- execSync(`tmux kill-session -t ${job.tmuxSession}`);
235
+ return null;
236
+ const started = Date.now();
237
+ let result;
238
+ if (!tmuxSessionExists(job.tmuxSession)) {
239
+ result = { outcome: "notRunning", note: null, waitedMs: 0 };
240
+ }
241
+ else {
242
+ // Mark the stop BEFORE the SIGINT: the job's own final updateJobState (status "done") must
243
+ // not overwrite it (updateJobState keeps "stopped").
244
+ job.status = "stopped";
245
+ saveJob(job);
246
+ const pid = jobNodePid(job.tmuxSession);
247
+ let note = null;
248
+ if (pid === null)
249
+ note = "the job's process could not be identified in its tmux pane — no SIGINT was sent";
250
+ else {
251
+ try {
252
+ process.kill(pid, "SIGINT");
253
+ }
254
+ catch (e) {
255
+ note = `SIGINT to pid ${pid} failed: ${e instanceof Error ? e.message : String(e)}`;
256
+ }
257
+ }
258
+ if (note === null) {
259
+ while (tmuxSessionExists(job.tmuxSession) && Date.now() - started < timeoutMs) {
260
+ await new Promise(r => setTimeout(r, 250));
261
+ }
262
+ }
263
+ if (!tmuxSessionExists(job.tmuxSession)) {
264
+ result = { outcome: "graceful", note: null, waitedMs: Date.now() - started };
265
+ }
266
+ else {
267
+ try {
268
+ killTmuxSession(job.tmuxSession);
269
+ }
270
+ catch { /* exited between has-session and kill-session */ }
271
+ result = { outcome: "forced", note: note ?? `the job did not exit within ${Math.round(timeoutMs / 1000)}s of SIGINT`, waitedMs: Date.now() - started };
105
272
  }
106
- catch { /* already dead */ }
107
273
  }
108
- job.status = "stopped";
109
- saveJob(job);
110
- return true;
274
+ // Re-read: the job wrote its final result while it shut down — keep it, add the stop outcome.
275
+ const latest = loadJob(id) ?? job;
276
+ latest.status = "stopped";
277
+ latest.result = { ...(latest.result ?? {}), stop: result };
278
+ saveJob(latest);
279
+ return result;
111
280
  }
112
281
  /**
113
282
  * Update job state file (called from within the running job).
@@ -116,7 +285,10 @@ export function updateJobState(id, data) {
116
285
  const job = loadJob(id);
117
286
  if (!job)
118
287
  return;
119
- Object.assign(job, data);
288
+ // A job stopped by `perp background stop` stays "stopped": its final write (done / error)
289
+ // only merges the result.
290
+ const status = job.status === "stopped" ? "stopped" : (data.status ?? job.status);
291
+ Object.assign(job, data, { status });
120
292
  saveJob(job);
121
293
  }
122
294
  /**
@@ -134,21 +306,47 @@ export function removeJob(id) {
134
306
  return true;
135
307
  }
136
308
  /**
137
- * Build env string to pass through to tmux session.
309
+ * Secret env vars the parent holds that the child could NOT re-read itself (i.e. not
310
+ * from ~/.perp/.env). Handing them over would put key material on the child's env;
311
+ * dropping them would make the child sign with a different account — so refuse.
138
312
  */
139
- function buildEnvString() {
140
- const keys = [
141
- // L2 agent key now lives in ~/.perp/lighter-agents/*.json (SSOT Rule #3),
142
- // accountIndex/apiKeyIndex live in settings.agents.lighter — none need env passthrough.
143
- "LIGHTER_PRIVATE_KEY",
144
- "PRIVATE_KEY", "pk",
145
- "PACIFICA_BUILDER_CODE", "NEXT_PUBLIC_BUILDER_CODE",
146
- "HL_REFERRAL_CODE", "LIGHTER_REFERRAL_CODE",
147
- ];
148
- const parts = [];
149
- for (const k of keys) {
150
- if (process.env[k])
151
- parts.push(`${k}='${process.env[k].replace(/'/g, "'\"'\"'")}'`);
313
+ function shellOnlySecrets(env) {
314
+ const envFile = join(process.env.HOME || homedir(), ".perp", ".env");
315
+ const fromFile = existsSync(envFile) ? parseDotenv(readFileSync(envFile, "utf-8")) : {};
316
+ return secretEnvNamesIn(env).filter((k) => {
317
+ const v = env[k];
318
+ return v !== undefined && v !== "" && fromFile[k] !== v;
319
+ });
320
+ }
321
+ /**
322
+ * The tmux shell command for a child: `env -u <secret>... node cli <argv>`.
323
+ * `env -u` removes secrets the tmux server's global environment may carry (a server started
324
+ * earlier from another shell). Only variable NAMES of secrets ever appear on the command line.
325
+ */
326
+ export function childShellCommand(nodeCmd, cliPath, argv, log, env) {
327
+ const unset = secretEnvNamesIn(env).flatMap((k) => ["-u", k]);
328
+ // No config env passthrough: builder fees come from settings.json (read by the child) only.
329
+ const parts = ["env", ...unset, nodeCmd, cliPath, ...argv].map(shQuote).join(" ");
330
+ return log === null ? parts : `${parts} 2>&1 | tee -a ${shQuote(log)}`;
331
+ }
332
+ /**
333
+ * Launch `node <cli> <argv>` in a detached tmux session. The child never receives key
334
+ * material: every secret var is stripped from the tmux client's env (a fresh tmux server
335
+ * inherits it) and from the session command (`env -u`). `needsCredentials`: the child
336
+ * signs, so a shell-only secret env var (one the child cannot re-read from ~/.perp/.env)
337
+ * is refused with INVALID_PARAMS instead of silently switching the child's account.
338
+ */
339
+ export function launchTmuxChild(session, argv, log, needsCredentials) {
340
+ const shellOnly = needsCredentials ? shellOnlySecrets(process.env) : [];
341
+ if (shellOnly.length > 0) {
342
+ throw new PerpError("INVALID_PARAMS", `--background cannot hand ${shellOnly.join(", ")} (set in the shell environment) to the background job`, {
343
+ remediation: `Unset ${shellOnly.join(", ")}. ${SECRET_HANDOFF_REMEDIATION}`,
344
+ });
152
345
  }
153
- return parts.join(" ");
346
+ assertTmuxSessionName(session);
347
+ // The log is created owner-only BEFORE tee appends to it (tee would create it 0644).
348
+ if (log !== null)
349
+ writePrivate(log, "", "a");
350
+ const cmd = childShellCommand(process.argv[0], process.argv[1], argv, log, process.env);
351
+ execFileSync("tmux", ["new-session", "-d", "-s", session, cmd], { env: scrubSecretEnv(process.env) });
154
352
  }
package/dist/landing.d.ts CHANGED
@@ -1,9 +1,14 @@
1
- export declare const LANDING_EXCHANGES: readonly ["pacifica", "hyperliquid", "lighter", "aster"];
1
+ import "./exchanges/venues/index.js";
2
+ /** Every registered venue, in registry order (SSOT: the venue registry). */
3
+ export declare const LANDING_EXCHANGES: readonly string[];
2
4
  export type LandingExchangeStatus = {
3
- exchange: typeof LANDING_EXCHANGES[number];
5
+ exchange: string;
6
+ /** Venue has no account API — rendered as market data only. */
7
+ marketDataOnly?: boolean;
4
8
  ok: boolean;
5
9
  equity: number;
6
10
  positions: number;
11
+ errorCode?: string;
7
12
  };
8
13
  export declare function asterAgentMissing(): boolean;
9
14
  export declare function renderLandingExchangeLine(status: LandingExchangeStatus, asterAgentMissing: boolean): string;
package/dist/landing.js CHANGED
@@ -1,16 +1,32 @@
1
1
  import chalk from "chalk";
2
2
  import { listAgents } from "./agent-wallet/store.js";
3
- export const LANDING_EXCHANGES = ["pacifica", "hyperliquid", "lighter", "aster"];
3
+ import { getVenue, listVenues } from "./exchanges/venue.js";
4
+ import "./exchanges/venues/index.js";
5
+ /** Every registered venue, in registry order (SSOT: the venue registry). */
6
+ export const LANDING_EXCHANGES = listVenues().map(v => v.id);
7
+ const ASTER_AGENT_REQUIRED_CODES = new Set([
8
+ "NOT_IMPLEMENTED",
9
+ "NO_SIGNER_AVAILABLE",
10
+ "AGENT_EXPIRED",
11
+ ]);
12
+ /** Single-token label (display name without spaces: "Lighter RH" → "LighterRH") so columns stay one word per venue. */
4
13
  function exchangeLabel(exchange) {
5
- return exchange === "pacifica" ? "Pacifica" : exchange === "hyperliquid" ? "Hyperliquid" : exchange === "lighter" ? "Lighter" : "Aster";
14
+ return getVenue(exchange).displayName.replace(/\s+/g, "");
6
15
  }
7
16
  export function asterAgentMissing() {
8
17
  return listAgents("aster").length === 0;
9
18
  }
10
19
  export function renderLandingExchangeLine(status, asterAgentMissing) {
11
- if (!status.ok && status.exchange === "aster" && asterAgentMissing) {
20
+ if (!status.ok &&
21
+ status.exchange === "aster" &&
22
+ asterAgentMissing &&
23
+ status.errorCode !== undefined &&
24
+ ASTER_AGENT_REQUIRED_CODES.has(status.errorCode)) {
12
25
  return ` ${chalk.yellow("⚙")} ${chalk.cyan(exchangeLabel(status.exchange).padEnd(14))} ${chalk.yellow("agent required")} ${chalk.gray("→ perp wallet agent approve aster")}`;
13
26
  }
27
+ if (status.marketDataOnly) {
28
+ return ` ${chalk.gray("◇")} ${chalk.cyan(exchangeLabel(status.exchange).padEnd(14))} ${chalk.gray("market data only")}`;
29
+ }
14
30
  const icon = status.ok ? chalk.green("●") : chalk.red("○");
15
31
  const equity = status.ok
16
32
  ? chalk.white(`$${Number(status.equity).toLocaleString("en", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`)
@@ -2,8 +2,18 @@
2
2
  /**
3
3
  * MCP (Model Context Protocol) server for perp-cli.
4
4
  *
5
- * Read-only advisor mode: provides market data, account balance, and CLI command suggestions.
6
- * Does NOT execute trades directly — instead suggests CLI commands for the user to run.
7
- * Adapters are created lazily from environment variables.
5
+ * Market data, account reads, CLI command suggestions, and two WRITE tools (`trade_execute`,
6
+ * `trade_close`) that place real orders through the same safety path as `perp trade`
7
+ * (local risk gate — no force override over MCP —, quarantine reject, execution log).
8
+ * Network: PERP_NETWORK (mainnet | testnet, validated like `-n`); unset = mainnet, the
9
+ * documented default. Every result's meta carries `network`.
10
+ * Adapters are created lazily from the registered agent / OWS wallet / settings (no CLI flags).
8
11
  */
9
- import "dotenv/config";
12
+ import "./exchanges/venues/index.js";
13
+ /**
14
+ * Venues with a `perp funds deposit <venue>` command. lighter takes a route (ethereum | cctp <chain>);
15
+ * qfex takes no amount (it shows the deposit address). Tested against the real command tree.
16
+ */
17
+ export declare const MCP_DEPOSIT_VENUES: readonly string[];
18
+ /** Venues with a `perp funds withdraw <venue>` command. Tested against the real command tree. */
19
+ export declare const MCP_WITHDRAW_VENUES: readonly string[];