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.
- package/README.md +247 -27
- package/dist/_stderr-filter.js +0 -1
- package/dist/agent-wallet/lighter-keystore.d.ts +11 -0
- package/dist/agent-wallet/lighter-keystore.js +92 -10
- package/dist/agent-wallet/passphrase.d.ts +1 -1
- package/dist/agent-wallet/passphrase.js +16 -3
- package/dist/agent-wallet/store.d.ts +37 -4
- package/dist/agent-wallet/store.js +132 -0
- package/dist/agent-wallet/venue-keystore.d.ts +14 -0
- package/dist/agent-wallet/venue-keystore.js +121 -0
- package/dist/api/public/hyperliquid.js +4 -4
- package/dist/api/public/lighter.js +5 -5
- package/dist/api/public/pacifica.d.ts +0 -1
- package/dist/api/public/pacifica.js +4 -13
- package/dist/api/public/urls.d.ts +9 -1
- package/dist/api/public/urls.js +35 -13
- package/dist/arb/file-lock.d.ts +11 -0
- package/dist/arb/file-lock.js +74 -0
- package/dist/arb/index.d.ts +2 -2
- package/dist/arb/index.js +2 -2
- package/dist/arb/pending-entry.d.ts +32 -0
- package/dist/arb/pending-entry.js +77 -0
- package/dist/arb/sizing.d.ts +6 -13
- package/dist/arb/sizing.js +12 -51
- package/dist/arb/state.d.ts +45 -12
- package/dist/arb/state.js +214 -42
- package/dist/arb/utils.d.ts +20 -6
- package/dist/arb/utils.js +43 -39
- package/dist/bot/conditions.d.ts +4 -4
- package/dist/bot/conditions.js +11 -6
- package/dist/bot/config.d.ts +17 -1
- package/dist/bot/config.js +48 -12
- package/dist/bot/engine.d.ts +61 -2
- package/dist/bot/engine.js +610 -144
- package/dist/bot/fill-size.d.ts +7 -0
- package/dist/bot/fill-size.js +18 -0
- package/dist/bot/index.d.ts +1 -1
- package/dist/bot/index.js +1 -1
- package/dist/bot/perp-order.d.ts +99 -0
- package/dist/bot/perp-order.js +299 -0
- package/dist/bot/presets.d.ts +7 -0
- package/dist/bot/presets.js +17 -0
- package/dist/bot/quarantine.d.ts +75 -0
- package/dist/bot/quarantine.js +190 -0
- package/dist/bot/quote-rules.d.ts +23 -0
- package/dist/bot/quote-rules.js +69 -0
- package/dist/bot/strategies/avellaneda-mm.d.ts +32 -2
- package/dist/bot/strategies/avellaneda-mm.js +72 -70
- package/dist/bot/strategies/basis-arb.js +2 -0
- package/dist/bot/strategies/dca-strategy.d.ts +2 -0
- package/dist/bot/strategies/dca-strategy.js +5 -0
- package/dist/bot/strategies/funding-arb-strategy.d.ts +43 -3
- package/dist/bot/strategies/funding-arb-strategy.js +571 -380
- package/dist/bot/strategies/funding-arb-utils.d.ts +48 -23
- package/dist/bot/strategies/funding-arb-utils.js +159 -195
- package/dist/bot/strategies/funding-arb-v2.d.ts +32 -0
- package/dist/bot/strategies/funding-arb-v2.js +258 -47
- package/dist/bot/strategies/funding-auto.js +215 -194
- package/dist/bot/strategies/spot-perp-arb-strategy.d.ts +14 -0
- package/dist/bot/strategies/spot-perp-arb-strategy.js +217 -50
- package/dist/bot/strategies/spot-perp-executor.d.ts +73 -19
- package/dist/bot/strategies/spot-perp-executor.js +319 -120
- package/dist/bot/strategies/spot-perp-state.d.ts +43 -0
- package/dist/bot/strategies/spot-perp-state.js +70 -0
- package/dist/bot/strategy-types.d.ts +6 -0
- package/dist/bot/tui/BotDashboard.d.ts +7 -2
- package/dist/bot/tui/BotDashboard.js +6 -3
- package/dist/bridge-engine.d.ts +120 -19
- package/dist/bridge-engine.js +710 -422
- package/dist/bridge-signers.d.ts +54 -0
- package/dist/bridge-signers.js +161 -0
- package/dist/cache.d.ts +4 -1
- package/dist/cache.js +11 -0
- package/dist/cli-flags.d.ts +50 -0
- package/dist/cli-flags.js +104 -0
- package/dist/cli-program.d.ts +35 -0
- package/dist/cli-program.js +303 -0
- package/dist/cli-spec.js +1 -1
- package/dist/client-id-tracker.js +11 -2
- package/dist/commands/account.d.ts +7 -1
- package/dist/commands/account.js +70 -92
- package/dist/commands/agent-import.d.ts +114 -0
- package/dist/commands/agent-import.js +794 -0
- package/dist/commands/agent.d.ts +232 -2
- package/dist/commands/agent.js +1858 -955
- package/dist/commands/alerts.d.ts +1 -0
- package/dist/commands/alerts.js +31 -28
- package/dist/commands/arb/index.d.ts +60 -1
- package/dist/commands/arb/index.js +381 -384
- package/dist/commands/arb-auto.d.ts +132 -4
- package/dist/commands/arb-auto.js +1474 -967
- package/dist/commands/arb.d.ts +3 -1
- package/dist/commands/arb.js +62 -30
- package/dist/commands/bot.js +127 -47
- package/dist/commands/bridge.d.ts +2 -1
- package/dist/commands/bridge.js +339 -192
- package/dist/commands/builder.d.ts +55 -0
- package/dist/commands/builder.js +387 -0
- package/dist/commands/funds-onchain.d.ts +32 -0
- package/dist/commands/funds-onchain.js +248 -0
- package/dist/commands/funds.d.ts +25 -0
- package/dist/commands/funds.js +925 -245
- package/dist/commands/history.js +102 -54
- package/dist/commands/init.d.ts +107 -15
- package/dist/commands/init.js +1116 -249
- package/dist/commands/jobs.d.ts +2 -1
- package/dist/commands/jobs.js +77 -15
- package/dist/commands/manage.js +136 -91
- package/dist/commands/market.d.ts +4 -1
- package/dist/commands/market.js +35 -32
- package/dist/commands/outcome.d.ts +10 -0
- package/dist/commands/outcome.js +396 -0
- package/dist/commands/plan.js +52 -12
- package/dist/commands/portfolio.d.ts +1 -0
- package/dist/commands/portfolio.js +60 -72
- package/dist/commands/rebalance.d.ts +2 -1
- package/dist/commands/rebalance.js +33 -43
- package/dist/commands/risk.js +46 -19
- package/dist/commands/settings.d.ts +1 -0
- package/dist/commands/settings.js +246 -51
- package/dist/commands/trade.d.ts +50 -0
- package/dist/commands/trade.js +1943 -695
- package/dist/commands/wallet.d.ts +30 -1
- package/dist/commands/wallet.js +357 -459
- package/dist/config.d.ts +60 -9
- package/dist/config.js +252 -82
- package/dist/constants.d.ts +29 -4
- package/dist/constants.js +62 -20
- package/dist/dashboard/server.d.ts +2 -0
- package/dist/dashboard/server.js +9 -6
- package/dist/dashboard/ui.js +6 -4
- package/dist/dashboard/ws-feeds.js +20 -8
- package/dist/dex-asset-map.js +5 -3
- package/dist/errors.d.ts +122 -0
- package/dist/errors.js +194 -0
- package/dist/event-stream.js +75 -19
- package/dist/evm-master-tx.d.ts +59 -0
- package/dist/evm-master-tx.js +154 -0
- package/dist/exchanges/adapter-network.d.ts +10 -0
- package/dist/exchanges/adapter-network.js +10 -0
- package/dist/exchanges/arcus/adapter.d.ts +196 -0
- package/dist/exchanges/arcus/adapter.js +1092 -0
- package/dist/exchanges/arcus/agent.d.ts +55 -0
- package/dist/exchanges/arcus/agent.js +188 -0
- package/dist/exchanges/arcus/client.d.ts +63 -0
- package/dist/exchanges/arcus/client.js +285 -0
- package/dist/exchanges/arcus/public.d.ts +65 -0
- package/dist/exchanges/arcus/public.js +138 -0
- package/dist/exchanges/arcus/signing.d.ts +176 -0
- package/dist/exchanges/arcus/signing.js +288 -0
- package/dist/exchanges/arcus/spot-chain.d.ts +60 -0
- package/dist/exchanges/arcus/spot-chain.js +241 -0
- package/dist/exchanges/arcus/spot-router.d.ts +177 -0
- package/dist/exchanges/arcus/spot-router.js +183 -0
- package/dist/exchanges/arcus/spot.d.ts +109 -0
- package/dist/exchanges/arcus/spot.js +645 -0
- package/dist/exchanges/aster-funds.d.ts +127 -0
- package/dist/exchanges/aster-funds.js +252 -0
- package/dist/exchanges/aster-nonce.d.ts +2 -0
- package/dist/exchanges/aster-nonce.js +107 -0
- package/dist/exchanges/aster-spot.d.ts +117 -0
- package/dist/exchanges/aster-spot.js +686 -0
- package/dist/exchanges/aster-typed-data.d.ts +133 -7
- package/dist/exchanges/aster-typed-data.js +121 -7
- package/dist/exchanges/aster.d.ts +240 -6
- package/dist/exchanges/aster.js +1139 -195
- package/dist/exchanges/builder-fee.d.ts +254 -0
- package/dist/exchanges/builder-fee.js +421 -0
- package/dist/exchanges/canonical-symbols.d.ts +32 -0
- package/dist/exchanges/canonical-symbols.js +96 -0
- package/dist/exchanges/capabilities.d.ts +7 -1
- package/dist/exchanges/capabilities.js +7 -0
- package/dist/exchanges/hl-signing.d.ts +165 -0
- package/dist/exchanges/hl-signing.js +158 -0
- package/dist/exchanges/hyperliquid-outcome.d.ts +211 -0
- package/dist/exchanges/hyperliquid-outcome.js +646 -0
- package/dist/exchanges/hyperliquid-spot.d.ts +44 -5
- package/dist/exchanges/hyperliquid-spot.js +308 -117
- package/dist/exchanges/hyperliquid.d.ts +462 -57
- package/dist/exchanges/hyperliquid.js +1575 -449
- package/dist/exchanges/index.d.ts +6 -0
- package/dist/exchanges/index.js +4 -0
- package/dist/exchanges/interface.d.ts +63 -10
- package/dist/exchanges/interface.js +18 -3
- package/dist/exchanges/ioc-outcome.d.ts +44 -0
- package/dist/exchanges/ioc-outcome.js +57 -0
- package/dist/exchanges/legacy-trading-v2.d.ts +39 -0
- package/dist/exchanges/legacy-trading-v2.js +940 -0
- package/dist/exchanges/lighter-public.d.ts +43 -0
- package/dist/exchanges/lighter-public.js +276 -0
- package/dist/exchanges/lighter-rh-deposit.d.ts +62 -0
- package/dist/exchanges/lighter-rh-deposit.js +185 -0
- package/dist/exchanges/lighter-signer.d.ts +123 -0
- package/dist/exchanges/lighter-signer.js +215 -0
- package/dist/exchanges/lighter-spot.d.ts +91 -13
- package/dist/exchanges/lighter-spot.js +595 -233
- package/dist/exchanges/lighter.d.ts +611 -39
- package/dist/exchanges/lighter.js +1890 -649
- package/dist/exchanges/ondo/adapter.d.ts +152 -0
- package/dist/exchanges/ondo/adapter.js +811 -0
- package/dist/exchanges/ondo/agent.d.ts +31 -0
- package/dist/exchanges/ondo/agent.js +101 -0
- package/dist/exchanges/ondo/client.d.ts +60 -0
- package/dist/exchanges/ondo/client.js +297 -0
- package/dist/exchanges/ondo/funds.d.ts +95 -0
- package/dist/exchanges/ondo/funds.js +198 -0
- package/dist/exchanges/ondo/public.d.ts +95 -0
- package/dist/exchanges/ondo/public.js +214 -0
- package/dist/exchanges/ondo/spot.d.ts +127 -0
- package/dist/exchanges/ondo/spot.js +392 -0
- package/dist/exchanges/outcome-interface.d.ts +140 -0
- package/dist/exchanges/outcome-interface.js +16 -0
- package/dist/exchanges/pacifica-spot.d.ts +83 -0
- package/dist/exchanges/pacifica-spot.js +324 -0
- package/dist/exchanges/pacifica-typed-data.d.ts +30 -13
- package/dist/exchanges/pacifica-typed-data.js +57 -9
- package/dist/exchanges/pacifica.d.ts +293 -4
- package/dist/exchanges/pacifica.js +1044 -90
- package/dist/exchanges/precision.d.ts +47 -0
- package/dist/exchanges/precision.js +209 -0
- package/dist/exchanges/qfex/adapter.d.ts +341 -0
- package/dist/exchanges/qfex/adapter.js +1386 -0
- package/dist/exchanges/qfex/agent.d.ts +58 -0
- package/dist/exchanges/qfex/agent.js +188 -0
- package/dist/exchanges/qfex/client.d.ts +54 -0
- package/dist/exchanges/qfex/client.js +138 -0
- package/dist/exchanges/qfex/errors.d.ts +70 -0
- package/dist/exchanges/qfex/errors.js +236 -0
- package/dist/exchanges/qfex/public.d.ts +122 -0
- package/dist/exchanges/qfex/public.js +268 -0
- package/dist/exchanges/qfex/signing.d.ts +160 -0
- package/dist/exchanges/qfex/signing.js +283 -0
- package/dist/exchanges/qfex/ws-transport.d.ts +294 -0
- package/dist/exchanges/qfex/ws-transport.js +798 -0
- package/dist/exchanges/rate-limiter.d.ts +12 -0
- package/dist/exchanges/rate-limiter.js +64 -0
- package/dist/exchanges/registry.d.ts +14 -2
- package/dist/exchanges/registry.js +20 -32
- package/dist/exchanges/slippage.d.ts +37 -0
- package/dist/exchanges/slippage.js +70 -0
- package/dist/exchanges/spot-factory.d.ts +166 -0
- package/dist/exchanges/spot-factory.js +431 -0
- package/dist/exchanges/spot-interface.d.ts +98 -4
- package/dist/exchanges/spot-interface.js +27 -1
- package/dist/exchanges/spot-result.d.ts +79 -0
- package/dist/exchanges/spot-result.js +273 -0
- package/dist/exchanges/trading-v2.d.ts +117 -0
- package/dist/exchanges/trading-v2.js +301 -0
- package/dist/exchanges/venue-types.d.ts +231 -0
- package/dist/exchanges/venue-types.js +87 -0
- package/dist/exchanges/venue.d.ts +211 -0
- package/dist/exchanges/venue.js +132 -0
- package/dist/exchanges/venues/arcus.d.ts +8 -0
- package/dist/exchanges/venues/arcus.js +109 -0
- package/dist/exchanges/venues/aster.d.ts +49 -0
- package/dist/exchanges/venues/aster.js +176 -0
- package/dist/exchanges/venues/create-helpers.d.ts +18 -0
- package/dist/exchanges/venues/create-helpers.js +199 -0
- package/dist/exchanges/venues/hyperliquid.d.ts +23 -0
- package/dist/exchanges/venues/hyperliquid.js +126 -0
- package/dist/exchanges/venues/index.d.ts +16 -0
- package/dist/exchanges/venues/index.js +26 -0
- package/dist/exchanges/venues/lighter-family.d.ts +92 -0
- package/dist/exchanges/venues/lighter-family.js +166 -0
- package/dist/exchanges/venues/lighter-rh.d.ts +7 -0
- package/dist/exchanges/venues/lighter-rh.js +58 -0
- package/dist/exchanges/venues/lighter.d.ts +6 -0
- package/dist/exchanges/venues/lighter.js +55 -0
- package/dist/exchanges/venues/ondo.d.ts +29 -0
- package/dist/exchanges/venues/ondo.js +136 -0
- package/dist/exchanges/venues/pacifica.d.ts +8 -0
- package/dist/exchanges/venues/pacifica.js +95 -0
- package/dist/exchanges/venues/qfex.d.ts +39 -0
- package/dist/exchanges/venues/qfex.js +140 -0
- package/dist/execution-log.js +10 -2
- package/dist/funding/legacy-public.d.ts +31 -0
- package/dist/funding/legacy-public.js +369 -0
- package/dist/funding/normalize.d.ts +28 -16
- package/dist/funding/normalize.js +61 -29
- package/dist/funding/rates.d.ts +46 -11
- package/dist/funding/rates.js +228 -277
- package/dist/guardrail/perp-guardrail.js +0 -0
- package/dist/index.js +156 -684
- package/dist/jobs.d.ts +84 -6
- package/dist/jobs.js +239 -41
- package/dist/landing.d.ts +7 -2
- package/dist/landing.js +19 -3
- package/dist/mcp-server.d.ts +14 -4
- package/dist/mcp-server.js +821 -403
- package/dist/mcp-trade-guard.d.ts +16 -0
- package/dist/mcp-trade-guard.js +59 -0
- package/dist/onchain-balances.d.ts +40 -0
- package/dist/onchain-balances.js +173 -0
- package/dist/pacifica/client.d.ts +41 -10
- package/dist/pacifica/client.js +167 -63
- package/dist/pacifica/constants.d.ts +0 -1
- package/dist/pacifica/constants.js +0 -5
- package/dist/pacifica/deposit.d.ts +30 -3
- package/dist/pacifica/deposit.js +119 -2
- package/dist/pacifica/faucet.d.ts +29 -0
- package/dist/pacifica/faucet.js +100 -0
- package/dist/pacifica/sign-tx.d.ts +15 -0
- package/dist/pacifica/sign-tx.js +41 -0
- package/dist/pacifica/types/account.d.ts +28 -4
- package/dist/pacifica/types/lake.d.ts +20 -5
- package/dist/pacifica/types/lake.js +4 -0
- package/dist/pacifica/types/market.d.ts +5 -0
- package/dist/pacifica/types/order.d.ts +4 -1
- package/dist/pacifica/types/position.d.ts +9 -1
- package/dist/plan-executor.d.ts +10 -5
- package/dist/plan-executor.js +165 -54
- package/dist/position-history.js +13 -0
- package/dist/rebalance.d.ts +26 -2
- package/dist/rebalance.js +86 -7
- package/dist/relay-swap.d.ts +232 -0
- package/dist/relay-swap.js +630 -0
- package/dist/risk.js +5 -1
- package/dist/secret-env.d.ts +15 -0
- package/dist/secret-env.js +36 -0
- package/dist/settings.d.ts +169 -6
- package/dist/settings.js +219 -38
- package/dist/signer/ows-evm.d.ts +13 -0
- package/dist/signer/ows-evm.js +22 -0
- package/dist/smart-order.d.ts +9 -0
- package/dist/smart-order.js +53 -8
- package/dist/strategies/dca.d.ts +6 -2
- package/dist/strategies/dca.js +66 -26
- package/dist/strategies/funding-arb.d.ts +11 -1
- package/dist/strategies/funding-arb.js +202 -89
- package/dist/strategies/grid.d.ts +31 -0
- package/dist/strategies/grid.js +89 -11
- package/dist/strategies/pending-stop.d.ts +8 -0
- package/dist/strategies/pending-stop.js +12 -0
- package/dist/strategies/split-order.d.ts +45 -17
- package/dist/strategies/split-order.js +157 -91
- package/dist/strategies/trailing-stop.d.ts +2 -1
- package/dist/strategies/trailing-stop.js +52 -6
- package/dist/strategies/twap.d.ts +6 -1
- package/dist/strategies/twap.js +39 -11
- package/dist/trade-validator.d.ts +22 -0
- package/dist/trade-validator.js +119 -15
- package/dist/utils/numeric.d.ts +46 -0
- package/dist/utils/numeric.js +79 -0
- package/dist/utils.d.ts +19 -0
- package/dist/utils.js +96 -19
- package/dist/withdraw-gate.d.ts +49 -0
- package/dist/withdraw-gate.js +131 -0
- package/package.json +15 -6
- package/skills/perp-cli/SKILL.md +159 -55
- package/skills/perp-cli/references/agent-operations.md +112 -58
- package/skills/perp-cli/references/commands.md +269 -73
- package/skills/perp-cli/references/strategies.md +34 -31
- package/skills/perp-cli/scripts/arb-monitor.sh +10 -5
- package/skills/perp-cli/scripts/funding-analysis.sh +14 -9
- package/skills/perp-cli/scripts/preflight.sh +21 -14
- package/skills/perp-cli/scripts/spot-perp-scan.sh +12 -7
- package/skills/perp-cli/scripts/validate-arb.sh +31 -15
- package/vendor/lighter-signer/SHA256SUMS +2 -0
- package/vendor/lighter-signer/VERSION +8 -0
- package/vendor/lighter-signer/lighter-signer.wasm +0 -0
- package/vendor/lighter-signer/wasm_exec.js +575 -0
- package/dist/bot/strategies/funding-arb-v2-strategy.d.ts +0 -42
- 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
|
-
*
|
|
18
|
-
*
|
|
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
|
|
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
|
-
}
|
|
60
|
+
}
|
|
26
61
|
/**
|
|
27
|
-
*
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
76
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
"
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
|
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 &&
|
|
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 })}`)
|
package/dist/mcp-server.d.ts
CHANGED
|
@@ -2,8 +2,18 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* MCP (Model Context Protocol) server for perp-cli.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
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 "
|
|
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[];
|