@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
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/utils/global-options.ts"],"sourcesContent":["/**\n * Global option resolution.\n *\n * Commander 13 makes the root program's options available on every\n * subcommand via `cmd.optsWithGlobals()`. This helper centralises the\n * shape so command modules type their access to it once.\n *\n * The flags fall into three buckets:\n *\n * 1. **Output controls** — `--json`, `--quiet`, `--no-color`, `--debug`.\n * Drive how output and traces are rendered.\n * 2. **Config selection** — `--profile`, `--config`. Decide which\n * credentials get loaded.\n * 3. **SDK overrides** — `--base-url`, `--max-retries`, `--timeout-ms`.\n * Per-invocation overrides for the SDK config; useful for CI\n * matrix builds and ad-hoc staging tests.\n */\n\nimport { CliValidationError } from '../errors.js';\n\nimport { parseFieldsList } from './fields.js';\nimport { parseFilter, type FilterAst } from './filter.js';\n\nimport type { Command } from 'commander';\n\nexport type GlobalOptions = {\n /** `--json` — emit machine-readable JSON instead of human output. */\n readonly json: boolean;\n /** `--quiet` — suppress informational human-mode output. */\n readonly quiet: boolean;\n /** `--no-color` — disable ANSI escapes (also honors `NO_COLOR`). */\n readonly noColor: boolean;\n /** `--debug` — emit SDK lifecycle traces (request/response/retry/error) to stderr. */\n readonly debug: boolean;\n /** `--profile <name>` — pick a named profile from `~/.kash/config.json`. */\n readonly profile: string | undefined;\n /** `--config <path>` — explicit config file path; overrides default + env. */\n readonly configPath: string | undefined;\n /** `--base-url <url>` — override the API base URL for this invocation. */\n readonly baseUrl: string | undefined;\n /** `--max-retries <n>` — override SDK retry budget for this invocation. */\n readonly maxRetries: number | undefined;\n /** `--timeout-ms <n>` — override SDK request timeout for this invocation. */\n readonly timeoutMs: number | undefined;\n /**\n * `--api-version <date>` — pin against a public-API contract date.\n * The value is sent as `X-Kash-Api-Version` on every request. The\n * server runs a version-appropriate code path; sending an\n * unrecognised date returns `410 API_VERSION_UNSUPPORTED`. Omit\n * (or `undefined`) → server uses its canonical `PUBLIC_API_VERSION`.\n */\n readonly apiVersion: string | undefined;\n /**\n * `--fields <list>` — gh-style projection on `--json` output. Parsed\n * into dot-segmented paths; `undefined` when the flag was not passed\n * (commands then emit the full payload).\n */\n readonly fields: ReadonlyArray<readonly string[]> | undefined;\n /**\n * `--filter <expr>` — boolean DSL applied before `--fields`\n * projection. Filters list/get-style payloads to entries matching\n * the predicate. `undefined` when the flag was not passed.\n */\n readonly filter: FilterAst | undefined;\n};\n\ntype RawGlobalOptions = {\n json?: boolean;\n quiet?: boolean;\n // Commander stores `--no-color` as `{ color: false }` — the default for a\n // `--no-X` boolean is `true`, so the explicit `false` means \"disabled.\"\n color?: boolean;\n debug?: boolean;\n profile?: string;\n config?: string;\n baseUrl?: string;\n maxRetries?: string;\n timeoutMs?: string;\n apiVersion?: string;\n fields?: string;\n filter?: string;\n};\n\n/**\n * Pull the globals from the active command. Falls back to defaults\n * for any flag the user did not pass. Validates numeric inputs with\n * structured errors (so an agent gets a `INVALID_INPUT` code, not a\n * cryptic NaN cascade).\n */\nexport function readGlobals(cmd: Command): GlobalOptions {\n const opts = cmd.optsWithGlobals<RawGlobalOptions>();\n return {\n json: opts.json === true,\n quiet: opts.quiet === true,\n noColor: opts.color === false,\n // `KASH_DEBUG=1` (or any truthy value) mirrors `--debug`. Common\n // case: CI matrix configs and parent processes that can't easily\n // edit the invocation. Explicit `--debug` always wins (a user can\n // force it on even when the env says off).\n debug: opts.debug === true || isTruthyEnv(process.env['KASH_DEBUG']),\n profile: opts.profile,\n configPath: opts.config,\n baseUrl: opts.baseUrl === undefined ? undefined : parseUrl(opts.baseUrl, 'base-url'),\n maxRetries:\n opts.maxRetries === undefined\n ? undefined\n : // Cap at 10 to match the SDK's `kashClientConfigSchema`\n // (`maxRetries: z.number().int().min(0).max(10)`). Bouncing\n // out-of-range values here gives the agent an `INVALID_INPUT`\n // with a clear field, instead of a cryptic SDK\n // `KashConfigurationError` after the client tries to construct.\n parseBoundedUnsigned(opts.maxRetries, 'max-retries', 0, 10),\n timeoutMs:\n opts.timeoutMs === undefined ? undefined : parsePositive(opts.timeoutMs, 'timeout-ms'),\n // `--api-version` is forwarded to the SDK as `apiVersion`. Format\n // validation (`YYYY-MM-DD`) is enforced by the SDK's\n // `kashClientConfigSchema`. We don't pre-validate here — letting\n // the SDK be the single source of truth keeps the format rule in\n // one place. A malformed value surfaces as\n // `KashConfigurationError` from client construction, which the\n // CLI's error mapper converts to a structured envelope.\n apiVersion: opts.apiVersion,\n fields: opts.fields === undefined ? undefined : parseFieldsList(opts.fields),\n filter: opts.filter === undefined ? undefined : parseFilter(opts.filter),\n };\n}\n\nfunction parseBoundedUnsigned(raw: string, name: string, min: number, max: number): number {\n // Same strict-shape rationale as `parsePositiveInt`: reject decimals\n // and scientific notation BEFORE `parseInt`'s silent precision loss\n // (`1.5 → 1`, `1e3 → 1`). Bounded numeric flags like `--max-retries`\n // shouldn't accept either form.\n if (!/^\\+?\\d+$/.test(raw)) {\n throw new CliValidationError(\n `--${name} must be an integer between ${String(min)} and ${String(max)}.`,\n `Got \"${raw}\". Pass digits only — no decimals or scientific notation.`,\n name\n );\n }\n const n = Number.parseInt(raw, 10);\n if (!Number.isFinite(n) || n < min || n > max) {\n throw new CliValidationError(\n `--${name} must be an integer between ${String(min)} and ${String(max)}.`,\n `Got \"${raw}\".`,\n name\n );\n }\n return n;\n}\n\nfunction parseUrl(raw: string, name: string): string {\n let url: URL;\n try {\n // `new URL()` catches \"no scheme\", \"no host\", \"malformed escape\",\n // etc. — but it's permissive about the scheme. `file:`, `data:`,\n // `javascript:`, `about:` all parse cleanly. For an HTTP API\n // base URL, restricting to http/https is the right invariant\n // (defence-in-depth: even if the SDK forwards the URL to fetch\n // and refuses non-http schemes, we surface the error with a\n // clear field name).\n url = new URL(raw);\n } catch {\n throw new CliValidationError(\n `--${name} must be a valid URL.`,\n `Got \"${raw}\". Include the scheme, e.g. https://api.kash.bot/v1.`,\n name\n );\n }\n if (url.protocol !== 'http:' && url.protocol !== 'https:') {\n throw new CliValidationError(\n `--${name} must use http:// or https:// (got \"${url.protocol}\").`,\n `Schemes like file:, data:, javascript:, and about: are rejected — they would either silently fail in the SDK or open an attack vector.`,\n name\n );\n }\n return raw;\n}\n\n/**\n * Parse a CLI flag value as a strictly positive integer.\n *\n * Throws a structured `CliValidationError` (with a `check_input`\n * action naming the bad field) so an AI agent gets `INVALID_INPUT`\n * with a concrete recovery hint instead of a NaN cascade.\n *\n * Exported because every command that takes a positive-integer flag\n * (`--limit`, `--timeout`, `--poll-interval`, …) routes through it\n * — keeps validation messages and error envelopes uniform.\n */\nexport function parsePositiveInt(raw: string, name: string): number {\n // `Number.parseInt('1.5', 10)` returns 1 and `Number.parseInt('1e3', 10)`\n // returns 1 — both are silent precision losses for what looks like\n // a typo. Reject the raw shape first (bare digits only, optional\n // leading +) so the user sees an explicit error instead of a value\n // they didn't type.\n if (!/^\\+?\\d+$/.test(raw)) {\n throw new CliValidationError(\n `--${name} must be a positive integer.`,\n `Got \"${raw}\". Pass digits only — no decimals, scientific notation, or sign except a leading +.`,\n name\n );\n }\n const n = Number.parseInt(raw, 10);\n if (!Number.isFinite(n) || n <= 0) {\n throw new CliValidationError(\n `--${name} must be a positive integer.`,\n `Got \"${raw}\". Pass an integer > 0.`,\n name\n );\n }\n return n;\n}\n\n// Internal alias kept so the existing call sites in this file don't\n// need to change. New consumers should import `parsePositiveInt`.\nconst parsePositive = parsePositiveInt;\n\n/**\n * Optional variant of {@link parsePositiveInt}. Returns `undefined`\n * when the flag is absent (the common \"default to SDK behaviour\"\n * pattern); otherwise validates and returns the integer.\n *\n * Hoisted from per-command copies so flag wording, bounds, and\n * envelope shape stay uniform across `--wait-timeout-ms`,\n * `--poll-interval-ms`, etc.\n */\nexport function parseOptionalPositiveInt(\n raw: string | undefined,\n name: string\n): number | undefined {\n return raw === undefined ? undefined : parsePositiveInt(raw, name);\n}\n\n/**\n * Positive-finite-float variant of {@link parsePositiveInt}. Used by\n * gas-percentile flags and similar continuous-quantity bounds where\n * an integer-only parser would surprise operators (e.g.\n * `--max-tip-gwei 1.5`).\n */\nexport function parsePositiveFloat(raw: string, name: string): number {\n const n = Number.parseFloat(raw);\n if (!Number.isFinite(n) || n <= 0) {\n throw new CliValidationError(\n `--${name} must be a positive number.`,\n `Got \"${raw}\". Pass a finite number > 0.`,\n name\n );\n }\n return n;\n}\n\n/** Optional variant of {@link parsePositiveFloat}. */\nexport function parseOptionalPositiveFloat(\n raw: string | undefined,\n name: string\n): number | undefined {\n return raw === undefined ? undefined : parsePositiveFloat(raw, name);\n}\n\n/**\n * Recognise truthy env values the way every other tool does: `1`,\n * `true`, `yes`, `on` (case-insensitive). Empty string and `0` are\n * falsy. Anything else is a no-op rather than an error — env\n * pollution is common and shouldn't fail builds.\n */\nfunction isTruthyEnv(value: string | undefined): boolean {\n if (value === undefined) return false;\n const v = value.trim().toLowerCase();\n return v === '1' || v === 'true' || v === 'yes' || v === 'on';\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,366 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ CliValidationError
4
+ } from "./chunk-UZNSYATZ.js";
5
+
6
+ // src/utils/fields.ts
7
+ var SEGMENT_REGEX = /^[A-Za-z_][A-Za-z0-9_]*$/;
8
+ function parseFieldsList(raw) {
9
+ const trimmed = raw.trim();
10
+ if (trimmed === "") {
11
+ throw new CliValidationError(
12
+ "--fields must list at least one path.",
13
+ "Pass a comma-separated list, e.g. `--fields id,title,status`.",
14
+ "fields"
15
+ );
16
+ }
17
+ const paths = [];
18
+ for (const rawPath of trimmed.split(",")) {
19
+ const path = rawPath.trim();
20
+ if (path === "") {
21
+ throw new CliValidationError(
22
+ "--fields contains an empty entry.",
23
+ "Strip leading/trailing commas or extra whitespace, e.g. `id,title` (not `,id,title,`).",
24
+ "fields"
25
+ );
26
+ }
27
+ const segments = path.split(".");
28
+ for (const segment of segments) {
29
+ if (!SEGMENT_REGEX.test(segment)) {
30
+ throw new CliValidationError(
31
+ `--fields path "${path}" contains an invalid segment "${segment}".`,
32
+ "Segments must match `[A-Za-z_][A-Za-z0-9_]*`. Use `parent.child` for nested fields.",
33
+ "fields"
34
+ );
35
+ }
36
+ }
37
+ paths.push(segments);
38
+ }
39
+ return paths;
40
+ }
41
+ function projectFields(value, paths) {
42
+ if (paths.length === 0) return value;
43
+ if (value === null || value === void 0) return value;
44
+ if (typeof value === "object" && !Array.isArray(value) && Array.isArray(value.data)) {
45
+ const envelope = value;
46
+ return {
47
+ ...envelope,
48
+ data: envelope.data.map((item) => projectObject(item, paths))
49
+ };
50
+ }
51
+ if (Array.isArray(value)) {
52
+ return value.map((item) => projectObject(item, paths));
53
+ }
54
+ return projectObject(value, paths);
55
+ }
56
+ function projectObject(obj, paths) {
57
+ if (obj === null || obj === void 0) return obj;
58
+ if (Array.isArray(obj)) {
59
+ return obj.map((item) => projectObject(item, paths));
60
+ }
61
+ if (typeof obj !== "object") return obj;
62
+ const groups = /* @__PURE__ */ new Map();
63
+ for (const path of paths) {
64
+ if (path.length === 0) continue;
65
+ const head = path[0];
66
+ const rest = path.slice(1);
67
+ const existing = groups.get(head) ?? [];
68
+ existing.push(rest);
69
+ groups.set(head, existing);
70
+ }
71
+ const result = {};
72
+ const source = obj;
73
+ for (const [head, subPaths] of groups) {
74
+ if (!(head in source)) continue;
75
+ const value = source[head];
76
+ const includeWhole = subPaths.some((p) => p.length === 0);
77
+ if (includeWhole) {
78
+ result[head] = value;
79
+ continue;
80
+ }
81
+ result[head] = projectObject(value, subPaths);
82
+ }
83
+ return result;
84
+ }
85
+
86
+ // src/utils/filter.ts
87
+ var IDENT_REGEX = /^[A-Za-z_][A-Za-z0-9_]*/;
88
+ function tokenize(input) {
89
+ const tokens = [];
90
+ let i = 0;
91
+ while (i < input.length) {
92
+ const ch = input[i];
93
+ if (ch === " " || ch === " ") {
94
+ i += 1;
95
+ continue;
96
+ }
97
+ if (ch === "&" && input[i + 1] === "&") {
98
+ tokens.push({ kind: "and" });
99
+ i += 2;
100
+ continue;
101
+ }
102
+ if (ch === "|" && input[i + 1] === "|") {
103
+ tokens.push({ kind: "or" });
104
+ i += 2;
105
+ continue;
106
+ }
107
+ if (ch === "=" && input[i + 1] === "=") {
108
+ tokens.push({ kind: "op", op: "==" });
109
+ i += 2;
110
+ continue;
111
+ }
112
+ if (ch === "!" && input[i + 1] === "=") {
113
+ tokens.push({ kind: "op", op: "!=" });
114
+ i += 2;
115
+ continue;
116
+ }
117
+ if (ch === "<" && input[i + 1] === "=") {
118
+ tokens.push({ kind: "op", op: "<=" });
119
+ i += 2;
120
+ continue;
121
+ }
122
+ if (ch === ">" && input[i + 1] === "=") {
123
+ tokens.push({ kind: "op", op: ">=" });
124
+ i += 2;
125
+ continue;
126
+ }
127
+ if (ch === "<") {
128
+ tokens.push({ kind: "op", op: "<" });
129
+ i += 1;
130
+ continue;
131
+ }
132
+ if (ch === ">") {
133
+ tokens.push({ kind: "op", op: ">" });
134
+ i += 1;
135
+ continue;
136
+ }
137
+ if (ch === ".") {
138
+ tokens.push({ kind: "dot" });
139
+ i += 1;
140
+ continue;
141
+ }
142
+ if (ch === "-" || ch >= "0" && ch <= "9") {
143
+ const numMatch = /^-?\d+(\.\d+)?/.exec(input.slice(i));
144
+ if (numMatch) {
145
+ const raw = numMatch[0];
146
+ const n = Number.parseFloat(raw);
147
+ if (!Number.isFinite(n)) {
148
+ throw filterError(`Invalid number "${raw}" at position ${String(i)}.`);
149
+ }
150
+ tokens.push({ kind: "number", value: n });
151
+ i += raw.length;
152
+ continue;
153
+ }
154
+ }
155
+ const identMatch = IDENT_REGEX.exec(input.slice(i));
156
+ if (identMatch) {
157
+ const raw = identMatch[0];
158
+ if (raw === "true" || raw === "false") {
159
+ tokens.push({ kind: "bool", value: raw === "true" });
160
+ } else if (raw === "null") {
161
+ tokens.push({ kind: "null" });
162
+ } else {
163
+ tokens.push({ kind: "ident", value: raw });
164
+ }
165
+ i += raw.length;
166
+ continue;
167
+ }
168
+ throw filterError(`Unexpected character "${ch}" at position ${String(i)}.`);
169
+ }
170
+ return tokens;
171
+ }
172
+ var Parser = class {
173
+ constructor(tokens) {
174
+ this.tokens = tokens;
175
+ }
176
+ tokens;
177
+ pos = 0;
178
+ parse() {
179
+ const ast = this.parseOr();
180
+ if (this.pos !== this.tokens.length) {
181
+ throw filterError(`Unexpected trailing token after position ${String(this.pos)}.`);
182
+ }
183
+ return ast;
184
+ }
185
+ parseOr() {
186
+ let left = this.parseAnd();
187
+ while (this.peek()?.kind === "or") {
188
+ this.pos += 1;
189
+ const right = this.parseAnd();
190
+ left = { kind: "or", left, right };
191
+ }
192
+ return left;
193
+ }
194
+ parseAnd() {
195
+ let left = this.parseCmp();
196
+ while (this.peek()?.kind === "and") {
197
+ this.pos += 1;
198
+ const right = this.parseCmp();
199
+ left = { kind: "and", left, right };
200
+ }
201
+ return left;
202
+ }
203
+ parseCmp() {
204
+ const path = this.parsePath();
205
+ const opToken = this.peek();
206
+ if (opToken?.kind !== "op") {
207
+ throw filterError("Expected an operator (`==`, `!=`, `<`, `>`, `<=`, `>=`).");
208
+ }
209
+ this.pos += 1;
210
+ const valueToken = this.peek();
211
+ if (!valueToken) {
212
+ throw filterError("Expected a value after the operator.");
213
+ }
214
+ let value;
215
+ switch (valueToken.kind) {
216
+ case "number":
217
+ value = valueToken.value;
218
+ break;
219
+ case "bool":
220
+ value = valueToken.value;
221
+ break;
222
+ case "null":
223
+ value = null;
224
+ break;
225
+ case "ident":
226
+ value = valueToken.value;
227
+ break;
228
+ default:
229
+ throw filterError(`Expected a value, got ${valueToken.kind}.`);
230
+ }
231
+ this.pos += 1;
232
+ return { kind: "cmp", path, op: opToken.op, value };
233
+ }
234
+ parsePath() {
235
+ const parts = [];
236
+ const first = this.peek();
237
+ if (first?.kind !== "ident") {
238
+ throw filterError("Expected a field path.");
239
+ }
240
+ parts.push(rejectReservedKey(first.value));
241
+ this.pos += 1;
242
+ while (this.peek()?.kind === "dot") {
243
+ this.pos += 1;
244
+ const next = this.peek();
245
+ if (next?.kind !== "ident") {
246
+ throw filterError("Expected a field name after `.`.");
247
+ }
248
+ parts.push(rejectReservedKey(next.value));
249
+ this.pos += 1;
250
+ }
251
+ return parts;
252
+ }
253
+ peek() {
254
+ return this.tokens[this.pos];
255
+ }
256
+ };
257
+ var RESERVED_PATH_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
258
+ function rejectReservedKey(segment) {
259
+ if (RESERVED_PATH_KEYS.has(segment)) {
260
+ throw filterError(
261
+ `Reserved field name "${segment}" is not allowed in --filter paths (prototype-walk guard).`
262
+ );
263
+ }
264
+ return segment;
265
+ }
266
+ function getAtPath(value, path) {
267
+ let cur = value;
268
+ for (const segment of path) {
269
+ if (cur === null || cur === void 0) return void 0;
270
+ if (typeof cur !== "object") return void 0;
271
+ if (!Object.hasOwn(cur, segment)) return void 0;
272
+ cur = cur[segment];
273
+ }
274
+ return cur;
275
+ }
276
+ function evaluate(ast, record) {
277
+ switch (ast.kind) {
278
+ case "or":
279
+ return evaluate(ast.left, record) || evaluate(ast.right, record);
280
+ case "and":
281
+ return evaluate(ast.left, record) && evaluate(ast.right, record);
282
+ case "cmp":
283
+ return evaluateCmp(ast, record);
284
+ }
285
+ }
286
+ function evaluateCmp(ast, record) {
287
+ const actual = getAtPath(record, ast.path);
288
+ const expected = ast.value;
289
+ if (ast.op === "==" || ast.op === "!=") {
290
+ let eq = actual === expected;
291
+ if (!eq && typeof actual === "string" && typeof expected === "number") {
292
+ const n = Number(actual);
293
+ eq = Number.isFinite(n) && n === expected;
294
+ }
295
+ if (!eq && typeof actual === "number" && typeof expected === "string") {
296
+ const n = Number(expected);
297
+ eq = Number.isFinite(n) && n === actual;
298
+ }
299
+ return ast.op === "==" ? eq : !eq;
300
+ }
301
+ const a = toFiniteNumber(actual);
302
+ const b = toFiniteNumber(expected);
303
+ if (a === void 0 || b === void 0) return false;
304
+ switch (ast.op) {
305
+ case "<":
306
+ return a < b;
307
+ case "<=":
308
+ return a <= b;
309
+ case ">":
310
+ return a > b;
311
+ case ">=":
312
+ return a >= b;
313
+ default:
314
+ return false;
315
+ }
316
+ }
317
+ function toFiniteNumber(value) {
318
+ if (typeof value === "number") {
319
+ return Number.isFinite(value) ? value : void 0;
320
+ }
321
+ if (typeof value === "string") {
322
+ const n = Number(value);
323
+ return Number.isFinite(n) ? n : void 0;
324
+ }
325
+ return void 0;
326
+ }
327
+ function parseFilter(expr) {
328
+ const trimmed = expr.trim();
329
+ if (trimmed === "") {
330
+ throw filterError("--filter expression is empty.");
331
+ }
332
+ const tokens = tokenize(trimmed);
333
+ if (tokens.length === 0) {
334
+ throw filterError("--filter expression has no tokens.");
335
+ }
336
+ return new Parser(tokens).parse();
337
+ }
338
+ function applyFilter(value, ast) {
339
+ if (value === null || value === void 0) return value;
340
+ if (typeof value === "object" && !Array.isArray(value) && Array.isArray(value.data)) {
341
+ const envelope = value;
342
+ return {
343
+ ...envelope,
344
+ data: envelope.data.filter((entry) => evaluate(ast, entry))
345
+ };
346
+ }
347
+ if (Array.isArray(value)) {
348
+ return value.filter((entry) => evaluate(ast, entry));
349
+ }
350
+ return evaluate(ast, value) ? value : null;
351
+ }
352
+ function filterError(message) {
353
+ return new CliValidationError(
354
+ `--filter parse error: ${message}`,
355
+ "See `kash --help` for filter syntax. Examples: `status==ACTIVE`, `side==buy && outcomeCount>2`.",
356
+ "filter"
357
+ );
358
+ }
359
+
360
+ export {
361
+ parseFieldsList,
362
+ projectFields,
363
+ parseFilter,
364
+ applyFilter
365
+ };
366
+ //# sourceMappingURL=chunk-MIXOZU2S.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/utils/fields.ts","../src/utils/filter.ts"],"sourcesContent":["/**\n * `--fields` projection helpers.\n *\n * Implements `gh`-style dot-path field selection for `--json` output.\n * Reduces tokens for AI agents and noise for humans piping responses\n * through `jq`. Applied AFTER schema validation so the projected\n * payload is a strict subset of the wire shape — never a re-shape.\n *\n * Path syntax:\n * - Comma-separated list: `id,title,status`\n * - Dot-separated nested paths: `pagination.cursor`\n * - Array splay (implicit): `outcomes.label` against an array of\n * outcome objects yields\n * `[{label}, {label}, …]`. Sub-fields\n * group: `outcomes.label,outcomes.tokenAddress`\n * yields `[{label, tokenAddress}, …]`.\n *\n * Paginated envelopes (`{ data: [...], pagination?: {...} }`) are\n * detected and the projection runs against each entry in `data`. Other\n * top-level keys (pagination, meta) pass through unchanged.\n *\n * Missing paths silently drop (matches jq semantics; surfacing every\n * typo as an INVALID_INPUT would be more friction than it's worth).\n */\n\nimport { CliValidationError } from '../errors.js';\n\n/**\n * Validate a single path segment. Restricted to the conservative\n * \"JSON-ish identifier\" subset so we don't accidentally accept paths\n * that overlap with future jq-syntax extensions (`[*]`, `?`, etc.).\n */\nconst SEGMENT_REGEX = /^[A-Za-z_][A-Za-z0-9_]*$/;\n\n/**\n * Parse the comma-separated `--fields` value into an array of\n * dot-segmented paths. Throws `INVALID_INPUT` on empty entries or bad\n * segments so an agent gets a clear error envelope.\n */\nexport function parseFieldsList(raw: string): ReadonlyArray<readonly string[]> {\n const trimmed = raw.trim();\n if (trimmed === '') {\n throw new CliValidationError(\n '--fields must list at least one path.',\n 'Pass a comma-separated list, e.g. `--fields id,title,status`.',\n 'fields'\n );\n }\n const paths: string[][] = [];\n for (const rawPath of trimmed.split(',')) {\n const path = rawPath.trim();\n if (path === '') {\n throw new CliValidationError(\n '--fields contains an empty entry.',\n 'Strip leading/trailing commas or extra whitespace, e.g. `id,title` (not `,id,title,`).',\n 'fields'\n );\n }\n const segments = path.split('.');\n for (const segment of segments) {\n if (!SEGMENT_REGEX.test(segment)) {\n throw new CliValidationError(\n `--fields path \"${path}\" contains an invalid segment \"${segment}\".`,\n 'Segments must match `[A-Za-z_][A-Za-z0-9_]*`. Use `parent.child` for nested fields.',\n 'fields'\n );\n }\n }\n paths.push(segments);\n }\n return paths;\n}\n\n/**\n * Project a value against a parsed field list. The value is preserved\n * structurally — paginated envelopes pass through with their `data`\n * array projected; arrays are projected element-wise; objects are\n * narrowed to the requested fields.\n */\nexport function projectFields(value: unknown, paths: ReadonlyArray<readonly string[]>): unknown {\n if (paths.length === 0) return value;\n if (value === null || value === undefined) return value;\n\n // Paginated envelope: project entries, preserve pagination/meta unchanged.\n if (\n typeof value === 'object' &&\n !Array.isArray(value) &&\n Array.isArray((value as { data?: unknown }).data)\n ) {\n const envelope = value as Record<string, unknown> & { data: unknown[] };\n return {\n ...envelope,\n data: envelope.data.map((item) => projectObject(item, paths)),\n };\n }\n\n if (Array.isArray(value)) {\n return value.map((item) => projectObject(item, paths));\n }\n\n return projectObject(value, paths);\n}\n\n/**\n * Core recursive projection. Groups requested paths by their head\n * segment so sub-fields of the same parent collapse into a single\n * sub-projection (rather than overwriting each other).\n */\nfunction projectObject(obj: unknown, paths: ReadonlyArray<readonly string[]>): unknown {\n if (obj === null || obj === undefined) return obj;\n\n // Array values: recurse into each element with the same paths.\n if (Array.isArray(obj)) {\n return obj.map((item) => projectObject(item, paths));\n }\n\n // Primitives: nothing to project.\n if (typeof obj !== 'object') return obj;\n\n // Group paths by head segment so `outcomes.label,outcomes.token` collapse\n // to a single recursive call into `outcomes` with sub-paths `[label]`\n // and `[token]`.\n const groups = new Map<string, Array<readonly string[]>>();\n for (const path of paths) {\n if (path.length === 0) continue;\n const head = path[0]!;\n const rest = path.slice(1);\n const existing = groups.get(head) ?? [];\n existing.push(rest);\n groups.set(head, existing);\n }\n\n const result: Record<string, unknown> = {};\n const source = obj as Record<string, unknown>;\n for (const [head, subPaths] of groups) {\n if (!(head in source)) continue;\n const value = source[head];\n\n // If any sub-path is empty, the user asked for the whole subtree —\n // include it as-is and skip narrower projections under the same head.\n const includeWhole = subPaths.some((p) => p.length === 0);\n if (includeWhole) {\n result[head] = value;\n continue;\n }\n result[head] = projectObject(value, subPaths);\n }\n return result;\n}\n","/**\n * `--filter` boolean DSL — narrows JSON output by predicate.\n *\n * Tiny intentionally: just enough to keep agents and shell pipelines\n * from reaching for `jq` for the common case. Pulling in a full\n * jq-compatible parser would (a) add weight to a CLI we're optimising\n * for cold-start, and (b) widen the contract surface beyond what we\n * can stably support.\n *\n * **Grammar** (informal):\n *\n * filter := or\n * or := and (`||` and)*\n * and := comparison (`&&` comparison)*\n * comparison := path op value\n * op := `==` | `!=` | `<=` | `>=` | `<` | `>`\n * path := identifier (`.` identifier)*\n * value := bareWord | number | true | false | null\n *\n * `bareWord` is `[A-Za-z_][A-Za-z0-9_]*` (e.g. `ACTIVE`, `buy`).\n * Numbers are decimal (`8453`, `0.62`).\n *\n * **Type coercion:** `<`, `>`, `<=`, `>=` only fire when both sides\n * coerce to finite numbers. `==` and `!=` first try strict equality,\n * then a number-coerce comparison so `outcomeCount==2` works whether\n * the field is a number or a numeric string.\n *\n * **Paginated envelopes:** filter applies to the entries inside\n * `data` (mirroring `--fields`); other top-level keys (pagination,\n * meta) pass through unchanged.\n *\n * Future extensions (kept off the surface for now): `~` regex,\n * `in [a,b,c]`, parenthesised sub-expressions. Most users reach for\n * `jq` once they need any of those — adding them piecemeal would\n * lock us into a dialect.\n */\n\nimport { CliValidationError } from '../errors.js';\n\n// ---------------------------------------------------------------------------\n// AST\n// ---------------------------------------------------------------------------\n\nexport type FilterAst =\n | { readonly kind: 'or'; readonly left: FilterAst; readonly right: FilterAst }\n | { readonly kind: 'and'; readonly left: FilterAst; readonly right: FilterAst }\n | {\n readonly kind: 'cmp';\n readonly path: readonly string[];\n readonly op: '==' | '!=' | '<' | '<=' | '>' | '>=';\n readonly value: string | number | boolean | null;\n };\n\n// ---------------------------------------------------------------------------\n// Lexer\n// ---------------------------------------------------------------------------\n\ntype Token =\n | { kind: 'and' | 'or' }\n | { kind: 'op'; op: '==' | '!=' | '<' | '<=' | '>' | '>=' }\n | { kind: 'ident'; value: string }\n | { kind: 'dot' }\n | { kind: 'number'; value: number }\n | { kind: 'bool'; value: boolean }\n | { kind: 'null' }\n | { kind: 'word'; value: string };\n\nconst IDENT_REGEX = /^[A-Za-z_][A-Za-z0-9_]*/;\n\nfunction tokenize(input: string): Token[] {\n const tokens: Token[] = [];\n let i = 0;\n while (i < input.length) {\n const ch = input[i]!;\n if (ch === ' ' || ch === '\\t') {\n i += 1;\n continue;\n }\n if (ch === '&' && input[i + 1] === '&') {\n tokens.push({ kind: 'and' });\n i += 2;\n continue;\n }\n if (ch === '|' && input[i + 1] === '|') {\n tokens.push({ kind: 'or' });\n i += 2;\n continue;\n }\n if (ch === '=' && input[i + 1] === '=') {\n tokens.push({ kind: 'op', op: '==' });\n i += 2;\n continue;\n }\n if (ch === '!' && input[i + 1] === '=') {\n tokens.push({ kind: 'op', op: '!=' });\n i += 2;\n continue;\n }\n if (ch === '<' && input[i + 1] === '=') {\n tokens.push({ kind: 'op', op: '<=' });\n i += 2;\n continue;\n }\n if (ch === '>' && input[i + 1] === '=') {\n tokens.push({ kind: 'op', op: '>=' });\n i += 2;\n continue;\n }\n if (ch === '<') {\n tokens.push({ kind: 'op', op: '<' });\n i += 1;\n continue;\n }\n if (ch === '>') {\n tokens.push({ kind: 'op', op: '>' });\n i += 1;\n continue;\n }\n if (ch === '.') {\n tokens.push({ kind: 'dot' });\n i += 1;\n continue;\n }\n // Number (possibly with decimal). Must come before identifier to\n // catch a leading digit; identifiers start with letter/underscore.\n if (ch === '-' || (ch >= '0' && ch <= '9')) {\n const numMatch = /^-?\\d+(\\.\\d+)?/.exec(input.slice(i));\n if (numMatch) {\n const raw = numMatch[0];\n const n = Number.parseFloat(raw);\n if (!Number.isFinite(n)) {\n throw filterError(`Invalid number \"${raw}\" at position ${String(i)}.`);\n }\n tokens.push({ kind: 'number', value: n });\n i += raw.length;\n continue;\n }\n }\n // Identifier or bareword.\n const identMatch = IDENT_REGEX.exec(input.slice(i));\n if (identMatch) {\n const raw = identMatch[0];\n if (raw === 'true' || raw === 'false') {\n tokens.push({ kind: 'bool', value: raw === 'true' });\n } else if (raw === 'null') {\n tokens.push({ kind: 'null' });\n } else {\n tokens.push({ kind: 'ident', value: raw });\n }\n i += raw.length;\n continue;\n }\n throw filterError(`Unexpected character \"${ch}\" at position ${String(i)}.`);\n }\n return tokens;\n}\n\n// ---------------------------------------------------------------------------\n// Parser (recursive descent: or → and → comparison)\n// ---------------------------------------------------------------------------\n\nclass Parser {\n private pos = 0;\n constructor(private readonly tokens: readonly Token[]) {}\n\n parse(): FilterAst {\n const ast = this.parseOr();\n if (this.pos !== this.tokens.length) {\n throw filterError(`Unexpected trailing token after position ${String(this.pos)}.`);\n }\n return ast;\n }\n\n private parseOr(): FilterAst {\n let left = this.parseAnd();\n while (this.peek()?.kind === 'or') {\n this.pos += 1;\n const right = this.parseAnd();\n left = { kind: 'or', left, right };\n }\n return left;\n }\n\n private parseAnd(): FilterAst {\n let left = this.parseCmp();\n while (this.peek()?.kind === 'and') {\n this.pos += 1;\n const right = this.parseCmp();\n left = { kind: 'and', left, right };\n }\n return left;\n }\n\n private parseCmp(): FilterAst {\n const path = this.parsePath();\n const opToken = this.peek();\n if (opToken?.kind !== 'op') {\n throw filterError('Expected an operator (`==`, `!=`, `<`, `>`, `<=`, `>=`).');\n }\n this.pos += 1;\n const valueToken = this.peek();\n if (!valueToken) {\n throw filterError('Expected a value after the operator.');\n }\n let value: string | number | boolean | null;\n switch (valueToken.kind) {\n case 'number':\n value = valueToken.value;\n break;\n case 'bool':\n value = valueToken.value;\n break;\n case 'null':\n value = null;\n break;\n case 'ident':\n // Bare-word value (e.g. `ACTIVE`, `buy`). The parser doesn't\n // distinguish \"this looks like an enum\" from \"this looks like\n // a path\"; on the right side of an op we always treat it as a\n // string value.\n value = valueToken.value;\n break;\n default:\n throw filterError(`Expected a value, got ${valueToken.kind}.`);\n }\n this.pos += 1;\n return { kind: 'cmp', path, op: opToken.op, value };\n }\n\n private parsePath(): readonly string[] {\n const parts: string[] = [];\n const first = this.peek();\n if (first?.kind !== 'ident') {\n throw filterError('Expected a field path.');\n }\n parts.push(rejectReservedKey(first.value));\n this.pos += 1;\n while (this.peek()?.kind === 'dot') {\n this.pos += 1;\n const next = this.peek();\n if (next?.kind !== 'ident') {\n throw filterError('Expected a field name after `.`.');\n }\n parts.push(rejectReservedKey(next.value));\n this.pos += 1;\n }\n return parts;\n }\n\n private peek(): Token | undefined {\n return this.tokens[this.pos];\n }\n}\n\n// ---------------------------------------------------------------------------\n// Evaluator\n// ---------------------------------------------------------------------------\n\n/**\n * Path segments that would walk into the prototype chain. Filtering\n * is read-only so this isn't prototype pollution per se, but\n * `__proto__.toString` would resolve to a function reference and\n * surface surprising truthy comparisons. Reject at parse time so the\n * AST never carries them and the evaluator stays simple.\n */\nconst RESERVED_PATH_KEYS: ReadonlySet<string> = new Set(['__proto__', 'constructor', 'prototype']);\n\nfunction rejectReservedKey(segment: string): string {\n if (RESERVED_PATH_KEYS.has(segment)) {\n throw filterError(\n `Reserved field name \"${segment}\" is not allowed in --filter paths (prototype-walk guard).`\n );\n }\n return segment;\n}\n\nfunction getAtPath(value: unknown, path: readonly string[]): unknown {\n let cur: unknown = value;\n for (const segment of path) {\n if (cur === null || cur === undefined) return undefined;\n if (typeof cur !== 'object') return undefined;\n // `Object.hasOwn` ensures we only read direct properties — even\n // though reserved keys are rejected at parse time, this is\n // defence-in-depth against any future code path that constructs\n // an AST without going through `parsePath`.\n if (!Object.hasOwn(cur, segment)) return undefined;\n cur = (cur as Record<string, unknown>)[segment];\n }\n return cur;\n}\n\nfunction evaluate(ast: FilterAst, record: unknown): boolean {\n switch (ast.kind) {\n case 'or':\n return evaluate(ast.left, record) || evaluate(ast.right, record);\n case 'and':\n return evaluate(ast.left, record) && evaluate(ast.right, record);\n case 'cmp':\n return evaluateCmp(ast, record);\n }\n}\n\ntype CmpAst = Extract<FilterAst, { kind: 'cmp' }>;\n\nfunction evaluateCmp(ast: CmpAst, record: unknown): boolean {\n const actual = getAtPath(record, ast.path);\n const expected = ast.value;\n\n if (ast.op === '==' || ast.op === '!=') {\n // First try strict equality, then number-coerce both sides so\n // numeric strings like \"2\" match `outcomeCount==2`.\n let eq = actual === expected;\n if (!eq && typeof actual === 'string' && typeof expected === 'number') {\n const n = Number(actual);\n eq = Number.isFinite(n) && n === expected;\n }\n if (!eq && typeof actual === 'number' && typeof expected === 'string') {\n const n = Number(expected);\n eq = Number.isFinite(n) && n === actual;\n }\n return ast.op === '==' ? eq : !eq;\n }\n\n // Ordered comparisons require both sides to coerce to finite numbers.\n const a = toFiniteNumber(actual);\n const b = toFiniteNumber(expected);\n if (a === undefined || b === undefined) return false;\n switch (ast.op) {\n case '<':\n return a < b;\n case '<=':\n return a <= b;\n case '>':\n return a > b;\n case '>=':\n return a >= b;\n default:\n // Unreachable — `==`/`!=` are handled above. The default\n // satisfies the compiler's exhaustiveness check.\n return false;\n }\n}\n\nfunction toFiniteNumber(value: unknown): number | undefined {\n if (typeof value === 'number') {\n return Number.isFinite(value) ? value : undefined;\n }\n if (typeof value === 'string') {\n const n = Number(value);\n return Number.isFinite(n) ? n : undefined;\n }\n return undefined;\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\nexport function parseFilter(expr: string): FilterAst {\n const trimmed = expr.trim();\n if (trimmed === '') {\n throw filterError('--filter expression is empty.');\n }\n const tokens = tokenize(trimmed);\n if (tokens.length === 0) {\n throw filterError('--filter expression has no tokens.');\n }\n return new Parser(tokens).parse();\n}\n\n/**\n * Apply a parsed filter to a value. Mirrors `projectFields`'s\n * dispatch:\n * - Paginated envelope (`{ data: [...], ... }`) → narrow `data`\n * to entries where the predicate is true; preserve other keys.\n * - Bare array → element-wise.\n * - Single record → return as-is when matching, `null` when not.\n *\n * The single-record path is uncommon (most filtering is over lists)\n * but is the obvious thing to do — agents that want to short-circuit\n * a `kash trade status` chain can do `kash trade status <id> --filter\n * 'status==completed' --json | …`.\n */\nexport function applyFilter(value: unknown, ast: FilterAst): unknown {\n if (value === null || value === undefined) return value;\n\n if (\n typeof value === 'object' &&\n !Array.isArray(value) &&\n Array.isArray((value as { data?: unknown }).data)\n ) {\n const envelope = value as Record<string, unknown> & { data: unknown[] };\n return {\n ...envelope,\n data: envelope.data.filter((entry) => evaluate(ast, entry)),\n };\n }\n\n if (Array.isArray(value)) {\n return value.filter((entry) => evaluate(ast, entry));\n }\n\n // Single record: pass through if matching, null otherwise. Keeps\n // the JSON contract stable (callers can null-check) without\n // breaking pipelines that expect an object.\n return evaluate(ast, value) ? value : null;\n}\n\nfunction filterError(message: string): CliValidationError {\n return new CliValidationError(\n `--filter parse error: ${message}`,\n 'See `kash --help` for filter syntax. Examples: `status==ACTIVE`, `side==buy && outcomeCount>2`.',\n 'filter'\n );\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,129 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ readConfig
4
+ } from "./chunk-KMBMQIZ7.js";
5
+ import {
6
+ CLI_VERSION
7
+ } from "./chunk-BN2CUM42.js";
8
+ import {
9
+ style
10
+ } from "./chunk-VIADBYFY.js";
11
+ import {
12
+ CliError,
13
+ EXIT_CODES
14
+ } from "./chunk-UZNSYATZ.js";
15
+
16
+ // src/utils/client.ts
17
+ import { KashClient } from "@kashdao/sdk";
18
+ async function buildClient(opts = {}) {
19
+ const globals = opts.globals;
20
+ const config = await readConfig({
21
+ ...globals?.profile === void 0 ? {} : { profile: globals.profile },
22
+ ...globals?.configPath === void 0 ? {} : { configPath: globals.configPath }
23
+ });
24
+ if (opts.requireAuth && !config.apiKey) {
25
+ throw new CliError("No API key configured.", {
26
+ code: "AUTH_REQUIRED",
27
+ exitCode: EXIT_CODES.AUTH,
28
+ suggestion: "First-time setup: run 'kash setup' (interactive wizard). Otherwise: 'kash auth set-key' or set KASH_API_KEY."
29
+ });
30
+ }
31
+ const baseUrl = globals?.baseUrl ?? config.baseUrl;
32
+ const client = new KashClient({
33
+ ...config.apiKey === void 0 ? {} : { apiKey: config.apiKey },
34
+ baseUrl,
35
+ ...globals?.maxRetries === void 0 ? {} : { maxRetries: globals.maxRetries },
36
+ ...globals?.timeoutMs === void 0 ? {} : { timeoutMs: globals.timeoutMs },
37
+ // Forward the public-API contract pin from `--api-version` (or
38
+ // `KASH_API_VERSION` env, if a future iteration plumbs it). The
39
+ // SDK validates the date format and sends the value as the
40
+ // `X-Kash-Api-Version` request header; the public-api routes the
41
+ // request through a version-appropriate code path.
42
+ ...globals?.apiVersion === void 0 ? {} : { apiVersion: globals.apiVersion },
43
+ userAgentSuffix: `kash-cli/${CLI_VERSION}`,
44
+ ...globals?.debug === true ? { hooks: buildDebugHooks(globals.json) } : {}
45
+ });
46
+ return { client, config };
47
+ }
48
+ function buildDebugHooks(json) {
49
+ const emit = (record) => {
50
+ if (json) {
51
+ process.stderr.write(`${JSON.stringify(record)}
52
+ `);
53
+ return;
54
+ }
55
+ const { event, method, url, status, attempt, durationMs, reason, delayMs, code } = record;
56
+ const prefix = style.dim("[debug]");
57
+ const path = typeof url === "string" ? url : "";
58
+ const tag = String(event);
59
+ if (event === "request") {
60
+ process.stderr.write(
61
+ `${prefix} ${tag} ${String(method)} ${path} (attempt ${String(attempt)})
62
+ `
63
+ );
64
+ } else if (event === "response") {
65
+ process.stderr.write(
66
+ `${prefix} ${tag} ${String(status)} ${String(method)} ${path} ${String(durationMs)}ms
67
+ `
68
+ );
69
+ } else if (event === "retry") {
70
+ process.stderr.write(
71
+ `${prefix} ${tag} ${String(method)} ${path} reason=${String(reason)} delay=${String(delayMs)}ms
72
+ `
73
+ );
74
+ } else if (event === "error") {
75
+ process.stderr.write(
76
+ `${prefix} ${tag} ${String(code)} ${String(method)} ${path} ${String(status ?? "-")} ${String(durationMs)}ms
77
+ `
78
+ );
79
+ }
80
+ };
81
+ return {
82
+ onRequest: (event) => {
83
+ emit({
84
+ event: "request",
85
+ method: event.method,
86
+ url: event.url,
87
+ attempt: event.attempt,
88
+ idempotencyKey: event.idempotencyKey ?? null
89
+ });
90
+ },
91
+ onResponse: (event) => {
92
+ emit({
93
+ event: "response",
94
+ method: event.method,
95
+ url: event.url,
96
+ attempt: event.attempt,
97
+ status: event.status,
98
+ durationMs: event.durationMs,
99
+ requestId: event.requestId ?? null
100
+ });
101
+ },
102
+ onRetry: (event) => {
103
+ emit({
104
+ event: "retry",
105
+ method: event.method,
106
+ url: event.url,
107
+ attempt: event.attempt,
108
+ reason: event.reason,
109
+ delayMs: event.delayMs
110
+ });
111
+ },
112
+ onError: (event) => {
113
+ emit({
114
+ event: "error",
115
+ method: event.method,
116
+ url: event.url,
117
+ attempt: event.attempt,
118
+ status: event.status ?? null,
119
+ code: event.code,
120
+ durationMs: event.durationMs
121
+ });
122
+ }
123
+ };
124
+ }
125
+
126
+ export {
127
+ buildClient
128
+ };
129
+ //# sourceMappingURL=chunk-QJMF73M5.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/utils/client.ts"],"sourcesContent":["/**\n * Build a `KashClient` from the resolved CLI configuration plus\n * per-invocation global flag overrides.\n *\n * Centralised here so command modules don't each have to know how to\n * read config, surface \"missing API key\" errors, or thread base URLs.\n * Every command that hits `api.kash.bot` must pass `requireAuth: true`\n * so a missing API key fails fast at the call site with a structured\n * `CliError` pointing the user at `kash auth set-key`. Only the offline\n * commands (`kash explain`, `kash --version`, `kash --help`) and the\n * `kash health` probe construct a client without `requireAuth`.\n *\n * Three layers feed the final SDK config:\n *\n * 1. The active profile + env vars (resolved by config-store).\n * 2. Per-invocation `--profile` / `--config` overrides (which select\n * *which* profile and file feed step 1).\n * 3. Per-invocation `--base-url` / `--max-retries` / `--timeout-ms`\n * flags, which override the SDK's runtime config directly.\n *\n * `--debug` wires a stderr-emitting hook into the SDK's lifecycle so\n * a developer or AI agent can see request/response/retry/error\n * traces without instrumenting the SDK.\n */\n\nimport { KashClient, type KashClientHooks } from '@kashdao/sdk';\n\nimport { CliError, EXIT_CODES } from '../errors.js';\nimport { CLI_VERSION } from '../version.js';\n\nimport { readConfig, type ResolvedConfig } from './config-store.js';\nimport { style } from './output.js';\n\nimport type { GlobalOptions } from './global-options.js';\n\nexport type ClientOptions = {\n /** When true, throw `CliError` if no API key is configured. */\n readonly requireAuth?: boolean;\n /**\n * Global flags from the root program. Optional so internal callers\n * (and tests) can build a client without going through Commander.\n */\n readonly globals?: GlobalOptions;\n};\n\nexport type BuiltClient = {\n readonly client: KashClient;\n readonly config: ResolvedConfig;\n};\n\nexport async function buildClient(opts: ClientOptions = {}): Promise<BuiltClient> {\n const globals = opts.globals;\n const config = await readConfig({\n ...(globals?.profile === undefined ? {} : { profile: globals.profile }),\n ...(globals?.configPath === undefined ? {} : { configPath: globals.configPath }),\n });\n\n if (opts.requireAuth && !config.apiKey) {\n throw new CliError('No API key configured.', {\n code: 'AUTH_REQUIRED',\n exitCode: EXIT_CODES.AUTH,\n suggestion:\n \"First-time setup: run 'kash setup' (interactive wizard). Otherwise: 'kash auth set-key' or set KASH_API_KEY.\",\n });\n }\n\n // Per-invocation overrides win over the resolved profile+env config.\n const baseUrl = globals?.baseUrl ?? config.baseUrl;\n\n const client = new KashClient({\n ...(config.apiKey === undefined ? {} : { apiKey: config.apiKey }),\n baseUrl,\n ...(globals?.maxRetries === undefined ? {} : { maxRetries: globals.maxRetries }),\n ...(globals?.timeoutMs === undefined ? {} : { timeoutMs: globals.timeoutMs }),\n // Forward the public-API contract pin from `--api-version` (or\n // `KASH_API_VERSION` env, if a future iteration plumbs it). The\n // SDK validates the date format and sends the value as the\n // `X-Kash-Api-Version` request header; the public-api routes the\n // request through a version-appropriate code path.\n ...(globals?.apiVersion === undefined ? {} : { apiVersion: globals.apiVersion }),\n userAgentSuffix: `kash-cli/${CLI_VERSION}`,\n ...(globals?.debug === true ? { hooks: buildDebugHooks(globals.json) } : {}),\n });\n\n return { client, config };\n}\n\n/**\n * Build SDK lifecycle hooks that emit traces to stderr.\n *\n * In `--debug --json` mode each event is a one-line JSON record so an\n * AI agent can stream-parse them. In human mode they're a compact\n * prefix plus method/path/status — close to what an HTTP client like\n * `curl -v` would surface, but at the SDK boundary so retries are\n * visible.\n *\n * Quiet mode silences nothing here: `--debug` is opt-in and the user\n * who passed it wants to see traces, full stop.\n */\nfunction buildDebugHooks(json: boolean): KashClientHooks {\n // Use stderr directly rather than the `log` helpers because the\n // hooks fire synchronously inside the SDK and we want zero overhead\n // in the common case (no quiet check, no chalk wrapping).\n const emit = (record: Record<string, unknown>): void => {\n if (json) {\n process.stderr.write(`${JSON.stringify(record)}\\n`);\n return;\n }\n const { event, method, url, status, attempt, durationMs, reason, delayMs, code } = record;\n const prefix = style.dim('[debug]');\n const path = typeof url === 'string' ? url : '';\n const tag = String(event);\n if (event === 'request') {\n process.stderr.write(\n `${prefix} ${tag} ${String(method)} ${path} (attempt ${String(attempt)})\\n`\n );\n } else if (event === 'response') {\n process.stderr.write(\n `${prefix} ${tag} ${String(status)} ${String(method)} ${path} ${String(durationMs)}ms\\n`\n );\n } else if (event === 'retry') {\n process.stderr.write(\n `${prefix} ${tag} ${String(method)} ${path} reason=${String(reason)} delay=${String(delayMs)}ms\\n`\n );\n } else if (event === 'error') {\n process.stderr.write(\n `${prefix} ${tag} ${String(code)} ${String(method)} ${path} ${String(status ?? '-')} ${String(durationMs)}ms\\n`\n );\n }\n };\n\n return {\n onRequest: (event) => {\n emit({\n event: 'request',\n method: event.method,\n url: event.url,\n attempt: event.attempt,\n idempotencyKey: event.idempotencyKey ?? null,\n });\n },\n onResponse: (event) => {\n emit({\n event: 'response',\n method: event.method,\n url: event.url,\n attempt: event.attempt,\n status: event.status,\n durationMs: event.durationMs,\n requestId: event.requestId ?? null,\n });\n },\n onRetry: (event) => {\n emit({\n event: 'retry',\n method: event.method,\n url: event.url,\n attempt: event.attempt,\n reason: event.reason,\n delayMs: event.delayMs,\n });\n },\n onError: (event) => {\n emit({\n event: 'error',\n method: event.method,\n url: event.url,\n attempt: event.attempt,\n status: event.status ?? null,\n code: event.code,\n durationMs: event.durationMs,\n });\n },\n };\n}\n"],"mappings":"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","names":[]}