pandora-cli-skills 1.1.33 → 1.1.35
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 +62 -13
- package/README_FOR_SHARING.md +56 -2
- package/SKILL.md +68 -8
- package/cli/lib/cli_output_service.cjs +25 -1
- package/cli/lib/command_executor_service.cjs +189 -0
- package/cli/lib/command_router.cjs +51 -0
- package/cli/lib/error_recovery_service.cjs +112 -0
- package/cli/lib/fork_runtime_service.cjs +79 -0
- package/cli/lib/lp_command_service.cjs +2 -2
- package/cli/lib/market_admin_service.cjs +70 -8
- package/cli/lib/mcp_server_service.cjs +147 -0
- package/cli/lib/mcp_tool_registry.cjs +377 -0
- package/cli/lib/parsers/core_command_flags.cjs +1450 -0
- package/cli/lib/parsers/lp_flags.cjs +33 -1
- package/cli/lib/parsers/polymarket_flags.cjs +50 -0
- package/cli/lib/parsers/resolve_flags.cjs +33 -1
- package/cli/lib/parsers/stream_flags.cjs +107 -0
- package/cli/lib/parsers/trade_flags.cjs +27 -0
- package/cli/lib/polymarket_command_service.cjs +91 -17
- package/cli/lib/resolve_command_service.cjs +2 -2
- package/cli/lib/schema_command_service.cjs +94 -7
- package/cli/lib/shared/parse_primitives.cjs +324 -0
- package/cli/lib/stream_command_service.cjs +278 -0
- package/cli/lib/trade_command_service.cjs +28 -0
- package/cli/pandora.cjs +268 -1701
- package/package.json +5 -3
- package/tests/cli/cli.integration.test.cjs +170 -0
- package/tests/cli/mcp.integration.test.cjs +142 -0
- package/tests/cli/stream.integration.test.cjs +218 -0
- package/tests/unit/new-features.test.cjs +47 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Pandora CLI & Skills
|
|
2
2
|
|
|
3
|
-
Production CLI for Pandora prediction markets with mirror + hedge tooling.
|
|
3
|
+
Production CLI for Pandora prediction markets with mirror + hedge tooling and agent-native interfaces.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
@@ -15,7 +15,58 @@ Or run without installing:
|
|
|
15
15
|
npx pandora-cli-skills@latest --help
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
##
|
|
18
|
+
## Agent-Native Features
|
|
19
|
+
|
|
20
|
+
- `pandora --output json schema`
|
|
21
|
+
- emits machine-readable envelope schema + command descriptors.
|
|
22
|
+
- `pandora mcp`
|
|
23
|
+
- runs an MCP server over stdio (`tools/list`, `tools/call`) for direct agent tool execution.
|
|
24
|
+
- JSON errors include optional next-best-action recovery hints:
|
|
25
|
+
- `error.recovery = { action, command, retryable }`.
|
|
26
|
+
- Attach-only fork runtime for on-chain command families:
|
|
27
|
+
- `--fork`, `--fork-rpc-url`, `--fork-chain-id`.
|
|
28
|
+
- Event-driven streaming:
|
|
29
|
+
- `pandora stream prices|events` emits NDJSON lines on stdout.
|
|
30
|
+
|
|
31
|
+
## Quickstart
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
# schema for typed consumers (Pydantic/Zod/etc.)
|
|
35
|
+
pandora --output json schema
|
|
36
|
+
|
|
37
|
+
# MCP server mode
|
|
38
|
+
pandora mcp
|
|
39
|
+
|
|
40
|
+
# Read-only market discovery
|
|
41
|
+
pandora --output json markets list --active --limit 10
|
|
42
|
+
|
|
43
|
+
# Dry-run trade with fork runtime
|
|
44
|
+
pandora --output json trade --dry-run \
|
|
45
|
+
--market-address 0x... --side yes --amount-usdc 10 \
|
|
46
|
+
--fork --fork-rpc-url http://127.0.0.1:8545
|
|
47
|
+
|
|
48
|
+
# NDJSON stream
|
|
49
|
+
pandora stream prices --indexer-url https://pandoraindexer.up.railway.app/ --interval-ms 1000
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Fork Mode Notes
|
|
53
|
+
|
|
54
|
+
- Runtime marker is included in payloads: `data.runtime.mode = "fork" | "live"`.
|
|
55
|
+
- Fork RPC precedence:
|
|
56
|
+
1. `--fork-rpc-url`
|
|
57
|
+
2. `FORK_RPC_URL` (when `--fork` is set)
|
|
58
|
+
3. command default live RPC path
|
|
59
|
+
- `polymarket trade --execute` in fork mode is simulation-only unless `--polymarket-mock-url` is provided.
|
|
60
|
+
|
|
61
|
+
## Streaming Contract
|
|
62
|
+
|
|
63
|
+
`pandora stream prices|events` outputs NDJSON only (one JSON object per line), for example:
|
|
64
|
+
|
|
65
|
+
```json
|
|
66
|
+
{"type":"stream.tick","channel":"prices","seq":1,"ts":"2026-03-01T12:00:00.000Z","source":{"transport":"polling"},"data":{"id":"market-1","yesPct":58.12}}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Command Surface
|
|
19
70
|
|
|
20
71
|
- `pandora markets list|get`
|
|
21
72
|
- `pandora quote`
|
|
@@ -26,17 +77,15 @@ npx pandora-cli-skills@latest --help
|
|
|
26
77
|
- `pandora autopilot run|once`
|
|
27
78
|
- `pandora mirror browse|plan|deploy|verify|lp-explain|hedge-calc|simulate|go|sync|status|close`
|
|
28
79
|
- `pandora polymarket check|approve|preflight|trade`
|
|
80
|
+
- `pandora resolve`
|
|
81
|
+
- `pandora lp add|remove|positions`
|
|
82
|
+
- `pandora stream prices|events`
|
|
83
|
+
- `pandora schema`
|
|
84
|
+
- `pandora mcp`
|
|
29
85
|
|
|
30
|
-
##
|
|
31
|
-
|
|
32
|
-
- `pandora mirror lp-explain --liquidity-usdc 10000 --source-yes-pct 58`
|
|
33
|
-
- `pandora mirror hedge-calc --reserve-yes-usdc 8 --reserve-no-usdc 12 --excess-no-usdc 2 --polymarket-yes-pct 60`
|
|
34
|
-
- `pandora mirror simulate --liquidity-usdc 10000 --source-yes-pct 58 --target-yes-pct 58 --volume-scenarios 1000,5000,10000`
|
|
86
|
+
## Docs
|
|
35
87
|
|
|
36
|
-
|
|
88
|
+
- Full command contract and workflows: [`SKILL.md`](./SKILL.md)
|
|
89
|
+
- Operator + package documentation: [`README_FOR_SHARING.md`](./README_FOR_SHARING.md)
|
|
37
90
|
|
|
38
|
-
|
|
39
|
-
- Full CLI skill contract, error semantics, and workflow references are documented in
|
|
40
|
-
[`SKILL.md`](./SKILL.md).
|
|
41
|
-
- Full operational and JSON contract documentation is in
|
|
42
|
-
[`README_FOR_SHARING.md`](./README_FOR_SHARING.md).
|
|
91
|
+
Node.js `>=18` required.
|
package/README_FOR_SHARING.md
CHANGED
|
@@ -56,6 +56,20 @@ Prerequisite: Node.js `>=18`.
|
|
|
56
56
|
- `pandora --output json doctor`
|
|
57
57
|
- `pandora --output table polls list --limit 10`
|
|
58
58
|
- `--output json` is supported for all commands except `launch`/`clone-bet`; those stream script output directly.
|
|
59
|
+
- Agent-native schema + MCP:
|
|
60
|
+
- `pandora --output json schema`
|
|
61
|
+
- `pandora mcp`
|
|
62
|
+
- MCP exposes one tool per command family entrypoint and reuses CLI JSON envelopes.
|
|
63
|
+
- Next Best Action recovery hints:
|
|
64
|
+
- JSON errors can include `error.recovery = { action, command, retryable }`.
|
|
65
|
+
- existing `error.code`, `error.message`, and `error.details` fields are preserved.
|
|
66
|
+
- Attach-only fork runtime:
|
|
67
|
+
- shared flags: `--fork`, `--fork-rpc-url <url>`, `--fork-chain-id <id>`.
|
|
68
|
+
- payloads include runtime marker (`runtime.mode = "fork" | "live"`).
|
|
69
|
+
- precedence: `--fork-rpc-url` > `FORK_RPC_URL` (when `--fork`) > normal runtime path.
|
|
70
|
+
- NDJSON streaming:
|
|
71
|
+
- `pandora stream prices|events [--indexer-url <url>] [--indexer-ws-url <url>] [--interval-ms <ms>] [--limit <n>]`.
|
|
72
|
+
- always emits NDJSON lines to stdout; WebSocket-first with polling fallback.
|
|
59
73
|
- Guided setup:
|
|
60
74
|
- `pandora setup`
|
|
61
75
|
- `pandora setup --check-usdc-code`
|
|
@@ -77,7 +91,9 @@ Prerequisite: Node.js `>=18`.
|
|
|
77
91
|
- `pandora markets list --with-odds` (include YES/NO percentage odds inline)
|
|
78
92
|
- `pandora markets list --active|--resolved|--expiring-soon` (lifecycle convenience filters)
|
|
79
93
|
- `pandora markets get --id <market-id> --id <market-id>` (batch lookup in one call)
|
|
80
|
-
|
|
94
|
+
- `pandora --output json scan` (single-pass market discovery payload for automation)
|
|
95
|
+
- `pandora stream prices` (reactive market ticks in NDJSON)
|
|
96
|
+
- `pandora stream events` (reactive event feed in NDJSON)
|
|
81
97
|
- Phase 2 trading helpers:
|
|
82
98
|
- `pandora quote --market-address <0x...> --side yes|no --amount-usdc <amount>`
|
|
83
99
|
- `pandora trade --dry-run --market-address <0x...> --side yes|no --amount-usdc <amount> [--max-amount-usdc <amount>] [--min-probability-pct <0-100>] [--max-probability-pct <0-100>]`
|
|
@@ -97,7 +113,45 @@ Prerequisite: Node.js `>=18`.
|
|
|
97
113
|
- `pandora analyze --market-address <0x...> --provider <name>`
|
|
98
114
|
- `pandora suggest --wallet <0x...> --risk low|medium|high --budget <amount>`
|
|
99
115
|
- `pandora resolve`
|
|
100
|
-
|
|
116
|
+
- `pandora lp add|remove|positions`
|
|
117
|
+
|
|
118
|
+
## Agent-native expansion details
|
|
119
|
+
|
|
120
|
+
### MCP server (`pandora mcp`)
|
|
121
|
+
- Runs MCP server over stdio with tool discovery + execution.
|
|
122
|
+
- `tools/list` includes JSON-capable command tools (for example `markets.list`, `trade`, `mirror.plan`, `polymarket.check`).
|
|
123
|
+
- `launch` and `clone-bet` are intentionally not exposed over MCP because they stream script output.
|
|
124
|
+
- MCP safety rails:
|
|
125
|
+
- mutating tools require explicit execute intent (`intent.execute=true`) for live execution.
|
|
126
|
+
- long-running modes are blocked in v1 (`watch`, `autopilot run`, `mirror sync run/start`).
|
|
127
|
+
|
|
128
|
+
### Next Best Action recovery hints
|
|
129
|
+
- Structured JSON errors may include:
|
|
130
|
+
- `error.recovery.action`
|
|
131
|
+
- `error.recovery.command`
|
|
132
|
+
- `error.recovery.retryable`
|
|
133
|
+
- This supports automatic recovery flows in agents while keeping backward compatibility for existing JSON parsers.
|
|
134
|
+
|
|
135
|
+
### Fork runtime support
|
|
136
|
+
- Supported families:
|
|
137
|
+
- `trade`
|
|
138
|
+
- `resolve`
|
|
139
|
+
- `lp`
|
|
140
|
+
- `polymarket check|approve|preflight|trade`
|
|
141
|
+
- `polymarket trade --execute` in fork mode is simulation-only unless `--polymarket-mock-url` is provided.
|
|
142
|
+
|
|
143
|
+
### NDJSON stream command
|
|
144
|
+
- `pandora stream prices|events` always emits NDJSON (ignores table rendering path for active stream output).
|
|
145
|
+
- Tick envelope includes:
|
|
146
|
+
- `type`
|
|
147
|
+
- `ts`
|
|
148
|
+
- `seq`
|
|
149
|
+
- `channel`
|
|
150
|
+
- `source.transport`
|
|
151
|
+
- `data`
|
|
152
|
+
- Transport behavior:
|
|
153
|
+
- primary: WebSocket (`--indexer-ws-url` or derived URL)
|
|
154
|
+
- fallback: polling (`source.transport = "polling"`)
|
|
101
155
|
|
|
102
156
|
Mirror advanced flags (for operator tuning):
|
|
103
157
|
- `--sync-interval-ms <ms>` on `mirror go` to control auto-sync tick cadence.
|
package/SKILL.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: pandora-cli-skills
|
|
3
3
|
summary: Canonical skill and operator guide for Pandora CLI including mirror, polymarket, resolve, and LP flows.
|
|
4
|
+
version: 1.1.35
|
|
4
5
|
---
|
|
5
6
|
|
|
6
7
|
# Pandora CLI & Skills
|
|
@@ -37,6 +38,8 @@ npm link
|
|
|
37
38
|
## CLI ergonomics
|
|
38
39
|
- Global output mode: `--output table|json` (default `table`)
|
|
39
40
|
- `--output json` is supported for all commands except `launch`/`clone-bet`; those stream script output directly.
|
|
41
|
+
- Agent schema command: `pandora --output json schema`
|
|
42
|
+
- MCP server mode: `pandora mcp`
|
|
40
43
|
- Guided setup command: `pandora setup`
|
|
41
44
|
- Phase 1 discovery command: `pandora scan`
|
|
42
45
|
- Phase 1 lifecycle filters: `pandora markets list --active|--resolved|--expiring-soon`
|
|
@@ -57,6 +60,13 @@ npm link
|
|
|
57
60
|
- `pandora suggest`
|
|
58
61
|
- `pandora resolve`
|
|
59
62
|
- `pandora lp add|remove|positions`
|
|
63
|
+
- `pandora stream prices|events`
|
|
64
|
+
- Fork runtime flags for transaction families:
|
|
65
|
+
- `--fork`
|
|
66
|
+
- `--fork-rpc-url <url>`
|
|
67
|
+
- `--fork-chain-id <id>`
|
|
68
|
+
- JSON errors can include additive recovery hints:
|
|
69
|
+
- `error.recovery = { action, command, retryable }`
|
|
60
70
|
- Doctor checks:
|
|
61
71
|
- env presence + format validation
|
|
62
72
|
- RPC reachability and chain id match
|
|
@@ -82,7 +92,7 @@ pandora [--output table|json] portfolio --wallet <address> [--chain-id <id>] [--
|
|
|
82
92
|
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]
|
|
83
93
|
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>] [--active|--resolved|--expiring-soon] [--expiring-hours <n>] [--expand]
|
|
84
94
|
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>]
|
|
85
|
-
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>]
|
|
95
|
+
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] [--fork] [--fork-rpc-url <url>] [--fork-chain-id <id>] [--chain-id <id>] [--rpc-url <url>] [--private-key <hex>] [--usdc <address>]
|
|
86
96
|
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]
|
|
87
97
|
pandora [--output table|json] export --wallet <address> --format csv|json [--chain-id <id>] [--year <yyyy>] [--from <unix>] [--to <unix>] [--out <path>]
|
|
88
98
|
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>]
|
|
@@ -93,8 +103,11 @@ pandora [--output table|json] webhook test [--webhook-url <url>] [--webhook-temp
|
|
|
93
103
|
pandora [--output table|json] leaderboard [--metric profit|volume|win-rate] [--chain-id <id>] [--limit <n>] [--min-trades <n>]
|
|
94
104
|
pandora [--output table|json] analyze --market-address <address> [--provider <name>] [--model <id>] [--max-cost-usd <n>] [--temperature <n>] [--timeout-ms <ms>]
|
|
95
105
|
pandora [--output table|json] suggest --wallet <address> --risk low|medium|high --budget <amount> [--count <n>] [--include-venues pandora,polymarket]
|
|
96
|
-
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>]
|
|
97
|
-
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>]
|
|
106
|
+
pandora [--output table|json] resolve --poll-address <address> --answer yes|no|invalid --reason <text> --dry-run|--execute [--fork] [--fork-rpc-url <url>] [--fork-chain-id <id>] [--chain-id <id>] [--rpc-url <url>] [--private-key <hex>]
|
|
107
|
+
pandora [--output table|json] lp add|remove|positions [--market-address <address>] [--wallet <address>] [--amount-usdc <n>] [--lp-tokens <n>] [--dry-run|--execute] [--fork] [--fork-rpc-url <url>] [--fork-chain-id <id>] [--chain-id <id>] [--rpc-url <url>] [--private-key <hex>] [--usdc <address>] [--deadline-seconds <n>] [--indexer-url <url>] [--timeout-ms <ms>]
|
|
108
|
+
pandora stream prices|events [--indexer-url <url>] [--indexer-ws-url <url>] [--timeout-ms <ms>] [--interval-ms <ms>] [--market-address <address>] [--chain-id <id>] [--limit <n>]
|
|
109
|
+
pandora [--output json] schema
|
|
110
|
+
pandora mcp
|
|
98
111
|
pandora launch [--dotenv-path <path>] [--skip-dotenv] [script args...]
|
|
99
112
|
pandora clone-bet [--dotenv-path <path>] [--skip-dotenv] [script args...]
|
|
100
113
|
```
|
|
@@ -125,10 +138,10 @@ sync status --pid-file <path>|--strategy-hash <hash>
|
|
|
125
138
|
Polymarket subcommand detail:
|
|
126
139
|
|
|
127
140
|
```text
|
|
128
|
-
check [--rpc-url <url>] [--private-key <hex>] [--funder <address>]
|
|
129
|
-
approve --dry-run|--execute [--rpc-url <url>] [--private-key <hex>] [--funder <address>]
|
|
130
|
-
preflight [--rpc-url <url>] [--private-key <hex>] [--funder <address>]
|
|
131
|
-
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>]
|
|
141
|
+
check [--fork] [--fork-rpc-url <url>] [--fork-chain-id <id>] [--rpc-url <url>] [--private-key <hex>] [--funder <address>]
|
|
142
|
+
approve --dry-run|--execute [--fork] [--fork-rpc-url <url>] [--fork-chain-id <id>] [--rpc-url <url>] [--private-key <hex>] [--funder <address>]
|
|
143
|
+
preflight [--fork] [--fork-rpc-url <url>] [--fork-chain-id <id>] [--rpc-url <url>] [--private-key <hex>] [--funder <address>]
|
|
144
|
+
trade --condition-id <id>|--slug <slug>|--token-id <id> --token yes|no --amount-usdc <n> --dry-run|--execute [--side buy|sell] [--polymarket-host <url>] [--polymarket-mock-url <url>] [--timeout-ms <ms>] [--fork] [--fork-rpc-url <url>] [--fork-chain-id <id>] [--rpc-url <url>] [--private-key <hex>] [--funder <address>]
|
|
132
145
|
```
|
|
133
146
|
|
|
134
147
|
## Read-only indexer commands
|
|
@@ -187,6 +200,7 @@ pandora --output json webhook test --webhook-url https://example.com/hook
|
|
|
187
200
|
pandora --output json leaderboard --metric profit --limit 20
|
|
188
201
|
pandora --output json analyze --market-address <0x...> --provider mock
|
|
189
202
|
pandora --output json suggest --wallet <0x...> --risk medium --budget 50 --include-venues pandora
|
|
203
|
+
pandora --output json schema
|
|
190
204
|
```
|
|
191
205
|
|
|
192
206
|
## Phase 1 JSON contracts
|
|
@@ -342,7 +356,53 @@ Common structured error codes for automation:
|
|
|
342
356
|
- `WEBHOOK_DELIVERY_FAILED`: webhook hard-fail when `--fail-on-webhook-error` is set.
|
|
343
357
|
|
|
344
358
|
Error envelope:
|
|
345
|
-
- `{ ok: false, error: { code, message, details
|
|
359
|
+
- `{ ok: false, error: { code, message, details?, recovery?: { action, command, retryable } } }`
|
|
360
|
+
|
|
361
|
+
## MCP server mode
|
|
362
|
+
- Start server:
|
|
363
|
+
- `pandora mcp`
|
|
364
|
+
- Transport:
|
|
365
|
+
- MCP stdio JSON-RPC.
|
|
366
|
+
- Tool model:
|
|
367
|
+
- one tool per command family entrypoint (for example `markets.list`, `trade`, `mirror.plan`, `polymarket.check`).
|
|
368
|
+
- Exclusions in v1:
|
|
369
|
+
- `launch`, `clone-bet` are intentionally not exposed because they stream interactive script output.
|
|
370
|
+
- Guardrails in v1:
|
|
371
|
+
- mutating tools require explicit execute intent (`intent.execute=true`) for live execution.
|
|
372
|
+
- long-running modes are blocked (`watch`, `autopilot run`, `mirror sync run|start`) with actionable structured errors.
|
|
373
|
+
|
|
374
|
+
## Next Best Action recovery hints
|
|
375
|
+
- JSON errors may include additive recovery guidance:
|
|
376
|
+
- `error.recovery.action`: short recovery label.
|
|
377
|
+
- `error.recovery.command`: copy-pasteable remediation command.
|
|
378
|
+
- `error.recovery.retryable`: whether retry is expected to succeed after recovery.
|
|
379
|
+
- `details.hints` is preserved for human operators.
|
|
380
|
+
|
|
381
|
+
## Fork runtime (attach-only)
|
|
382
|
+
- Shared flags:
|
|
383
|
+
- `--fork`
|
|
384
|
+
- `--fork-rpc-url <url>`
|
|
385
|
+
- `--fork-chain-id <id>`
|
|
386
|
+
- URL precedence in fork mode:
|
|
387
|
+
1. `--fork-rpc-url`
|
|
388
|
+
2. `FORK_RPC_URL` (when `--fork`)
|
|
389
|
+
3. command live RPC path
|
|
390
|
+
- Commands annotate runtime mode in payload:
|
|
391
|
+
- `runtime.mode = "fork" | "live"`.
|
|
392
|
+
- `polymarket trade` in fork mode:
|
|
393
|
+
- default is simulation-only.
|
|
394
|
+
- `--execute` requires `--polymarket-mock-url`.
|
|
395
|
+
|
|
396
|
+
## Stream command (NDJSON)
|
|
397
|
+
- Usage:
|
|
398
|
+
- `pandora stream prices|events [--indexer-url <url>] [--indexer-ws-url <url>] [--timeout-ms <ms>] [--interval-ms <ms>] [--market-address <address>] [--chain-id <id>] [--limit <n>]`
|
|
399
|
+
- Output:
|
|
400
|
+
- NDJSON only (one JSON object per line to stdout), regardless of table/json global mode.
|
|
401
|
+
- Tick envelope fields:
|
|
402
|
+
- `type`, `ts`, `seq`, `channel`, `source.transport`, `source.url`, `data`.
|
|
403
|
+
- Transport behavior:
|
|
404
|
+
- WebSocket-first when `--indexer-ws-url` is provided/derivable.
|
|
405
|
+
- polling fallback with `source.transport = "polling"`.
|
|
346
406
|
|
|
347
407
|
## Additional JSON response shapes
|
|
348
408
|
- `doctor`:
|
|
@@ -9,6 +9,7 @@ function createCliOutputService(options = {}) {
|
|
|
9
9
|
? options.defaultSchemaVersion.trim()
|
|
10
10
|
: '1.0.0';
|
|
11
11
|
const CliError = options.CliError;
|
|
12
|
+
const getRecoveryForError = typeof options.getRecoveryForError === 'function' ? options.getRecoveryForError : null;
|
|
12
13
|
|
|
13
14
|
if (typeof CliError !== 'function') {
|
|
14
15
|
throw new Error('createCliOutputService requires CliError class.');
|
|
@@ -43,16 +44,36 @@ function createCliOutputService(options = {}) {
|
|
|
43
44
|
if (error.details !== undefined) {
|
|
44
45
|
envelope.error.details = error.details;
|
|
45
46
|
}
|
|
47
|
+
if (getRecoveryForError) {
|
|
48
|
+
const recovery = getRecoveryForError({
|
|
49
|
+
code: error.code,
|
|
50
|
+
message: error.message,
|
|
51
|
+
details: error.details,
|
|
52
|
+
});
|
|
53
|
+
if (recovery) {
|
|
54
|
+
envelope.error.recovery = recovery;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
46
57
|
return envelope;
|
|
47
58
|
}
|
|
48
59
|
|
|
49
|
-
|
|
60
|
+
const fallback = {
|
|
50
61
|
ok: false,
|
|
51
62
|
error: {
|
|
52
63
|
code: 'UNEXPECTED_ERROR',
|
|
53
64
|
message: formatErrorValue(error && error.message ? error.message : error),
|
|
54
65
|
},
|
|
55
66
|
};
|
|
67
|
+
if (getRecoveryForError) {
|
|
68
|
+
const recovery = getRecoveryForError({
|
|
69
|
+
code: 'UNEXPECTED_ERROR',
|
|
70
|
+
message: fallback.error.message,
|
|
71
|
+
});
|
|
72
|
+
if (recovery) {
|
|
73
|
+
fallback.error.recovery = recovery;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return fallback;
|
|
56
77
|
}
|
|
57
78
|
|
|
58
79
|
function attachJsonMetadata(data) {
|
|
@@ -100,6 +121,9 @@ function createCliOutputService(options = {}) {
|
|
|
100
121
|
console.error(`Details: ${String(envelope.error.details)}`);
|
|
101
122
|
}
|
|
102
123
|
}
|
|
124
|
+
if (envelope.error.recovery && envelope.error.recovery.command) {
|
|
125
|
+
console.error(`Next: ${envelope.error.recovery.command}`);
|
|
126
|
+
}
|
|
103
127
|
}
|
|
104
128
|
|
|
105
129
|
process.exit(error instanceof CliError ? error.exitCode : 1);
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const { spawnSync } = require('child_process');
|
|
3
|
+
|
|
4
|
+
function coerceErrorMessage(value) {
|
|
5
|
+
if (typeof value === 'string') return value;
|
|
6
|
+
if (value && typeof value.message === 'string') return value.message;
|
|
7
|
+
try {
|
|
8
|
+
return JSON.stringify(value);
|
|
9
|
+
} catch {
|
|
10
|
+
return String(value);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function parseEnvelopeCandidate(text) {
|
|
15
|
+
const candidate = String(text || '').trim();
|
|
16
|
+
if (!candidate) return null;
|
|
17
|
+
try {
|
|
18
|
+
const parsed = JSON.parse(candidate);
|
|
19
|
+
if (parsed && typeof parsed === 'object' && typeof parsed.ok === 'boolean') {
|
|
20
|
+
return parsed;
|
|
21
|
+
}
|
|
22
|
+
} catch {
|
|
23
|
+
// continue with extraction path
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function extractLastJsonObject(text) {
|
|
29
|
+
const source = String(text || '');
|
|
30
|
+
if (!source) return null;
|
|
31
|
+
|
|
32
|
+
let depth = 0;
|
|
33
|
+
let start = -1;
|
|
34
|
+
let inString = false;
|
|
35
|
+
let escaped = false;
|
|
36
|
+
let last = null;
|
|
37
|
+
|
|
38
|
+
for (let i = 0; i < source.length; i += 1) {
|
|
39
|
+
const ch = source[i];
|
|
40
|
+
if (inString) {
|
|
41
|
+
if (escaped) {
|
|
42
|
+
escaped = false;
|
|
43
|
+
} else if (ch === '\\') {
|
|
44
|
+
escaped = true;
|
|
45
|
+
} else if (ch === '"') {
|
|
46
|
+
inString = false;
|
|
47
|
+
}
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (ch === '"') {
|
|
52
|
+
inString = true;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (ch === '{') {
|
|
57
|
+
if (depth === 0) start = i;
|
|
58
|
+
depth += 1;
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (ch === '}' && depth > 0) {
|
|
63
|
+
depth -= 1;
|
|
64
|
+
if (depth === 0 && start >= 0) {
|
|
65
|
+
last = source.slice(start, i + 1);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return parseEnvelopeCandidate(last);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function parseEnvelopeFromOutput(stdout, stderr) {
|
|
74
|
+
const candidates = [stdout, stderr, `${stdout || ''}\n${stderr || ''}`];
|
|
75
|
+
for (const candidate of candidates) {
|
|
76
|
+
const direct = parseEnvelopeCandidate(candidate);
|
|
77
|
+
if (direct) return direct;
|
|
78
|
+
const extracted = extractLastJsonObject(candidate);
|
|
79
|
+
if (extracted) return extracted;
|
|
80
|
+
}
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Build a child-process command executor for JSON-mode CLI invocations.
|
|
86
|
+
* @param {{cliPath?: string, defaultTimeoutMs?: number, env?: object}} [options]
|
|
87
|
+
* @returns {{executeJsonCommand: (commandArgs: string[], runtime?: {timeoutMs?: number, env?: object}) => {ok: boolean, envelope: object, exitCode: number, stdout: string, stderr: string}}}
|
|
88
|
+
*/
|
|
89
|
+
function createCommandExecutorService(options = {}) {
|
|
90
|
+
const cliPath =
|
|
91
|
+
typeof options.cliPath === 'string' && options.cliPath.trim()
|
|
92
|
+
? options.cliPath.trim()
|
|
93
|
+
: path.resolve(__dirname, '..', 'pandora.cjs');
|
|
94
|
+
const defaultTimeoutMs = Number.isFinite(options.defaultTimeoutMs) ? Math.max(1_000, Math.trunc(options.defaultTimeoutMs)) : 60_000;
|
|
95
|
+
const baseEnv = options.env && typeof options.env === 'object' ? options.env : process.env;
|
|
96
|
+
|
|
97
|
+
function executeJsonCommand(commandArgs, runtime = {}) {
|
|
98
|
+
const timeoutMs = Number.isFinite(runtime.timeoutMs)
|
|
99
|
+
? Math.max(1_000, Math.trunc(runtime.timeoutMs))
|
|
100
|
+
: defaultTimeoutMs;
|
|
101
|
+
const env = runtime.env && typeof runtime.env === 'object' ? runtime.env : baseEnv;
|
|
102
|
+
const argv = ['--output', 'json', ...commandArgs];
|
|
103
|
+
|
|
104
|
+
const result = spawnSync(process.execPath, [cliPath, ...argv], {
|
|
105
|
+
encoding: 'utf8',
|
|
106
|
+
env,
|
|
107
|
+
timeout: timeoutMs,
|
|
108
|
+
killSignal: 'SIGKILL',
|
|
109
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
if (result.error && result.error.code === 'ETIMEDOUT') {
|
|
113
|
+
return {
|
|
114
|
+
ok: false,
|
|
115
|
+
exitCode: 1,
|
|
116
|
+
stdout: result.stdout || '',
|
|
117
|
+
stderr: result.stderr || '',
|
|
118
|
+
envelope: {
|
|
119
|
+
ok: false,
|
|
120
|
+
error: {
|
|
121
|
+
code: 'COMMAND_TIMEOUT',
|
|
122
|
+
message: `Command timed out after ${timeoutMs}ms.`,
|
|
123
|
+
details: { commandArgs, timeoutMs },
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (result.error) {
|
|
130
|
+
return {
|
|
131
|
+
ok: false,
|
|
132
|
+
exitCode: typeof result.status === 'number' ? result.status : 1,
|
|
133
|
+
stdout: result.stdout || '',
|
|
134
|
+
stderr: result.stderr || '',
|
|
135
|
+
envelope: {
|
|
136
|
+
ok: false,
|
|
137
|
+
error: {
|
|
138
|
+
code: 'COMMAND_EXEC_FAILED',
|
|
139
|
+
message: result.error.message || 'CLI command execution failed.',
|
|
140
|
+
details: {
|
|
141
|
+
commandArgs,
|
|
142
|
+
code: result.error.code || null,
|
|
143
|
+
signal: result.signal || null,
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const envelope = parseEnvelopeFromOutput(result.stdout, result.stderr);
|
|
151
|
+
if (!envelope) {
|
|
152
|
+
return {
|
|
153
|
+
ok: false,
|
|
154
|
+
exitCode: typeof result.status === 'number' ? result.status : 1,
|
|
155
|
+
stdout: result.stdout || '',
|
|
156
|
+
stderr: result.stderr || '',
|
|
157
|
+
envelope: {
|
|
158
|
+
ok: false,
|
|
159
|
+
error: {
|
|
160
|
+
code: 'COMMAND_OUTPUT_PARSE_FAILED',
|
|
161
|
+
message: 'CLI command returned non-JSON output.',
|
|
162
|
+
details: {
|
|
163
|
+
commandArgs,
|
|
164
|
+
stdout: String(result.stdout || '').slice(0, 10_000),
|
|
165
|
+
stderr: String(result.stderr || '').slice(0, 10_000),
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
ok: envelope && envelope.ok === true,
|
|
174
|
+
exitCode: typeof result.status === 'number' ? result.status : 1,
|
|
175
|
+
stdout: result.stdout || '',
|
|
176
|
+
stderr: result.stderr || '',
|
|
177
|
+
envelope,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return {
|
|
182
|
+
executeJsonCommand,
|
|
183
|
+
coerceErrorMessage,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
module.exports = {
|
|
188
|
+
createCommandExecutorService,
|
|
189
|
+
};
|
|
@@ -37,6 +37,8 @@ function createCommandRouter(deps = {}) {
|
|
|
37
37
|
runSuggestCommand,
|
|
38
38
|
runResolveCommand,
|
|
39
39
|
runLpCommand,
|
|
40
|
+
runMcpCommand,
|
|
41
|
+
runStreamCommand,
|
|
40
42
|
runScriptCommand,
|
|
41
43
|
} = deps;
|
|
42
44
|
|
|
@@ -48,6 +50,45 @@ function createCommandRouter(deps = {}) {
|
|
|
48
50
|
throw new Error('createCommandRouter requires emitSuccess.');
|
|
49
51
|
}
|
|
50
52
|
|
|
53
|
+
function requireFn(name, fn) {
|
|
54
|
+
if (typeof fn !== 'function') {
|
|
55
|
+
throw new Error(`createCommandRouter requires ${name}.`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
requireFn('helpJsonPayload', helpJsonPayload);
|
|
60
|
+
requireFn('printHelpTable', printHelpTable);
|
|
61
|
+
requireFn('includesHelpFlag', includesHelpFlag);
|
|
62
|
+
requireFn('commandHelpPayload', commandHelpPayload);
|
|
63
|
+
requireFn('runInitEnv', runInitEnv);
|
|
64
|
+
requireFn('runDoctor', runDoctor);
|
|
65
|
+
requireFn('runSetup', runSetup);
|
|
66
|
+
requireFn('runMarketsCommand', runMarketsCommand);
|
|
67
|
+
requireFn('runScanCommand', runScanCommand);
|
|
68
|
+
requireFn('runQuoteCommand', runQuoteCommand);
|
|
69
|
+
requireFn('runTradeCommand', runTradeCommand);
|
|
70
|
+
requireFn('runPollsCommand', runPollsCommand);
|
|
71
|
+
requireFn('runEventsCommand', runEventsCommand);
|
|
72
|
+
requireFn('runPositionsCommand', runPositionsCommand);
|
|
73
|
+
requireFn('runPortfolioCommand', runPortfolioCommand);
|
|
74
|
+
requireFn('runWatchCommand', runWatchCommand);
|
|
75
|
+
requireFn('runHistoryCommand', runHistoryCommand);
|
|
76
|
+
requireFn('runExportCommand', runExportCommand);
|
|
77
|
+
requireFn('runArbitrageCommand', runArbitrageCommand);
|
|
78
|
+
requireFn('runAutopilotCommand', runAutopilotCommand);
|
|
79
|
+
requireFn('runMirrorCommand', runMirrorCommand);
|
|
80
|
+
requireFn('runPolymarketCommand', runPolymarketCommand);
|
|
81
|
+
requireFn('runWebhookCommand', runWebhookCommand);
|
|
82
|
+
requireFn('runLeaderboardCommand', runLeaderboardCommand);
|
|
83
|
+
requireFn('runAnalyzeCommand', runAnalyzeCommand);
|
|
84
|
+
requireFn('runSuggestCommand', runSuggestCommand);
|
|
85
|
+
requireFn('runResolveCommand', runResolveCommand);
|
|
86
|
+
requireFn('runLpCommand', runLpCommand);
|
|
87
|
+
requireFn('runMcpCommand', runMcpCommand);
|
|
88
|
+
requireFn('runStreamCommand', runStreamCommand);
|
|
89
|
+
requireFn('runSchemaCommand', deps.runSchemaCommand);
|
|
90
|
+
requireFn('runScriptCommand', runScriptCommand);
|
|
91
|
+
|
|
51
92
|
return async function dispatch(command, args, context) {
|
|
52
93
|
if (!command || command === 'help' || command === '--help' || command === '-h') {
|
|
53
94
|
if (context.outputMode === 'json') {
|
|
@@ -131,6 +172,16 @@ function createCommandRouter(deps = {}) {
|
|
|
131
172
|
suggest: async (handlerArgs, handlerContext) => runSuggestCommand(handlerArgs, handlerContext),
|
|
132
173
|
resolve: async (handlerArgs, handlerContext) => runResolveCommand(handlerArgs, handlerContext),
|
|
133
174
|
lp: async (handlerArgs, handlerContext) => runLpCommand(handlerArgs, handlerContext),
|
|
175
|
+
mcp: async (handlerArgs, handlerContext) => {
|
|
176
|
+
if (handlerContext.outputMode === 'json') {
|
|
177
|
+
throw new CliError(
|
|
178
|
+
'UNSUPPORTED_OUTPUT_MODE',
|
|
179
|
+
'--output json is not supported for mcp because MCP uses raw stdio transport.',
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
await runMcpCommand(handlerArgs, handlerContext);
|
|
183
|
+
},
|
|
184
|
+
stream: async (handlerArgs, handlerContext) => runStreamCommand(handlerArgs, handlerContext),
|
|
134
185
|
schema: async (handlerArgs, handlerContext) => deps.runSchemaCommand(handlerArgs, handlerContext),
|
|
135
186
|
launch: async (handlerArgs, handlerContext) => {
|
|
136
187
|
if (handlerContext.outputMode === 'json') {
|