pandora-cli-skills 1.1.25 → 1.1.27
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_FOR_SHARING.md +17 -1
- package/SKILL.md +82 -1
- package/cli/lib/cli_output_service.cjs +130 -0
- package/cli/lib/command_router.cjs +162 -0
- package/cli/lib/export_service.cjs +18 -1
- package/cli/lib/mirror_manifest_store.cjs +17 -3
- package/cli/lib/polymarket_trade_adapter.cjs +19 -2
- package/cli/pandora.cjs +239 -264
- package/package.json +1 -1
package/README_FOR_SHARING.md
CHANGED
|
@@ -143,7 +143,7 @@ Prerequisite: Node.js `>=18`.
|
|
|
143
143
|
- each scan item includes at minimum `id`, `chainId`, `marketType`, `question`, `marketCloseTimestamp`, and `odds`.
|
|
144
144
|
|
|
145
145
|
## Phase 1 limitations
|
|
146
|
-
- `--expand` and `--with-odds` are supported on `markets list`
|
|
146
|
+
- `--expand` and `--with-odds` are supported on both `markets list` and `scan`.
|
|
147
147
|
- `--active|--resolved|--expiring-soon` are client-side filters on fetched list pages.
|
|
148
148
|
- Odds are indexer-derived and read-only; missing upstream liquidity/price fields can produce partial odds in some environments.
|
|
149
149
|
- `scan` is indexer-backed only (no direct chain reads); freshness depends on indexer sync state.
|
|
@@ -239,6 +239,22 @@ Prerequisite: Node.js `>=18`.
|
|
|
239
239
|
## Resolve/LP commands
|
|
240
240
|
- `resolve` and `lp` are active command paths with strict flag validation, runtime preflight checks, and decoded on-chain revert reporting.
|
|
241
241
|
|
|
242
|
+
### Resolve command
|
|
243
|
+
- Usage:
|
|
244
|
+
- `pandora [--output table|json] resolve --poll-address <address> --answer yes|no --reason <text> --dry-run|--execute [--chain-id <id>] [--rpc-url <url>] [--private-key <hex>]`
|
|
245
|
+
- Behavior:
|
|
246
|
+
- `--dry-run` returns a deterministic execution plan.
|
|
247
|
+
- `--execute` submits the resolution transaction with decoded revert diagnostics on failure.
|
|
248
|
+
|
|
249
|
+
### LP command
|
|
250
|
+
- Usage:
|
|
251
|
+
- `pandora [--output table|json] lp add --market-address <address> --amount-usdc <n> --dry-run|--execute [--deadline-seconds <n>] [--chain-id <id>] [--rpc-url <url>] [--private-key <hex>] [--usdc <address>]`
|
|
252
|
+
- `pandora [--output table|json] lp remove --market-address <address> --lp-tokens <n> --dry-run|--execute [--deadline-seconds <n>] [--chain-id <id>] [--rpc-url <url>] [--private-key <hex>] [--usdc <address>]`
|
|
253
|
+
- `pandora [--output table|json] lp positions --wallet <address> [--market-address <address>] [--chain-id <id>] [--indexer-url <url>] [--timeout-ms <ms>]`
|
|
254
|
+
- Behavior:
|
|
255
|
+
- `add/remove` use simulation-first transaction flow.
|
|
256
|
+
- `positions` returns LP holdings and preview diagnostics.
|
|
257
|
+
|
|
242
258
|
## Pandora Mainnet Reference
|
|
243
259
|
- PredictionOracle (Factory): `0x259308E7d8557e4Ba192De1aB8Cf7e0E21896442`
|
|
244
260
|
- PredictionPoll (Implementation): `0xC49c177736107fD8351ed6564136B9ADbE5B1eC3`
|
package/SKILL.md
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pandora-cli-skills
|
|
3
|
+
summary: Canonical skill and operator guide for Pandora CLI including mirror, polymarket, resolve, and LP flows.
|
|
4
|
+
---
|
|
5
|
+
|
|
1
6
|
# Pandora CLI & Skills
|
|
2
7
|
|
|
3
8
|
Create and publish Pandora prediction markets from deployed contracts.
|
|
@@ -57,6 +62,65 @@ npm link
|
|
|
57
62
|
- RPC reachability and chain id match
|
|
58
63
|
- bytecode checks for `ORACLE` + `FACTORY` (`--check-usdc-code` optional)
|
|
59
64
|
|
|
65
|
+
## Complete command + flag reference (authoritative)
|
|
66
|
+
This section mirrors live CLI help output so agent runs can rely on one source of truth.
|
|
67
|
+
|
|
68
|
+
```text
|
|
69
|
+
pandora [--output table|json] --version
|
|
70
|
+
pandora [--output table|json] init-env [--force] [--dotenv-path <path>] [--example <path>]
|
|
71
|
+
pandora [--output table|json] doctor [--dotenv-path <path>] [--skip-dotenv] [--check-usdc-code] [--check-polymarket] [--rpc-timeout-ms <ms>]
|
|
72
|
+
pandora [--output table|json] setup [--force] [--dotenv-path <path>] [--example <path>] [--check-usdc-code] [--check-polymarket] [--rpc-timeout-ms <ms>]
|
|
73
|
+
pandora [--output table|json] markets list [--limit <n>] [--after <cursor>] [--before <cursor>] [--order-by <field>] [--order-direction asc|desc] [--chain-id <id>] [--creator <address>] [--poll-address <address>] [--market-type <type>] [--where-json <json>] [--active|--resolved|--expiring-soon] [--expiring-hours <n>] [--expand] [--with-odds]
|
|
74
|
+
pandora [--output table|json] markets get [--id <id> ...] [--stdin]
|
|
75
|
+
pandora [--output table|json] polls list [--limit <n>] [--after <cursor>] [--before <cursor>] [--order-by <field>] [--order-direction asc|desc] [--chain-id <id>] [--creator <address>] [--status <int>] [--category <int>] [--question-contains <text>] [--where-json <json>]
|
|
76
|
+
pandora [--output table|json] polls get --id <id>
|
|
77
|
+
pandora [--output table|json] events list [--type all|liquidity|oracle-fee|claim] [--limit <n>] [--after <cursor>] [--before <cursor>] [--order-direction asc|desc] [--chain-id <id>] [--wallet <address>] [--market-address <address>] [--poll-address <address>] [--tx-hash <hash>]
|
|
78
|
+
pandora [--output table|json] events get --id <id> [--type all|liquidity|oracle-fee|claim]
|
|
79
|
+
pandora [--output table|json] positions list [--wallet <address>] [--market-address <address>] [--chain-id <id>] [--limit <n>] [--after <cursor>] [--before <cursor>] [--order-by <field>] [--order-direction asc|desc] [--where-json <json>]
|
|
80
|
+
pandora [--output table|json] portfolio --wallet <address> [--chain-id <id>] [--limit <n>] [--include-events|--no-events] [--with-lp] [--rpc-url <url>]
|
|
81
|
+
pandora [--output table|json] watch [--wallet <address>] [--market-address <address>] [--side yes|no] [--amount-usdc <amount>] [--iterations <n>] [--interval-ms <ms>] [--chain-id <id>] [--include-events|--no-events] [--yes-pct <0-100>] [--alert-yes-below <0-100>] [--alert-yes-above <0-100>] [--alert-net-liquidity-below <amount>] [--alert-net-liquidity-above <amount>] [--fail-on-alert]
|
|
82
|
+
pandora [--output table|json] scan [--limit <n>] [--after <cursor>] [--before <cursor>] [--order-by <field>] [--order-direction asc|desc] [--chain-id <id>] [--creator <address>] [--poll-address <address>] [--market-type <type>] [--where-json <json>] [--expand] [--with-odds]
|
|
83
|
+
pandora [--output table|json] quote [--indexer-url <url>] [--timeout-ms <ms>] --market-address <address> --side yes|no --amount-usdc <amount> [--yes-pct <0-100>] [--slippage-bps <0-10000>]
|
|
84
|
+
pandora [--output table|json] trade [--indexer-url <url>] [--timeout-ms <ms>] [--dotenv-path <path>] [--skip-dotenv] --market-address <address> --side yes|no --amount-usdc <amount> --dry-run|--execute [--yes-pct <0-100>] [--slippage-bps <0-10000>] [--min-shares-out-raw <uint>] [--max-amount-usdc <amount>] [--min-probability-pct <0-100>] [--max-probability-pct <0-100>] [--allow-unquoted-execute] [--chain-id <id>] [--rpc-url <url>] [--private-key <hex>] [--usdc <address>]
|
|
85
|
+
pandora [--output table|json] history --wallet <address> [--chain-id <id>] [--market-address <address>] [--side yes|no|both] [--status all|open|won|lost|closed] [--limit <n>] [--after <cursor>] [--before <cursor>] [--order-by timestamp|pnl|entry-price|mark-price] [--order-direction asc|desc] [--include-seed]
|
|
86
|
+
pandora [--output table|json] export --wallet <address> --format csv|json [--chain-id <id>] [--year <yyyy>] [--from <unix>] [--to <unix>] [--out <path>]
|
|
87
|
+
pandora [--output table|json] arbitrage [--chain-id <id>] [--venues pandora,polymarket] [--limit <n>] [--min-spread-pct <n>] [--min-liquidity-usdc <n>] [--max-close-diff-hours <n>] [--similarity-threshold <0-1>] [--cross-venue-only|--allow-same-venue] [--with-rules] [--include-similarity] [--question-contains <text>] [--polymarket-host <url>] [--polymarket-mock-url <url>]
|
|
88
|
+
pandora [--output table|json] autopilot run|once --market-address <address> --side yes|no --amount-usdc <amount> [--trigger-yes-below <0-100>] [--trigger-yes-above <0-100>] [--paper|--execute-live] [--interval-ms <ms>] [--cooldown-ms <ms>] [--max-amount-usdc <amount>] [--max-open-exposure-usdc <amount>] [--max-trades-per-day <n>] [--state-file <path>] [--kill-switch-file <path>] [--webhook-url <url>] [--telegram-bot-token <token>] [--telegram-chat-id <id>] [--discord-webhook-url <url>]
|
|
89
|
+
pandora [--output table|json] mirror browse|plan|deploy|verify|lp-explain|hedge-calc|simulate|go|sync|status|close ...
|
|
90
|
+
pandora [--output table|json] polymarket check|approve|preflight|trade ...
|
|
91
|
+
pandora [--output table|json] webhook test [--webhook-url <url>] [--webhook-template <json>] [--webhook-secret <secret>] [--telegram-bot-token <token>] [--telegram-chat-id <id>] [--discord-webhook-url <url>] [--webhook-timeout-ms <ms>] [--webhook-retries <n>]
|
|
92
|
+
pandora [--output table|json] leaderboard [--metric profit|volume|win-rate] [--chain-id <id>] [--limit <n>] [--min-trades <n>]
|
|
93
|
+
pandora [--output table|json] analyze --market-address <address> [--provider <name>] [--model <id>] [--max-cost-usd <n>] [--temperature <n>] [--timeout-ms <ms>]
|
|
94
|
+
pandora [--output table|json] suggest --wallet <address> --risk low|medium|high --budget <amount> [--count <n>] [--include-venues pandora,polymarket]
|
|
95
|
+
pandora [--output table|json] resolve --poll-address <address> --answer yes|no --reason <text> --dry-run|--execute [--chain-id <id>] [--rpc-url <url>] [--private-key <hex>]
|
|
96
|
+
pandora [--output table|json] lp add|remove|positions [--market-address <address>] [--wallet <address>] [--amount-usdc <n>] [--lp-tokens <n>] [--dry-run|--execute] [--chain-id <id>] [--rpc-url <url>] [--private-key <hex>] [--usdc <address>] [--deadline-seconds <n>] [--indexer-url <url>] [--timeout-ms <ms>]
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Mirror subcommand detail:
|
|
100
|
+
|
|
101
|
+
```text
|
|
102
|
+
browse --min-yes-pct <n> --max-yes-pct <n> --min-volume-24h <n> [--closes-after <date>] [--closes-before <date>] [--question-contains <text>] [--limit <n>]
|
|
103
|
+
plan --source polymarket --polymarket-market-id <id>|--polymarket-slug <slug> [--target-slippage-bps <n>] [--turnover-target <n>] [--depth-slippage-bps <n>] [--safety-multiplier <n>] [--min-liquidity-usdc <n>] [--max-liquidity-usdc <n>] [--with-rules] [--include-similarity] [--polymarket-gamma-url <url>]
|
|
104
|
+
deploy --plan-file <path>|--polymarket-market-id <id>|--polymarket-slug <slug> --dry-run|--execute [--liquidity-usdc <n>] [--fee-tier 500|3000|10000] [--max-imbalance <n>] [--arbiter <address>] [--category <n>] [--manifest-file <path>]
|
|
105
|
+
verify --pandora-market-address <address>|--market-address <address> --polymarket-market-id <id>|--polymarket-slug <slug> [--trust-deploy] [--manifest-file <path>] [--include-similarity] [--with-rules] [--allow-rule-mismatch]
|
|
106
|
+
lp-explain --liquidity-usdc <n> [--source-yes-pct <0-100>] [--distribution-yes <parts>] [--distribution-no <parts>]
|
|
107
|
+
hedge-calc [--reserve-yes-usdc <n> --reserve-no-usdc <n>] [--excess-yes-usdc <n>] [--excess-no-usdc <n>] [--polymarket-yes-pct <0-100>] [--hedge-ratio <n>] [--hedge-cost-bps <n>] [--volume-scenarios <csv>] [--pandora-market-address <address>|--market-address <address> --polymarket-market-id <id>|--polymarket-slug <slug>]
|
|
108
|
+
simulate --liquidity-usdc <n> [--source-yes-pct <0-100>] [--target-yes-pct <0-100>] [--distribution-yes <parts>] [--distribution-no <parts>] [--fee-tier 500|3000|10000] [--volume-scenarios <csv>] [--hedge-ratio <n>] [--polymarket-yes-pct <0-100>]
|
|
109
|
+
go --polymarket-market-id <id>|--polymarket-slug <slug> [--liquidity-usdc <n>] [--paper|--dry-run|--execute-live|--execute] [--private-key <hex>] [--funder <address>] [--auto-sync] [--sync-once] [--skip-gate]
|
|
110
|
+
sync run|once|start|stop|status --pandora-market-address <address>|--market-address <address> --polymarket-market-id <id>|--polymarket-slug <slug> [--paper|--dry-run|--execute-live|--execute] [--private-key <hex>] [--funder <address>] [--trust-deploy] [--skip-gate] [--daemon] [--stream] [--interval-ms <ms>] [--drift-trigger-bps <n>] [--hedge-trigger-usdc <n>] [--hedge-ratio <n>] [--no-hedge] [--max-rebalance-usdc <n>] [--max-hedge-usdc <n>] [--max-open-exposure-usdc <n>] [--max-trades-per-day <n>] [--cooldown-ms <ms>] [--state-file <path>] [--kill-switch-file <path>]
|
|
111
|
+
status --state-file <path>|--strategy-hash <hash> [--with-live] [--trust-deploy]
|
|
112
|
+
close --pandora-market-address <address>|--market-address <address> --polymarket-market-id <id>|--polymarket-slug <slug> --dry-run|--execute
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Polymarket subcommand detail:
|
|
116
|
+
|
|
117
|
+
```text
|
|
118
|
+
check [--rpc-url <url>] [--private-key <hex>] [--funder <address>]
|
|
119
|
+
approve --dry-run|--execute [--rpc-url <url>] [--private-key <hex>] [--funder <address>]
|
|
120
|
+
preflight [--rpc-url <url>] [--private-key <hex>] [--funder <address>]
|
|
121
|
+
trade --condition-id <id>|--slug <slug>|--token-id <id> --token yes|no --amount-usdc <n> --dry-run|--execute [--side buy|sell] [--polymarket-host <url>] [--timeout-ms <ms>] [--rpc-url <url>] [--private-key <hex>] [--funder <address>]
|
|
122
|
+
```
|
|
123
|
+
|
|
60
124
|
## Read-only indexer commands
|
|
61
125
|
Indexer URL resolution order:
|
|
62
126
|
1. `--indexer-url`
|
|
@@ -125,7 +189,7 @@ pandora --output json suggest --wallet <0x...> --risk medium --budget 50 --inclu
|
|
|
125
189
|
- each scan item includes at minimum `id`, `chainId`, `marketType`, `question`, `marketCloseTimestamp`, and `odds`.
|
|
126
190
|
|
|
127
191
|
## Phase 1 limitations
|
|
128
|
-
- `--expand` and `--with-odds` are supported on `markets list`
|
|
192
|
+
- `--expand` and `--with-odds` are supported on both `markets list` and `scan`.
|
|
129
193
|
- `--active|--resolved|--expiring-soon` are client-side filters over fetched pages.
|
|
130
194
|
- Odds are indexer-derived and read-only; missing upstream liquidity/price fields can produce partial odds in some environments.
|
|
131
195
|
- `scan` is indexer-backed only (no direct chain reads); freshness depends on indexer sync state.
|
|
@@ -193,6 +257,23 @@ pandora --output json suggest --wallet <0x...> --risk medium --budget 50 --inclu
|
|
|
193
257
|
- `suggest`: risk/budget-ranked opportunities seeded from arbitrage output and wallet history.
|
|
194
258
|
- `resolve` and `lp`: enabled command paths with strict flag/runtime validation and decoded on-chain revert reporting.
|
|
195
259
|
|
|
260
|
+
### Resolve command
|
|
261
|
+
- Usage:
|
|
262
|
+
- `pandora [--output table|json] resolve --poll-address <address> --answer yes|no --reason <text> --dry-run|--execute [--chain-id <id>] [--rpc-url <url>] [--private-key <hex>]`
|
|
263
|
+
- Behavior:
|
|
264
|
+
- `--dry-run` returns the call plan and decode-ready payload.
|
|
265
|
+
- `--execute` submits on-chain resolution through configured oracle/operator path.
|
|
266
|
+
- Reverts are surfaced through decoded custom errors when ABI matches.
|
|
267
|
+
|
|
268
|
+
### LP command
|
|
269
|
+
- Usage:
|
|
270
|
+
- `pandora [--output table|json] lp add --market-address <address> --amount-usdc <n> --dry-run|--execute [--deadline-seconds <n>] [--chain-id <id>] [--rpc-url <url>] [--private-key <hex>] [--usdc <address>]`
|
|
271
|
+
- `pandora [--output table|json] lp remove --market-address <address> --lp-tokens <n> --dry-run|--execute [--deadline-seconds <n>] [--chain-id <id>] [--rpc-url <url>] [--private-key <hex>] [--usdc <address>]`
|
|
272
|
+
- `pandora [--output table|json] lp positions --wallet <address> [--market-address <address>] [--chain-id <id>] [--indexer-url <url>] [--timeout-ms <ms>]`
|
|
273
|
+
- Behavior:
|
|
274
|
+
- `add/remove` path runs transaction simulation before submit.
|
|
275
|
+
- `positions` combines indexer + on-chain LP state when available.
|
|
276
|
+
|
|
196
277
|
## Polymarket command group
|
|
197
278
|
- `pandora polymarket check [--rpc-url <url>] [--private-key <hex>] [--funder <address>]`
|
|
198
279
|
- Discovers signer + proxy wallet readiness and reports balances/allowances/ownership checks.
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
function createCliOutputService(options = {}) {
|
|
2
|
+
const defaultSchemaVersion =
|
|
3
|
+
typeof options.defaultSchemaVersion === 'string' && options.defaultSchemaVersion.trim()
|
|
4
|
+
? options.defaultSchemaVersion.trim()
|
|
5
|
+
: '1.0.0';
|
|
6
|
+
const CliError = options.CliError;
|
|
7
|
+
|
|
8
|
+
if (typeof CliError !== 'function') {
|
|
9
|
+
throw new Error('createCliOutputService requires CliError class.');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
let failureAlreadyEmitted = false;
|
|
13
|
+
|
|
14
|
+
function emitJson(payload) {
|
|
15
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function emitJsonError(payload) {
|
|
19
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function formatErrorValue(value) {
|
|
23
|
+
if (typeof value === 'string') return value;
|
|
24
|
+
if (value && typeof value.message === 'string') return value.message;
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
return JSON.stringify(value);
|
|
28
|
+
} catch {
|
|
29
|
+
return String(value);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function toErrorEnvelope(error) {
|
|
34
|
+
if (error instanceof CliError) {
|
|
35
|
+
const envelope = {
|
|
36
|
+
ok: false,
|
|
37
|
+
error: {
|
|
38
|
+
code: error.code,
|
|
39
|
+
message: error.message,
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
if (error.details !== undefined) {
|
|
43
|
+
envelope.error.details = error.details;
|
|
44
|
+
}
|
|
45
|
+
return envelope;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
ok: false,
|
|
50
|
+
error: {
|
|
51
|
+
code: 'UNEXPECTED_ERROR',
|
|
52
|
+
message: formatErrorValue(error && error.message ? error.message : error),
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function attachJsonMetadata(data) {
|
|
58
|
+
if (!data || typeof data !== 'object' || Array.isArray(data)) return data;
|
|
59
|
+
const payload = { ...data };
|
|
60
|
+
if (typeof payload.schemaVersion !== 'string' || !payload.schemaVersion.trim()) {
|
|
61
|
+
payload.schemaVersion = defaultSchemaVersion;
|
|
62
|
+
}
|
|
63
|
+
if (typeof payload.generatedAt !== 'string' || Number.isNaN(Date.parse(payload.generatedAt))) {
|
|
64
|
+
payload.generatedAt = new Date().toISOString();
|
|
65
|
+
}
|
|
66
|
+
return payload;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function emitFailure(outputMode, error) {
|
|
70
|
+
if (failureAlreadyEmitted) {
|
|
71
|
+
process.exit(error instanceof CliError ? error.exitCode : 1);
|
|
72
|
+
}
|
|
73
|
+
failureAlreadyEmitted = true;
|
|
74
|
+
|
|
75
|
+
const envelope = toErrorEnvelope(error);
|
|
76
|
+
|
|
77
|
+
if (outputMode === 'json') {
|
|
78
|
+
emitJsonError(envelope);
|
|
79
|
+
} else {
|
|
80
|
+
console.error(`[${envelope.error.code}] ${envelope.error.message}`);
|
|
81
|
+
if (envelope.error.details && Array.isArray(envelope.error.details.errors) && envelope.error.details.errors.length) {
|
|
82
|
+
for (const err of envelope.error.details.errors) {
|
|
83
|
+
console.error(`- ${formatErrorValue(err)}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (envelope.error.details && Array.isArray(envelope.error.details.hints) && envelope.error.details.hints.length) {
|
|
87
|
+
for (const hint of envelope.error.details.hints) {
|
|
88
|
+
console.error(`Hint: ${hint}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (
|
|
92
|
+
envelope.error.details &&
|
|
93
|
+
!Array.isArray(envelope.error.details.errors) &&
|
|
94
|
+
!Array.isArray(envelope.error.details.hints)
|
|
95
|
+
) {
|
|
96
|
+
try {
|
|
97
|
+
console.error(`Details: ${JSON.stringify(envelope.error.details)}`);
|
|
98
|
+
} catch {
|
|
99
|
+
console.error(`Details: ${String(envelope.error.details)}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
process.exit(error instanceof CliError ? error.exitCode : 1);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function emitSuccess(outputMode, command, data, tableRenderer) {
|
|
108
|
+
if (outputMode === 'json') {
|
|
109
|
+
emitJson({ ok: true, command, data: attachJsonMetadata(data) });
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (typeof tableRenderer === 'function') {
|
|
114
|
+
tableRenderer(data);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
console.log('Done.');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
emitFailure,
|
|
123
|
+
emitSuccess,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
module.exports = {
|
|
128
|
+
createCliOutputService,
|
|
129
|
+
};
|
|
130
|
+
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
function createCommandRouter(deps = {}) {
|
|
2
|
+
const {
|
|
3
|
+
CliError,
|
|
4
|
+
packageVersion,
|
|
5
|
+
emitSuccess,
|
|
6
|
+
helpJsonPayload,
|
|
7
|
+
printHelpTable,
|
|
8
|
+
includesHelpFlag,
|
|
9
|
+
commandHelpPayload,
|
|
10
|
+
runInitEnv,
|
|
11
|
+
runDoctor,
|
|
12
|
+
runSetup,
|
|
13
|
+
runMarketsCommand,
|
|
14
|
+
runScanCommand,
|
|
15
|
+
runQuoteCommand,
|
|
16
|
+
runTradeCommand,
|
|
17
|
+
runPollsCommand,
|
|
18
|
+
runEventsCommand,
|
|
19
|
+
runPositionsCommand,
|
|
20
|
+
runPortfolioCommand,
|
|
21
|
+
runWatchCommand,
|
|
22
|
+
runHistoryCommand,
|
|
23
|
+
runExportCommand,
|
|
24
|
+
runArbitrageCommand,
|
|
25
|
+
runAutopilotCommand,
|
|
26
|
+
runMirrorCommand,
|
|
27
|
+
runPolymarketCommand,
|
|
28
|
+
runWebhookCommand,
|
|
29
|
+
runLeaderboardCommand,
|
|
30
|
+
runAnalyzeCommand,
|
|
31
|
+
runSuggestCommand,
|
|
32
|
+
runResolveCommand,
|
|
33
|
+
runLpCommand,
|
|
34
|
+
runScriptCommand,
|
|
35
|
+
} = deps;
|
|
36
|
+
|
|
37
|
+
if (typeof CliError !== 'function') {
|
|
38
|
+
throw new Error('createCommandRouter requires CliError.');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (typeof emitSuccess !== 'function') {
|
|
42
|
+
throw new Error('createCommandRouter requires emitSuccess.');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return async function dispatch(command, args, context) {
|
|
46
|
+
if (!command || command === 'help' || command === '--help' || command === '-h') {
|
|
47
|
+
if (context.outputMode === 'json') {
|
|
48
|
+
emitSuccess(context.outputMode, 'help', helpJsonPayload());
|
|
49
|
+
} else {
|
|
50
|
+
printHelpTable();
|
|
51
|
+
}
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (command === '--version' || command === '-V' || command === 'version') {
|
|
56
|
+
if (context.outputMode === 'json') {
|
|
57
|
+
emitSuccess(context.outputMode, 'version', { version: packageVersion });
|
|
58
|
+
} else {
|
|
59
|
+
// eslint-disable-next-line no-console
|
|
60
|
+
console.log(packageVersion);
|
|
61
|
+
}
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const handlers = {
|
|
66
|
+
'init-env': async (handlerArgs, handlerContext) => {
|
|
67
|
+
if (includesHelpFlag(handlerArgs)) {
|
|
68
|
+
const usage = 'pandora [--output table|json] init-env [--force] [--dotenv-path <path>] [--example <path>]';
|
|
69
|
+
if (handlerContext.outputMode === 'json') {
|
|
70
|
+
emitSuccess(handlerContext.outputMode, 'init-env.help', commandHelpPayload(usage));
|
|
71
|
+
} else {
|
|
72
|
+
// eslint-disable-next-line no-console
|
|
73
|
+
console.log(`Usage: ${usage}`);
|
|
74
|
+
}
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
runInitEnv(handlerArgs, handlerContext.outputMode);
|
|
78
|
+
},
|
|
79
|
+
doctor: async (handlerArgs, handlerContext) => {
|
|
80
|
+
if (includesHelpFlag(handlerArgs)) {
|
|
81
|
+
const usage =
|
|
82
|
+
'pandora [--output table|json] doctor [--dotenv-path <path>] [--skip-dotenv] [--check-usdc-code] [--check-polymarket] [--rpc-timeout-ms <ms>]';
|
|
83
|
+
if (handlerContext.outputMode === 'json') {
|
|
84
|
+
emitSuccess(handlerContext.outputMode, 'doctor.help', commandHelpPayload(usage));
|
|
85
|
+
} else {
|
|
86
|
+
// eslint-disable-next-line no-console
|
|
87
|
+
console.log(`Usage: ${usage}`);
|
|
88
|
+
}
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
await runDoctor(handlerArgs, handlerContext.outputMode);
|
|
92
|
+
},
|
|
93
|
+
setup: async (handlerArgs, handlerContext) => {
|
|
94
|
+
if (includesHelpFlag(handlerArgs)) {
|
|
95
|
+
const usage =
|
|
96
|
+
'pandora [--output table|json] setup [--force] [--dotenv-path <path>] [--example <path>] [--check-usdc-code] [--check-polymarket] [--rpc-timeout-ms <ms>]';
|
|
97
|
+
if (handlerContext.outputMode === 'json') {
|
|
98
|
+
emitSuccess(handlerContext.outputMode, 'setup.help', commandHelpPayload(usage));
|
|
99
|
+
} else {
|
|
100
|
+
// eslint-disable-next-line no-console
|
|
101
|
+
console.log(`Usage: ${usage}`);
|
|
102
|
+
}
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
await runSetup(handlerArgs, handlerContext.outputMode);
|
|
106
|
+
},
|
|
107
|
+
markets: async (handlerArgs, handlerContext) => runMarketsCommand(handlerArgs, handlerContext),
|
|
108
|
+
scan: async (handlerArgs, handlerContext) => runScanCommand(handlerArgs, handlerContext),
|
|
109
|
+
quote: async (handlerArgs, handlerContext) => runQuoteCommand(handlerArgs, handlerContext),
|
|
110
|
+
trade: async (handlerArgs, handlerContext) => runTradeCommand(handlerArgs, handlerContext),
|
|
111
|
+
polls: async (handlerArgs, handlerContext) => runPollsCommand(handlerArgs, handlerContext),
|
|
112
|
+
events: async (handlerArgs, handlerContext) => runEventsCommand(handlerArgs, handlerContext),
|
|
113
|
+
positions: async (handlerArgs, handlerContext) => runPositionsCommand(handlerArgs, handlerContext),
|
|
114
|
+
portfolio: async (handlerArgs, handlerContext) => runPortfolioCommand(handlerArgs, handlerContext),
|
|
115
|
+
watch: async (handlerArgs, handlerContext) => runWatchCommand(handlerArgs, handlerContext),
|
|
116
|
+
history: async (handlerArgs, handlerContext) => runHistoryCommand(handlerArgs, handlerContext),
|
|
117
|
+
export: async (handlerArgs, handlerContext) => runExportCommand(handlerArgs, handlerContext),
|
|
118
|
+
arbitrage: async (handlerArgs, handlerContext) => runArbitrageCommand(handlerArgs, handlerContext),
|
|
119
|
+
autopilot: async (handlerArgs, handlerContext) => runAutopilotCommand(handlerArgs, handlerContext),
|
|
120
|
+
mirror: async (handlerArgs, handlerContext) => runMirrorCommand(handlerArgs, handlerContext),
|
|
121
|
+
polymarket: async (handlerArgs, handlerContext) => runPolymarketCommand(handlerArgs, handlerContext),
|
|
122
|
+
webhook: async (handlerArgs, handlerContext) => runWebhookCommand(handlerArgs, handlerContext),
|
|
123
|
+
leaderboard: async (handlerArgs, handlerContext) => runLeaderboardCommand(handlerArgs, handlerContext),
|
|
124
|
+
analyze: async (handlerArgs, handlerContext) => runAnalyzeCommand(handlerArgs, handlerContext),
|
|
125
|
+
suggest: async (handlerArgs, handlerContext) => runSuggestCommand(handlerArgs, handlerContext),
|
|
126
|
+
resolve: async (handlerArgs, handlerContext) => runResolveCommand(handlerArgs, handlerContext),
|
|
127
|
+
lp: async (handlerArgs, handlerContext) => runLpCommand(handlerArgs, handlerContext),
|
|
128
|
+
launch: async (handlerArgs, handlerContext) => {
|
|
129
|
+
if (handlerContext.outputMode === 'json') {
|
|
130
|
+
throw new CliError(
|
|
131
|
+
'UNSUPPORTED_OUTPUT_MODE',
|
|
132
|
+
'--output json is not supported for launch/clone-bet because these commands stream script output directly.',
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
runScriptCommand('launch', handlerArgs);
|
|
136
|
+
},
|
|
137
|
+
'clone-bet': async (handlerArgs, handlerContext) => {
|
|
138
|
+
if (handlerContext.outputMode === 'json') {
|
|
139
|
+
throw new CliError(
|
|
140
|
+
'UNSUPPORTED_OUTPUT_MODE',
|
|
141
|
+
'--output json is not supported for launch/clone-bet because these commands stream script output directly.',
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
runScriptCommand('clone-bet', handlerArgs);
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const handler = handlers[command];
|
|
149
|
+
if (!handler) {
|
|
150
|
+
throw new CliError('UNKNOWN_COMMAND', `Unknown command: ${command}`, {
|
|
151
|
+
hints: ['Run `pandora help` to see available commands.'],
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
await handler(args, context);
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
module.exports = {
|
|
160
|
+
createCommandRouter,
|
|
161
|
+
};
|
|
162
|
+
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
+
const crypto = require('crypto');
|
|
3
4
|
|
|
4
5
|
const EXPORT_SCHEMA_VERSION = '1.0.0';
|
|
5
6
|
|
|
@@ -85,7 +86,23 @@ function maybeWriteOutput(content, outPath) {
|
|
|
85
86
|
if (!outPath) return null;
|
|
86
87
|
const resolved = path.resolve(outPath);
|
|
87
88
|
fs.mkdirSync(path.dirname(resolved), { recursive: true });
|
|
88
|
-
|
|
89
|
+
const tmpPath = `${resolved}.${process.pid}.${Date.now()}.${crypto.randomBytes(4).toString('hex')}.tmp`;
|
|
90
|
+
try {
|
|
91
|
+
fs.writeFileSync(tmpPath, content, { mode: 0o600 });
|
|
92
|
+
fs.renameSync(tmpPath, resolved);
|
|
93
|
+
try {
|
|
94
|
+
fs.chmodSync(resolved, 0o600);
|
|
95
|
+
} catch {
|
|
96
|
+
// best-effort hardening on platforms that ignore/limit chmod
|
|
97
|
+
}
|
|
98
|
+
} catch (err) {
|
|
99
|
+
try {
|
|
100
|
+
if (fs.existsSync(tmpPath)) fs.unlinkSync(tmpPath);
|
|
101
|
+
} catch {
|
|
102
|
+
// best-effort temp cleanup
|
|
103
|
+
}
|
|
104
|
+
throw err;
|
|
105
|
+
}
|
|
89
106
|
return resolved;
|
|
90
107
|
}
|
|
91
108
|
|
|
@@ -56,9 +56,23 @@ function saveManifest(filePath, manifest) {
|
|
|
56
56
|
const resolved = resolveManifestPath(filePath);
|
|
57
57
|
fs.mkdirSync(path.dirname(resolved), { recursive: true });
|
|
58
58
|
const tmpPath = `${resolved}.${process.pid}.${Date.now()}.${crypto.randomBytes(4).toString('hex')}.tmp`;
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
try {
|
|
60
|
+
fs.writeFileSync(tmpPath, JSON.stringify(manifest, null, 2), { mode: 0o600 });
|
|
61
|
+
fs.renameSync(tmpPath, resolved);
|
|
62
|
+
try {
|
|
63
|
+
fs.chmodSync(resolved, 0o600);
|
|
64
|
+
} catch {
|
|
65
|
+
// best-effort hardening on platforms that ignore/limit chmod
|
|
66
|
+
}
|
|
67
|
+
return resolved;
|
|
68
|
+
} catch (err) {
|
|
69
|
+
try {
|
|
70
|
+
if (fs.existsSync(tmpPath)) fs.unlinkSync(tmpPath);
|
|
71
|
+
} catch {
|
|
72
|
+
// best-effort temp cleanup
|
|
73
|
+
}
|
|
74
|
+
throw err;
|
|
75
|
+
}
|
|
62
76
|
}
|
|
63
77
|
|
|
64
78
|
function pairMatches(record, selector = {}) {
|
|
@@ -190,8 +190,25 @@ function writeCacheFile(cacheFile, payload) {
|
|
|
190
190
|
const dir = path.dirname(cacheFile);
|
|
191
191
|
fs.mkdirSync(dir, { recursive: true });
|
|
192
192
|
const tmpPath = `${cacheFile}.${process.pid}.${Date.now()}.${crypto.randomBytes(4).toString('hex')}.tmp`;
|
|
193
|
-
|
|
194
|
-
|
|
193
|
+
let wroteTmp = false;
|
|
194
|
+
try {
|
|
195
|
+
fs.writeFileSync(tmpPath, JSON.stringify(payload, null, 2), { mode: 0o600 });
|
|
196
|
+
wroteTmp = true;
|
|
197
|
+
fs.renameSync(tmpPath, cacheFile);
|
|
198
|
+
try {
|
|
199
|
+
fs.chmodSync(cacheFile, 0o600);
|
|
200
|
+
} catch {
|
|
201
|
+
// best-effort hardening on platforms that ignore/limit chmod
|
|
202
|
+
}
|
|
203
|
+
} finally {
|
|
204
|
+
if (wroteTmp && fs.existsSync(tmpPath)) {
|
|
205
|
+
try {
|
|
206
|
+
fs.unlinkSync(tmpPath);
|
|
207
|
+
} catch {
|
|
208
|
+
// best-effort cleanup
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
195
212
|
}
|
|
196
213
|
|
|
197
214
|
function buildCachePayload(options, host, marketRow, orderbooks = null, sourceType = 'polymarket:clob') {
|
package/cli/pandora.cjs
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const { spawnSync } = require('child_process');
|
|
6
|
+
const { createCommandRouter } = require('./lib/command_router.cjs');
|
|
7
|
+
const { createCliOutputService } = require('./lib/cli_output_service.cjs');
|
|
6
8
|
const {
|
|
7
9
|
fetchHistory,
|
|
8
10
|
} = require('./lib/history_service.cjs');
|
|
@@ -78,6 +80,7 @@ const COMMAND_TARGETS = {
|
|
|
78
80
|
};
|
|
79
81
|
|
|
80
82
|
const OUTPUT_MODES = new Set(['table', 'json']);
|
|
83
|
+
const CLI_JSON_SCHEMA_VERSION = '1.0.0';
|
|
81
84
|
const DEFAULT_RPC_TIMEOUT_MS = 12_000;
|
|
82
85
|
const DEFAULT_INDEXER_TIMEOUT_MS = 12_000;
|
|
83
86
|
const DEFAULT_POLYMARKET_HOST = 'https://clob.polymarket.com';
|
|
@@ -231,6 +234,11 @@ class CliError extends Error {
|
|
|
231
234
|
}
|
|
232
235
|
}
|
|
233
236
|
|
|
237
|
+
const { emitFailure, emitSuccess } = createCliOutputService({
|
|
238
|
+
defaultSchemaVersion: CLI_JSON_SCHEMA_VERSION,
|
|
239
|
+
CliError,
|
|
240
|
+
});
|
|
241
|
+
|
|
234
242
|
function printHelpTable() {
|
|
235
243
|
console.log(`
|
|
236
244
|
pandora - Prediction market CLI
|
|
@@ -292,7 +300,7 @@ Examples:
|
|
|
292
300
|
pandora mirror hedge-calc --reserve-yes-usdc 8 --reserve-no-usdc 12 --excess-no-usdc 2 --polymarket-yes-pct 60
|
|
293
301
|
pandora mirror simulate --liquidity-usdc 10000 --source-yes-pct 58 --target-yes-pct 58 --volume-scenarios 1000,5000,10000
|
|
294
302
|
pandora mirror go --polymarket-slug nba-mia-phi-2026-02-28 --liquidity-usdc 10 --paper
|
|
295
|
-
pandora mirror sync once --pandora-market-address 0xabc... --polymarket-market-id 0xdef... --paper --hedge-ratio 1.0 --
|
|
303
|
+
pandora mirror sync once --pandora-market-address 0xabc... --polymarket-market-id 0xdef... --paper --hedge-ratio 1.0 --skip-gate
|
|
296
304
|
pandora polymarket check --rpc-url https://polygon-bor-rpc.publicnode.com --private-key 0x... --funder 0xproxy...
|
|
297
305
|
pandora polymarket approve --dry-run --rpc-url https://polygon-bor-rpc.publicnode.com --private-key 0x... --funder 0xproxy...
|
|
298
306
|
pandora polymarket preflight --rpc-url https://polygon-bor-rpc.publicnode.com --private-key 0x... --funder 0xproxy...
|
|
@@ -613,6 +621,12 @@ function extractOutputMode(argv) {
|
|
|
613
621
|
|
|
614
622
|
function parseDateLikeFlag(value, flagName) {
|
|
615
623
|
const text = String(value || '').trim();
|
|
624
|
+
if (/^-?\d+(\.\d+)?$/.test(text)) {
|
|
625
|
+
throw new CliError(
|
|
626
|
+
'INVALID_FLAG_VALUE',
|
|
627
|
+
`${flagName} must be a date/time string (for example: "2026-03-15" or "2026-03-15T18:00:00Z"), not a bare number.`,
|
|
628
|
+
);
|
|
629
|
+
}
|
|
616
630
|
const parsed = Date.parse(text);
|
|
617
631
|
if (Number.isNaN(parsed)) {
|
|
618
632
|
throw new CliError(
|
|
@@ -623,38 +637,6 @@ function parseDateLikeFlag(value, flagName) {
|
|
|
623
637
|
return text;
|
|
624
638
|
}
|
|
625
639
|
|
|
626
|
-
function emitJson(payload) {
|
|
627
|
-
console.log(JSON.stringify(payload, null, 2));
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
function emitJsonError(payload) {
|
|
631
|
-
console.log(JSON.stringify(payload, null, 2));
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
function toErrorEnvelope(error) {
|
|
635
|
-
if (error instanceof CliError) {
|
|
636
|
-
const envelope = {
|
|
637
|
-
ok: false,
|
|
638
|
-
error: {
|
|
639
|
-
code: error.code,
|
|
640
|
-
message: error.message,
|
|
641
|
-
},
|
|
642
|
-
};
|
|
643
|
-
if (error.details !== undefined) {
|
|
644
|
-
envelope.error.details = error.details;
|
|
645
|
-
}
|
|
646
|
-
return envelope;
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
return {
|
|
650
|
-
ok: false,
|
|
651
|
-
error: {
|
|
652
|
-
code: 'UNEXPECTED_ERROR',
|
|
653
|
-
message: formatErrorValue(error && error.message ? error.message : error),
|
|
654
|
-
},
|
|
655
|
-
};
|
|
656
|
-
}
|
|
657
|
-
|
|
658
640
|
function formatErrorValue(value) {
|
|
659
641
|
if (typeof value === 'string') return value;
|
|
660
642
|
if (value && typeof value.message === 'string') return value.message;
|
|
@@ -666,60 +648,6 @@ function formatErrorValue(value) {
|
|
|
666
648
|
}
|
|
667
649
|
}
|
|
668
650
|
|
|
669
|
-
let failureAlreadyEmitted = false;
|
|
670
|
-
|
|
671
|
-
function emitFailure(outputMode, error) {
|
|
672
|
-
if (failureAlreadyEmitted) {
|
|
673
|
-
process.exit(error instanceof CliError ? error.exitCode : 1);
|
|
674
|
-
}
|
|
675
|
-
failureAlreadyEmitted = true;
|
|
676
|
-
|
|
677
|
-
const envelope = toErrorEnvelope(error);
|
|
678
|
-
|
|
679
|
-
if (outputMode === 'json') {
|
|
680
|
-
emitJsonError(envelope);
|
|
681
|
-
} else {
|
|
682
|
-
console.error(`[${envelope.error.code}] ${envelope.error.message}`);
|
|
683
|
-
if (envelope.error.details && Array.isArray(envelope.error.details.errors) && envelope.error.details.errors.length) {
|
|
684
|
-
for (const err of envelope.error.details.errors) {
|
|
685
|
-
console.error(`- ${formatErrorValue(err)}`);
|
|
686
|
-
}
|
|
687
|
-
}
|
|
688
|
-
if (envelope.error.details && Array.isArray(envelope.error.details.hints) && envelope.error.details.hints.length) {
|
|
689
|
-
for (const hint of envelope.error.details.hints) {
|
|
690
|
-
console.error(`Hint: ${hint}`);
|
|
691
|
-
}
|
|
692
|
-
}
|
|
693
|
-
if (
|
|
694
|
-
envelope.error.details &&
|
|
695
|
-
!Array.isArray(envelope.error.details.errors) &&
|
|
696
|
-
!Array.isArray(envelope.error.details.hints)
|
|
697
|
-
) {
|
|
698
|
-
try {
|
|
699
|
-
console.error(`Details: ${JSON.stringify(envelope.error.details)}`);
|
|
700
|
-
} catch {
|
|
701
|
-
console.error(`Details: ${String(envelope.error.details)}`);
|
|
702
|
-
}
|
|
703
|
-
}
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
process.exit(error instanceof CliError ? error.exitCode : 1);
|
|
707
|
-
}
|
|
708
|
-
|
|
709
|
-
function emitSuccess(outputMode, command, data, tableRenderer) {
|
|
710
|
-
if (outputMode === 'json') {
|
|
711
|
-
emitJson({ ok: true, command, data });
|
|
712
|
-
return;
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
if (typeof tableRenderer === 'function') {
|
|
716
|
-
tableRenderer(data);
|
|
717
|
-
return;
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
console.log('Done.');
|
|
721
|
-
}
|
|
722
|
-
|
|
723
651
|
function parseDotEnv(content) {
|
|
724
652
|
const env = {};
|
|
725
653
|
const lines = content.split(/\r?\n/);
|
|
@@ -753,6 +681,22 @@ function isValidHttpUrl(value) {
|
|
|
753
681
|
}
|
|
754
682
|
}
|
|
755
683
|
|
|
684
|
+
function isLocalHostname(hostname) {
|
|
685
|
+
const host = String(hostname || '').trim().toLowerCase();
|
|
686
|
+
return host === 'localhost' || host === '127.0.0.1' || host === '::1';
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
function isSecureHttpUrlOrLocal(value) {
|
|
690
|
+
try {
|
|
691
|
+
const parsed = new URL(value);
|
|
692
|
+
if (parsed.protocol === 'https:') return true;
|
|
693
|
+
if (parsed.protocol !== 'http:') return false;
|
|
694
|
+
return isLocalHostname(parsed.hostname);
|
|
695
|
+
} catch {
|
|
696
|
+
return false;
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
|
|
756
700
|
function isValidAddress(value) {
|
|
757
701
|
return /^0x[a-fA-F0-9]{40}$/.test(value);
|
|
758
702
|
}
|
|
@@ -963,6 +907,13 @@ function mergeWhere(where, jsonText, flagName) {
|
|
|
963
907
|
throw new CliError('INVALID_JSON', `${flagName} must decode to a JSON object.`);
|
|
964
908
|
}
|
|
965
909
|
|
|
910
|
+
const forbiddenKeys = new Set(['__proto__', 'prototype', 'constructor']);
|
|
911
|
+
for (const key of Object.keys(parsed)) {
|
|
912
|
+
if (forbiddenKeys.has(key)) {
|
|
913
|
+
throw new CliError('INVALID_JSON', `${flagName} contains forbidden key: "${key}".`);
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
|
|
966
917
|
return { ...where, ...parsed };
|
|
967
918
|
}
|
|
968
919
|
|
|
@@ -1837,7 +1788,14 @@ function parsePortfolioFlags(args) {
|
|
|
1837
1788
|
|
|
1838
1789
|
function parseWebhookFlagIntoOptions(args, i, token, options) {
|
|
1839
1790
|
if (token === '--webhook-url') {
|
|
1840
|
-
|
|
1791
|
+
const value = requireFlagValue(args, i, '--webhook-url');
|
|
1792
|
+
if (!isSecureHttpUrlOrLocal(value)) {
|
|
1793
|
+
throw new CliError(
|
|
1794
|
+
'INVALID_FLAG_VALUE',
|
|
1795
|
+
'--webhook-url must use https:// (or http://localhost/127.0.0.1 for local testing).',
|
|
1796
|
+
);
|
|
1797
|
+
}
|
|
1798
|
+
options.webhookUrl = value;
|
|
1841
1799
|
return 1;
|
|
1842
1800
|
}
|
|
1843
1801
|
if (token === '--webhook-template') {
|
|
@@ -1871,7 +1829,14 @@ function parseWebhookFlagIntoOptions(args, i, token, options) {
|
|
|
1871
1829
|
return 1;
|
|
1872
1830
|
}
|
|
1873
1831
|
if (token === '--discord-webhook-url') {
|
|
1874
|
-
|
|
1832
|
+
const value = requireFlagValue(args, i, '--discord-webhook-url');
|
|
1833
|
+
if (!isSecureHttpUrlOrLocal(value)) {
|
|
1834
|
+
throw new CliError(
|
|
1835
|
+
'INVALID_FLAG_VALUE',
|
|
1836
|
+
'--discord-webhook-url must use https:// (or http://localhost/127.0.0.1 for local testing).',
|
|
1837
|
+
);
|
|
1838
|
+
}
|
|
1839
|
+
options.discordWebhookUrl = value;
|
|
1875
1840
|
return 1;
|
|
1876
1841
|
}
|
|
1877
1842
|
if (token === '--fail-on-webhook-error') {
|
|
@@ -3377,6 +3342,7 @@ function parseMirrorSyncFlags(args) {
|
|
|
3377
3342
|
failOnWebhookError: false,
|
|
3378
3343
|
daemon: false,
|
|
3379
3344
|
forceGate: false,
|
|
3345
|
+
forceGateDeprecatedUsed: false,
|
|
3380
3346
|
};
|
|
3381
3347
|
let sawPaperModeFlag = false;
|
|
3382
3348
|
let sawExecuteLiveModeFlag = false;
|
|
@@ -3524,7 +3490,14 @@ function parseMirrorSyncFlags(args) {
|
|
|
3524
3490
|
continue;
|
|
3525
3491
|
}
|
|
3526
3492
|
if (token === '--rpc-url') {
|
|
3527
|
-
|
|
3493
|
+
const rpcUrl = requireFlagValue(rest, i, '--rpc-url');
|
|
3494
|
+
if (!isSecureHttpUrlOrLocal(rpcUrl)) {
|
|
3495
|
+
throw new CliError(
|
|
3496
|
+
'INVALID_FLAG_VALUE',
|
|
3497
|
+
'--rpc-url must use https:// (or http://localhost/127.0.0.1 for local testing).',
|
|
3498
|
+
);
|
|
3499
|
+
}
|
|
3500
|
+
options.rpcUrl = rpcUrl;
|
|
3528
3501
|
i += 1;
|
|
3529
3502
|
continue;
|
|
3530
3503
|
}
|
|
@@ -3544,7 +3517,14 @@ function parseMirrorSyncFlags(args) {
|
|
|
3544
3517
|
continue;
|
|
3545
3518
|
}
|
|
3546
3519
|
if (token === '--polymarket-host') {
|
|
3547
|
-
|
|
3520
|
+
const polymarketHost = requireFlagValue(rest, i, '--polymarket-host');
|
|
3521
|
+
if (!isSecureHttpUrlOrLocal(polymarketHost)) {
|
|
3522
|
+
throw new CliError(
|
|
3523
|
+
'INVALID_FLAG_VALUE',
|
|
3524
|
+
'--polymarket-host must use https:// (or http://localhost/127.0.0.1 for local testing).',
|
|
3525
|
+
);
|
|
3526
|
+
}
|
|
3527
|
+
options.polymarketHost = polymarketHost;
|
|
3548
3528
|
i += 1;
|
|
3549
3529
|
continue;
|
|
3550
3530
|
}
|
|
@@ -3567,8 +3547,13 @@ function parseMirrorSyncFlags(args) {
|
|
|
3567
3547
|
options.trustDeploy = true;
|
|
3568
3548
|
continue;
|
|
3569
3549
|
}
|
|
3570
|
-
if (token === '--
|
|
3550
|
+
if (token === '--skip-gate') {
|
|
3551
|
+
options.forceGate = true;
|
|
3552
|
+
continue;
|
|
3553
|
+
}
|
|
3554
|
+
if (token === '--force-gate') {
|
|
3571
3555
|
options.forceGate = true;
|
|
3556
|
+
options.forceGateDeprecatedUsed = true;
|
|
3572
3557
|
continue;
|
|
3573
3558
|
}
|
|
3574
3559
|
if (token === '--manifest-file') {
|
|
@@ -3753,7 +3738,7 @@ function buildMirrorSyncDaemonCliArgs(options, shared) {
|
|
|
3753
3738
|
if (options.polymarketGammaMockUrl) args.push('--polymarket-gamma-mock-url', options.polymarketGammaMockUrl);
|
|
3754
3739
|
if (options.polymarketMockUrl) args.push('--polymarket-mock-url', options.polymarketMockUrl);
|
|
3755
3740
|
if (options.trustDeploy) args.push('--trust-deploy');
|
|
3756
|
-
if (options.forceGate) args.push('--
|
|
3741
|
+
if (options.forceGate) args.push('--skip-gate');
|
|
3757
3742
|
if (options.manifestFile) args.push('--manifest-file', options.manifestFile);
|
|
3758
3743
|
|
|
3759
3744
|
if (options.webhookUrl) args.push('--webhook-url', options.webhookUrl);
|
|
@@ -3803,6 +3788,7 @@ function parseMirrorGoFlags(args) {
|
|
|
3803
3788
|
manifestFile: null,
|
|
3804
3789
|
trustDeploy: false,
|
|
3805
3790
|
forceGate: false,
|
|
3791
|
+
forceGateDeprecatedUsed: false,
|
|
3806
3792
|
polymarketHost: null,
|
|
3807
3793
|
polymarketGammaUrl: null,
|
|
3808
3794
|
polymarketGammaMockUrl: null,
|
|
@@ -3932,7 +3918,14 @@ function parseMirrorGoFlags(args) {
|
|
|
3932
3918
|
continue;
|
|
3933
3919
|
}
|
|
3934
3920
|
if (token === '--rpc-url') {
|
|
3935
|
-
|
|
3921
|
+
const rpcUrl = requireFlagValue(args, i, '--rpc-url');
|
|
3922
|
+
if (!isSecureHttpUrlOrLocal(rpcUrl)) {
|
|
3923
|
+
throw new CliError(
|
|
3924
|
+
'INVALID_FLAG_VALUE',
|
|
3925
|
+
'--rpc-url must use https:// (or http://localhost/127.0.0.1 for local testing).',
|
|
3926
|
+
);
|
|
3927
|
+
}
|
|
3928
|
+
options.rpcUrl = rpcUrl;
|
|
3936
3929
|
i += 1;
|
|
3937
3930
|
continue;
|
|
3938
3931
|
}
|
|
@@ -3984,12 +3977,24 @@ function parseMirrorGoFlags(args) {
|
|
|
3984
3977
|
options.trustDeploy = true;
|
|
3985
3978
|
continue;
|
|
3986
3979
|
}
|
|
3987
|
-
if (token === '--
|
|
3980
|
+
if (token === '--skip-gate') {
|
|
3988
3981
|
options.forceGate = true;
|
|
3989
3982
|
continue;
|
|
3990
3983
|
}
|
|
3984
|
+
if (token === '--force-gate') {
|
|
3985
|
+
options.forceGate = true;
|
|
3986
|
+
options.forceGateDeprecatedUsed = true;
|
|
3987
|
+
continue;
|
|
3988
|
+
}
|
|
3991
3989
|
if (token === '--polymarket-host') {
|
|
3992
|
-
|
|
3990
|
+
const polymarketHost = requireFlagValue(args, i, '--polymarket-host');
|
|
3991
|
+
if (!isSecureHttpUrlOrLocal(polymarketHost)) {
|
|
3992
|
+
throw new CliError(
|
|
3993
|
+
'INVALID_FLAG_VALUE',
|
|
3994
|
+
'--polymarket-host must use https:// (or http://localhost/127.0.0.1 for local testing).',
|
|
3995
|
+
);
|
|
3996
|
+
}
|
|
3997
|
+
options.polymarketHost = polymarketHost;
|
|
3993
3998
|
i += 1;
|
|
3994
3999
|
continue;
|
|
3995
4000
|
}
|
|
@@ -4440,7 +4445,14 @@ function parsePolymarketSharedFlags(args, actionLabel) {
|
|
|
4440
4445
|
for (let i = 0; i < args.length; i += 1) {
|
|
4441
4446
|
const token = args[i];
|
|
4442
4447
|
if (token === '--rpc-url') {
|
|
4443
|
-
|
|
4448
|
+
const rpcUrl = requireFlagValue(args, i, '--rpc-url');
|
|
4449
|
+
if (!isSecureHttpUrlOrLocal(rpcUrl)) {
|
|
4450
|
+
throw new CliError(
|
|
4451
|
+
'INVALID_FLAG_VALUE',
|
|
4452
|
+
'--rpc-url must use https:// (or http://localhost/127.0.0.1 for local testing).',
|
|
4453
|
+
);
|
|
4454
|
+
}
|
|
4455
|
+
options.rpcUrl = rpcUrl;
|
|
4444
4456
|
i += 1;
|
|
4445
4457
|
continue;
|
|
4446
4458
|
}
|
|
@@ -4557,7 +4569,14 @@ function parsePolymarketTradeFlags(args) {
|
|
|
4557
4569
|
continue;
|
|
4558
4570
|
}
|
|
4559
4571
|
if (token === '--polymarket-host') {
|
|
4560
|
-
|
|
4572
|
+
const host = requireFlagValue(args, i, '--polymarket-host');
|
|
4573
|
+
if (!isSecureHttpUrlOrLocal(host)) {
|
|
4574
|
+
throw new CliError(
|
|
4575
|
+
'INVALID_FLAG_VALUE',
|
|
4576
|
+
'--polymarket-host must use https:// (or http://localhost/127.0.0.1 for local testing).',
|
|
4577
|
+
);
|
|
4578
|
+
}
|
|
4579
|
+
options.host = host;
|
|
4561
4580
|
i += 1;
|
|
4562
4581
|
continue;
|
|
4563
4582
|
}
|
|
@@ -5109,8 +5128,8 @@ function validateEnvValues() {
|
|
|
5109
5128
|
}
|
|
5110
5129
|
|
|
5111
5130
|
const rpcUrl = String(process.env.RPC_URL || '').trim();
|
|
5112
|
-
if (!missingSet.has('RPC_URL') && !
|
|
5113
|
-
errors.push(`RPC_URL must
|
|
5131
|
+
if (!missingSet.has('RPC_URL') && !isSecureHttpUrlOrLocal(rpcUrl)) {
|
|
5132
|
+
errors.push(`RPC_URL must use https:// (or http://localhost/127.0.0.1 for local testing). Received: "${rpcUrl}"`);
|
|
5114
5133
|
}
|
|
5115
5134
|
|
|
5116
5135
|
const privateKey = String(process.env.PANDORA_PRIVATE_KEY || process.env.PRIVATE_KEY || '').trim();
|
|
@@ -7313,8 +7332,11 @@ function resolveTradeRuntimeConfig(options) {
|
|
|
7313
7332
|
}
|
|
7314
7333
|
|
|
7315
7334
|
const rpcUrl = options.rpcUrl || process.env.RPC_URL || DEFAULT_RPC_BY_CHAIN_ID[chainIdRaw];
|
|
7316
|
-
if (!
|
|
7317
|
-
throw new CliError(
|
|
7335
|
+
if (!isSecureHttpUrlOrLocal(rpcUrl)) {
|
|
7336
|
+
throw new CliError(
|
|
7337
|
+
'INVALID_FLAG_VALUE',
|
|
7338
|
+
`RPC URL must use https:// (or http://localhost/127.0.0.1 for local testing). Received: "${rpcUrl}"`,
|
|
7339
|
+
);
|
|
7318
7340
|
}
|
|
7319
7341
|
|
|
7320
7342
|
const privateKey = options.privateKey || process.env.PANDORA_PRIVATE_KEY || process.env.PRIVATE_KEY;
|
|
@@ -7704,6 +7726,23 @@ async function runMarketsCommand(args, context) {
|
|
|
7704
7726
|
|
|
7705
7727
|
async function runScanCommand(args, context) {
|
|
7706
7728
|
const shared = parseIndexerSharedFlags(args);
|
|
7729
|
+
if (includesHelpFlag(shared.rest)) {
|
|
7730
|
+
if (context.outputMode === 'json') {
|
|
7731
|
+
emitSuccess(
|
|
7732
|
+
context.outputMode,
|
|
7733
|
+
'scan.help',
|
|
7734
|
+
commandHelpPayload(
|
|
7735
|
+
'pandora [--output table|json] scan [--limit <n>] [--after <cursor>] [--before <cursor>] [--order-by <field>] [--order-direction asc|desc] [--chain-id <id>] [--creator <address>] [--poll-address <address>] [--market-type <type>] [--where-json <json>] [--expand] [--with-odds]',
|
|
7736
|
+
),
|
|
7737
|
+
);
|
|
7738
|
+
} else {
|
|
7739
|
+
console.log(
|
|
7740
|
+
'Usage: pandora [--output table|json] scan [--limit <n>] [--after <cursor>] [--before <cursor>] [--order-by <field>] [--order-direction asc|desc] [--chain-id <id>] [--creator <address>] [--poll-address <address>] [--market-type <type>] [--where-json <json>] [--expand] [--with-odds]',
|
|
7741
|
+
);
|
|
7742
|
+
}
|
|
7743
|
+
return;
|
|
7744
|
+
}
|
|
7745
|
+
|
|
7707
7746
|
maybeLoadIndexerEnv(shared);
|
|
7708
7747
|
const indexerUrl = resolveIndexerUrl(shared.indexerUrl);
|
|
7709
7748
|
|
|
@@ -8300,10 +8339,12 @@ async function runPortfolioCommand(args, context) {
|
|
|
8300
8339
|
if (context.outputMode === 'json') {
|
|
8301
8340
|
emitSuccess(context.outputMode, 'portfolio.help', {
|
|
8302
8341
|
usage:
|
|
8303
|
-
'pandora [--output table|json] portfolio --wallet <address> [--chain-id <id>] [--limit <n>] [--include-events|--no-events]',
|
|
8342
|
+
'pandora [--output table|json] portfolio --wallet <address> [--chain-id <id>] [--limit <n>] [--include-events|--no-events] [--with-lp] [--rpc-url <url>]',
|
|
8304
8343
|
});
|
|
8305
8344
|
} else {
|
|
8306
|
-
console.log(
|
|
8345
|
+
console.log(
|
|
8346
|
+
'Usage: pandora [--output table|json] portfolio --wallet <address> [--chain-id <id>] [--limit <n>] [--include-events|--no-events] [--with-lp] [--rpc-url <url>]',
|
|
8347
|
+
);
|
|
8307
8348
|
}
|
|
8308
8349
|
return;
|
|
8309
8350
|
}
|
|
@@ -8888,10 +8929,10 @@ async function runMirrorCommand(args, context) {
|
|
|
8888
8929
|
' simulate --liquidity-usdc <n> [--source-yes-pct <0-100>] [--target-yes-pct <0-100>] [--distribution-yes <parts>] [--distribution-no <parts>] [--fee-tier 500|3000|10000] [--volume-scenarios <csv>] [--hedge-ratio <n>] [--polymarket-yes-pct <0-100>]',
|
|
8889
8930
|
);
|
|
8890
8931
|
console.log(
|
|
8891
|
-
' go --polymarket-market-id <id>|--polymarket-slug <slug> [--liquidity-usdc <n>] [--paper|--dry-run|--execute-live|--execute] [--private-key <hex>] [--funder <address>] [--auto-sync] [--sync-once] [--
|
|
8932
|
+
' go --polymarket-market-id <id>|--polymarket-slug <slug> [--liquidity-usdc <n>] [--paper|--dry-run|--execute-live|--execute] [--private-key <hex>] [--funder <address>] [--auto-sync] [--sync-once] [--skip-gate]',
|
|
8892
8933
|
);
|
|
8893
8934
|
console.log(
|
|
8894
|
-
' sync run|once|start|stop|status --pandora-market-address <address>|--market-address <address> --polymarket-market-id <id>|--polymarket-slug <slug> [--paper|--dry-run|--execute-live|--execute] [--private-key <hex>] [--funder <address>] [--trust-deploy] [--
|
|
8935
|
+
' sync run|once|start|stop|status --pandora-market-address <address>|--market-address <address> --polymarket-market-id <id>|--polymarket-slug <slug> [--paper|--dry-run|--execute-live|--execute] [--private-key <hex>] [--funder <address>] [--trust-deploy] [--skip-gate] [--daemon] [--stream] [--interval-ms <ms>] [--drift-trigger-bps <n>] [--hedge-trigger-usdc <n>] [--hedge-ratio <n>] [--no-hedge] [--max-rebalance-usdc <n>] [--max-hedge-usdc <n>] [--max-open-exposure-usdc <n>] [--max-trades-per-day <n>] [--cooldown-ms <ms>] [--state-file <path>] [--kill-switch-file <path>]',
|
|
8895
8936
|
);
|
|
8896
8937
|
console.log(' status --state-file <path>|--strategy-hash <hash> [--with-live] [--trust-deploy]');
|
|
8897
8938
|
console.log(' close --pandora-market-address <address>|--market-address <address> --polymarket-market-id <id>|--polymarket-slug <slug> --dry-run|--execute');
|
|
@@ -9335,7 +9376,7 @@ async function runMirrorCommand(args, context) {
|
|
|
9335
9376
|
if (action === 'go') {
|
|
9336
9377
|
if (includesHelpFlag(shared.rest)) {
|
|
9337
9378
|
const usage =
|
|
9338
|
-
'pandora [--output table|json] mirror go --polymarket-market-id <id>|--polymarket-slug <slug> [--liquidity-usdc <n>] [--paper|--dry-run|--execute-live|--execute] [--private-key <hex>] [--funder <address>] [--auto-sync] [--sync-once] [--
|
|
9379
|
+
'pandora [--output table|json] mirror go --polymarket-market-id <id>|--polymarket-slug <slug> [--liquidity-usdc <n>] [--paper|--dry-run|--execute-live|--execute] [--private-key <hex>] [--funder <address>] [--auto-sync] [--sync-once] [--skip-gate]';
|
|
9339
9380
|
if (context.outputMode === 'json') {
|
|
9340
9381
|
emitSuccess(context.outputMode, 'mirror.go.help', commandHelpPayload(usage));
|
|
9341
9382
|
} else {
|
|
@@ -9347,6 +9388,12 @@ async function runMirrorCommand(args, context) {
|
|
|
9347
9388
|
maybeLoadTradeEnv(shared);
|
|
9348
9389
|
const indexerUrl = resolveIndexerUrl(shared.indexerUrl);
|
|
9349
9390
|
const options = parseMirrorGoFlags(shared.rest);
|
|
9391
|
+
const deprecatedForceGateWarning = options.forceGateDeprecatedUsed
|
|
9392
|
+
? 'Flag --force-gate is deprecated; use --skip-gate instead.'
|
|
9393
|
+
: null;
|
|
9394
|
+
if (deprecatedForceGateWarning && context.outputMode === 'table') {
|
|
9395
|
+
console.error(`Warning: ${deprecatedForceGateWarning}`);
|
|
9396
|
+
}
|
|
9350
9397
|
|
|
9351
9398
|
let planPayload;
|
|
9352
9399
|
let deployPayload;
|
|
@@ -9385,6 +9432,7 @@ async function runMirrorCommand(args, context) {
|
|
|
9385
9432
|
trustDeploy = true;
|
|
9386
9433
|
}
|
|
9387
9434
|
const diagnostics = [];
|
|
9435
|
+
if (deprecatedForceGateWarning) diagnostics.push(deprecatedForceGateWarning);
|
|
9388
9436
|
|
|
9389
9437
|
if (pandoraMarketAddress) {
|
|
9390
9438
|
if (!trustManifest && (options.executeLive || options.trustDeploy)) {
|
|
@@ -9538,7 +9586,7 @@ async function runMirrorCommand(args, context) {
|
|
|
9538
9586
|
'--paper',
|
|
9539
9587
|
`--drift-trigger-bps ${options.driftTriggerBps}`,
|
|
9540
9588
|
`--hedge-trigger-usdc ${options.hedgeTriggerUsdc}`,
|
|
9541
|
-
options.forceGate ? '--
|
|
9589
|
+
options.forceGate ? '--skip-gate' : null,
|
|
9542
9590
|
]
|
|
9543
9591
|
.filter(Boolean)
|
|
9544
9592
|
.join(' ');
|
|
@@ -9581,7 +9629,7 @@ async function runMirrorCommand(args, context) {
|
|
|
9581
9629
|
'mirror.sync.help',
|
|
9582
9630
|
{
|
|
9583
9631
|
usage:
|
|
9584
|
-
'pandora [--output table|json] mirror sync run|once|start|stop|status --pandora-market-address <address>|--market-address <address> --polymarket-market-id <id>|--polymarket-slug <slug> [--paper|--dry-run|--execute-live|--execute] [--private-key <hex>] [--funder <address>] [--trust-deploy] [--
|
|
9632
|
+
'pandora [--output table|json] mirror sync run|once|start|stop|status --pandora-market-address <address>|--market-address <address> --polymarket-market-id <id>|--polymarket-slug <slug> [--paper|--dry-run|--execute-live|--execute] [--private-key <hex>] [--funder <address>] [--trust-deploy] [--skip-gate] [--daemon] [--stream] [--interval-ms <ms>] [--drift-trigger-bps <n>] [--hedge-trigger-usdc <n>] [--hedge-ratio <n>] [--no-hedge] [--max-rebalance-usdc <n>] [--max-hedge-usdc <n>] [--max-open-exposure-usdc <n>] [--max-trades-per-day <n>] [--cooldown-ms <ms>] [--min-time-to-close-sec <n>] [--state-file <path>] [--kill-switch-file <path>]',
|
|
9585
9633
|
daemonLifecycle: {
|
|
9586
9634
|
start:
|
|
9587
9635
|
'pandora [--output table|json] mirror sync start --pandora-market-address <address>|--market-address <address> --polymarket-market-id <id>|--polymarket-slug <slug> [run flags]',
|
|
@@ -9610,7 +9658,7 @@ async function runMirrorCommand(args, context) {
|
|
|
9610
9658
|
);
|
|
9611
9659
|
} else {
|
|
9612
9660
|
console.log(
|
|
9613
|
-
'Usage: pandora [--output table|json] mirror sync run|once|start|stop|status --pandora-market-address <address>|--market-address <address> --polymarket-market-id <id>|--polymarket-slug <slug> [--paper|--dry-run|--execute-live|--execute] [--private-key <hex>] [--funder <address>] [--trust-deploy] [--
|
|
9661
|
+
'Usage: pandora [--output table|json] mirror sync run|once|start|stop|status --pandora-market-address <address>|--market-address <address> --polymarket-market-id <id>|--polymarket-slug <slug> [--paper|--dry-run|--execute-live|--execute] [--private-key <hex>] [--funder <address>] [--trust-deploy] [--skip-gate] [--daemon] [--stream] [--interval-ms <ms>] [--drift-trigger-bps <n>] [--hedge-trigger-usdc <n>] [--hedge-ratio <n>] [--no-hedge] [--max-rebalance-usdc <n>] [--max-hedge-usdc <n>] [--max-open-exposure-usdc <n>] [--max-trades-per-day <n>] [--cooldown-ms <ms>] [--min-time-to-close-sec <n>] [--state-file <path>] [--kill-switch-file <path>]',
|
|
9614
9662
|
);
|
|
9615
9663
|
console.log('Daemon stop: pandora mirror sync stop --pid-file <path>|--strategy-hash <hash>');
|
|
9616
9664
|
console.log('Daemon status: pandora mirror sync status --pid-file <path>|--strategy-hash <hash>');
|
|
@@ -9657,6 +9705,12 @@ async function runMirrorCommand(args, context) {
|
|
|
9657
9705
|
maybeLoadTradeEnv(shared);
|
|
9658
9706
|
const indexerUrl = resolveIndexerUrl(shared.indexerUrl);
|
|
9659
9707
|
const options = parseMirrorSyncFlags(syncRunArgs);
|
|
9708
|
+
const deprecatedForceGateWarning = options.forceGateDeprecatedUsed
|
|
9709
|
+
? 'Flag --force-gate is deprecated; use --skip-gate instead.'
|
|
9710
|
+
: null;
|
|
9711
|
+
if (deprecatedForceGateWarning && context.outputMode === 'table') {
|
|
9712
|
+
console.error(`Warning: ${deprecatedForceGateWarning}`);
|
|
9713
|
+
}
|
|
9660
9714
|
if (isStartAction) {
|
|
9661
9715
|
options.daemon = true;
|
|
9662
9716
|
}
|
|
@@ -9723,6 +9777,10 @@ async function runMirrorCommand(args, context) {
|
|
|
9723
9777
|
if (trustManifest) {
|
|
9724
9778
|
daemonPayload.trustManifest = trustManifest;
|
|
9725
9779
|
}
|
|
9780
|
+
if (deprecatedForceGateWarning) {
|
|
9781
|
+
const existingDiagnostics = Array.isArray(daemonPayload.diagnostics) ? daemonPayload.diagnostics : [];
|
|
9782
|
+
daemonPayload.diagnostics = [...existingDiagnostics, deprecatedForceGateWarning];
|
|
9783
|
+
}
|
|
9726
9784
|
|
|
9727
9785
|
emitSuccess(
|
|
9728
9786
|
context.outputMode,
|
|
@@ -9733,6 +9791,13 @@ async function runMirrorCommand(args, context) {
|
|
|
9733
9791
|
return;
|
|
9734
9792
|
}
|
|
9735
9793
|
|
|
9794
|
+
if (context.outputMode === 'json' && options.stream) {
|
|
9795
|
+
throw new CliError(
|
|
9796
|
+
'INVALID_ARGS',
|
|
9797
|
+
'--stream is only supported in table output mode. Use --output table or remove --stream.',
|
|
9798
|
+
);
|
|
9799
|
+
}
|
|
9800
|
+
|
|
9736
9801
|
const streamTicks = options.stream || (options.mode === 'run' && context.outputMode === 'table');
|
|
9737
9802
|
let polymarketPreflight = null;
|
|
9738
9803
|
|
|
@@ -9814,6 +9879,10 @@ async function runMirrorCommand(args, context) {
|
|
|
9814
9879
|
if (polymarketPreflight) {
|
|
9815
9880
|
payload.polymarketPreflight = polymarketPreflight;
|
|
9816
9881
|
}
|
|
9882
|
+
if (deprecatedForceGateWarning) {
|
|
9883
|
+
const existingDiagnostics = Array.isArray(payload.diagnostics) ? payload.diagnostics : [];
|
|
9884
|
+
payload.diagnostics = [...existingDiagnostics, deprecatedForceGateWarning];
|
|
9885
|
+
}
|
|
9817
9886
|
|
|
9818
9887
|
emitSuccess(context.outputMode, 'mirror.sync', payload, renderMirrorSyncTable);
|
|
9819
9888
|
return;
|
|
@@ -10066,6 +10135,17 @@ async function runWebhookCommand(args, context) {
|
|
|
10066
10135
|
throw new CliError('INVALID_ARGS', 'webhook requires subcommand: test');
|
|
10067
10136
|
}
|
|
10068
10137
|
|
|
10138
|
+
if (includesHelpFlag(actionArgs)) {
|
|
10139
|
+
const usage =
|
|
10140
|
+
'pandora [--output table|json] webhook test [--webhook-url <url>] [--webhook-template <json>] [--webhook-secret <secret>] [--telegram-bot-token <token>] [--telegram-chat-id <id>] [--discord-webhook-url <url>] [--webhook-timeout-ms <ms>] [--webhook-retries <n>]';
|
|
10141
|
+
if (context.outputMode === 'json') {
|
|
10142
|
+
emitSuccess(context.outputMode, 'webhook.test.help', commandHelpPayload(usage));
|
|
10143
|
+
} else {
|
|
10144
|
+
console.log(`Usage: ${usage}`);
|
|
10145
|
+
}
|
|
10146
|
+
return;
|
|
10147
|
+
}
|
|
10148
|
+
|
|
10069
10149
|
const options = parseWebhookTestFlags(actionArgs);
|
|
10070
10150
|
const payload = await sendWebhookNotifications(options, {
|
|
10071
10151
|
event: 'webhook.test',
|
|
@@ -10358,6 +10438,11 @@ function runInitEnv(args, outputMode) {
|
|
|
10358
10438
|
|
|
10359
10439
|
fs.mkdirSync(path.dirname(options.envFile), { recursive: true });
|
|
10360
10440
|
fs.copyFileSync(options.exampleFile, options.envFile);
|
|
10441
|
+
try {
|
|
10442
|
+
fs.chmodSync(options.envFile, 0o600);
|
|
10443
|
+
} catch {
|
|
10444
|
+
// best-effort hardening on platforms that ignore/limit chmod
|
|
10445
|
+
}
|
|
10361
10446
|
|
|
10362
10447
|
emitSuccess(outputMode, 'init-env', {
|
|
10363
10448
|
envFile: options.envFile,
|
|
@@ -10416,6 +10501,11 @@ async function runSetup(args, outputMode) {
|
|
|
10416
10501
|
|
|
10417
10502
|
let envStep;
|
|
10418
10503
|
if (fs.existsSync(options.envFile) && !options.force) {
|
|
10504
|
+
try {
|
|
10505
|
+
fs.chmodSync(options.envFile, 0o600);
|
|
10506
|
+
} catch {
|
|
10507
|
+
// best-effort hardening on pre-existing files
|
|
10508
|
+
}
|
|
10419
10509
|
envStep = {
|
|
10420
10510
|
status: 'skipped',
|
|
10421
10511
|
message: `Env file exists at ${options.envFile}. Reusing existing file.`,
|
|
@@ -10425,6 +10515,11 @@ async function runSetup(args, outputMode) {
|
|
|
10425
10515
|
} else {
|
|
10426
10516
|
fs.mkdirSync(path.dirname(options.envFile), { recursive: true });
|
|
10427
10517
|
fs.copyFileSync(options.exampleFile, options.envFile);
|
|
10518
|
+
try {
|
|
10519
|
+
fs.chmodSync(options.envFile, 0o600);
|
|
10520
|
+
} catch {
|
|
10521
|
+
// best-effort hardening on platforms that ignore/limit chmod
|
|
10522
|
+
}
|
|
10428
10523
|
envStep = {
|
|
10429
10524
|
status: 'written',
|
|
10430
10525
|
message: `Wrote env file: ${options.envFile}`,
|
|
@@ -10460,160 +10555,40 @@ async function runSetup(args, outputMode) {
|
|
|
10460
10555
|
emitSuccess(outputMode, 'setup', payload, renderSetupTable);
|
|
10461
10556
|
}
|
|
10462
10557
|
|
|
10463
|
-
|
|
10464
|
-
|
|
10465
|
-
|
|
10466
|
-
|
|
10467
|
-
|
|
10468
|
-
|
|
10469
|
-
|
|
10470
|
-
|
|
10471
|
-
|
|
10472
|
-
|
|
10473
|
-
|
|
10474
|
-
|
|
10475
|
-
|
|
10476
|
-
|
|
10477
|
-
|
|
10478
|
-
|
|
10479
|
-
|
|
10480
|
-
|
|
10481
|
-
|
|
10482
|
-
|
|
10483
|
-
|
|
10484
|
-
|
|
10485
|
-
|
|
10486
|
-
|
|
10487
|
-
|
|
10488
|
-
|
|
10489
|
-
|
|
10490
|
-
|
|
10491
|
-
|
|
10492
|
-
|
|
10493
|
-
|
|
10494
|
-
|
|
10495
|
-
|
|
10496
|
-
|
|
10497
|
-
if (command === 'markets') {
|
|
10498
|
-
await runMarketsCommand(args, context);
|
|
10499
|
-
return;
|
|
10500
|
-
}
|
|
10501
|
-
|
|
10502
|
-
if (command === 'scan') {
|
|
10503
|
-
await runScanCommand(args, context);
|
|
10504
|
-
return;
|
|
10505
|
-
}
|
|
10506
|
-
|
|
10507
|
-
if (command === 'quote') {
|
|
10508
|
-
await runQuoteCommand(args, context);
|
|
10509
|
-
return;
|
|
10510
|
-
}
|
|
10511
|
-
|
|
10512
|
-
if (command === 'trade') {
|
|
10513
|
-
await runTradeCommand(args, context);
|
|
10514
|
-
return;
|
|
10515
|
-
}
|
|
10516
|
-
|
|
10517
|
-
if (command === 'polls') {
|
|
10518
|
-
await runPollsCommand(args, context);
|
|
10519
|
-
return;
|
|
10520
|
-
}
|
|
10521
|
-
|
|
10522
|
-
if (command === 'events') {
|
|
10523
|
-
await runEventsCommand(args, context);
|
|
10524
|
-
return;
|
|
10525
|
-
}
|
|
10526
|
-
|
|
10527
|
-
if (command === 'positions') {
|
|
10528
|
-
await runPositionsCommand(args, context);
|
|
10529
|
-
return;
|
|
10530
|
-
}
|
|
10531
|
-
|
|
10532
|
-
if (command === 'portfolio') {
|
|
10533
|
-
await runPortfolioCommand(args, context);
|
|
10534
|
-
return;
|
|
10535
|
-
}
|
|
10536
|
-
|
|
10537
|
-
if (command === 'watch') {
|
|
10538
|
-
await runWatchCommand(args, context);
|
|
10539
|
-
return;
|
|
10540
|
-
}
|
|
10541
|
-
|
|
10542
|
-
if (command === 'history') {
|
|
10543
|
-
await runHistoryCommand(args, context);
|
|
10544
|
-
return;
|
|
10545
|
-
}
|
|
10546
|
-
|
|
10547
|
-
if (command === 'export') {
|
|
10548
|
-
await runExportCommand(args, context);
|
|
10549
|
-
return;
|
|
10550
|
-
}
|
|
10551
|
-
|
|
10552
|
-
if (command === 'arbitrage') {
|
|
10553
|
-
await runArbitrageCommand(args, context);
|
|
10554
|
-
return;
|
|
10555
|
-
}
|
|
10556
|
-
|
|
10557
|
-
if (command === 'autopilot') {
|
|
10558
|
-
await runAutopilotCommand(args, context);
|
|
10559
|
-
return;
|
|
10560
|
-
}
|
|
10561
|
-
|
|
10562
|
-
if (command === 'mirror') {
|
|
10563
|
-
await runMirrorCommand(args, context);
|
|
10564
|
-
return;
|
|
10565
|
-
}
|
|
10566
|
-
|
|
10567
|
-
if (command === 'polymarket') {
|
|
10568
|
-
await runPolymarketCommand(args, context);
|
|
10569
|
-
return;
|
|
10570
|
-
}
|
|
10571
|
-
|
|
10572
|
-
if (command === 'webhook') {
|
|
10573
|
-
await runWebhookCommand(args, context);
|
|
10574
|
-
return;
|
|
10575
|
-
}
|
|
10576
|
-
|
|
10577
|
-
if (command === 'leaderboard') {
|
|
10578
|
-
await runLeaderboardCommand(args, context);
|
|
10579
|
-
return;
|
|
10580
|
-
}
|
|
10581
|
-
|
|
10582
|
-
if (command === 'analyze') {
|
|
10583
|
-
await runAnalyzeCommand(args, context);
|
|
10584
|
-
return;
|
|
10585
|
-
}
|
|
10586
|
-
|
|
10587
|
-
if (command === 'suggest') {
|
|
10588
|
-
await runSuggestCommand(args, context);
|
|
10589
|
-
return;
|
|
10590
|
-
}
|
|
10591
|
-
|
|
10592
|
-
if (command === 'resolve') {
|
|
10593
|
-
await runResolveCommand(args, context);
|
|
10594
|
-
return;
|
|
10595
|
-
}
|
|
10596
|
-
|
|
10597
|
-
if (command === 'lp') {
|
|
10598
|
-
await runLpCommand(args, context);
|
|
10599
|
-
return;
|
|
10600
|
-
}
|
|
10601
|
-
|
|
10602
|
-
if (command === 'launch' || command === 'clone-bet') {
|
|
10603
|
-
if (context.outputMode === 'json') {
|
|
10604
|
-
throw new CliError(
|
|
10605
|
-
'UNSUPPORTED_OUTPUT_MODE',
|
|
10606
|
-
'--output json is not supported for launch/clone-bet because these commands stream script output directly.',
|
|
10607
|
-
);
|
|
10608
|
-
}
|
|
10609
|
-
runScriptCommand(command, args);
|
|
10610
|
-
return;
|
|
10611
|
-
}
|
|
10612
|
-
|
|
10613
|
-
throw new CliError('UNKNOWN_COMMAND', `Unknown command: ${command}`, {
|
|
10614
|
-
hints: ['Run `pandora help` to see available commands.'],
|
|
10615
|
-
});
|
|
10616
|
-
}
|
|
10558
|
+
const dispatch = createCommandRouter({
|
|
10559
|
+
CliError,
|
|
10560
|
+
packageVersion: PACKAGE_VERSION,
|
|
10561
|
+
emitSuccess,
|
|
10562
|
+
helpJsonPayload,
|
|
10563
|
+
printHelpTable,
|
|
10564
|
+
includesHelpFlag,
|
|
10565
|
+
commandHelpPayload,
|
|
10566
|
+
runInitEnv,
|
|
10567
|
+
runDoctor,
|
|
10568
|
+
runSetup,
|
|
10569
|
+
runMarketsCommand,
|
|
10570
|
+
runScanCommand,
|
|
10571
|
+
runQuoteCommand,
|
|
10572
|
+
runTradeCommand,
|
|
10573
|
+
runPollsCommand,
|
|
10574
|
+
runEventsCommand,
|
|
10575
|
+
runPositionsCommand,
|
|
10576
|
+
runPortfolioCommand,
|
|
10577
|
+
runWatchCommand,
|
|
10578
|
+
runHistoryCommand,
|
|
10579
|
+
runExportCommand,
|
|
10580
|
+
runArbitrageCommand,
|
|
10581
|
+
runAutopilotCommand,
|
|
10582
|
+
runMirrorCommand,
|
|
10583
|
+
runPolymarketCommand,
|
|
10584
|
+
runWebhookCommand,
|
|
10585
|
+
runLeaderboardCommand,
|
|
10586
|
+
runAnalyzeCommand,
|
|
10587
|
+
runSuggestCommand,
|
|
10588
|
+
runResolveCommand,
|
|
10589
|
+
runLpCommand,
|
|
10590
|
+
runScriptCommand,
|
|
10591
|
+
});
|
|
10617
10592
|
|
|
10618
10593
|
async function main() {
|
|
10619
10594
|
const rawArgv = expandEqualsStyleFlags(process.argv.slice(2));
|