echopai 2.0.0
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 +386 -0
- package/dist/_generated/commands.js +274 -0
- package/dist/_generated/help.js +190 -0
- package/dist/_generated/operations.js +1306 -0
- package/dist/bin.js +170 -0
- package/dist/runtime/auth.js +95 -0
- package/dist/runtime/envelope.js +52 -0
- package/dist/runtime/errors.js +186 -0
- package/dist/runtime/filters.js +153 -0
- package/dist/runtime/format.js +143 -0
- package/dist/runtime/http.js +65 -0
- package/dist/runtime/idempotency.js +18 -0
- package/dist/runtime/invoker.js +387 -0
- package/dist/runtime/io.js +16 -0
- package/dist/runtime/paginator.js +146 -0
- package/dist/runtime/trace.js +99 -0
- package/dist/runtime/tty.js +51 -0
- package/dist/runtime/verb_cmd.js +70 -0
- package/dist/runtime/verb_runner.js +152 -0
- package/dist/runtime/whoami_cache.js +109 -0
- package/dist/tools/api.js +81 -0
- package/dist/tools/completion.js +116 -0
- package/dist/tools/config.js +123 -0
- package/dist/tools/doctor.js +183 -0
- package/dist/tools/login.js +99 -0
- package/dist/tools/mcp.js +141 -0
- package/dist/tools/raw.js +96 -0
- package/dist/tools/schema.js +58 -0
- package/dist/tools/trace.js +54 -0
- package/dist/tools/whoami.js +132 -0
- package/dist/verbs/_spec.js +15 -0
- package/dist/verbs/bars_batch.js +66 -0
- package/dist/verbs/chart.js +110 -0
- package/dist/verbs/digest.js +342 -0
- package/dist/verbs/hot.js +29 -0
- package/dist/verbs/index.js +49 -0
- package/dist/verbs/lookup.js +72 -0
- package/dist/verbs/news.js +67 -0
- package/dist/verbs/quote.js +53 -0
- package/dist/verbs/research.js +44 -0
- package/dist/verbs/scan.js +42 -0
- package/dist/verbs/sentiment.js +46 -0
- package/dist/verbs/views.js +83 -0
- package/dist/version.js +5 -0
- package/package.json +58 -0
package/README.md
ADDED
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
# echopai
|
|
2
|
+
|
|
3
|
+
> **v2.0.0**: npm package renamed from `@echopai/cli` → `echopai` to match
|
|
4
|
+
> the binary name. The scoped package has been unpublished — update your
|
|
5
|
+
> installer to `npm install -g echopai`.
|
|
6
|
+
|
|
7
|
+
Command-line interface for the EchoPai Open Platform. Programmatic access
|
|
8
|
+
to stock-market data, news, analyst views, sentiment indicators, signals,
|
|
9
|
+
and backtests over `https://api.echopai.com`.
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- **11 curated agent verbs** at the top level (`lookup`, `digest`, `quote`,
|
|
14
|
+
`views`, `research`, `news`, `sentiment`, `hot`, `chart`, `bars-batch`,
|
|
15
|
+
`scan`) with task-level defaults, agent-ready outputs, and product-priority
|
|
16
|
+
encoding (`views` is the PRIMARY research source; `news` is supplementary).
|
|
17
|
+
- **`digest`**: one HTTP call returns views + research-entity performance +
|
|
18
|
+
quote + sentiment + news for a single security; server-side composite
|
|
19
|
+
with `meta.partial_failures[]` tolerance; falls back to client-side
|
|
20
|
+
fan-out if `/v1/digest/{code}` is rolling.
|
|
21
|
+
- **Built-in MCP stdio server** (`echopai mcp serve`) exposing curated verbs
|
|
22
|
+
to Claude Desktop / Cursor / Claude Code; `tools/list` filtered to the
|
|
23
|
+
subset your token can actually call.
|
|
24
|
+
- **37 OpenAPI operations as raw mirror** under `echopai raw <noun> <verb>`,
|
|
25
|
+
auto-generated from `docs/api-contract/openapi.yaml`.
|
|
26
|
+
- **Capability introspection** — `echopai whoami` (token kind / scopes /
|
|
27
|
+
available verbs / rate limits) + `echopai doctor` (3-state diagnostics).
|
|
28
|
+
- **Write safety** — non-TTY writes refuse without `--yes`
|
|
29
|
+
(`confirmation_required`); `--dry-run` sends `X-Dry-Run: 1` for ops the
|
|
30
|
+
server advertises support for; auto `Idempotency-Key` UUIDv4 on every
|
|
31
|
+
`x-idempotency-required: true` endpoint.
|
|
32
|
+
- **Local trace** — `~/.echopai/trace.ndjson` (50 MB ring buffer);
|
|
33
|
+
`echopai trace tail` / `trace get <request_id>` for post-hoc inspection.
|
|
34
|
+
- **JSON-first I/O** — JSON envelope on stdout, JSON error on stderr,
|
|
35
|
+
three-state exit codes (0 / 1 / 2); six output formats
|
|
36
|
+
(`json` / `ndjson` / `table` / `csv` / `tsv` / `yaml`).
|
|
37
|
+
- **JMESPath filter** — global `--jq <expr>`, `--fields`, `--max-bytes`
|
|
38
|
+
for shaping responses without piping to `jq`.
|
|
39
|
+
- **Pagination** — `--all` for cursor/offset endpoints, bounded by
|
|
40
|
+
`--max-pages` / `--max-items`.
|
|
41
|
+
- **Multi-profile config** TOML at `~/.config/echopai/config.toml` (mode 0600).
|
|
42
|
+
- **Shell completion** for bash / zsh / fish.
|
|
43
|
+
|
|
44
|
+
## Requirements
|
|
45
|
+
|
|
46
|
+
Node.js 20 or later.
|
|
47
|
+
|
|
48
|
+
## Installation
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
npm install -g echopai
|
|
52
|
+
echopai --version
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Or invoke without installation:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
npx -y echopai market status
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Authentication
|
|
62
|
+
|
|
63
|
+
Credentials take the form `eps_live_<lookup>_<secret>` and are issued via the
|
|
64
|
+
EchoPai admin console.
|
|
65
|
+
|
|
66
|
+
Resolution order (highest precedence first):
|
|
67
|
+
|
|
68
|
+
1. `ECHOPAI_KEY` environment variable
|
|
69
|
+
2. `--profile <name>` flag (on subcommands that accept it)
|
|
70
|
+
3. Default profile in `~/.config/echopai/config.toml`
|
|
71
|
+
|
|
72
|
+
Persist a credential locally:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
echopai login --key eps_live_<lookup>_<secret>
|
|
76
|
+
echopai status
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Manage multiple profiles:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
echopai config profile add prod \
|
|
83
|
+
--key eps_live_<lookup>_<secret> \
|
|
84
|
+
--base-url https://api.echopai.com
|
|
85
|
+
echopai config profile switch prod
|
|
86
|
+
echopai config profile list
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## AI agent integration (MCP)
|
|
90
|
+
|
|
91
|
+
The CLI doubles as a Model Context Protocol (MCP) stdio server, exposing
|
|
92
|
+
curated agent verbs as MCP tools to Claude Desktop, Cursor, Claude Code, or
|
|
93
|
+
any MCP-compatible host. The server filters its `tools/list` to the subset
|
|
94
|
+
your token can actually call (intersection of curated-verb scopes with token
|
|
95
|
+
scopes).
|
|
96
|
+
|
|
97
|
+
Run as an MCP server:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
ECHOPAI_KEY=eps_live_... echopai mcp serve
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Claude Desktop / Cursor / Claude Code config (`~/.config/claude/...` or
|
|
104
|
+
equivalent):
|
|
105
|
+
|
|
106
|
+
```json
|
|
107
|
+
{
|
|
108
|
+
"mcpServers": {
|
|
109
|
+
"echopai": {
|
|
110
|
+
"command": "echopai",
|
|
111
|
+
"args": ["mcp", "serve"],
|
|
112
|
+
"env": {
|
|
113
|
+
"ECHOPAI_KEY": "eps_live_<lookup>_<secret>"
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Available MCP tools (subset shown to LLM depends on token scopes):
|
|
121
|
+
|
|
122
|
+
| Tool | Purpose | Backing API |
|
|
123
|
+
|---|---|---|
|
|
124
|
+
| `lookup` | Resolve Chinese name / code / pinyin → canonical_code | `semantic.find` |
|
|
125
|
+
| `digest` | One-shot research digest: 5 buckets in one call | `digest.get` + fan-out fallback |
|
|
126
|
+
| `quote` | Real-time quote for 1-200 codes | `quote` |
|
|
127
|
+
| `views` | **PRIMARY** research source (analyst views w/ entity attribution) | `views.recent` |
|
|
128
|
+
| `research` | Research-entity performance (hit rate / coverage) | `research.entity-performance-list` |
|
|
129
|
+
| `news` | **SUPPLEMENTARY** news / market briefs | `news.search` / `news.feed` |
|
|
130
|
+
| `sentiment` | Aggregate market sentiment | `sentiment.overview` |
|
|
131
|
+
| `hot` | Today's hot-stock leaderboard | `stocks.hot` |
|
|
132
|
+
| `chart` | Single-security K-line (daily / minute) | `bars.daily` / `bars.minute` |
|
|
133
|
+
| `bars_batch` | Batch K-line (≤100 codes × 1yr daily; ≤20 × 7d minute) | `bars.daily-batch` / `bars.minute-batch` |
|
|
134
|
+
| `scan` | Full-market quote snapshot (~5800 securities) | `quote.scan` |
|
|
135
|
+
|
|
136
|
+
The agent should prefer `views` over `news` when forming an investment
|
|
137
|
+
opinion (views carries research-entity attribution; news is breadth-only).
|
|
138
|
+
|
|
139
|
+
The MCP transport sends `X-Client-Channel: mcp` upstream so server-side
|
|
140
|
+
audit / per-channel rate limiting can distinguish MCP traffic from CLI.
|
|
141
|
+
|
|
142
|
+
## Common usage
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
# Market session state
|
|
146
|
+
echopai market status
|
|
147
|
+
|
|
148
|
+
# Real-time quotes (CSV codes)
|
|
149
|
+
echopai quote --codes "SSE:600519,SZSE:000001"
|
|
150
|
+
|
|
151
|
+
# News search with table output
|
|
152
|
+
echopai news search --query "AI" --since-hours 24 --output table
|
|
153
|
+
|
|
154
|
+
# Daily bars for a security over a date range
|
|
155
|
+
echopai chart --code SSE:600519 --from 2026-01-01 --to 2026-05-01
|
|
156
|
+
|
|
157
|
+
# Iterate all pages of analyst views (NDJSON one item per line)
|
|
158
|
+
echopai raw views feed --since-days 7 --all | jq '.title'
|
|
159
|
+
|
|
160
|
+
# One-shot research digest (server-side composite, 5 buckets, partial-failure tolerant)
|
|
161
|
+
echopai digest --code SSE:600519
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Output formats
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
echopai news search --query AI --output json # default; pretty in TTY
|
|
168
|
+
echopai news search --query AI --output ndjson # one JSON object per line
|
|
169
|
+
echopai news search --query AI --output table # ASCII table
|
|
170
|
+
echopai news search --query AI --output csv # RFC-4180
|
|
171
|
+
echopai news search --query AI --output tsv
|
|
172
|
+
echopai news search --query AI --output yaml
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## Pagination
|
|
176
|
+
|
|
177
|
+
Endpoints that declare `x-cli-pagination: cursor|offset` accept `--all`:
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
echopai news feed --all --since-hours 168 | jq '.title'
|
|
181
|
+
echopai news feed --all --max-pages 10 --max-items 500
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## WebSocket streaming
|
|
185
|
+
|
|
186
|
+
The CLI does **not** expose `/v1/ws/{news,views}` directly. Those upstream
|
|
187
|
+
handlers gate on user-JWT JSON-auth, which is incompatible with the CLI's
|
|
188
|
+
partner static-key bearer-subprotocol model. Partner SDKs that need WS
|
|
189
|
+
should target the endpoints directly (the OpenAPI ops are still public);
|
|
190
|
+
the CLI sticks to HTTP for now.
|
|
191
|
+
|
|
192
|
+
## Write safety and dry-run
|
|
193
|
+
|
|
194
|
+
Curated verbs are all read-only. The raw mirror surfaces a handful of
|
|
195
|
+
writes (e.g. `agent.session-start`); these are gated:
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
# In a non-TTY (script / CI / agent), writes refuse without --yes:
|
|
199
|
+
$ echo "" | echopai raw agent session-start --agent-id my-agent
|
|
200
|
+
{"error":{"code":"confirmation_required","message":"Operation agent.session-start is a write (side-effect=write) and stdout is not a TTY. Pass --yes to confirm."}}
|
|
201
|
+
|
|
202
|
+
# --dry-run sends X-Dry-Run:1 on ops the server advertises support for.
|
|
203
|
+
# Useful for previewing what a write would do without persisting.
|
|
204
|
+
echopai --yes --dry-run raw agent session-start --agent-id my-agent --budget-usd 1.0
|
|
205
|
+
|
|
206
|
+
# --dry-run on a write op that doesn't advertise support → refused
|
|
207
|
+
$ echopai --yes --dry-run raw agent session-end <session_id>
|
|
208
|
+
{"error":{"code":"dry_run_unsupported","message":"..."}}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## Channel enforcement
|
|
212
|
+
|
|
213
|
+
Every outbound HTTP request carries `X-Client-Channel: cli` (or `mcp`
|
|
214
|
+
when running under `echopai mcp serve`). Tokens may carry an
|
|
215
|
+
`allowed_clients` JWT claim restricting which channels the token may use,
|
|
216
|
+
plus `client_overrides` for per-channel `rate_limit_qps` tightening,
|
|
217
|
+
`extra_scopes_deny`, or `read_only` on non-GET methods. Configure these
|
|
218
|
+
on the credential when issuing through admin console.
|
|
219
|
+
|
|
220
|
+
## Idempotency
|
|
221
|
+
|
|
222
|
+
For endpoints that declare `x-idempotency-required: true` (for example,
|
|
223
|
+
`agent session-start`), the CLI generates a UUIDv4 `Idempotency-Key` and
|
|
224
|
+
echoes it to stderr so it can be reused on retry:
|
|
225
|
+
|
|
226
|
+
```text
|
|
227
|
+
$ echopai agent session-start
|
|
228
|
+
[idempotency-key] agent.session-start: 7d8f2c00-3b9a-4f1d-8e2c-9c0a1b2d3e4f
|
|
229
|
+
[idempotency-key] retry: pass --idempotency-key 7d8f2c00-... to dedupe.
|
|
230
|
+
{"data":{"session_id":"sess_..."}}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
Provide an explicit key with `--idempotency-key <uuid>` to dedupe at the
|
|
234
|
+
server.
|
|
235
|
+
|
|
236
|
+
## Exit codes
|
|
237
|
+
|
|
238
|
+
| Code | Meaning |
|
|
239
|
+
|------|---------|
|
|
240
|
+
| 0 | Success |
|
|
241
|
+
| 1 | User error (4xx, invalid arguments, authentication or scope failure) |
|
|
242
|
+
| 2 | Service error (5xx, network failure, internal error) |
|
|
243
|
+
|
|
244
|
+
Error envelope (single line on stderr):
|
|
245
|
+
|
|
246
|
+
```json
|
|
247
|
+
{"error":{"code":"scope_insufficient","message":"...","recovery_hint":"...","request_id":"req_...","http_status":403}}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
In a TTY the same envelope is rendered as a coloured, multi-line message
|
|
251
|
+
with the `recovery_hint` highlighted. Non-TTY environments (CI, pipelines)
|
|
252
|
+
always receive single-line JSON.
|
|
253
|
+
|
|
254
|
+
## Global flags
|
|
255
|
+
|
|
256
|
+
| Flag | Description |
|
|
257
|
+
|------|-------------|
|
|
258
|
+
| `--debug` | Print HTTP wire trace to stderr; the bearer token is redacted |
|
|
259
|
+
| `--raw` | Skip envelope unwrapping; emit the raw response body |
|
|
260
|
+
| `--jq <expr>` | JMESPath transform applied to `data` (renamed from `--query` in v2.0 to avoid clashing with subcommand `--query` body params, e.g. `news.search`) |
|
|
261
|
+
| `--fields <a,b,c>` | Keep only listed top-level fields per item |
|
|
262
|
+
| `--max-bytes <n>` | Truncate serialised envelope; sets `meta.truncated=true` |
|
|
263
|
+
| `--yes` | Confirm a write op in non-TTY mode (required for `sideEffect=write` ops) |
|
|
264
|
+
| `--dry-run` | Send `X-Dry-Run: 1` on writes where the server advertises support |
|
|
265
|
+
| `--output <format>` | Override per-command default output format |
|
|
266
|
+
|
|
267
|
+
Profile and credential overrides are exposed on the relevant subcommands
|
|
268
|
+
(`echopai status --profile <name>`, `echopai login --key ...`); they are
|
|
269
|
+
intentionally not declared as global options to avoid conflicting with
|
|
270
|
+
subcommand parameters of the same name.
|
|
271
|
+
|
|
272
|
+
## Schema introspection
|
|
273
|
+
|
|
274
|
+
```bash
|
|
275
|
+
echopai schema list # one line per command (cliKey, method, path, summary)
|
|
276
|
+
echopai schema get news.search
|
|
277
|
+
echopai schema export # full surface as NDJSON, including JSON Schemas
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
## Raw HTTP passthrough
|
|
281
|
+
|
|
282
|
+
For arbitrary requests (debugging, unmirrored endpoints):
|
|
283
|
+
|
|
284
|
+
```bash
|
|
285
|
+
echopai raw call GET /v1/some/path -q since_hours=24
|
|
286
|
+
echopai raw call POST /v1/some/path -d '{"key":"value"}'
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
`echopai api call` is a deprecated alias of `raw call`; it will be removed
|
|
290
|
+
in the next major.
|
|
291
|
+
|
|
292
|
+
## Shell completion
|
|
293
|
+
|
|
294
|
+
```bash
|
|
295
|
+
echopai completion bash > ~/.local/share/bash-completion/completions/echopai
|
|
296
|
+
echopai completion zsh > "${fpath[1]}/_echopai"
|
|
297
|
+
echopai completion fish > ~/.config/fish/completions/echopai.fish
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
## Architecture
|
|
301
|
+
|
|
302
|
+
```
|
|
303
|
+
docs/api-contract/openapi.yaml (single source of truth)
|
|
304
|
+
│
|
|
305
|
+
▼ scripts/codegen/generate_cli_v2.py
|
|
306
|
+
src/_generated/ (operations + command tree + help text)
|
|
307
|
+
│
|
|
308
|
+
▼
|
|
309
|
+
src/runtime/ (invoker, auth, http, envelope, errors, filters,
|
|
310
|
+
trace, paginator, verb_runner, verb_cmd, whoami_cache)
|
|
311
|
+
│
|
|
312
|
+
▼
|
|
313
|
+
src/tools/ (login, status, config, raw, schema, completion,
|
|
314
|
+
whoami, doctor, trace, mcp)
|
|
315
|
+
src/verbs/ (lookup, digest, quote, views, research, news,
|
|
316
|
+
sentiment, hot, chart, bars_batch, scan + _spec)
|
|
317
|
+
│
|
|
318
|
+
▼
|
|
319
|
+
src/bin.ts
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
New raw mirror endpoints are added by setting `x-cli-key` (and optionally
|
|
323
|
+
`x-cli-pagination`, `x-cli-output-default`) on the OpenAPI operation; the
|
|
324
|
+
command tree is regenerated automatically. Curated verbs (`src/verbs/`)
|
|
325
|
+
are hand-written task-level wrappers that can fan-out over multiple raw
|
|
326
|
+
ops; each declares a `VerbSpec` shared by CLI and MCP entries.
|
|
327
|
+
|
|
328
|
+
## Development
|
|
329
|
+
|
|
330
|
+
```bash
|
|
331
|
+
cd cli
|
|
332
|
+
npm install
|
|
333
|
+
npm run codegen # regenerate src/_generated/ from the OpenAPI spec
|
|
334
|
+
npm test # vitest: codegen, runtime, paginator, idempotency, tools
|
|
335
|
+
npx tsx src/bin.ts --help
|
|
336
|
+
|
|
337
|
+
# Run against the staging environment
|
|
338
|
+
ECHOPAI_BASE_URL=https://staging.echopai.com \
|
|
339
|
+
ECHOPAI_KEY=eps_live_<lookup>_<secret> \
|
|
340
|
+
npx tsx src/bin.ts news search --query AI --output table
|
|
341
|
+
|
|
342
|
+
# HTTP wire trace (bearer redacted)
|
|
343
|
+
npx tsx src/bin.ts market status --debug
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
## Releasing
|
|
347
|
+
|
|
348
|
+
Two paths are supported. Both are documented in `docs/PLAN_CLI_V2_REWRITE.md`.
|
|
349
|
+
|
|
350
|
+
### Tag-triggered release (recommended)
|
|
351
|
+
|
|
352
|
+
After a version bump is merged to `main`, push a tag named `cli-v<version>`:
|
|
353
|
+
|
|
354
|
+
```bash
|
|
355
|
+
git tag cli-v2.0.0
|
|
356
|
+
git push origin cli-v2.0.0
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
`.github/workflows/release-cli.yml` builds, tests, runs `npm publish
|
|
360
|
+
--access public --provenance`, and verifies the new version on the registry.
|
|
361
|
+
The workflow requires a repository secret named `NPM_ACCESS_TOKEN` (an npm
|
|
362
|
+
granular access token with the *Bypass 2FA* flag enabled).
|
|
363
|
+
|
|
364
|
+
### Local script
|
|
365
|
+
|
|
366
|
+
For ad-hoc releases without GitHub Actions:
|
|
367
|
+
|
|
368
|
+
```bash
|
|
369
|
+
bash scripts/publish-cli.sh # publish the current cli/package.json version
|
|
370
|
+
bash scripts/publish-cli.sh --dry-run # inspect the tarball without publishing
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
The script reads `NPM_ACCESS_TOKEN` from `server/.env`, writes it to a
|
|
374
|
+
temporary `.npmrc` (mode 0600), invokes `npm publish`, then deletes the
|
|
375
|
+
file via a shell `trap` (the token never appears on the command line).
|
|
376
|
+
|
|
377
|
+
## Links
|
|
378
|
+
|
|
379
|
+
- Platform overview: `https://echopai.com`
|
|
380
|
+
- Partner documentation: `docs/partner-api/`
|
|
381
|
+
- Source: `https://github.com/evanzhangx/EchoPulse/tree/main/cli`
|
|
382
|
+
- Issues: `https://github.com/evanzhangx/EchoPulse/issues`
|
|
383
|
+
|
|
384
|
+
## License
|
|
385
|
+
|
|
386
|
+
MIT
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AUTO-GENERATED — DO NOT EDIT BY HAND.
|
|
3
|
+
*
|
|
4
|
+
* Source: docs/api-contract/openapi.yaml
|
|
5
|
+
* Generator: scripts/codegen/generate_cli_v2.py
|
|
6
|
+
*/
|
|
7
|
+
import { Option } from "commander";
|
|
8
|
+
import { OPERATIONS } from "./operations.js";
|
|
9
|
+
/**
|
|
10
|
+
* commander v12 把 --since-hours 自动 camelCase 成 sinceHours,但 OpenAPI 这
|
|
11
|
+
* 边期待 snake_case (since_hours)。一次性 camelCase → snake_case 反转。
|
|
12
|
+
*/
|
|
13
|
+
function camelToSnake(s) {
|
|
14
|
+
return s.replace(/[A-Z]/g, (m) => "_" + m.toLowerCase());
|
|
15
|
+
}
|
|
16
|
+
function attachOperation(cmd, op, dispatch) {
|
|
17
|
+
cmd.description(op.summary || op.description);
|
|
18
|
+
// positional args
|
|
19
|
+
for (const pname of op.positional) {
|
|
20
|
+
const required = (op.inputSchema.required || []).includes(pname);
|
|
21
|
+
cmd.argument(required ? `<${pname}>` : `[${pname}]`, String(op.inputSchema.properties[pname]?.description ?? ""));
|
|
22
|
+
}
|
|
23
|
+
// remaining params → --flag (dashes for ergonomics; reverse camelCase later)
|
|
24
|
+
for (const [pname, pschema] of Object.entries(op.inputSchema.properties)) {
|
|
25
|
+
if (op.positional.includes(pname))
|
|
26
|
+
continue;
|
|
27
|
+
const required = (op.inputSchema.required || []).includes(pname);
|
|
28
|
+
const flagName = `--${pname.replace(/_/g, "-")} <value>`;
|
|
29
|
+
const desc = String(pschema?.description ?? "");
|
|
30
|
+
const opt = new Option(flagName, desc);
|
|
31
|
+
if (required)
|
|
32
|
+
opt.makeOptionMandatory(true);
|
|
33
|
+
cmd.addOption(opt);
|
|
34
|
+
}
|
|
35
|
+
cmd.addOption(new Option("--output <format>", "Output format: json|ndjson|table|csv|tsv|yaml").choices(["json", "ndjson", "table", "csv", "tsv", "yaml"]).default(op.outputDefault));
|
|
36
|
+
if (op.pagination !== 'none') {
|
|
37
|
+
cmd.addOption(new Option("--all", "Fetch all pages and stream NDJSON"));
|
|
38
|
+
cmd.addOption(new Option("--max-pages <n>", "Stop after N pages (default 1000)"));
|
|
39
|
+
cmd.addOption(new Option("--max-items <n>", "Stop after N items (default 100000)"));
|
|
40
|
+
}
|
|
41
|
+
if (op.idempotencyRequired) {
|
|
42
|
+
cmd.addOption(new Option("--idempotency-key <uuid>", "Override auto-generated Idempotency-Key (debug / replay)"));
|
|
43
|
+
}
|
|
44
|
+
// WS stream options removed (--reconnect / --max-frames). CLI no longer
|
|
45
|
+
// exposes /v1/ws/{news,views} as commands; codegen now drops x-cli-key
|
|
46
|
+
// for those ops so `op.stream` is always false here.
|
|
47
|
+
cmd.action(async (...rawArgs) => {
|
|
48
|
+
const opts = cmd.opts();
|
|
49
|
+
// reverse commander's camelCase: sinceHours → since_hours
|
|
50
|
+
const args = {};
|
|
51
|
+
for (const [k, v] of Object.entries(opts)) {
|
|
52
|
+
args[camelToSnake(k)] = v;
|
|
53
|
+
}
|
|
54
|
+
op.positional.forEach((pname, i) => {
|
|
55
|
+
if (rawArgs[i] !== undefined)
|
|
56
|
+
args[pname] = rawArgs[i];
|
|
57
|
+
});
|
|
58
|
+
await dispatch(op, args);
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
export function buildCommandTree(program, dispatch) {
|
|
62
|
+
{
|
|
63
|
+
const noun = program.command("agent");
|
|
64
|
+
noun.description("agent commands");
|
|
65
|
+
{
|
|
66
|
+
const cmd = noun.command("session-end");
|
|
67
|
+
attachOperation(cmd, OPERATIONS["agent.session-end"], dispatch);
|
|
68
|
+
}
|
|
69
|
+
{
|
|
70
|
+
const cmd = noun.command("session-start");
|
|
71
|
+
attachOperation(cmd, OPERATIONS["agent.session-start"], dispatch);
|
|
72
|
+
}
|
|
73
|
+
{
|
|
74
|
+
const cmd = noun.command("session-usage");
|
|
75
|
+
attachOperation(cmd, OPERATIONS["agent.session-usage"], dispatch);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
{
|
|
79
|
+
const noun = program.command("auth");
|
|
80
|
+
noun.description("auth commands");
|
|
81
|
+
{
|
|
82
|
+
const cmd = noun.command("whoami");
|
|
83
|
+
attachOperation(cmd, OPERATIONS["auth.whoami"], dispatch);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
{
|
|
87
|
+
const noun = program.command("backtest");
|
|
88
|
+
noun.description("backtest commands");
|
|
89
|
+
{
|
|
90
|
+
const cmd = noun.command("excess-distribution");
|
|
91
|
+
attachOperation(cmd, OPERATIONS["backtest.excess-distribution"], dispatch);
|
|
92
|
+
}
|
|
93
|
+
{
|
|
94
|
+
const cmd = noun.command("rolling-win-rate");
|
|
95
|
+
attachOperation(cmd, OPERATIONS["backtest.rolling-win-rate"], dispatch);
|
|
96
|
+
}
|
|
97
|
+
{
|
|
98
|
+
const cmd = noun.command("summary");
|
|
99
|
+
attachOperation(cmd, OPERATIONS["backtest.summary"], dispatch);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
{
|
|
103
|
+
const noun = program.command("bars");
|
|
104
|
+
noun.description("bars commands");
|
|
105
|
+
{
|
|
106
|
+
const cmd = noun.command("daily");
|
|
107
|
+
attachOperation(cmd, OPERATIONS["bars.daily"], dispatch);
|
|
108
|
+
}
|
|
109
|
+
{
|
|
110
|
+
const cmd = noun.command("daily-batch");
|
|
111
|
+
attachOperation(cmd, OPERATIONS["bars.daily-batch"], dispatch);
|
|
112
|
+
}
|
|
113
|
+
{
|
|
114
|
+
const cmd = noun.command("minute");
|
|
115
|
+
attachOperation(cmd, OPERATIONS["bars.minute"], dispatch);
|
|
116
|
+
}
|
|
117
|
+
{
|
|
118
|
+
const cmd = noun.command("minute-batch");
|
|
119
|
+
attachOperation(cmd, OPERATIONS["bars.minute-batch"], dispatch);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
{
|
|
123
|
+
const noun = program.command("digest");
|
|
124
|
+
noun.description("digest commands");
|
|
125
|
+
{
|
|
126
|
+
const cmd = noun.command("get");
|
|
127
|
+
attachOperation(cmd, OPERATIONS["digest.get"], dispatch);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
{
|
|
131
|
+
const noun = program.command("market");
|
|
132
|
+
noun.description("market commands");
|
|
133
|
+
{
|
|
134
|
+
const cmd = noun.command("status");
|
|
135
|
+
attachOperation(cmd, OPERATIONS["market.status"], dispatch);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
{
|
|
139
|
+
const noun = program.command("news");
|
|
140
|
+
noun.description("news commands");
|
|
141
|
+
{
|
|
142
|
+
const cmd = noun.command("feed");
|
|
143
|
+
attachOperation(cmd, OPERATIONS["news.feed"], dispatch);
|
|
144
|
+
}
|
|
145
|
+
{
|
|
146
|
+
const cmd = noun.command("get");
|
|
147
|
+
attachOperation(cmd, OPERATIONS["news.get"], dispatch);
|
|
148
|
+
}
|
|
149
|
+
{
|
|
150
|
+
const cmd = noun.command("list");
|
|
151
|
+
attachOperation(cmd, OPERATIONS["news.list"], dispatch);
|
|
152
|
+
}
|
|
153
|
+
{
|
|
154
|
+
const cmd = noun.command("search");
|
|
155
|
+
attachOperation(cmd, OPERATIONS["news.search"], dispatch);
|
|
156
|
+
}
|
|
157
|
+
{
|
|
158
|
+
const cmd = noun.command("sources");
|
|
159
|
+
attachOperation(cmd, OPERATIONS["news.sources"], dispatch);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
{
|
|
163
|
+
const noun = program.command("payment");
|
|
164
|
+
noun.description("payment commands");
|
|
165
|
+
{
|
|
166
|
+
const cmd = noun.command("plans");
|
|
167
|
+
attachOperation(cmd, OPERATIONS["payment.plans"], dispatch);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
{
|
|
171
|
+
const noun = program.command("quote");
|
|
172
|
+
noun.description("quote commands");
|
|
173
|
+
{
|
|
174
|
+
const cmd = noun.command("quote");
|
|
175
|
+
attachOperation(cmd, OPERATIONS["quote"], dispatch);
|
|
176
|
+
}
|
|
177
|
+
{
|
|
178
|
+
const cmd = noun.command("scan");
|
|
179
|
+
attachOperation(cmd, OPERATIONS["quote.scan"], dispatch);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
{
|
|
183
|
+
const noun = program.command("research");
|
|
184
|
+
noun.description("research commands");
|
|
185
|
+
{
|
|
186
|
+
const cmd = noun.command("entity-performance");
|
|
187
|
+
attachOperation(cmd, OPERATIONS["research.entity-performance"], dispatch);
|
|
188
|
+
}
|
|
189
|
+
{
|
|
190
|
+
const cmd = noun.command("entity-performance-list");
|
|
191
|
+
attachOperation(cmd, OPERATIONS["research.entity-performance-list"], dispatch);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
{
|
|
195
|
+
const noun = program.command("semantic");
|
|
196
|
+
noun.description("semantic commands");
|
|
197
|
+
{
|
|
198
|
+
const cmd = noun.command("find");
|
|
199
|
+
attachOperation(cmd, OPERATIONS["semantic.find"], dispatch);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
{
|
|
203
|
+
const noun = program.command("sentiment");
|
|
204
|
+
noun.description("sentiment commands");
|
|
205
|
+
{
|
|
206
|
+
const cmd = noun.command("breadth");
|
|
207
|
+
attachOperation(cmd, OPERATIONS["sentiment.breadth"], dispatch);
|
|
208
|
+
}
|
|
209
|
+
{
|
|
210
|
+
const cmd = noun.command("overview");
|
|
211
|
+
attachOperation(cmd, OPERATIONS["sentiment.overview"], dispatch);
|
|
212
|
+
}
|
|
213
|
+
{
|
|
214
|
+
const cmd = noun.command("pct-distribution");
|
|
215
|
+
attachOperation(cmd, OPERATIONS["sentiment.pct-distribution"], dispatch);
|
|
216
|
+
}
|
|
217
|
+
{
|
|
218
|
+
const cmd = noun.command("turnover");
|
|
219
|
+
attachOperation(cmd, OPERATIONS["sentiment.turnover"], dispatch);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
{
|
|
223
|
+
const noun = program.command("signals");
|
|
224
|
+
noun.description("signals commands");
|
|
225
|
+
{
|
|
226
|
+
const cmd = noun.command("outcome");
|
|
227
|
+
attachOperation(cmd, OPERATIONS["signals.outcome"], dispatch);
|
|
228
|
+
}
|
|
229
|
+
{
|
|
230
|
+
const cmd = noun.command("outcomes");
|
|
231
|
+
attachOperation(cmd, OPERATIONS["signals.outcomes"], dispatch);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
{
|
|
235
|
+
const noun = program.command("squawk");
|
|
236
|
+
noun.description("squawk commands");
|
|
237
|
+
{
|
|
238
|
+
const cmd = noun.command("audio");
|
|
239
|
+
attachOperation(cmd, OPERATIONS["squawk.audio"], dispatch);
|
|
240
|
+
}
|
|
241
|
+
{
|
|
242
|
+
const cmd = noun.command("waveform");
|
|
243
|
+
attachOperation(cmd, OPERATIONS["squawk.waveform"], dispatch);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
{
|
|
247
|
+
const noun = program.command("stocks");
|
|
248
|
+
noun.description("stocks commands");
|
|
249
|
+
{
|
|
250
|
+
const cmd = noun.command("hot");
|
|
251
|
+
attachOperation(cmd, OPERATIONS["stocks.hot"], dispatch);
|
|
252
|
+
}
|
|
253
|
+
{
|
|
254
|
+
const cmd = noun.command("social-hot");
|
|
255
|
+
attachOperation(cmd, OPERATIONS["stocks.social-hot"], dispatch);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
{
|
|
259
|
+
const noun = program.command("views");
|
|
260
|
+
noun.description("views commands");
|
|
261
|
+
{
|
|
262
|
+
const cmd = noun.command("feed");
|
|
263
|
+
attachOperation(cmd, OPERATIONS["views.feed"], dispatch);
|
|
264
|
+
}
|
|
265
|
+
{
|
|
266
|
+
const cmd = noun.command("get");
|
|
267
|
+
attachOperation(cmd, OPERATIONS["views.get"], dispatch);
|
|
268
|
+
}
|
|
269
|
+
{
|
|
270
|
+
const cmd = noun.command("recent");
|
|
271
|
+
attachOperation(cmd, OPERATIONS["views.recent"], dispatch);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|