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.
Files changed (45) hide show
  1. package/README.md +386 -0
  2. package/dist/_generated/commands.js +274 -0
  3. package/dist/_generated/help.js +190 -0
  4. package/dist/_generated/operations.js +1306 -0
  5. package/dist/bin.js +170 -0
  6. package/dist/runtime/auth.js +95 -0
  7. package/dist/runtime/envelope.js +52 -0
  8. package/dist/runtime/errors.js +186 -0
  9. package/dist/runtime/filters.js +153 -0
  10. package/dist/runtime/format.js +143 -0
  11. package/dist/runtime/http.js +65 -0
  12. package/dist/runtime/idempotency.js +18 -0
  13. package/dist/runtime/invoker.js +387 -0
  14. package/dist/runtime/io.js +16 -0
  15. package/dist/runtime/paginator.js +146 -0
  16. package/dist/runtime/trace.js +99 -0
  17. package/dist/runtime/tty.js +51 -0
  18. package/dist/runtime/verb_cmd.js +70 -0
  19. package/dist/runtime/verb_runner.js +152 -0
  20. package/dist/runtime/whoami_cache.js +109 -0
  21. package/dist/tools/api.js +81 -0
  22. package/dist/tools/completion.js +116 -0
  23. package/dist/tools/config.js +123 -0
  24. package/dist/tools/doctor.js +183 -0
  25. package/dist/tools/login.js +99 -0
  26. package/dist/tools/mcp.js +141 -0
  27. package/dist/tools/raw.js +96 -0
  28. package/dist/tools/schema.js +58 -0
  29. package/dist/tools/trace.js +54 -0
  30. package/dist/tools/whoami.js +132 -0
  31. package/dist/verbs/_spec.js +15 -0
  32. package/dist/verbs/bars_batch.js +66 -0
  33. package/dist/verbs/chart.js +110 -0
  34. package/dist/verbs/digest.js +342 -0
  35. package/dist/verbs/hot.js +29 -0
  36. package/dist/verbs/index.js +49 -0
  37. package/dist/verbs/lookup.js +72 -0
  38. package/dist/verbs/news.js +67 -0
  39. package/dist/verbs/quote.js +53 -0
  40. package/dist/verbs/research.js +44 -0
  41. package/dist/verbs/scan.js +42 -0
  42. package/dist/verbs/sentiment.js +46 -0
  43. package/dist/verbs/views.js +83 -0
  44. package/dist/version.js +5 -0
  45. 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
+ }