@kashdao/cli 0.1.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 (85) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/CONTRIBUTING.md +159 -0
  3. package/LICENSE +21 -0
  4. package/README.md +868 -0
  5. package/SECURITY.md +110 -0
  6. package/dist/account-AVFLEM5D.js +90 -0
  7. package/dist/account-AVFLEM5D.js.map +1 -0
  8. package/dist/auth-GUJCVKTD.js +239 -0
  9. package/dist/auth-GUJCVKTD.js.map +1 -0
  10. package/dist/chunk-BN2CUM42.js +9 -0
  11. package/dist/chunk-BN2CUM42.js.map +1 -0
  12. package/dist/chunk-BRK7KJ4O.js +154 -0
  13. package/dist/chunk-BRK7KJ4O.js.map +1 -0
  14. package/dist/chunk-KMBMQIZ7.js +393 -0
  15. package/dist/chunk-KMBMQIZ7.js.map +1 -0
  16. package/dist/chunk-LBIRQHX5.js +133 -0
  17. package/dist/chunk-LBIRQHX5.js.map +1 -0
  18. package/dist/chunk-MIXOZU2S.js +366 -0
  19. package/dist/chunk-MIXOZU2S.js.map +1 -0
  20. package/dist/chunk-QJMF73M5.js +129 -0
  21. package/dist/chunk-QJMF73M5.js.map +1 -0
  22. package/dist/chunk-UZNSYATZ.js +680 -0
  23. package/dist/chunk-UZNSYATZ.js.map +1 -0
  24. package/dist/chunk-VIADBYFY.js +94 -0
  25. package/dist/chunk-VIADBYFY.js.map +1 -0
  26. package/dist/chunk-YHCG2SUC.js +159 -0
  27. package/dist/chunk-YHCG2SUC.js.map +1 -0
  28. package/dist/chunk-YJX3JJ4M.js +174 -0
  29. package/dist/chunk-YJX3JJ4M.js.map +1 -0
  30. package/dist/client-IOM55ZCS.js +14 -0
  31. package/dist/client-IOM55ZCS.js.map +1 -0
  32. package/dist/completion-ZZGX5BQC.js +100 -0
  33. package/dist/completion-ZZGX5BQC.js.map +1 -0
  34. package/dist/config-XJL5TSYK.js +690 -0
  35. package/dist/config-XJL5TSYK.js.map +1 -0
  36. package/dist/config-store-3ZSYGXMQ.js +44 -0
  37. package/dist/config-store-3ZSYGXMQ.js.map +1 -0
  38. package/dist/docs-MDKKSJDC.js +102 -0
  39. package/dist/docs-MDKKSJDC.js.map +1 -0
  40. package/dist/eoa-IQ72EIHR.js +551 -0
  41. package/dist/eoa-IQ72EIHR.js.map +1 -0
  42. package/dist/errors-WMZIEGQI.js +18 -0
  43. package/dist/errors-WMZIEGQI.js.map +1 -0
  44. package/dist/explain-H4EH3KH3.js +165 -0
  45. package/dist/explain-H4EH3KH3.js.map +1 -0
  46. package/dist/global-options-XOLJUPTT.js +18 -0
  47. package/dist/global-options-XOLJUPTT.js.map +1 -0
  48. package/dist/health-VZEIII74.js +98 -0
  49. package/dist/health-VZEIII74.js.map +1 -0
  50. package/dist/help-footer-GTANVDNP.js +43 -0
  51. package/dist/help-footer-GTANVDNP.js.map +1 -0
  52. package/dist/index.d.ts +2 -0
  53. package/dist/index.js +251 -0
  54. package/dist/index.js.map +1 -0
  55. package/dist/intro-PQQTYWR7.js +41 -0
  56. package/dist/intro-PQQTYWR7.js.map +1 -0
  57. package/dist/markets-C37JZDPE.js +295 -0
  58. package/dist/markets-C37JZDPE.js.map +1 -0
  59. package/dist/output-VCBZ3FM7.js +22 -0
  60. package/dist/output-VCBZ3FM7.js.map +1 -0
  61. package/dist/portfolio-2AEPIJIG.js +115 -0
  62. package/dist/portfolio-2AEPIJIG.js.map +1 -0
  63. package/dist/protocol-XEQXF2GW.js +1572 -0
  64. package/dist/protocol-XEQXF2GW.js.map +1 -0
  65. package/dist/quote-I4DVJ7ZB.js +144 -0
  66. package/dist/quote-I4DVJ7ZB.js.map +1 -0
  67. package/dist/schema-PIFQ65TS.js +410 -0
  68. package/dist/schema-PIFQ65TS.js.map +1 -0
  69. package/dist/setup-USZ6IODD.js +260 -0
  70. package/dist/setup-USZ6IODD.js.map +1 -0
  71. package/dist/stdin-YW2CEQXU.js +28 -0
  72. package/dist/stdin-YW2CEQXU.js.map +1 -0
  73. package/dist/trace-IZBYTUFO.js +103 -0
  74. package/dist/trace-IZBYTUFO.js.map +1 -0
  75. package/dist/trade-IIZSEXEI.js +586 -0
  76. package/dist/trade-IIZSEXEI.js.map +1 -0
  77. package/dist/version-JDK3PEP5.js +117 -0
  78. package/dist/version-JDK3PEP5.js.map +1 -0
  79. package/dist/version-check-TDCION37.js +138 -0
  80. package/dist/version-check-TDCION37.js.map +1 -0
  81. package/dist/webhooks-PTVRKICZ.js +680 -0
  82. package/dist/webhooks-PTVRKICZ.js.map +1 -0
  83. package/dist/with-retry-4FIZG3A7.js +223 -0
  84. package/dist/with-retry-4FIZG3A7.js.map +1 -0
  85. package/package.json +99 -0
package/README.md ADDED
@@ -0,0 +1,868 @@
1
+ # `@kashdao/cli`
2
+
3
+ Official command-line interface for the [Kash](https://kash.bot) prediction-market protocol.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@kashdao/cli.svg)](https://www.npmjs.com/package/@kashdao/cli)
6
+ [![types](https://img.shields.io/npm/types/@kashdao/cli.svg)](https://www.npmjs.com/package/@kashdao/cli)
7
+ [![license](https://img.shields.io/npm/l/@kashdao/cli.svg)](./LICENSE)
8
+ [![Node 22+](https://img.shields.io/node/v/@kashdao/cli.svg)](https://nodejs.org)
9
+ [![Homebrew](https://img.shields.io/badge/homebrew-kashdao%2Ftap%2Fkash-orange)](https://github.com/KashDAO/homebrew-tap)
10
+
11
+ Single binary, both modes — **both non-custodial**; user funds always
12
+ live in Privy-managed MPC smart accounts the user controls. The split
13
+ is about who orchestrates execution:
14
+
15
+ - **Kash-orchestrated** (default) — wraps [`@kashdao/sdk`](https://www.npmjs.com/package/@kashdao/sdk),
16
+ API-key auth, hits the public REST API. The API key is a scoped,
17
+ revocable delegation the user issues against their own Privy-managed
18
+ smart account; the user retains full custody at all times.
19
+ - **Self-orchestrated** (`kash protocol …`) — wraps
20
+ [`@kashdao/protocol-sdk`](https://www.npmjs.com/package/@kashdao/protocol-sdk),
21
+ signer + RPC + bundler, reads/writes on-chain. Zero Kash backend
22
+ dependency.
23
+
24
+ On both paths Kash never holds funds, never moves funds, never holds
25
+ keys, and never signs anything. See
26
+ [SECURITY.md § Non-custodial design](./SECURITY.md#non-custodial-design)
27
+ for the full statement.
28
+
29
+ The two SDKs are fully decoupled at the npm-package level (so API-only
30
+ consumers don't pay the viem cost), but the CLI integrates both behind
31
+ clearly-separated namespaces. The protocol-sdk loads lazily on the
32
+ first `kash protocol …` invocation — `kash --version` and the entire
33
+ Kash-orchestrated surface keep their fast cold start.
34
+
35
+ ```sh
36
+ npm install -g @kashdao/cli
37
+ kash auth set-key kash_live_…
38
+ kash markets list --status ACTIVE
39
+ kash trade buy <market-id> --outcome 0 --amount 10 --wait
40
+ ```
41
+
42
+ - **Two audiences, equally first-class.** Humans get colored tables, spinners,
43
+ and tab completion; AI agents get `--json --quiet`, structured errors with
44
+ machine-readable recovery actions, and full command-tree introspection via
45
+ `kash docs --json`.
46
+ - **Stable JSON contracts.** Every shape an agent or script consumes is
47
+ pinned to a Zod schema and exposed via `kash schema --json`.
48
+ - **Multi-profile.** AWS-CLI-style profile system for juggling
49
+ test/staging/prod keys.
50
+ - **Zero-runtime config.** Drop in a `kash_*` API key and go — no OAuth,
51
+ no SSO, no browser flow.
52
+
53
+ ---
54
+
55
+ ## Contents
56
+
57
+ - [Install](#install) · [Quickstart](#quickstart) · [Authentication](#authentication)
58
+ - [Commands](#commands) · [Multi-profile workflow](#multi-profile-workflow)
59
+ - [AI-agent / scripting mode](#ai-agent-scripting-mode) · [Webhook signing](#webhook-signing)
60
+ - [Configuration reference](#configuration-reference) · [Operational flags](#operational-flags)
61
+ - [Stability promise](#stability-promise) · [Troubleshooting](#troubleshooting)
62
+ - [Examples](#examples) · [Development](#development) · [License](#license)
63
+
64
+ ---
65
+
66
+ > 🧪 **Staging release.** Production endpoints (`api.kash.bot`) are
67
+ > not yet live. Today only `kash_test_*` keys work — the CLI
68
+ > auto-routes them to staging (`api-staging.kash.bot`). To request a
69
+ > staging key, email [`engineering@kash.bot`](mailto:engineering@kash.bot)
70
+ > with your intended use case. Self-service key issuance, production
71
+ > endpoints, and the Homebrew tap all land with the production launch.
72
+
73
+ ## Install
74
+
75
+ Pick whichever installer fits your environment:
76
+
77
+ ```sh
78
+ # 1. One-line installer (POSIX shell — checks Node version,
79
+ # picks pnpm/yarn/npm automatically, idempotent on re-run).
80
+ curl -fsSL https://raw.githubusercontent.com/KashDAO/cli/main/scripts/install.sh | sh
81
+
82
+ # 2. npm / pnpm / yarn directly:
83
+ npm install -g @kashdao/cli
84
+ pnpm add -g @kashdao/cli
85
+ yarn global add @kashdao/cli
86
+
87
+ # 3. Zero-install (one-shot via npx — useful for CI smoke checks):
88
+ npx -y @kashdao/cli@latest --version
89
+ npx -y @kashdao/cli@latest markets list --json
90
+ ```
91
+
92
+ A `kashdao/tap` Homebrew tap is planned for the production launch.
93
+
94
+ The package installs a `kash` binary. (The internal admin tooling that previously
95
+ shipped under the same name is now `kash-admin`.)
96
+
97
+ **Requirements:** Node.js 22 or newer. Works on macOS, Linux, and Windows
98
+ (WSL recommended). Chmod-based permission tightening is best-effort and skipped
99
+ on Windows.
100
+
101
+ The one-line installer accepts `--version <semver>`, `--pm <pnpm|yarn|npm>`,
102
+ and `--dry-run` if you want to inspect the resolved command before running it.
103
+
104
+ ## Quickstart
105
+
106
+ ```sh
107
+ # 1. Configure an API key (request a `kash_test_*` staging key by emailing engineering@kash.bot)
108
+ kash auth set-key kash_test_…
109
+
110
+ # 2. Browse markets
111
+ kash markets list --status ACTIVE
112
+
113
+ # 3. Place a trade and wait for settlement
114
+ kash trade buy <market-id> --outcome 0 --amount 10 --wait
115
+
116
+ # 4. Inspect your portfolio
117
+ kash portfolio show
118
+ kash portfolio positions
119
+ ```
120
+
121
+ ## Authentication
122
+
123
+ Request a `kash_test_*` staging key by emailing
124
+ [`engineering@kash.bot`](mailto:engineering@kash.bot) with your
125
+ intended use case, then store it locally with one of:
126
+
127
+ ```sh
128
+ # Persisted in ~/.kash/config.json (mode 0600)
129
+ kash auth set-key kash_test_…
130
+
131
+ # Per-shell, no on-disk persistence
132
+ export KASH_API_KEY=kash_test_…
133
+
134
+ # Per-invocation, no persistence
135
+ KASH_API_KEY=kash_live_… kash markets list
136
+ ```
137
+
138
+ Inspect the resolved auth state with `kash auth status` (offline; does not call
139
+ the API). When you need a fresh shell or to log out:
140
+
141
+ ```sh
142
+ kash auth logout # clears apiKey from the active profile
143
+ kash config reset --yes # nuclear: deletes ~/.kash/config.json entirely
144
+ ```
145
+
146
+ Every authenticated `kash` command requires an API key. The CLI fails
147
+ fast with a clear `AUTH_REQUIRED` message if no key is configured
148
+ (`kash config get apiKey` to check). Per-key rate limits and audit
149
+ attribution apply on every request.
150
+
151
+ ---
152
+
153
+ ## Commands
154
+
155
+ | Group | Subcommands |
156
+ | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
157
+ | `auth` | `set-key`, `status`, `logout` |
158
+ | `markets` | `list`, `get`, `predictions` |
159
+ | `quote` | `buy`, `sell` — AMM price quotes (`markets:quote` scope) |
160
+ | `trade` | `buy`, `sell`, `status`, `list`, `confirm` |
161
+ | `portfolio` | `show`, `positions` |
162
+ | `webhooks` | `list`, `rotate-secret`, `redeliver`, `verify`, `replay` |
163
+ | `protocol` | `balance`, `market`, `quote`, `position`, `allowance`, `smart-account`, `fees`, `token-id`, `decode-revert`, `trade`, `userop`, `watch` — direct mode (smart account, ERC-4337) |
164
+ | `eoa` | `balance`, `market`, `quote`, `position`, `allowance`, `fees`, `trade` — direct mode (vanilla EOA, EIP-1559) |
165
+ | `config` | `show`, `set`, `profiles`, `use`, `remove`, `reset`, `export`, `import` |
166
+ | `health` | (top-level; honors `--timeout-ms`, exits 1 when down) |
167
+ | `version` | (top-level; also accepts `--json`) |
168
+ | `explain` | `[codes...]` — error code lookup (multi-code allowed) |
169
+ | `schema` | `[name]` — JSON Schema for SDK + CLI envelopes |
170
+ | `setup` | first-run interactive wizard (auth + verify + completion) |
171
+ | `trace` | `<correlationId>` — curated event timeline for a trade |
172
+ | `with-retry` | `-- <command> [args...]` — retry on recoverable failures |
173
+ | `docs` | full command tree (use `--json` for the agent surface) |
174
+ | `completion` | `install`, `uninstall` |
175
+
176
+ Run `kash <command> --help` for full option reference. Every command's `--help`
177
+ includes worked examples for both human and `--json --quiet` invocations.
178
+
179
+ ## Multi-profile workflow
180
+
181
+ The CLI supports AWS-CLI-style profiles for juggling multiple keys:
182
+
183
+ ```sh
184
+ # Issue keys against multiple environments
185
+ kash --profile prod auth set-key kash_live_…
186
+ kash --profile staging auth set-key kash_test_…
187
+ kash --profile ci auth set-key kash_live_…
188
+
189
+ # Switch the active profile (writes currentProfile to ~/.kash/config.json)
190
+ kash config use staging
191
+ # Or the shell-friendly alias `su`:
192
+ kash su staging
193
+
194
+ # List configured profiles
195
+ kash config profiles
196
+ # {
197
+ # "current": "staging",
198
+ # "profiles": ["ci", "prod", "staging"]
199
+ # }
200
+
201
+ # Override the active profile per-invocation
202
+ kash --profile prod markets list
203
+
204
+ # Override via environment for a sub-shell
205
+ KASH_PROFILE=ci kash trade list
206
+
207
+ # Remove a profile (refuses to remove the active one)
208
+ kash config remove staging
209
+ ```
210
+
211
+ The on-disk file at `~/.kash/config.json` looks like:
212
+
213
+ ```json
214
+ {
215
+ "version": 1,
216
+ "currentProfile": "staging",
217
+ "profiles": {
218
+ "prod": { "apiKey": "kash_live_…" },
219
+ "staging": { "apiKey": "kash_test_…", "baseUrl": "https://api-staging.kash.bot/v1" },
220
+ "ci": { "apiKey": "kash_live_…" }
221
+ }
222
+ }
223
+ ```
224
+
225
+ Resolution order: explicit `--profile <name>` flag → `KASH_PROFILE` env →
226
+ `currentProfile` in the file → `default`.
227
+
228
+ ---
229
+
230
+ ## AI-agent / scripting mode
231
+
232
+ Every command supports `--json` and `--quiet` for machine consumption:
233
+
234
+ ```sh
235
+ # JSON mode, suppress spinners/info — ideal for AI agents and CI.
236
+ kash markets list --status ACTIVE --json --quiet | jq '.data[0].id'
237
+
238
+ # Place a trade, block on settlement, parse the resulting tx hash.
239
+ kash trade buy <id> --outcome 0 --amount 5 --wait --json --quiet | jq -r .txHash
240
+
241
+ # Stream paginated reads as NDJSON (one record per line).
242
+ kash markets list --ndjson | while read -r line; do echo "$line" | jq -r .id; done
243
+ ```
244
+
245
+ ### Agent discovery surface
246
+
247
+ Three commands expose the CLI's shape in machine-readable form so an
248
+ AI agent can plan calls without scraping help text:
249
+
250
+ | Command | What it returns |
251
+ | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------ |
252
+ | `kash docs --json` | Full command tree (every command, argument, option, alias, default value). |
253
+ | `kash schema [<name>] --json` | JSON Schema for SDK request/response shapes + CLI-owned envelopes (`CreateTradeBody`, `TradeResource`, `MarketResource`, `CliErrorEnvelope`, …). |
254
+ | `kash explain [<code>] --json` | Error catalog with `recoverable`, `retryAfterMs`, `docsUrl`, and structured recovery `actions[]`. |
255
+
256
+ For first-time agent setup, dump everything into one document:
257
+
258
+ ```sh
259
+ kash version --json > kash-surface.json # cli/sdk/node/platform versions
260
+ kash docs --json >> kash-surface.json # full command tree
261
+ kash schema --json >> kash-surface.json # every JSON Schema
262
+ kash explain --json >> kash-surface.json # every error code + recovery actions
263
+ ```
264
+
265
+ See [`examples/agent-discovery.py`](./examples/agent-discovery.py) for a runnable
266
+ recipe that loads this into an agent's startup context.
267
+
268
+ ### Error envelope contract
269
+
270
+ Every command emits this shape on `--json` failures. The shape is
271
+ SemVer-stable; pin to it.
272
+
273
+ ```json
274
+ {
275
+ "ok": false,
276
+ "error": {
277
+ "code": "RATE_LIMITED",
278
+ "message": "Rate limit exceeded",
279
+ "recoverable": true,
280
+ "retryAfterMs": 30000,
281
+ "docsUrl": "https://kash.bot/docs/api/rate-limits",
282
+ "requestId": "req_abc",
283
+ "suggestion": "Retry in 30s. Upgrade for higher limits: https://kash.bot/pricing",
284
+ "actions": [
285
+ {
286
+ "type": "wait_and_retry",
287
+ "delayMs": 30000,
288
+ "description": "Wait 30s then re-run the same command."
289
+ },
290
+ {
291
+ "type": "open_url",
292
+ "url": "https://kash.bot/pricing",
293
+ "description": "Upgrade tier for higher rate limits."
294
+ }
295
+ ]
296
+ }
297
+ }
298
+ ```
299
+
300
+ **Required:** `code`, `message`, `recoverable`, `actions`. **Optional:**
301
+ `retryAfterMs`, `docsUrl`, `requestId`, `suggestion`. Action variants:
302
+ `run_command`, `set_env`, `wait_and_retry`, `open_url`, `check_input`.
303
+
304
+ Fetch the formal Zod-derived JSON Schema with:
305
+
306
+ ```sh
307
+ kash schema CliErrorEnvelope --json
308
+ ```
309
+
310
+ ### Exit codes
311
+
312
+ - `0` — success
313
+ - `1` — generic error (validation, server, network, etc.)
314
+ - `2` — auth failure (missing or invalid API key, missing scope)
315
+
316
+ ### Idempotent retries
317
+
318
+ For trade-creation calls (`kash trade buy/sell`), pass either an explicit
319
+ `--idempotency-key <uuid>` or `--auto-idempotency-key` to let the CLI generate
320
+ one. The resolved key is surfaced in the response, so a transient failure
321
+ mid-creation can be retried with the same key — the server guarantees the
322
+ trade is created at most once.
323
+
324
+ ```sh
325
+ # Generate, capture, retry safely:
326
+ kash trade buy <id> --outcome 0 --amount 10 \
327
+ --auto-idempotency-key --wait --json --quiet
328
+ ```
329
+
330
+ ### Retry-loop wrapper
331
+
332
+ `kash with-retry [opts] -- <command> [args...]` re-runs any kash
333
+ command when the structured error envelope reports a recoverable
334
+ failure. The retry policy reads `code` (`RATE_LIMITED`, `NETWORK`,
335
+ `TIMEOUT`, `MAINTENANCE`, `SERVER_ERROR` are retryable;
336
+ `INVALID_INPUT`, `AUTH_REQUIRED`, `NOT_FOUND` etc. fail fast) and
337
+ honours the `retryAfterMs` field when present, falling back to
338
+ exponential backoff otherwise.
339
+
340
+ ```sh
341
+ # Retry up to 5 times, with the wait dictated by the server.
342
+ kash with-retry --max-attempts 5 -- markets list --status ACTIVE --json --quiet
343
+
344
+ # Idempotent retry across attempts (the inner key persists).
345
+ kash with-retry -- trade buy <id> --outcome 0 --amount 10 \
346
+ --auto-idempotency-key --wait --json --quiet
347
+ ```
348
+
349
+ The wrapped command MUST come after `--`. Without `--json`, the
350
+ wrapper falls back to a fixed exponential schedule (1s, 2s, 4s, …
351
+ capped at `--max-delay-ms`).
352
+
353
+ ### Tracing a trade end-to-end
354
+
355
+ `kash trace <correlationId>` returns the curated event timeline for a
356
+ single trade — every event the pipeline emits as the trade moves through
357
+ intent parsing → funding → bridge → execution → webhook delivery.
358
+
359
+ ```sh
360
+ # Get the correlation id from any trade response and trace it.
361
+ CID=$(kash trade buy <market-id> --outcome 0 --amount 10 --json --quiet | jq -r .correlationId)
362
+ kash trace "$CID"
363
+ ```
364
+
365
+ The server returns a sanitized timeline — raw event payloads are never
366
+ exposed; only an allowlisted subset of fields (`txHash`, `tokensOut`,
367
+ `errorCode`, etc.) appears. JSON output is pinned to `GetTraceResponse`
368
+ (fetch the schema with `kash schema TraceResource --json`).
369
+
370
+ ### Dry-run preview
371
+
372
+ Pass `--dry-run` to `kash trade buy/sell` to preview the request without
373
+ sending it. The CLI validates inputs, resolves the idempotency key, and
374
+ emits the would-be body — no API call, no auth required. Useful for
375
+ agents planning trades and humans sanity-checking before committing.
376
+
377
+ ```sh
378
+ $ kash trade buy <id> --outcome 0 --amount 10 --dry-run --json
379
+ {
380
+ "wouldSend": {
381
+ "marketId": "9f0b…",
382
+ "outcomeIndex": 0,
383
+ "amount": "10",
384
+ "side": "buy"
385
+ },
386
+ "idempotencyKey": null,
387
+ "endpoint": { "method": "POST", "path": "/v1/trades" }
388
+ }
389
+ ```
390
+
391
+ The envelope is pinned to `TradeDryRunEnvelope` — fetch the full Zod
392
+ schema with `kash schema TradeDryRunEnvelope --json`.
393
+
394
+ ---
395
+
396
+ ## Webhook signing
397
+
398
+ Kash webhooks are signed with HMAC-SHA256 in a Stripe-compatible format
399
+ (`X-Kash-Signature: t=<unix-ms>,v1=<hex>`). The SDK's `verifySignature`
400
+ helper handles parsing, replay-window enforcement, and constant-time
401
+ comparison.
402
+
403
+ ```ts
404
+ import { KashClient } from '@kashdao/sdk';
405
+ const kash = new KashClient({}); // no apiKey needed for verifySignature
406
+
407
+ // In your HTTP handler — use the *raw* request body, not a re-serialised JSON.
408
+ const result = await kash.webhooks.verifySignature(rawBody, signatureHeader, secret);
409
+ if (!result.valid) {
410
+ return res.status(400).send(result.reason);
411
+ }
412
+ ```
413
+
414
+ Rotate the signing secret with `kash webhooks rotate-secret` (the new
415
+ plaintext is shown ONCE — capture it). See
416
+ [`examples/webhook-receiver.ts`](./examples/webhook-receiver.ts) for a
417
+ production-shaped Fastify receiver.
418
+
419
+ ---
420
+
421
+ ## Direct mode (`kash protocol …`)
422
+
423
+ Direct mode bypasses the Kash backend entirely and talks to the on-chain
424
+ contracts via [`@kashdao/protocol-sdk`](https://www.npmjs.com/package/@kashdao/protocol-sdk). It's for users
425
+ who want to read AMM state, quote trades, or submit UserOps from their
426
+ own signer without ever touching the public API.
427
+
428
+ The protocol-sdk loads lazily on first use, so Kash-orchestrated users
429
+ pay zero cold-start cost for it. `kash --version` and the entire
430
+ `kash <auth|markets|trade|…>` surface stay fast.
431
+
432
+ ### What's wired today (read-only + offline helpers)
433
+
434
+ | Command | What it does |
435
+ | --------------------------------------------------- | ----------------------------------------------------------------------------- |
436
+ | `kash protocol balance [account]` | On-chain USDC + native gas balances. Defaults to the profile's smart account. |
437
+ | `kash protocol market <address>` | Full AMM state: status, reserve, outstanding tokens, weights, probabilities. |
438
+ | `kash protocol quote <address> --side …` | Buy/sell quote against on-chain reserves. |
439
+ | `kash protocol position <market> [account]` | On-chain ERC-1155 outcome-token holdings (per outcome). |
440
+ | `kash protocol allowance <spender> [account]` | USDC allowance from `account` → `spender`. Skips `approve` when sufficient. |
441
+ | `kash protocol smart-account compute --owner …` | Derive the deterministic SA address for an EOA owner (no deployment needed). |
442
+ | `kash protocol smart-account is-deployed [address]` | Check whether an SA has bytecode on-chain. |
443
+ | `kash protocol fees` | EIP-1559 fee estimate via `eth_feeHistory`. Tunable percentile / multiplier. |
444
+ | `kash protocol token-id --market-id … --outcome …` | Compute the ERC-1155 token id (offline; no RPC). |
445
+ | `kash protocol decode-revert <0x…>` | Decode raw revert data into `(name, args)` via Market + EntryPoint ABIs. |
446
+
447
+ ### Trade execution (smart-account mode)
448
+
449
+ `kash protocol trade {buy,sell,close,approve}` runs the full one-shot
450
+ flow (prepare → simulate → sign → submit → wait). Default `--wait`,
451
+ default 0.5% slippage tolerance, default 5-minute deadline.
452
+
453
+ ```sh
454
+ # Place a BUY using the configured signerKeyRef.
455
+ kash protocol trade buy 0xMarket... -o 0 -a 10
456
+
457
+ # Preview only — populated UserOp + hash, no signing.
458
+ kash protocol trade buy 0xMarket... -o 0 -a 10 --dry-run --json
459
+
460
+ # Fire-and-forget; print userOpHash and exit.
461
+ kash protocol trade buy 0xMarket... -o 0 -a 10 --no-wait --json --quiet
462
+ ```
463
+
464
+ ### Cold-storage flow (`kash protocol userop`)
465
+
466
+ For operators who sign on a different machine than the one preparing
467
+ or submitting:
468
+
469
+ ```sh
470
+ # Machine A (no signer): prepare a fully-populated UserOp + hash.
471
+ kash protocol userop build buy 0xMarket... -o 0 -a 10 --out trade.json
472
+
473
+ # Machine B (signer-only): sign trade.json externally, write
474
+ # the resulting signature into the userOp.signature field.
475
+
476
+ # Machine C: submit and wait.
477
+ kash protocol userop submit signed.json --wait
478
+ ```
479
+
480
+ `kash protocol userop {hash,simulate,receipt,wait}` are also exposed.
481
+
482
+ ### Streaming (`kash protocol watch`)
483
+
484
+ Long-running NDJSON event stream for a market. Best-effort delivery —
485
+ on RPC reconnect missed events are NOT replayed; pair with
486
+ `kash markets predictions <id>` (indexer-backed) for gap-free coverage.
487
+
488
+ ```sh
489
+ kash protocol watch 0xMarket... --json --quiet | jq -c
490
+ ```
491
+
492
+ Press Ctrl-C to terminate cleanly. `--max-events <n>` and
493
+ `--timeout-ms <n>` bound the run.
494
+
495
+ ### EOA mode (`kash eoa …`)
496
+
497
+ Parallel namespace for operators who sign vanilla EIP-1559
498
+ transactions (no smart account, no bundler). Same surface as
499
+ `kash protocol` minus the UserOp lifecycle:
500
+
501
+ | Command | Notes |
502
+ | ----------------------------------------- | --------------------------------------- |
503
+ | `kash eoa balance [account]` | Defaults to the EOA address (signer's). |
504
+ | `kash eoa market <address>` | Same as `kash protocol market`. |
505
+ | `kash eoa quote <address>` | Same as `kash protocol quote`. |
506
+ | `kash eoa position <market>` | Same as `kash protocol position`. |
507
+ | `kash eoa allowance <spender>` | Same as `kash protocol allowance`. |
508
+ | `kash eoa fees` | Same as `kash protocol fees`. |
509
+ | `kash eoa trade {buy,sell,close,approve}` | Vanilla tx (no UserOp). |
510
+
511
+ Required config: `rpcUrl`, `defaultChainId`, `signerKeyRef`. EOA mode
512
+ ignores `smartAccount`, `bundlerUrl`, and `bundlerProvider`.
513
+
514
+ ```sh
515
+ kash eoa balance
516
+ kash eoa trade buy 0xMarket... -o 0 -a 10 --json
517
+ ```
518
+
519
+ ### Configuration
520
+
521
+ Direct mode requires four pieces of config, all per-profile or via env:
522
+
523
+ | Field | Env | Notes |
524
+ | ----------------- | ----------------------- | ---------------------------------------------------- |
525
+ | `rpcUrl` | `KASH_RPC_URL` | EVM RPC URL (Alchemy, Infura, your own node, anvil). |
526
+ | `smartAccount` | `KASH_SMART_ACCOUNT` | The 0x-prefixed smart account address to read. |
527
+ | `bundlerUrl` | `KASH_BUNDLER_URL` | ERC-4337 bundler. Required only for write paths. |
528
+ | `bundlerProvider` | `KASH_BUNDLER_PROVIDER` | One of `flashbots`, `pimlico`, `alchemy`, `generic`. |
529
+ | `signerKeyRef` | `KASH_SIGNER_KEY_REF` | `file:<path>` or `env:<NAME>`. Required for writes. |
530
+
531
+ ```sh
532
+ kash config set rpcUrl https://base-mainnet.g.alchemy.com/v2/<key>
533
+ kash config set smartAccount 0xabc…
534
+ kash protocol balance --json
535
+ ```
536
+
537
+ The CLI never persists raw private keys — only references. `file:` reads
538
+ from a 0x-prefixed hex file at the path; `env:` reads from a process env
539
+ var at invocation time.
540
+
541
+ ### Examples
542
+
543
+ ```sh
544
+ # Read your own balances
545
+ kash protocol balance --json
546
+ # → { "account": "0x…", "chainId": 8453, "usdcAtomic": "1000000", "gasWei": "5000000000000000" }
547
+
548
+ # Inspect a market on-chain
549
+ kash protocol market 0xMarket… --json | jq '.outcomes[].probability'
550
+
551
+ # Quote a $10 buy on outcome 0
552
+ kash protocol quote 0xMarket… --side buy --outcome 0 --amount 10 --json
553
+ ```
554
+
555
+ ---
556
+
557
+ ## Configuration reference
558
+
559
+ ### Per-profile fields (in `~/.kash/config.json`)
560
+
561
+ | Field | Type | Default | Notes |
562
+ | ----------------- | --------- | ------------------------- | --------------------------------------------------- |
563
+ | `apiKey` | `string?` | unset | Must start with `kash_`. Stored at mode `0600`. |
564
+ | `baseUrl` | `string?` | `https://api.kash.bot/v1` | Validated as a URL. |
565
+ | `defaultChainId` | `number?` | `8453` (Base mainnet) | Used when chain id matters; positive integer. |
566
+ | `rpcUrl` | `string?` | unset | Direct-mode EVM RPC URL. |
567
+ | `smartAccount` | `string?` | unset | Direct-mode smart account address (`0x…`). |
568
+ | `bundlerUrl` | `string?` | unset | ERC-4337 bundler URL (write paths only). |
569
+ | `bundlerProvider` | `string?` | unset | `flashbots` \| `pimlico` \| `alchemy` \| `generic`. |
570
+ | `signerKeyRef` | `string?` | unset | `file:<path>` or `env:<NAME>` — never raw keys. |
571
+
572
+ ### Environment variables (override file)
573
+
574
+ | Variable | Field | Notes |
575
+ | ----------------------- | ------------------- | --------------------------------------------------------- |
576
+ | `KASH_API_KEY` | `apiKey` | Highest precedence for the auth key. |
577
+ | `KASH_BASE_URL` | `baseUrl` | |
578
+ | `KASH_CHAIN_ID` | `defaultChainId` | Must parse as a positive integer. |
579
+ | `KASH_DEBUG` | (mirrors `--debug`) | Set to `1`/`true`/`yes`/`on` to enable lifecycle traces. |
580
+ | `KASH_RPC_URL` | `rpcUrl` | Direct-mode RPC URL. |
581
+ | `KASH_SMART_ACCOUNT` | `smartAccount` | Direct-mode smart account address. |
582
+ | `KASH_BUNDLER_URL` | `bundlerUrl` | Direct-mode ERC-4337 bundler URL. |
583
+ | `KASH_BUNDLER_PROVIDER` | `bundlerProvider` | Direct-mode bundler provider preset. |
584
+ | `KASH_SIGNER_KEY_REF` | `signerKeyRef` | `file:<path>` or `env:<NAME>` — never raw keys. |
585
+ | `KASH_PROFILE` | (active profile) | Equivalent to `--profile <name>` for the next invocation. |
586
+ | `KASH_CONFIG` | (config path) | Equivalent to `--config <path>`. |
587
+ | `NO_COLOR` | (color output) | Set to anything truthy to disable ANSI escapes. |
588
+
589
+ ### Resolution order
590
+
591
+ For each field, highest precedence first:
592
+
593
+ 1. Environment variable.
594
+ 2. Active profile in `~/.kash/config.json`.
595
+ 3. Built-in default.
596
+
597
+ For the active profile name itself:
598
+
599
+ 1. Explicit `--profile <name>` flag.
600
+ 2. `KASH_PROFILE` environment variable.
601
+ 3. `currentProfile` field in the config file.
602
+ 4. `default`.
603
+
604
+ For the config file path itself:
605
+
606
+ 1. Explicit `--config <path>` flag.
607
+ 2. `KASH_CONFIG` environment variable.
608
+ 3. `~/.kash/config.json`.
609
+
610
+ ### Operational flags
611
+
612
+ | Flag | Purpose |
613
+ | ------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
614
+ | `--profile <name>` | Pick a stored credential profile. |
615
+ | `--config <path>` | Override `~/.kash/config.json` location. |
616
+ | `--debug` | Stream SDK request/response/retry/error traces to stderr. With `--json` becomes NDJSON. |
617
+ | `--base-url <url>` | Override API base URL (staging tests, CI matrix builds). |
618
+ | `--max-retries <n>` | Override SDK retry budget (0-10). |
619
+ | `--timeout-ms <n>` | Override SDK request timeout. |
620
+ | `--json` | Emit machine-readable JSON instead of human-formatted output. |
621
+ | `--fields <list>` | Project comma-separated dot-paths on `--json`/`--ndjson` output (e.g. `id,outcomes.label`). See [Field projection](#field-projection). |
622
+ | `--filter <expr>` | Boolean predicate on `--json`/`--ndjson` entries (e.g. `'status==ACTIVE && outcomeCount>2'`). See [Filter DSL](#filter-dsl). |
623
+ | `--quiet` | Suppress spinners, progress, and informational logs. |
624
+ | `--no-color` | Disable ANSI escapes (also honors `NO_COLOR`). |
625
+
626
+ ### Field projection
627
+
628
+ Pass `--fields <list>` alongside `--json` or `--ndjson` to narrow output to
629
+ the dot-paths you care about. Reduces tokens for AI agents and noise for
630
+ shell pipelines, without spinning up `jq`.
631
+
632
+ ```sh
633
+ # Top-level fields:
634
+ kash markets list --json --fields id,title,status
635
+
636
+ # Nested paths and array splay (entries inside arrays project per-element):
637
+ kash markets get <id> --json --fields title,outcomes.label,outcomes.tokenAddress
638
+
639
+ # Paginated envelopes preserve `pagination`/`meta` unchanged; only the
640
+ # `data` array entries are projected.
641
+ kash trade list --json --fields id,status,txHash --quiet | jq -c
642
+ ```
643
+
644
+ ### Filter DSL
645
+
646
+ Pass `--filter <expr>` alongside `--json` or `--ndjson` to keep only
647
+ entries matching a boolean predicate. Tiny DSL — `==`, `!=`, `<`,
648
+ `<=`, `>`, `>=`, `&&`, `||`, dotted field paths, numbers, booleans,
649
+ `null`, bare-word string values. Composes with `--fields`: filter
650
+ runs first, then projection narrows the survivors.
651
+
652
+ ```sh
653
+ # Equality + comparison + boolean composition.
654
+ kash markets list --json --filter 'status==ACTIVE && outcomeCount>2'
655
+
656
+ # Filter on a dotted path.
657
+ kash trade list --json --filter 'webhookDelivery.status==delivered'
658
+
659
+ # Compose with --fields. The filter sees the FULL record; projection
660
+ # runs on what survives.
661
+ kash markets list --json --filter 'status==ACTIVE' --fields id
662
+
663
+ # NDJSON streams skip non-matching records entirely.
664
+ kash trade list --ndjson --filter 'side==buy && status==completed' | wc -l
665
+ ```
666
+
667
+ Type-coerced equality means `outcomeCount==2` matches both `2` and
668
+ `"2"`. Ordered comparisons (`<`, `>`, `<=`, `>=`) require both sides
669
+ to be finite numbers; otherwise the entry fails the predicate. The
670
+ DSL is intentionally narrow — for richer queries, pipe `--json` through
671
+ `jq`.
672
+
673
+ Path syntax: comma-separated, dot-segmented. Segments must match
674
+ `[A-Za-z_][A-Za-z0-9_]*`. Missing paths drop silently (jq semantics).
675
+ The flag is a no-op for human output and for non-JSON commands.
676
+
677
+ ### `--debug` trace shape
678
+
679
+ With `--debug --json`, each SDK lifecycle event is emitted to **stderr** as a
680
+ single line of NDJSON. Pipe stderr separately if your tooling expects clean
681
+ NDJSON on a single stream.
682
+
683
+ ```jsonc
684
+ // onRequest
685
+ { "event": "request", "method": "GET", "url": "/v1/markets", "attempt": 1, "idempotencyKey": null }
686
+ // onResponse
687
+ { "event": "response", "method": "GET", "url": "/v1/markets", "attempt": 1, "status": 200, "durationMs": 142, "requestId": "req_abc" }
688
+ // onRetry
689
+ { "event": "retry", "method": "POST", "url": "/v1/trades", "attempt": 2, "reason": "rate_limit", "delayMs": 1000 }
690
+ // onError
691
+ { "event": "error", "method": "POST", "url": "/v1/trades", "attempt": 3, "status": 429, "code": "RATE_LIMIT_EXCEEDED", "durationMs": 87 }
692
+ ```
693
+
694
+ `reason` is one of `rate_limit`, `server_error`, `network`, `timeout`. Without
695
+ `--json`, the same events are rendered as compact human-readable lines on
696
+ stderr.
697
+
698
+ ---
699
+
700
+ ## Stability promise
701
+
702
+ `@kashdao/cli` is currently `0.x` — under [Semantic Versioning](https://semver.org/)'s
703
+ own rules, `0.x` minor bumps may technically break anything. **We commit to
704
+ treating the contracts below as if SemVer-stable even before 1.0.** Additions
705
+ are minor bumps, behaviour changes or removals are major bumps. The 1.0
706
+ release will lock this in formally and remove the asterisk; until then, every
707
+ 0.x release that ships will be reviewed against this list.
708
+
709
+ ### Stable contracts
710
+
711
+ - **`--json` output shapes** for every command (validated by Zod schemas
712
+ available via `kash schema --json`).
713
+ - **The CLI error envelope** (`kash schema CliErrorEnvelope --json`).
714
+ - **The version manifest shape** (`kash version --json`).
715
+ - **The `--debug` NDJSON trace shape** (documented above).
716
+ - **Exit codes** (`0` ok, `1` generic error, `2` auth failure).
717
+ - **Error `code` strings** in the catalog (`kash explain --json`). New codes
718
+ appear in minor versions; existing codes never change meaning.
719
+ - **The `~/.kash/config.json` v1 file format**. Migrations to v2 will be
720
+ automatic and forward-compatible.
721
+
722
+ ### Not stable
723
+
724
+ - Human-mode (non-`--json`) output formatting (tables, prose, color choices).
725
+ Scripts that rely on it should switch to `--json --quiet`.
726
+ - Internal module structure (`packages/cli/src/`); only the binary surface
727
+ is the public API.
728
+ - Help text wording.
729
+
730
+ ### Deprecation policy
731
+
732
+ Behaviour changes that don't break the stable contracts are minor bumps
733
+ without warning. Anything that does will:
734
+
735
+ 1. Be announced in the `CHANGELOG.md` of the deprecating release.
736
+ 2. Continue to work for at least one minor version.
737
+ 3. Emit a stderr warning when used (humans only; agents using `--json` see
738
+ no functional change until the major bump).
739
+
740
+ ---
741
+
742
+ ## Troubleshooting
743
+
744
+ ### `[AUTH_REQUIRED] No API key configured.`
745
+
746
+ You haven't set an API key. Run `kash auth set-key kash_test_…`
747
+ (request a staging key by emailing `engineering@kash.bot`) or set
748
+ `KASH_API_KEY` in your environment. Every authenticated command needs
749
+ a key — only `kash --version`, `kash --help`, and `kash explain <code>`
750
+ work fully offline.
751
+
752
+ ### `[INVALID_INPUT] --max-retries must be …`
753
+
754
+ CLI flag values are validated up front. The error envelope's `actions[0]`
755
+ of type `check_input` names the bad field. Look up the constraints in
756
+ the [Operational flags](#operational-flags) table or run
757
+ `kash <command> --help`.
758
+
759
+ ### `[RATE_LIMITED]` with `retryAfterMs`
760
+
761
+ You're over your tier's rate limit. The error envelope tells you exactly
762
+ how long to wait. Either honor it programmatically (see
763
+ [`examples/trade-replay.sh`](./examples/trade-replay.sh)) or upgrade at
764
+ https://kash.bot/pricing.
765
+
766
+ ### `[CONFLICT]` on `kash trade buy/sell`
767
+
768
+ A duplicate Idempotency-Key, the trade is awaiting high-value confirmation,
769
+ or the market closed between fetch and order. Inspect with
770
+ `kash trade status <id>` before retrying.
771
+
772
+ ### `[NETWORK]` or `[TIMEOUT]`
773
+
774
+ Network path issues. The CLI already retries automatically; this means
775
+ retries were exhausted. Check connectivity to `api.kash.bot`. Retry with
776
+ `--timeout-ms 60000 --max-retries 5` if your environment has latency
777
+ spikes.
778
+
779
+ ### `[CONFIGURATION] Config file at … is invalid`
780
+
781
+ The on-disk `~/.kash/config.json` is malformed. The error message names
782
+ the field. Run `kash config reset` to start fresh, or hand-edit the file
783
+ (it's valid JSON).
784
+
785
+ ### `kash` collides with the admin CLI
786
+
787
+ The internal admin tooling used to ship a binary also called `kash`. It
788
+ has been renamed to `kash-admin`. If you have both installed, run
789
+ `which kash` to confirm you're invoking the public CLI.
790
+
791
+ ### Diagnosing any other failure
792
+
793
+ Run with `--debug` to see SDK request/response/retry traces. With
794
+ `--debug --json` you get NDJSON on stderr — pipe it through `jq` to inspect
795
+ the request flow:
796
+
797
+ ```sh
798
+ kash trade buy … --debug --json --quiet 2> >(jq .)
799
+ ```
800
+
801
+ Capture `kash version --json` for issue triage:
802
+
803
+ ```sh
804
+ kash version --json
805
+ # {
806
+ # "cli": "0.1.0",
807
+ # "sdk": "0.1.0",
808
+ # "node": "v22.4.1",
809
+ # "platform": "darwin",
810
+ # "release": "23.6.0",
811
+ # "arch": "arm64"
812
+ # }
813
+ ```
814
+
815
+ Run `kash explain <code>` for any error code to get the catalog entry
816
+ including recommended recovery actions. File a bug at
817
+ https://github.com/KashDAO/cli/issues with the version manifest, the
818
+ `requestId` from the failing envelope, and the failing command.
819
+
820
+ ---
821
+
822
+ ## Examples
823
+
824
+ Worked recipes for both human scripts and AI agents in
825
+ [`examples/`](./examples/):
826
+
827
+ | File | Audience | Demonstrates |
828
+ | --------------------- | --------------------- | ------------------------------------------------------------------------------------------------- |
829
+ | `buy-and-follow.sh` | Bash scripts, CI | Place a trade, block on settlement with `--wait`, parse the tx hash from `--json --quiet` output. |
830
+ | `trade-replay.sh` | Reliability engineers | `--auto-idempotency-key` for safe retries; capture and reuse the generated key on failure. |
831
+ | `portfolio-export.sh` | Data ops, accountants | Stream all positions and trades as NDJSON; pipe through `jq` for filtering. |
832
+ | `webhook-receiver.ts` | Backend engineers | Production-shaped Fastify receiver verifying `X-Kash-Signature` with `verifySignature`. |
833
+ | `ai-agent.py` | LLM/agent engineers | Python loop calling `kash --json --quiet`, recovering from errors via `kash explain`. |
834
+ | `agent-discovery.py` | LLM/agent engineers | Use `kash docs --json` and `kash schema` to teach an agent the CLI surface at startup. |
835
+
836
+ ---
837
+
838
+ ## Development
839
+
840
+ ```sh
841
+ pnpm --filter @kashdao/cli build
842
+ pnpm --filter @kashdao/cli test:unit
843
+ pnpm --filter @kashdao/cli typecheck
844
+ pnpm --filter @kashdao/cli lint
845
+ ```
846
+
847
+ Run the bundled binary against a local API:
848
+
849
+ ```sh
850
+ KASH_BASE_URL=http://localhost:3001/v1 \
851
+ KASH_API_KEY=kash_test_… \
852
+ node packages/cli/dist/index.js markets list
853
+ ```
854
+
855
+ The CLI ships as a single ESM bundle (~85 KB) with an executable
856
+ shebang. Dependencies pinned in `package.json`: `commander`, `chalk`,
857
+ `cli-table3`, `omelette`, `ora`, `zod`, `zod-to-json-schema`, plus
858
+ `@kashdao/sdk` (workspace).
859
+
860
+ ## Reporting issues / security
861
+
862
+ - General bugs: https://github.com/KashDAO/cli/issues
863
+ - Security disclosures: see [`SECURITY.md`](./SECURITY.md). Email
864
+ `security@kash.bot`; do not file a public issue.
865
+
866
+ ## License
867
+
868
+ MIT — see [`LICENSE`](./LICENSE).