@kashdao/cli 0.1.1 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +96 -0
- package/README.md +78 -27
- package/dist/{account-CVJZ3HVT.js → account-QHPOIZ6I.js} +9 -9
- package/dist/{auth-V5WVZBCO.js → auth-TE7BCN5Q.js} +12 -12
- package/dist/{auth-V5WVZBCO.js.map → auth-TE7BCN5Q.js.map} +1 -1
- package/dist/{chunk-MQUJG224.js → chunk-2HFKULGG.js} +2 -2
- package/dist/{chunk-BVFDUI73.js → chunk-2SR77BYA.js} +10 -9
- package/dist/chunk-2SR77BYA.js.map +1 -0
- package/dist/chunk-6QSTFU4R.js +9 -0
- package/dist/{chunk-KAM6KOFD.js.map → chunk-6QSTFU4R.js.map} +1 -1
- package/dist/{chunk-R4NGUZKN.js → chunk-6UU43MF5.js} +5 -3
- package/dist/chunk-6UU43MF5.js.map +1 -0
- package/dist/{chunk-A3IH5HUV.js → chunk-AROIP4EQ.js} +3 -3
- package/dist/chunk-AROIP4EQ.js.map +1 -0
- package/dist/{chunk-QALDZHTX.js → chunk-H2QGFDZP.js} +9 -11
- package/dist/chunk-H2QGFDZP.js.map +1 -0
- package/dist/{chunk-L4AFV2HK.js → chunk-IOLKGV45.js} +3 -3
- package/dist/chunk-IOLKGV45.js.map +1 -0
- package/dist/{chunk-KADB3OKE.js → chunk-VFBQVUL4.js} +2 -2
- package/dist/{chunk-X2VM6VKM.js → chunk-WL7XUYPH.js} +19 -20
- package/dist/chunk-WL7XUYPH.js.map +1 -0
- package/dist/{chunk-N26VGVGB.js → chunk-ZSMI63N3.js} +109 -37
- package/dist/chunk-ZSMI63N3.js.map +1 -0
- package/dist/client-REDY6K7J.js +14 -0
- package/dist/{completion-PK74S3EZ.js → completion-MXTJYK3D.js} +4 -4
- package/dist/{config-T5U6E6KU.js → config-TIJXUJ5N.js} +32 -23
- package/dist/config-TIJXUJ5N.js.map +1 -0
- package/dist/{config-store-UTFDUCKW.js → config-store-INJY4DDT.js} +6 -4
- package/dist/{docs-VA7CJ2ZF.js → docs-M5RZAQ6P.js} +5 -5
- package/dist/{eoa-RL3NLN3H.js → eoa-N55DYIN3.js} +18 -16
- package/dist/eoa-N55DYIN3.js.map +1 -0
- package/dist/{errors-2NNXRNES.js → errors-SMR7X5DU.js} +2 -2
- package/dist/{explain-3SPMWDYX.js → explain-HIDEZVLV.js} +31 -8
- package/dist/explain-HIDEZVLV.js.map +1 -0
- package/dist/{global-options-WFQ6CEFK.js → global-options-YONBFB7N.js} +4 -4
- package/dist/{health-2KD6XUTJ.js → health-LSJKP3NH.js} +9 -9
- package/dist/index.js +31 -28
- package/dist/index.js.map +1 -1
- package/dist/{markets-H6EK2XDE.js → markets-AGL6Q2ES.js} +23 -12
- package/dist/markets-AGL6Q2ES.js.map +1 -0
- package/dist/{output-FGTXXZEQ.js → output-M4NFSWGW.js} +4 -4
- package/dist/{portfolio-R63D5QB2.js → portfolio-RBRVUEYP.js} +11 -11
- package/dist/{portfolio-R63D5QB2.js.map → portfolio-RBRVUEYP.js.map} +1 -1
- package/dist/{protocol-ZB6R25IM.js → protocol-WYQAKN6N.js} +18 -16
- package/dist/protocol-WYQAKN6N.js.map +1 -0
- package/dist/{quote-OF6BTAPQ.js → quote-6CNHU36A.js} +10 -10
- package/dist/redeem-JESSTZ46.js +108 -0
- package/dist/redeem-JESSTZ46.js.map +1 -0
- package/dist/{schema-3HQN4RFL.js → schema-6EXADLRW.js} +12 -8
- package/dist/{schema-3HQN4RFL.js.map → schema-6EXADLRW.js.map} +1 -1
- package/dist/{setup-IIPQXLPL.js → setup-H46GTSVW.js} +8 -8
- package/dist/{trace-WMO3KDHS.js → trace-VDBWWTYL.js} +10 -10
- package/dist/{trade-YBCI6XD2.js → trade-FTSY23QG.js} +10 -10
- package/dist/trade-FTSY23QG.js.map +1 -0
- package/dist/{version-WB2ACHMU.js → version-JTLF3464.js} +7 -7
- package/dist/{version-check-PRAVPFC6.js → version-check-7JGBY6FZ.js} +4 -4
- package/dist/{webhooks-DVS55O4T.js → webhooks-JEWNX5HZ.js} +10 -10
- package/dist/{webhooks-DVS55O4T.js.map → webhooks-JEWNX5HZ.js.map} +1 -1
- package/dist/{with-retry-2MD2CNK2.js → with-retry-DYSHNLG7.js} +12 -6
- package/dist/with-retry-DYSHNLG7.js.map +1 -0
- package/package.json +5 -5
- package/dist/chunk-A3IH5HUV.js.map +0 -1
- package/dist/chunk-BVFDUI73.js.map +0 -1
- package/dist/chunk-KAM6KOFD.js +0 -9
- package/dist/chunk-L4AFV2HK.js.map +0 -1
- package/dist/chunk-N26VGVGB.js.map +0 -1
- package/dist/chunk-QALDZHTX.js.map +0 -1
- package/dist/chunk-R4NGUZKN.js.map +0 -1
- package/dist/chunk-X2VM6VKM.js.map +0 -1
- package/dist/client-75FVHGOH.js +0 -14
- package/dist/config-T5U6E6KU.js.map +0 -1
- package/dist/eoa-RL3NLN3H.js.map +0 -1
- package/dist/explain-3SPMWDYX.js.map +0 -1
- package/dist/markets-H6EK2XDE.js.map +0 -1
- package/dist/protocol-ZB6R25IM.js.map +0 -1
- package/dist/trade-YBCI6XD2.js.map +0 -1
- package/dist/with-retry-2MD2CNK2.js.map +0 -1
- /package/dist/{account-CVJZ3HVT.js.map → account-QHPOIZ6I.js.map} +0 -0
- /package/dist/{chunk-MQUJG224.js.map → chunk-2HFKULGG.js.map} +0 -0
- /package/dist/{chunk-KADB3OKE.js.map → chunk-VFBQVUL4.js.map} +0 -0
- /package/dist/{client-75FVHGOH.js.map → client-REDY6K7J.js.map} +0 -0
- /package/dist/{completion-PK74S3EZ.js.map → completion-MXTJYK3D.js.map} +0 -0
- /package/dist/{config-store-UTFDUCKW.js.map → config-store-INJY4DDT.js.map} +0 -0
- /package/dist/{docs-VA7CJ2ZF.js.map → docs-M5RZAQ6P.js.map} +0 -0
- /package/dist/{errors-2NNXRNES.js.map → errors-SMR7X5DU.js.map} +0 -0
- /package/dist/{global-options-WFQ6CEFK.js.map → global-options-YONBFB7N.js.map} +0 -0
- /package/dist/{health-2KD6XUTJ.js.map → health-LSJKP3NH.js.map} +0 -0
- /package/dist/{output-FGTXXZEQ.js.map → output-M4NFSWGW.js.map} +0 -0
- /package/dist/{quote-OF6BTAPQ.js.map → quote-6CNHU36A.js.map} +0 -0
- /package/dist/{setup-IIPQXLPL.js.map → setup-H46GTSVW.js.map} +0 -0
- /package/dist/{trace-WMO3KDHS.js.map → trace-VDBWWTYL.js.map} +0 -0
- /package/dist/{version-WB2ACHMU.js.map → version-JTLF3464.js.map} +0 -0
- /package/dist/{version-check-PRAVPFC6.js.map → version-check-7JGBY6FZ.js.map} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/commands/webhooks/index.ts","../src/commands/webhooks/list.ts","../src/commands/webhooks/redeliver.ts","../src/commands/webhooks/replay.ts","../src/commands/webhooks/rotate-secret.ts","../src/commands/webhooks/verify.ts"],"sourcesContent":["/**\n * `kash webhooks` — manage webhook delivery for the authenticating\n * API key.\n */\n\nimport { Command } from 'commander';\n\nimport { listWebhookEventsCommand } from './list.js';\nimport { redeliverCommand } from './redeliver.js';\nimport { replayWebhookCommand } from './replay.js';\nimport { rotateSecretCommand } from './rotate-secret.js';\nimport { verifyWebhookCommand } from './verify.js';\n\nexport const webhooksCommand = new Command('webhooks')\n .description('Manage webhook delivery, signing secrets, and signature verification.')\n .addCommand(listWebhookEventsCommand)\n .addCommand(rotateSecretCommand)\n .addCommand(redeliverCommand)\n .addCommand(verifyWebhookCommand)\n .addCommand(replayWebhookCommand);\n","/**\n * `kash webhooks list` — paginated listing of webhook delivery events\n * for the authenticating key.\n *\n * Wraps `GET /v1/webhooks/events` (DX6). The same shape that surfaces\n * inline on a trade resource's `webhookDelivery` field is now also\n * accessible as a flat feed — useful for auditing what shipped and\n * triaging delivery failures without N trade lookups.\n *\n * Filter shape mirrors the SDK: `--status` accepts a comma-separated\n * list of derived statuses or can be repeated. Both forms collapse to\n * the same comma-string the server expects.\n *\n * Flag conventions mirror `kash markets list` and `kash trade list`:\n * `-l/-c/-a` short forms for `--limit/--cursor/--all`, default page\n * size 20, `--ndjson` streaming for AI-agent consumption.\n */\n\nimport { Command } from 'commander';\n\nimport { CliValidationError, toCliError } from '../../errors.js';\nimport { buildClient } from '../../utils/client.js';\nimport { parsePositiveInt, readGlobals } from '../../utils/global-options.js';\nimport { log, print, printJson, style, writeNdjson } from '../../utils/output.js';\n\nimport type { WebhookEventResource } from '@kashdao/sdk';\n\nconst STATUS_VALUES = ['none', 'pending', 'delivered', 'retrying', 'failed'] as const;\ntype StatusValue = (typeof STATUS_VALUES)[number];\n\ntype ListOptions = {\n readonly limit?: string;\n readonly cursor?: string;\n readonly status?: readonly StatusValue[];\n readonly all?: boolean;\n readonly ndjson?: boolean;\n};\n\nexport const listWebhookEventsCommand = new Command('list')\n .description('List recent webhook delivery events for the calling key.')\n .option('-l, --limit <n>', 'page size (1-100)', '20')\n .option('-c, --cursor <cursor>', 'opaque cursor from a previous response')\n .option('-a, --all', 'walk every page (subject to --limit per page)', false)\n .option(\n '--ndjson',\n 'stream results as newline-delimited JSON (one record per line); implies --all'\n )\n // Note: we deliberately do NOT use Commander's `.choices()` here.\n // Commander validates each variadic entry against the choices list\n // BEFORE our normaliser runs, so `--status failed,retrying` (a\n // single comma-string entry) gets rejected as not matching any\n // choice. The normaliser below splits commas first and then\n // validates, so both shapes (`--status failed,retrying` and\n // `--status failed --status retrying`) work.\n .option(\n '--status <status...>',\n `filter by derived status (one of: ${STATUS_VALUES.join(', ')}); repeat or comma-separate`\n )\n .addHelpText(\n 'after',\n `\nExamples:\n $ kash webhooks list\n $ kash webhooks list --status failed --status retrying\n $ kash webhooks list --status failed,retrying -l 100\n $ kash webhooks list --all --json | jq '.[] | select(.status == \"failed\")'\n $ kash webhooks list --ndjson | while read -r line; do echo \"$line\" | jq -r .id; done\n`\n )\n .action(async (opts: ListOptions, cmd: Command) => {\n const globals = readGlobals(cmd);\n\n const limit = opts.limit === undefined ? 20 : parsePositiveInt(opts.limit, 'limit');\n if (limit < 1 || limit > 100) {\n throw new CliValidationError('--limit must be between 1 and 100.', undefined, 'limit');\n }\n\n // `--status failed,retrying` (single comma-string) and\n // `--status failed --status retrying` (repeated) both reach us\n // as an array; flatten + split + dedupe so the SDK call is uniform.\n const status = normaliseStatus(opts.status);\n\n try {\n const { client } = await buildClient({ requireAuth: true, globals });\n const page = await client.webhooks.list({\n limit,\n ...(opts.cursor === undefined ? {} : { cursor: opts.cursor }),\n ...(status === undefined ? {} : { status }),\n });\n\n if (opts.ndjson === true) {\n // Stream every record as one line of NDJSON to stdout. Avoids\n // buffering the full result set — the agent reads as we go.\n for await (const event of page) {\n writeNdjson(event);\n }\n return;\n }\n\n if (opts.all === true) {\n const collected: WebhookEventResource[] = [];\n for await (const event of page) collected.push(event);\n if (globals.json) {\n // Canonical `{data, pagination, count}` — see markets/list.ts\n // for rationale. Same shape across single-page and walked\n // modes lets agents pin to one schema.\n printJson({\n data: collected,\n pagination: { hasMore: false, cursor: null },\n count: collected.length,\n });\n return;\n }\n renderRows(collected);\n return;\n }\n\n // Default: first page only.\n if (globals.json) {\n printJson({\n data: page.data,\n pagination: page.pagination,\n count: page.data.length,\n });\n return;\n }\n renderRows(page.data);\n if (page.pagination.hasMore && page.pagination.cursor) {\n log.detail('Next page', `--cursor ${page.pagination.cursor}`);\n }\n } catch (cause) {\n throw toCliError(cause);\n }\n });\n\nfunction renderRows(rows: readonly WebhookEventResource[]): void {\n if (rows.length === 0) {\n print(style.dim(' (no webhook events)'));\n return;\n }\n print('');\n for (const row of rows) {\n const statusBadge = renderStatus(row.status);\n const tradeCol = row.tradeRequestId ? row.tradeRequestId.slice(0, 8) : style.dim('—');\n print(\n ` ${statusBadge} ${style.dim(row.id.slice(0, 8))} ` +\n `${row.eventType.padEnd(28)} trade=${tradeCol} ` +\n `attempts=${String(row.delivery.attempts).padStart(2)} ` +\n `${style.dim(row.emittedAt)}`\n );\n }\n print('');\n}\n\nfunction normaliseStatus(raw: ListOptions['status']): readonly StatusValue[] | undefined {\n if (!raw || raw.length === 0) return undefined;\n // Each element may itself be comma-separated. Flatten, dedupe, and\n // validate. We throw on unknown statuses (instead of silently\n // dropping them) so a typo like `--status faild` surfaces as\n // INVALID_INPUT rather than a confusing empty result set.\n const seen = new Set<StatusValue>();\n for (const entry of raw) {\n for (const piece of entry.split(',')) {\n const trimmed = piece.trim();\n if (trimmed.length === 0) continue;\n if (!(STATUS_VALUES as readonly string[]).includes(trimmed)) {\n throw new CliValidationError(\n `Unknown --status \"${trimmed}\".`,\n `Allowed: ${STATUS_VALUES.join(', ')}.`,\n 'status'\n );\n }\n seen.add(trimmed as StatusValue);\n }\n }\n return seen.size === 0 ? undefined : [...seen];\n}\n\nfunction renderStatus(status: WebhookEventResource['status']): string {\n switch (status) {\n case 'delivered':\n return style.success('✓ delivered');\n case 'failed':\n return style.error('✗ failed ');\n case 'retrying':\n return style.warn('⟳ retrying ');\n case 'pending':\n return style.dim('… pending ');\n case 'none':\n return style.dim('· none ');\n default: {\n // Exhaustiveness guard — if a new status is added to the\n // union, TS surfaces the missing case here at compile time\n // rather than the function silently returning `undefined`.\n const _exhaustive: never = status;\n return _exhaustive;\n }\n }\n}\n","/**\n * `kash webhooks redeliver <eventId>` — re-queue an existing webhook\n * for delivery. Useful when a downstream receiver was offline.\n */\n\nimport { Command } from 'commander';\n\nimport { CliValidationError, toCliError } from '../../errors.js';\nimport { buildClient } from '../../utils/client.js';\nimport { formatDate, shortId } from '../../utils/formatting.js';\nimport { readGlobals } from '../../utils/global-options.js';\nimport { log, print, printJson, style } from '../../utils/output.js';\n\n/**\n * Event ID shape: a UUID v1/v3/v4/v5 — matches what\n * `WebhookEventResourceSchema.id` (`z.string().uuid()`) accepts. We\n * pre-flight the shape so a typo (a trade id with surrounding\n * quotes, a partial paste, the wrong field) surfaces as\n * `INVALID_INPUT` before we burn an API round-trip.\n */\nconst EVENT_ID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\nexport const redeliverCommand = new Command('redeliver')\n .description('Re-queue a webhook event for delivery.')\n .argument('<eventId>', 'webhook event UUID')\n .addHelpText(\n 'after',\n `\nExamples:\n $ kash webhooks redeliver 99999999-9999-9999-9999-999999999999\n $ kash webhooks redeliver \"$(kash webhooks list --json --quiet | jq -r '.data[0].id')\"\n`\n )\n .action(async (eventId: string, _opts, cmd: Command) => {\n const globals = readGlobals(cmd);\n if (!EVENT_ID_REGEX.test(eventId)) {\n throw new CliValidationError(\n `<eventId> must be a UUID.`,\n `Got \"${eventId}\". Look up the id in \\`kash webhooks list --json --quiet | jq -r '.data[].id'\\`.`,\n 'eventId'\n );\n }\n let event;\n try {\n const { client } = await buildClient({ requireAuth: true, globals });\n event = await client.webhooks.redeliver(eventId);\n } catch (cause) {\n throw toCliError(cause);\n }\n\n if (globals.json) {\n printJson(event);\n return;\n }\n\n // All labels are padded to 16 chars so the values line up in a\n // visually consistent column. Show the FULL event id (not a\n // shortId) so the operator can correlate against logs / docs\n // without re-running `kash webhooks list`.\n log.success(`Re-delivery queued for ${event.id}.`);\n print(` ${style.dim('Event id ')} ${event.id}`);\n print(` ${style.dim('Event type ')} ${event.eventType}`);\n print(` ${style.dim('Emitted at ')} ${formatDate(event.emittedAt)}`);\n print(` ${style.dim('Attempts ')} ${String(event.deliveryAttempts)}`);\n if (event.lastDeliveredAt) {\n print(` ${style.dim('Last delivered ')} ${formatDate(event.lastDeliveredAt)}`);\n }\n // Also keep `shortId` available for CI logs that don't want the\n // full UUID — surface as a hint, not the primary value.\n log.detail('Short id', shortId(event.id));\n });\n","/**\n * `kash webhooks replay <body-file> --target <url> --secret <s>` —\n * sign a captured webhook payload with a local secret and POST it to\n * a target URL. Pairs with `kash webhooks verify` for round-trip\n * testing against a development tunnel (ngrok, cloudflared) or a\n * local server.\n *\n * **Why a built-in.** The signing format is `t=<unix-ms>,v1=<hex>`\n * (Stripe-compatible). Replicating the HMAC + header construction in\n * shell + jq is tedious and error-prone; this command does it\n * deterministically.\n *\n * **Privacy.** The body is read verbatim and forwarded — `kash\n * webhooks replay` is intentionally a thin wrapper over\n * `fetch(target)`, NOT an event reformatter. If your captured payload\n * contains PII, redact it locally before passing it in.\n *\n * **Custom timestamp.** Defaults to `Date.now()`. Pass `--timestamp-ms`\n * to replay an old payload — useful for testing the receiver's\n * replay-window enforcement (e.g. \"does my server reject deliveries\n * older than 5 minutes?\").\n */\n\nimport { createHmac } from 'node:crypto';\nimport { readFile } from 'node:fs/promises';\n\nimport { Command } from 'commander';\n\nimport { CliError, CliValidationError, toCliError } from '../../errors.js';\nimport { parsePositiveInt, readGlobals } from '../../utils/global-options.js';\nimport { log, print, printJson, style } from '../../utils/output.js';\n\ntype ReplayOptions = {\n target: string;\n secret?: string;\n secretFile?: string;\n secretEnv?: string;\n timestampMs?: string;\n signatureHeader?: string;\n timeoutMs?: string;\n dryRun?: boolean;\n refusePrivateAddresses?: boolean;\n};\n\nconst SIGNATURE_HEADER_DEFAULT = 'X-Kash-Signature';\n\nexport const replayWebhookCommand = new Command('replay')\n .description('Re-sign a captured webhook payload and POST it to a target URL.')\n .argument('[body]', 'path to a JSON body file (omit or pass \"-\" for stdin)')\n .requiredOption('-t, --target <url>', 'destination URL (e.g. ngrok tunnel or localhost endpoint)')\n .option('-s, --secret <secret>', 'webhook signing secret (overrides KASH_WEBHOOK_SECRET)')\n .option(\n '--secret-file <path>',\n 'read the signing secret from a file (preferred — keeps the value out of argv and env)'\n )\n .option(\n '--secret-env <name>',\n 'read the signing secret from this environment variable (default: KASH_WEBHOOK_SECRET)'\n )\n .option(\n '--timestamp-ms <ms>',\n 'unix-ms timestamp for the signature header (default: current time)'\n )\n .option(\n '--signature-header <name>',\n `override the signature header name (default: ${SIGNATURE_HEADER_DEFAULT})`\n )\n .option('--timeout-ms <ms>', 'fetch timeout (default 10000)')\n .option(\n '--dry-run',\n 'compute the signature header and inspect the would-be POST without sending it'\n )\n .option(\n '--refuse-private-addresses',\n 'hard-fail (instead of warning) when --target is a loopback / private / link-local address — recommended for CI'\n )\n .addHelpText(\n 'after',\n `\nExamples:\n # Auto-reads KASH_WEBHOOK_SECRET (matches \\`kash webhooks verify\\`).\n $ KASH_WEBHOOK_SECRET=whsec_… kash webhooks replay payload.json -t http://localhost:3000/webhook\n\n # Explicit secret file (preferred — keeps the value out of argv/env).\n $ kash webhooks replay payload.json -t http://localhost:3000/webhook --secret-file ~/.kash/webhook.secret\n\n # Pipe captured payload from stdin.\n $ cat payload.json | kash webhooks replay - -t … -s whsec_…\n\n # CI guardrails: refuse private targets + inspect the signed bytes\n # before sending.\n $ kash webhooks replay payload.json -t https://api.example.com --secret-file s.txt --refuse-private-addresses\n $ kash webhooks replay payload.json -t https://api.example.com --secret-file s.txt --dry-run --json\n\nNotes:\n - The signature format is \\`t=<unix-ms>,v1=<hex-hmac-sha256>\\` —\n Stripe-compatible. The receiver verifies via \\`kash webhooks verify\\`\n or the SDK's \\`webhooks.verifySignature\\`.\n - Defaults the timestamp to \\`Date.now()\\`. Pass \\`--timestamp-ms\\`\n with an old timestamp to test the receiver's replay-window\n rejection (the SDK's default is 5 min).\n - Secret resolution order: --secret > --secret-file > --secret-env\n > KASH_WEBHOOK_SECRET. --secret-file is preferred — it keeps the\n value out of argv (where \\`ps\\` could see it) and out of env (where\n a child process would inherit it).\n - --dry-run is safe to run anywhere — it never opens a network\n socket. Use it to inspect the signature header before pointing\n a fresh receiver at production traffic.\n`\n )\n .action(async (body: string | undefined, options: ReplayOptions, cmd: Command) => {\n const globals = readGlobals(cmd);\n try {\n // Resolve secret: --secret > --secret-file > --secret-env > KASH_WEBHOOK_SECRET.\n // Refuse if all are missing/empty.\n const secret = await resolveSecret(options);\n\n // Resolve target (URL validation + private-address policy). The\n // policy is hard-refuse when --refuse-private-addresses is set\n // (recommended for CI), otherwise warn-only (the historical\n // default — needed for local-tunnel testing).\n const targetUrl = validateTarget(options.target, {\n refusePrivate: options.refusePrivateAddresses === true,\n });\n\n // Read the body verbatim. We sign whatever bytes the user\n // captured — bit-exact match is what the receiver's verifier\n // expects.\n const rawBody = await readBody(body);\n\n const timestampMs = options.timestampMs\n ? parsePositiveInt(options.timestampMs, 'timestamp-ms')\n : Date.now();\n const timeoutMs = options.timeoutMs\n ? parsePositiveInt(options.timeoutMs, 'timeout-ms')\n : 10_000;\n\n // Compute HMAC: `t=<ms>,v1=<hex>` over the canonical\n // `<timestamp>.<body>` payload. Same shape `verifySignature`\n // checks against.\n const headerName = options.signatureHeader ?? SIGNATURE_HEADER_DEFAULT;\n const headerValue = computeSignatureHeader(rawBody, secret, timestampMs);\n\n // --dry-run short-circuit: emit the would-be POST envelope and\n // return without opening a network socket. Useful for previewing\n // the signature against a fresh receiver before pointing real\n // traffic at it.\n if (options.dryRun === true) {\n if (globals.json) {\n printJson({\n targetUrl,\n headerName,\n headerValue,\n timestampMs,\n bodyBytes: Buffer.byteLength(rawBody, 'utf8'),\n dryRun: true,\n });\n return;\n }\n print('');\n log.success('Dry run — signature computed, no request sent.');\n print(` ${style.dim('Target ')} ${targetUrl}`);\n print(` ${style.dim('Header ')} ${headerName}: ${headerValue}`);\n print(` ${style.dim('Body ')} ${String(Buffer.byteLength(rawBody, 'utf8'))} bytes`);\n log.info('Re-run without --dry-run to send.');\n return;\n }\n\n // POST. Use `fetch` (Node 22+ ships it). Bound by a timeout so\n // a wedged target doesn't hang the CLI.\n const result = await deliver({\n targetUrl,\n body: rawBody,\n headerName,\n headerValue,\n timeoutMs,\n });\n\n if (globals.json) {\n printJson({\n targetUrl,\n headerName,\n headerValue,\n timestampMs,\n status: result.status,\n ok: result.ok,\n durationMs: result.durationMs,\n responseBody: result.body,\n });\n return;\n }\n\n // Human mode: brief result + the response body for inspection.\n print('');\n if (result.ok) {\n log.success(`Delivered in ${String(result.durationMs)}ms — HTTP ${String(result.status)}.`);\n } else {\n log.error(\n `Receiver returned HTTP ${String(result.status)} (after ${String(result.durationMs)}ms).`\n );\n }\n print(` ${style.dim('Target ')} ${targetUrl}`);\n print(` ${style.dim('Header ')} ${headerName}: ${headerValue}`);\n if (result.body.length > 0) {\n print('');\n print(style.dim('--- Response body ---'));\n print(result.body);\n }\n } catch (cause) {\n throw toCliError(cause);\n }\n });\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nasync function resolveSecret(options: ReplayOptions): Promise<string> {\n // Resolution order matches `kash webhooks verify` so the two\n // commands are interchangeable from the operator's POV:\n // 1. --secret (explicit; convenient but lands in argv)\n // 2. --secret-file (preferred — value never crosses argv/env)\n // 3. --secret-env <NAME> (read from named env var)\n // 4. KASH_WEBHOOK_SECRET (default env, no flag needed)\n if (options.secret !== undefined) return options.secret;\n if (options.secretFile !== undefined) {\n try {\n const raw = await readFile(options.secretFile, 'utf8');\n // Strip a trailing newline that text editors append; secrets\n // never include leading/trailing whitespace.\n return raw.trim();\n } catch {\n throw new CliValidationError(\n `--secret-file: failed to read ${options.secretFile}.`,\n 'Verify the file exists and is readable.',\n 'secret-file'\n );\n }\n }\n if (options.secretEnv !== undefined) {\n const value = process.env[options.secretEnv];\n if (!value || value.length === 0) {\n throw new CliError(`--secret-env ${options.secretEnv} is not set or empty.`, {\n code: 'INVALID_INPUT',\n recoverable: true,\n suggestion: `Set ${options.secretEnv}=<webhook-secret> before re-running.`,\n });\n }\n return value;\n }\n const fromEnv = process.env['KASH_WEBHOOK_SECRET'];\n if (fromEnv !== undefined && fromEnv.length > 0) return fromEnv;\n throw new CliValidationError(\n 'A signing secret is required.',\n 'Pass --secret-file (preferred), --secret, --secret-env <NAME>, or set KASH_WEBHOOK_SECRET.',\n 'secret'\n );\n}\n\nfunction validateTarget(raw: string, policy: { refusePrivate: boolean }): string {\n let url: URL;\n try {\n url = new URL(raw);\n } catch {\n throw new CliValidationError(\n '--target must be a valid URL.',\n `Got \"${raw}\". Include the scheme (http:// or https://).`,\n 'target'\n );\n }\n if (url.protocol !== 'http:' && url.protocol !== 'https:') {\n throw new CliValidationError(\n `--target must use http:// or https:// (got \"${url.protocol}\").`,\n undefined,\n 'target'\n );\n }\n // SSRF guard: by default warn when the target host is a loopback /\n // private / link-local address. `kash webhooks replay` is a\n // developer tool, not a server, so the risk is bounded — but a\n // misconfigured run against `169.254.169.254` (cloud metadata\n // service) or a VPN-bound private range can still exfiltrate\n // sensitive data through the signed POST body. The default warn\n // (instead of refuse) preserves local-tunnel testing (the common\n // case: `localhost:3000`, `127.0.0.1:3000`).\n //\n // CI runs and production-flavoured tests should pass\n // `--refuse-private-addresses` to upgrade the warning to a hard\n // failure — the policy parameter does that.\n if (policy.refusePrivate && isPrivateAddress(url)) {\n throw new CliValidationError(\n `--target points at a private address (${url.hostname}) and --refuse-private-addresses is set.`,\n 'Drop the flag to allow private targets, or change --target to a public URL.',\n 'target'\n );\n }\n warnIfPrivateAddress(url);\n return url.toString();\n}\n\n/**\n * Best-effort literal-IP-shape check. Matches:\n *\n * - IPv4 loopback (127/8 — silent, expected for dev tunnels)\n * - IPv4 link-local (169.254/16 — louder warning; cloud metadata\n * services live here)\n * - IPv4 private ranges (10/8, 172.16/12, 192.168/16)\n * - IPv6 loopback (::1)\n * - IPv6 link-local (fe80::/10)\n * - IPv6 unique local (fc00::/7, which covers fc00:: and fd00::)\n *\n * Hostnames are intentionally NOT resolved here — full DNS resolution\n * would couple this CLI command to an async resolver and add a\n * network round-trip on every replay. That means cloud metadata\n * hostnames like `metadata.google.internal` are NOT caught (they\n * resolve to `169.254.169.254` only at request time); operators\n * relying on hostname-based aliases for sensitive endpoints must\n * audit their `--target` values themselves. The literal-IP check\n * here catches the most common foot-gun: a copy-pasted\n * `169.254.169.254` or private-range URL.\n *\n * The IPv4 octet regex bounds octets to 0–255 explicitly so a\n * pathological host like `999.999.999.999` (which `URL` happily\n * accepts as a hostname string) does not silently coerce into a\n * matching octet via `parseInt`.\n */\nexport function warnIfPrivateAddress(url: URL): void {\n // `url.hostname` keeps the surrounding [...] for IPv6 literals\n // (per the WHATWG URL spec — Node, browsers, and the URL standard\n // all agree). Strip them for the prefix matching below so the\n // regexes deal with bare hex hextets.\n const rawHost = url.hostname;\n const host = rawHost.startsWith('[') && rawHost.endsWith(']') ? rawHost.slice(1, -1) : rawHost;\n\n // IPv6 loopback.\n if (host === '::1') {\n emitPrivateWarning(rawHost);\n return;\n }\n\n // Localhost name (and `*.localhost` per RFC 6761) — silent.\n if (host === 'localhost' || host.endsWith('.localhost')) return;\n\n // IPv6 link-local (fe80::/10) — first 10 bits are 1111 1110 10.\n // The leading hextet `fexx` ranges from fe80 to febf. The textual\n // prefix is therefore `fe` followed by one of `8`, `9`, `a`, `b`,\n // followed by any hex digit, followed by either `:` (short form,\n // e.g. `fe80::1`) or another hex digit then `:` (full form,\n // e.g. `fe80:0:…`). Match case-insensitively.\n if (/^fe[89ab][0-9a-f]{0,2}:/i.test(host)) {\n emitLinkLocalWarning(rawHost);\n return;\n }\n\n // IPv6 unique-local (fc00::/7) — first 7 bits are 1111 110, so any\n // address whose leading hextet starts with `fc` or `fd`. Allow up\n // to two more hex digits before the `:` separator (covers `fc::`,\n // `fc1::`, `fcab::`, `fd00::`, etc.).\n if (/^f[cd][0-9a-f]{0,2}:/i.test(host)) {\n emitPrivateWarning(rawHost);\n return;\n }\n\n // IPv4 octet match — bounded 0..255 so `999.999.999.999` doesn't\n // silently match. `URL` accepts these strings as hostnames, so the\n // bound matters.\n const ipv4 =\n /^(25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]?\\d)\\.(25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]?\\d)\\.(25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]?\\d)\\.(25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]?\\d)$/.exec(\n host\n );\n if (!ipv4) return; // hostname (no DNS check — see jsdoc)\n const [, a, b] = ipv4;\n const oct1 = Number.parseInt(a!, 10);\n const oct2 = Number.parseInt(b!, 10);\n if (oct1 === 127) return; // loopback — expected for `127.0.0.1` etc.\n if (oct1 === 10) {\n emitPrivateWarning(host);\n return;\n }\n if (oct1 === 172 && oct2 >= 16 && oct2 <= 31) {\n emitPrivateWarning(host);\n return;\n }\n if (oct1 === 192 && oct2 === 168) {\n emitPrivateWarning(host);\n return;\n }\n if (oct1 === 169 && oct2 === 254) {\n emitLinkLocalWarning(host);\n return;\n }\n}\n\n/**\n * Pure predicate version of {@link warnIfPrivateAddress} — same set\n * of literal-IP shapes (loopback, link-local, RFC1918 private,\n * IPv6 ::1 / fe80::/10 / fc00::/7), but returns a boolean instead of\n * writing to stderr. Used by the `--refuse-private-addresses` policy\n * to hard-fail the run; the warn-only path stays as before.\n *\n * Hostnames that aren't literal IPs are treated as \"not private\"\n * (same documented limitation as the warning function — no DNS).\n */\nexport function isPrivateAddress(url: URL): boolean {\n const rawHost = url.hostname;\n const host = rawHost.startsWith('[') && rawHost.endsWith(']') ? rawHost.slice(1, -1) : rawHost;\n\n if (host === '::1') return true;\n if (host === 'localhost' || host.endsWith('.localhost')) return true;\n if (/^fe[89ab][0-9a-f]{0,2}:/i.test(host)) return true;\n if (/^f[cd][0-9a-f]{0,2}:/i.test(host)) return true;\n\n const ipv4 =\n /^(25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]?\\d)\\.(25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]?\\d)\\.(25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]?\\d)\\.(25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]?\\d)$/.exec(\n host\n );\n if (!ipv4) return false;\n const oct1 = Number.parseInt(ipv4[1]!, 10);\n const oct2 = Number.parseInt(ipv4[2]!, 10);\n if (oct1 === 127) return true; // loopback\n if (oct1 === 10) return true;\n if (oct1 === 172 && oct2 >= 16 && oct2 <= 31) return true;\n if (oct1 === 192 && oct2 === 168) return true;\n if (oct1 === 169 && oct2 === 254) return true; // link-local\n return false;\n}\n\nfunction emitLinkLocalWarning(host: string): void {\n // Worth a louder warning since link-local is the textbook SSRF\n // target — AWS / GCP / Azure metadata services live here.\n process.stderr.write(\n `\\u26a0 --target points at a link-local address (${host}). This range hosts cloud metadata services. Verify the target is intentional.\\n`\n );\n}\n\nfunction emitPrivateWarning(host: string): void {\n process.stderr.write(\n `\\u26a0 --target points at a private address (${host}). Verify this is your dev tunnel and not an internal service.\\n`\n );\n}\n\nasync function readBody(file: string | undefined): Promise<string> {\n if (file !== undefined && file !== '-') {\n let raw: string;\n try {\n raw = await readFile(file, 'utf8');\n } catch (cause) {\n throw new CliError(`Failed to read ${file}.`, {\n code: 'CONFIGURATION',\n recoverable: true,\n suggestion: 'Verify the path exists and is readable.',\n cause,\n });\n }\n // Strip the BOM here too — the file branch is the more common\n // case when operators are testing with captured payloads dropped\n // by Notepad or VSCode-with-utf8bom.\n const { stripBom } = await import('../../utils/stdin.js');\n return stripBom(raw);\n }\n // Stdin.\n const chunks: Buffer[] = [];\n for await (const chunk of process.stdin) {\n if (typeof chunk === 'string') chunks.push(Buffer.from(chunk));\n else if (chunk instanceof Buffer) chunks.push(chunk);\n else chunks.push(Buffer.from(chunk as Uint8Array));\n }\n if (chunks.length === 0) {\n throw new CliValidationError(\n 'No input received on stdin.',\n 'Pass a file path or pipe a webhook body into stdin.'\n );\n }\n // Strip a leading UTF-8 BOM. Notepad / VSCode-with-utf8bom and many\n // Windows tools prepend U+FEFF; without the strip the receiver\n // would HMAC-verify a body that starts with the BOM, but its own\n // JSON.parse would fail (BOM isn't valid leading whitespace in\n // JSON). Strip on read so the signature matches the parsed body.\n const { stripBom } = await import('../../utils/stdin.js');\n return stripBom(Buffer.concat(chunks).toString('utf8'));\n}\n\n/**\n * Build the Stripe-compatible signature header. Format is exactly\n * `t=<unix-ms>,v1=<hex-hmac-sha256>` — same shape the SDK's\n * `parseSignatureHeader` accepts. We replicate the math here rather\n * than reaching into the SDK's private helpers (which aren't\n * exported); the impl is small and the algorithm is stable.\n */\nexport function computeSignatureHeader(body: string, secret: string, timestampMs: number): string {\n const payload = `${String(timestampMs)}.${body}`;\n const hex = createHmac('sha256', secret).update(payload).digest('hex');\n return `t=${String(timestampMs)},v1=${hex}`;\n}\n\ntype DeliveryResult = {\n readonly status: number;\n readonly ok: boolean;\n readonly body: string;\n readonly durationMs: number;\n};\n\nasync function deliver(args: {\n readonly targetUrl: string;\n readonly body: string;\n readonly headerName: string;\n readonly headerValue: string;\n readonly timeoutMs: number;\n}): Promise<DeliveryResult> {\n const start = Date.now();\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), args.timeoutMs);\n\n // The CLI is a published-to-npm public package that intentionally\n // has zero `@kashdao/*` runtime deps beyond `@kashdao/sdk` and\n // `@kashdao/protocol-sdk` (enforced by sync-to-public-mirror.ts).\n // Pulling in `@kashdao/http-client` would break that invariant.\n // The webhook replay is to a USER-supplied URL (already SSRF-guarded\n // by `--refuse-private-addresses` upstream of this call); the call\n // is one-shot, has a caller-set timeout via AbortController, and\n // propagates failures explicitly via `DeliveryResult`.\n try {\n const response = await /* eslint-disable-line @kashdao/no-bare-fetch */ fetch(args.targetUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n [args.headerName]: args.headerValue,\n },\n body: args.body,\n signal: controller.signal,\n });\n const responseBody = await response.text();\n return {\n status: response.status,\n ok: response.ok,\n body: responseBody,\n durationMs: Date.now() - start,\n };\n } catch (cause) {\n // Timeout detection. `AbortError` covers most cases, but Node's\n // fetch can surface a header-stage timeout via `cause.cause` with\n // an UND_ERR_HEADERS_TIMEOUT-style code in which `name` is not\n // `AbortError`. The AbortController is the authoritative signal:\n // if we triggered it, this is a timeout regardless of the error's\n // visible shape.\n const wasAborted = controller.signal.aborted;\n const looksLikeAbort = cause instanceof Error && cause.name === 'AbortError';\n if (wasAborted || looksLikeAbort) {\n throw new CliError(`Delivery timed out after ${String(args.timeoutMs)}ms.`, {\n code: 'TIMEOUT',\n recoverable: true,\n suggestion: 'Raise --timeout-ms or check the target endpoint is responsive.',\n cause,\n });\n }\n throw new CliError(\n `Delivery failed: ${cause instanceof Error ? cause.message : String(cause)}`,\n {\n code: 'NETWORK',\n recoverable: true,\n suggestion: 'Verify the target URL is reachable from this machine.',\n cause,\n }\n );\n } finally {\n clearTimeout(timer);\n }\n}\n","/**\n * `kash webhooks rotate-secret` — rotate the signing secret for the\n * authenticating API key. The plaintext secret is returned ONCE.\n *\n * This is a destructive operation: the current secret is invalidated\n * immediately on success. Interactive callers (TTY, not --json, not\n * --quiet) get a confirmation prompt to prevent one-keystroke prod\n * webhook breakage. CI / scripted callers can bypass with `--yes`,\n * which is also what `--json --quiet` implies (no TTY for prompting,\n * caller knows what it's doing).\n */\n\nimport readline from 'node:readline/promises';\n\nimport { Command } from 'commander';\n\nimport { toCliError } from '../../errors.js';\nimport { buildClient } from '../../utils/client.js';\nimport { formatDate } from '../../utils/formatting.js';\nimport { readGlobals } from '../../utils/global-options.js';\nimport { log, print, printJson, style } from '../../utils/output.js';\n\ntype RotateOptions = {\n yes?: boolean;\n};\n\nexport const rotateSecretCommand = new Command('rotate-secret')\n .description('Rotate the webhook signing secret for the current API key.')\n .option('-y, --yes', 'skip the interactive confirmation (required for --json --quiet)')\n .addHelpText(\n 'after',\n `\nExamples:\n $ kash webhooks rotate-secret # interactive confirm\n $ kash webhooks rotate-secret --yes # skip confirm\n $ kash webhooks rotate-secret --yes --json --quiet | jq -r '.secret' > new-secret.txt\n\nNotes:\n This invalidates your current webhook secret immediately. Receivers\n that haven't been updated will start failing signature verification\n on the next event. Have your secret-store update plan ready before\n confirming.\n`\n )\n .action(async (options: RotateOptions, cmd: Command) => {\n const globals = readGlobals(cmd);\n\n const interactive = process.stdin.isTTY && !globals.quiet && !globals.json;\n const confirmed = options.yes === true || !interactive ? true : await confirm();\n if (!confirmed) {\n log.info('Aborted.');\n return;\n }\n\n let secret;\n try {\n const { client } = await buildClient({ requireAuth: true, globals });\n secret = await client.webhooks.rotateSecret();\n } catch (cause) {\n throw toCliError(cause);\n }\n\n if (globals.json) {\n printJson(secret);\n return;\n }\n\n log.warn('Webhook secret rotated. The new secret is shown ONCE.');\n print('');\n print(` ${style.bold('Secret')}: ${secret.secret}`);\n print(` ${style.dim('Rotated at ')}: ${formatDate(secret.rotatedAt)}`);\n print(` ${style.dim('Previous valid until')}: ${formatDate(secret.previousRetainedUntil)}`);\n print('');\n log.info('Store this secret in your webhook receiver immediately.');\n });\n\nasync function confirm(): Promise<boolean> {\n const rl = readline.createInterface({ input: process.stdin, output: process.stderr });\n try {\n const answer = await rl.question(\n 'This will invalidate your current webhook secret. Continue? [y/N] '\n );\n return answer.trim().toLowerCase().startsWith('y');\n } finally {\n rl.close();\n }\n}\n","/**\n * `kash webhooks verify` — offline HMAC-SHA256 signature verification.\n *\n * Wraps the SDK's `webhooks.verifySignature` so an operator\n * debugging webhook delivery can ad-hoc verify a captured payload\n * against the signing secret. Useful when:\n *\n * - A webhook receiver rejected an event and the operator wants to\n * confirm whether the signature was actually valid.\n * - An agent pipes a captured webhook through `kash webhooks\n * verify --quiet --json` to check authenticity before acting.\n *\n * Reads the body from `--body-file <path>` (recommended — preserves\n * exact bytes) or `--body <string>` (convenient for short payloads).\n * Reads the secret from `--secret <value>`, `--secret-file <path>`,\n * or `KASH_WEBHOOK_SECRET` env. The signature comes from the captured\n * `X-Kash-Signature` header value.\n */\n\nimport { readFile } from 'node:fs/promises';\n\nimport { Command } from 'commander';\n\nimport { CliValidationError, toCliError } from '../../errors.js';\nimport { buildClient } from '../../utils/client.js';\nimport { parsePositiveInt, readGlobals } from '../../utils/global-options.js';\nimport { log, print, printJson, style } from '../../utils/output.js';\n\ntype VerifyOptions = {\n signature: string;\n body?: string;\n bodyFile?: string;\n secret?: string;\n secretFile?: string;\n toleranceMs?: string;\n};\n\nexport const verifyWebhookCommand = new Command('verify')\n .description('Verify a captured X-Kash-Signature against the raw body and secret.')\n .requiredOption('-s, --signature <header>', 'the X-Kash-Signature header value (e.g. \"t=…,v1=…\")')\n .option('--body <string>', 'raw request body as a string (use --body-file for binary-safe input)')\n .option('--body-file <path>', 'path to a file containing the raw request body')\n .option('--secret <value>', 'webhook signing secret (overrides KASH_WEBHOOK_SECRET)')\n .option(\n '--secret-file <path>',\n 'read secret from a file (preferred over --secret on shared shells)'\n )\n .option(\n '--tolerance-ms <n>',\n 'replay-window tolerance in milliseconds (default 300000 = 5 minutes)'\n )\n .addHelpText(\n 'after',\n `\nExamples:\n $ kash webhooks verify --signature \"t=…,v1=…\" --body-file event.json\n $ kash webhooks verify -s \"$SIG\" --body \"$BODY\" --secret-file ~/.kash/webhook.secret --json\n`\n )\n .action(async (options: VerifyOptions, cmd: Command) => {\n const globals = readGlobals(cmd);\n\n if (options.body !== undefined && options.bodyFile !== undefined) {\n throw new CliValidationError('Pass exactly one of --body or --body-file.', undefined, 'body');\n }\n if (options.body === undefined && options.bodyFile === undefined) {\n throw new CliValidationError('--body or --body-file is required.', undefined, 'body');\n }\n if (options.secret !== undefined && options.secretFile !== undefined) {\n throw new CliValidationError(\n 'Pass at most one of --secret or --secret-file.',\n undefined,\n 'secret'\n );\n }\n\n const body =\n options.body !== undefined\n ? options.body\n : await readFileChecked(options.bodyFile!, 'body-file');\n const secret = await resolveSecret(options);\n const toleranceMs =\n options.toleranceMs === undefined\n ? undefined\n : parsePositiveInt(options.toleranceMs, 'tolerance-ms');\n\n let result;\n try {\n const { client } = await buildClient({ globals });\n result = await client.webhooks.verifySignature(body, options.signature, secret, {\n ...(toleranceMs === undefined ? {} : { toleranceMs }),\n });\n } catch (cause) {\n throw toCliError(cause);\n }\n\n if (globals.json) {\n printJson(result);\n return;\n }\n if (result.valid) {\n print(`${style.success('✓')} Signature is valid.`);\n } else {\n log.error(`Signature is INVALID: ${result.reason}`);\n }\n });\n\nasync function readFileChecked(path: string, flag: string): Promise<string> {\n try {\n return await readFile(path, 'utf8');\n } catch (cause) {\n throw new CliValidationError(\n `Failed to read ${flag} ${path}: ${(cause as Error).message}`,\n 'Verify the path exists and is readable.',\n flag\n );\n }\n}\n\nasync function resolveSecret(options: VerifyOptions): Promise<string> {\n if (options.secret !== undefined) return options.secret;\n if (options.secretFile !== undefined) {\n const raw = await readFileChecked(options.secretFile, 'secret-file');\n // Strip a trailing newline that text editors append; secrets\n // never include leading/trailing whitespace.\n return raw.trim();\n }\n const fromEnv = process.env['KASH_WEBHOOK_SECRET'];\n if (fromEnv !== undefined && fromEnv.length > 0) return fromEnv;\n throw new CliValidationError(\n 'Webhook secret not provided.',\n 'Pass --secret-file (preferred), --secret, or set KASH_WEBHOOK_SECRET.',\n 'secret'\n );\n}\n"],"mappings":"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","names":["Command","Command","Command","Command","Command","stripBom","Command","Command","readFile","Command","Command","resolveSecret","readFile","Command"]}
|
|
1
|
+
{"version":3,"sources":["../src/commands/webhooks/index.ts","../src/commands/webhooks/list.ts","../src/commands/webhooks/redeliver.ts","../src/commands/webhooks/replay.ts","../src/commands/webhooks/rotate-secret.ts","../src/commands/webhooks/verify.ts"],"sourcesContent":["/**\n * `kash webhooks` — manage webhook delivery for the authenticating\n * API key.\n */\n\nimport { Command } from 'commander';\n\nimport { listWebhookEventsCommand } from './list.js';\nimport { redeliverCommand } from './redeliver.js';\nimport { replayWebhookCommand } from './replay.js';\nimport { rotateSecretCommand } from './rotate-secret.js';\nimport { verifyWebhookCommand } from './verify.js';\n\nexport const webhooksCommand = new Command('webhooks')\n .description('Manage webhook delivery, signing secrets, and signature verification.')\n .addCommand(listWebhookEventsCommand)\n .addCommand(rotateSecretCommand)\n .addCommand(redeliverCommand)\n .addCommand(verifyWebhookCommand)\n .addCommand(replayWebhookCommand);\n","/**\n * `kash webhooks list` — paginated listing of webhook delivery events\n * for the authenticating key.\n *\n * Wraps `GET /v1/webhooks/events` (DX6). The same shape that surfaces\n * inline on a trade resource's `webhookDelivery` field is now also\n * accessible as a flat feed — useful for auditing what shipped and\n * triaging delivery failures without N trade lookups.\n *\n * Filter shape mirrors the SDK: `--status` accepts a comma-separated\n * list of derived statuses or can be repeated. Both forms collapse to\n * the same comma-string the server expects.\n *\n * Flag conventions mirror `kash markets list` and `kash trade list`:\n * `-l/-c/-a` short forms for `--limit/--cursor/--all`, default page\n * size 20, `--ndjson` streaming for AI-agent consumption.\n */\n\nimport { Command } from 'commander';\n\nimport { CliValidationError, toCliError } from '../../errors.js';\nimport { buildClient } from '../../utils/client.js';\nimport { parsePositiveInt, readGlobals } from '../../utils/global-options.js';\nimport { log, print, printJson, style, writeNdjson } from '../../utils/output.js';\n\nimport type { WebhookEventResource } from '@kashdao/sdk';\n\nconst STATUS_VALUES = ['none', 'pending', 'delivered', 'retrying', 'failed'] as const;\ntype StatusValue = (typeof STATUS_VALUES)[number];\n\ntype ListOptions = {\n readonly limit?: string;\n readonly cursor?: string;\n readonly status?: readonly StatusValue[];\n readonly all?: boolean;\n readonly ndjson?: boolean;\n};\n\nexport const listWebhookEventsCommand = new Command('list')\n .description('List recent webhook delivery events for the calling key.')\n .option('-l, --limit <n>', 'page size (1-100)', '20')\n .option('-c, --cursor <cursor>', 'opaque cursor from a previous response')\n .option('-a, --all', 'walk every page (subject to --limit per page)', false)\n .option(\n '--ndjson',\n 'stream results as newline-delimited JSON (one record per line); implies --all'\n )\n // Note: we deliberately do NOT use Commander's `.choices()` here.\n // Commander validates each variadic entry against the choices list\n // BEFORE our normaliser runs, so `--status failed,retrying` (a\n // single comma-string entry) gets rejected as not matching any\n // choice. The normaliser below splits commas first and then\n // validates, so both shapes (`--status failed,retrying` and\n // `--status failed --status retrying`) work.\n .option(\n '--status <status...>',\n `filter by derived status (one of: ${STATUS_VALUES.join(', ')}); repeat or comma-separate`\n )\n .addHelpText(\n 'after',\n `\nExamples:\n $ kash webhooks list\n $ kash webhooks list --status failed --status retrying\n $ kash webhooks list --status failed,retrying -l 100\n $ kash webhooks list --all --json | jq '.[] | select(.status == \"failed\")'\n $ kash webhooks list --ndjson | while read -r line; do echo \"$line\" | jq -r .id; done\n`\n )\n .action(async (opts: ListOptions, cmd: Command) => {\n const globals = readGlobals(cmd);\n\n const limit = opts.limit === undefined ? 20 : parsePositiveInt(opts.limit, 'limit');\n if (limit < 1 || limit > 100) {\n throw new CliValidationError('--limit must be between 1 and 100.', undefined, 'limit');\n }\n\n // `--status failed,retrying` (single comma-string) and\n // `--status failed --status retrying` (repeated) both reach us\n // as an array; flatten + split + dedupe so the SDK call is uniform.\n const status = normaliseStatus(opts.status);\n\n try {\n const { client } = await buildClient({ requireAuth: true, globals });\n const page = await client.webhooks.list({\n limit,\n ...(opts.cursor === undefined ? {} : { cursor: opts.cursor }),\n ...(status === undefined ? {} : { status }),\n });\n\n if (opts.ndjson === true) {\n // Stream every record as one line of NDJSON to stdout. Avoids\n // buffering the full result set — the agent reads as we go.\n for await (const event of page) {\n writeNdjson(event);\n }\n return;\n }\n\n if (opts.all === true) {\n const collected: WebhookEventResource[] = [];\n for await (const event of page) collected.push(event);\n if (globals.json) {\n // Canonical `{data, pagination, count}` — see markets/list.ts\n // for rationale. Same shape across single-page and walked\n // modes lets agents pin to one schema.\n printJson({\n data: collected,\n pagination: { hasMore: false, cursor: null },\n count: collected.length,\n });\n return;\n }\n renderRows(collected);\n return;\n }\n\n // Default: first page only.\n if (globals.json) {\n printJson({\n data: page.data,\n pagination: page.pagination,\n count: page.data.length,\n });\n return;\n }\n renderRows(page.data);\n if (page.pagination.hasMore && page.pagination.cursor) {\n log.detail('Next page', `--cursor ${page.pagination.cursor}`);\n }\n } catch (cause) {\n throw toCliError(cause);\n }\n });\n\nfunction renderRows(rows: readonly WebhookEventResource[]): void {\n if (rows.length === 0) {\n print(style.dim(' (no webhook events)'));\n return;\n }\n print('');\n for (const row of rows) {\n const statusBadge = renderStatus(row.status);\n const tradeCol = row.tradeRequestId ? row.tradeRequestId.slice(0, 8) : style.dim('—');\n print(\n ` ${statusBadge} ${style.dim(row.id.slice(0, 8))} ` +\n `${row.eventType.padEnd(28)} trade=${tradeCol} ` +\n `attempts=${String(row.delivery.attempts).padStart(2)} ` +\n `${style.dim(row.emittedAt)}`\n );\n }\n print('');\n}\n\nfunction normaliseStatus(raw: ListOptions['status']): readonly StatusValue[] | undefined {\n if (!raw || raw.length === 0) return undefined;\n // Each element may itself be comma-separated. Flatten, dedupe, and\n // validate. We throw on unknown statuses (instead of silently\n // dropping them) so a typo like `--status faild` surfaces as\n // INVALID_INPUT rather than a confusing empty result set.\n const seen = new Set<StatusValue>();\n for (const entry of raw) {\n for (const piece of entry.split(',')) {\n const trimmed = piece.trim();\n if (trimmed.length === 0) continue;\n if (!(STATUS_VALUES as readonly string[]).includes(trimmed)) {\n throw new CliValidationError(\n `Unknown --status \"${trimmed}\".`,\n `Allowed: ${STATUS_VALUES.join(', ')}.`,\n 'status'\n );\n }\n seen.add(trimmed as StatusValue);\n }\n }\n return seen.size === 0 ? undefined : [...seen];\n}\n\nfunction renderStatus(status: WebhookEventResource['status']): string {\n switch (status) {\n case 'delivered':\n return style.success('✓ delivered');\n case 'failed':\n return style.error('✗ failed ');\n case 'retrying':\n return style.warn('⟳ retrying ');\n case 'pending':\n return style.dim('… pending ');\n case 'none':\n return style.dim('· none ');\n default: {\n // Exhaustiveness guard — if a new status is added to the\n // union, TS surfaces the missing case here at compile time\n // rather than the function silently returning `undefined`.\n const _exhaustive: never = status;\n return _exhaustive;\n }\n }\n}\n","/**\n * `kash webhooks redeliver <eventId>` — re-queue an existing webhook\n * for delivery. Useful when a downstream receiver was offline.\n */\n\nimport { Command } from 'commander';\n\nimport { CliValidationError, toCliError } from '../../errors.js';\nimport { buildClient } from '../../utils/client.js';\nimport { formatDate, shortId } from '../../utils/formatting.js';\nimport { readGlobals } from '../../utils/global-options.js';\nimport { log, print, printJson, style } from '../../utils/output.js';\n\n/**\n * Event ID shape: a UUID v1/v3/v4/v5 — matches what\n * `WebhookEventResourceSchema.id` (`z.string().uuid()`) accepts. We\n * pre-flight the shape so a typo (a trade id with surrounding\n * quotes, a partial paste, the wrong field) surfaces as\n * `INVALID_INPUT` before we burn an API round-trip.\n */\nconst EVENT_ID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\nexport const redeliverCommand = new Command('redeliver')\n .description('Re-queue a webhook event for delivery.')\n .argument('<eventId>', 'webhook event UUID')\n .addHelpText(\n 'after',\n `\nExamples:\n $ kash webhooks redeliver 99999999-9999-9999-9999-999999999999\n $ kash webhooks redeliver \"$(kash webhooks list --json --quiet | jq -r '.data[0].id')\"\n`\n )\n .action(async (eventId: string, _opts, cmd: Command) => {\n const globals = readGlobals(cmd);\n if (!EVENT_ID_REGEX.test(eventId)) {\n throw new CliValidationError(\n `<eventId> must be a UUID.`,\n `Got \"${eventId}\". Look up the id in \\`kash webhooks list --json --quiet | jq -r '.data[].id'\\`.`,\n 'eventId'\n );\n }\n let event;\n try {\n const { client } = await buildClient({ requireAuth: true, globals });\n event = await client.webhooks.redeliver(eventId);\n } catch (cause) {\n throw toCliError(cause);\n }\n\n if (globals.json) {\n printJson(event);\n return;\n }\n\n // All labels are padded to 16 chars so the values line up in a\n // visually consistent column. Show the FULL event id (not a\n // shortId) so the operator can correlate against logs / docs\n // without re-running `kash webhooks list`.\n log.success(`Re-delivery queued for ${event.id}.`);\n print(` ${style.dim('Event id ')} ${event.id}`);\n print(` ${style.dim('Event type ')} ${event.eventType}`);\n print(` ${style.dim('Emitted at ')} ${formatDate(event.emittedAt)}`);\n print(` ${style.dim('Attempts ')} ${String(event.deliveryAttempts)}`);\n if (event.lastDeliveredAt) {\n print(` ${style.dim('Last delivered ')} ${formatDate(event.lastDeliveredAt)}`);\n }\n // Also keep `shortId` available for CI logs that don't want the\n // full UUID — surface as a hint, not the primary value.\n log.detail('Short id', shortId(event.id));\n });\n","/**\n * `kash webhooks replay <body-file> --target <url> --secret <s>` —\n * sign a captured webhook payload with a local secret and POST it to\n * a target URL. Pairs with `kash webhooks verify` for round-trip\n * testing against a development tunnel (ngrok, cloudflared) or a\n * local server.\n *\n * **Why a built-in.** The signing format is `t=<unix-ms>,v1=<hex>`\n * (Stripe-compatible). Replicating the HMAC + header construction in\n * shell + jq is tedious and error-prone; this command does it\n * deterministically.\n *\n * **Privacy.** The body is read verbatim and forwarded — `kash\n * webhooks replay` is intentionally a thin wrapper over\n * `fetch(target)`, NOT an event reformatter. If your captured payload\n * contains PII, redact it locally before passing it in.\n *\n * **Custom timestamp.** Defaults to `Date.now()`. Pass `--timestamp-ms`\n * to replay an old payload — useful for testing the receiver's\n * replay-window enforcement (e.g. \"does my server reject deliveries\n * older than 5 minutes?\").\n */\n\nimport { createHmac } from 'node:crypto';\nimport { readFile } from 'node:fs/promises';\n\nimport { Command } from 'commander';\n\nimport { CliError, CliValidationError, toCliError } from '../../errors.js';\nimport { parsePositiveInt, readGlobals } from '../../utils/global-options.js';\nimport { log, print, printJson, style } from '../../utils/output.js';\n\ntype ReplayOptions = {\n target: string;\n secret?: string;\n secretFile?: string;\n secretEnv?: string;\n timestampMs?: string;\n signatureHeader?: string;\n timeoutMs?: string;\n dryRun?: boolean;\n refusePrivateAddresses?: boolean;\n};\n\nconst SIGNATURE_HEADER_DEFAULT = 'X-Kash-Signature';\n\nexport const replayWebhookCommand = new Command('replay')\n .description('Re-sign a captured webhook payload and POST it to a target URL.')\n .argument('[body]', 'path to a JSON body file (omit or pass \"-\" for stdin)')\n .requiredOption('-t, --target <url>', 'destination URL (e.g. ngrok tunnel or localhost endpoint)')\n .option('-s, --secret <secret>', 'webhook signing secret (overrides KASH_WEBHOOK_SECRET)')\n .option(\n '--secret-file <path>',\n 'read the signing secret from a file (preferred — keeps the value out of argv and env)'\n )\n .option(\n '--secret-env <name>',\n 'read the signing secret from this environment variable (default: KASH_WEBHOOK_SECRET)'\n )\n .option(\n '--timestamp-ms <ms>',\n 'unix-ms timestamp for the signature header (default: current time)'\n )\n .option(\n '--signature-header <name>',\n `override the signature header name (default: ${SIGNATURE_HEADER_DEFAULT})`\n )\n .option('--timeout-ms <ms>', 'fetch timeout (default 10000)')\n .option(\n '--dry-run',\n 'compute the signature header and inspect the would-be POST without sending it'\n )\n .option(\n '--refuse-private-addresses',\n 'hard-fail (instead of warning) when --target is a loopback / private / link-local address — recommended for CI'\n )\n .addHelpText(\n 'after',\n `\nExamples:\n # Auto-reads KASH_WEBHOOK_SECRET (matches \\`kash webhooks verify\\`).\n $ KASH_WEBHOOK_SECRET=whsec_… kash webhooks replay payload.json -t http://localhost:3000/webhook\n\n # Explicit secret file (preferred — keeps the value out of argv/env).\n $ kash webhooks replay payload.json -t http://localhost:3000/webhook --secret-file ~/.kash/webhook.secret\n\n # Pipe captured payload from stdin.\n $ cat payload.json | kash webhooks replay - -t … -s whsec_…\n\n # CI guardrails: refuse private targets + inspect the signed bytes\n # before sending.\n $ kash webhooks replay payload.json -t https://api.example.com --secret-file s.txt --refuse-private-addresses\n $ kash webhooks replay payload.json -t https://api.example.com --secret-file s.txt --dry-run --json\n\nNotes:\n - The signature format is \\`t=<unix-ms>,v1=<hex-hmac-sha256>\\` —\n Stripe-compatible. The receiver verifies via \\`kash webhooks verify\\`\n or the SDK's \\`webhooks.verifySignature\\`.\n - Defaults the timestamp to \\`Date.now()\\`. Pass \\`--timestamp-ms\\`\n with an old timestamp to test the receiver's replay-window\n rejection (the SDK's default is 5 min).\n - Secret resolution order: --secret > --secret-file > --secret-env\n > KASH_WEBHOOK_SECRET. --secret-file is preferred — it keeps the\n value out of argv (where \\`ps\\` could see it) and out of env (where\n a child process would inherit it).\n - --dry-run is safe to run anywhere — it never opens a network\n socket. Use it to inspect the signature header before pointing\n a fresh receiver at production traffic.\n`\n )\n .action(async (body: string | undefined, options: ReplayOptions, cmd: Command) => {\n const globals = readGlobals(cmd);\n try {\n // Resolve secret: --secret > --secret-file > --secret-env > KASH_WEBHOOK_SECRET.\n // Refuse if all are missing/empty.\n const secret = await resolveSecret(options);\n\n // Resolve target (URL validation + private-address policy). The\n // policy is hard-refuse when --refuse-private-addresses is set\n // (recommended for CI), otherwise warn-only (the historical\n // default — needed for local-tunnel testing).\n const targetUrl = validateTarget(options.target, {\n refusePrivate: options.refusePrivateAddresses === true,\n });\n\n // Read the body verbatim. We sign whatever bytes the user\n // captured — bit-exact match is what the receiver's verifier\n // expects.\n const rawBody = await readBody(body);\n\n const timestampMs = options.timestampMs\n ? parsePositiveInt(options.timestampMs, 'timestamp-ms')\n : Date.now();\n const timeoutMs = options.timeoutMs\n ? parsePositiveInt(options.timeoutMs, 'timeout-ms')\n : 10_000;\n\n // Compute HMAC: `t=<ms>,v1=<hex>` over the canonical\n // `<timestamp>.<body>` payload. Same shape `verifySignature`\n // checks against.\n const headerName = options.signatureHeader ?? SIGNATURE_HEADER_DEFAULT;\n const headerValue = computeSignatureHeader(rawBody, secret, timestampMs);\n\n // --dry-run short-circuit: emit the would-be POST envelope and\n // return without opening a network socket. Useful for previewing\n // the signature against a fresh receiver before pointing real\n // traffic at it.\n if (options.dryRun === true) {\n if (globals.json) {\n printJson({\n targetUrl,\n headerName,\n headerValue,\n timestampMs,\n bodyBytes: Buffer.byteLength(rawBody, 'utf8'),\n dryRun: true,\n });\n return;\n }\n print('');\n log.success('Dry run — signature computed, no request sent.');\n print(` ${style.dim('Target ')} ${targetUrl}`);\n print(` ${style.dim('Header ')} ${headerName}: ${headerValue}`);\n print(` ${style.dim('Body ')} ${String(Buffer.byteLength(rawBody, 'utf8'))} bytes`);\n log.info('Re-run without --dry-run to send.');\n return;\n }\n\n // POST. Use `fetch` (Node 22+ ships it). Bound by a timeout so\n // a wedged target doesn't hang the CLI.\n const result = await deliver({\n targetUrl,\n body: rawBody,\n headerName,\n headerValue,\n timeoutMs,\n });\n\n if (globals.json) {\n printJson({\n targetUrl,\n headerName,\n headerValue,\n timestampMs,\n status: result.status,\n ok: result.ok,\n durationMs: result.durationMs,\n responseBody: result.body,\n });\n return;\n }\n\n // Human mode: brief result + the response body for inspection.\n print('');\n if (result.ok) {\n log.success(`Delivered in ${String(result.durationMs)}ms — HTTP ${String(result.status)}.`);\n } else {\n log.error(\n `Receiver returned HTTP ${String(result.status)} (after ${String(result.durationMs)}ms).`\n );\n }\n print(` ${style.dim('Target ')} ${targetUrl}`);\n print(` ${style.dim('Header ')} ${headerName}: ${headerValue}`);\n if (result.body.length > 0) {\n print('');\n print(style.dim('--- Response body ---'));\n print(result.body);\n }\n } catch (cause) {\n throw toCliError(cause);\n }\n });\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nasync function resolveSecret(options: ReplayOptions): Promise<string> {\n // Resolution order matches `kash webhooks verify` so the two\n // commands are interchangeable from the operator's POV:\n // 1. --secret (explicit; convenient but lands in argv)\n // 2. --secret-file (preferred — value never crosses argv/env)\n // 3. --secret-env <NAME> (read from named env var)\n // 4. KASH_WEBHOOK_SECRET (default env, no flag needed)\n if (options.secret !== undefined) return options.secret;\n if (options.secretFile !== undefined) {\n try {\n const raw = await readFile(options.secretFile, 'utf8');\n // Strip a trailing newline that text editors append; secrets\n // never include leading/trailing whitespace.\n return raw.trim();\n } catch {\n throw new CliValidationError(\n `--secret-file: failed to read ${options.secretFile}.`,\n 'Verify the file exists and is readable.',\n 'secret-file'\n );\n }\n }\n if (options.secretEnv !== undefined) {\n const value = process.env[options.secretEnv];\n if (!value || value.length === 0) {\n throw new CliError(`--secret-env ${options.secretEnv} is not set or empty.`, {\n code: 'INVALID_INPUT',\n recoverable: true,\n suggestion: `Set ${options.secretEnv}=<webhook-secret> before re-running.`,\n });\n }\n return value;\n }\n const fromEnv = process.env['KASH_WEBHOOK_SECRET'];\n if (fromEnv !== undefined && fromEnv.length > 0) return fromEnv;\n throw new CliValidationError(\n 'A signing secret is required.',\n 'Pass --secret-file (preferred), --secret, --secret-env <NAME>, or set KASH_WEBHOOK_SECRET.',\n 'secret'\n );\n}\n\nfunction validateTarget(raw: string, policy: { refusePrivate: boolean }): string {\n let url: URL;\n try {\n url = new URL(raw);\n } catch {\n throw new CliValidationError(\n '--target must be a valid URL.',\n `Got \"${raw}\". Include the scheme (http:// or https://).`,\n 'target'\n );\n }\n if (url.protocol !== 'http:' && url.protocol !== 'https:') {\n throw new CliValidationError(\n `--target must use http:// or https:// (got \"${url.protocol}\").`,\n undefined,\n 'target'\n );\n }\n // SSRF guard: by default warn when the target host is a loopback /\n // private / link-local address. `kash webhooks replay` is a\n // developer tool, not a server, so the risk is bounded — but a\n // misconfigured run against `169.254.169.254` (cloud metadata\n // service) or a VPN-bound private range can still exfiltrate\n // sensitive data through the signed POST body. The default warn\n // (instead of refuse) preserves local-tunnel testing (the common\n // case: `localhost:3000`, `127.0.0.1:3000`).\n //\n // CI runs and production-flavoured tests should pass\n // `--refuse-private-addresses` to upgrade the warning to a hard\n // failure — the policy parameter does that.\n if (policy.refusePrivate && isPrivateAddress(url)) {\n throw new CliValidationError(\n `--target points at a private address (${url.hostname}) and --refuse-private-addresses is set.`,\n 'Drop the flag to allow private targets, or change --target to a public URL.',\n 'target'\n );\n }\n warnIfPrivateAddress(url);\n return url.toString();\n}\n\n/**\n * Best-effort literal-IP-shape check. Matches:\n *\n * - IPv4 loopback (127/8 — silent, expected for dev tunnels)\n * - IPv4 link-local (169.254/16 — louder warning; cloud metadata\n * services live here)\n * - IPv4 private ranges (10/8, 172.16/12, 192.168/16)\n * - IPv6 loopback (::1)\n * - IPv6 link-local (fe80::/10)\n * - IPv6 unique local (fc00::/7, which covers fc00:: and fd00::)\n *\n * Hostnames are intentionally NOT resolved here — full DNS resolution\n * would couple this CLI command to an async resolver and add a\n * network round-trip on every replay. That means cloud metadata\n * hostnames like `metadata.google.internal` are NOT caught (they\n * resolve to `169.254.169.254` only at request time); operators\n * relying on hostname-based aliases for sensitive endpoints must\n * audit their `--target` values themselves. The literal-IP check\n * here catches the most common foot-gun: a copy-pasted\n * `169.254.169.254` or private-range URL.\n *\n * The IPv4 octet regex bounds octets to 0–255 explicitly so a\n * pathological host like `999.999.999.999` (which `URL` happily\n * accepts as a hostname string) does not silently coerce into a\n * matching octet via `parseInt`.\n */\nexport function warnIfPrivateAddress(url: URL): void {\n // `url.hostname` keeps the surrounding [...] for IPv6 literals\n // (per the WHATWG URL spec — Node, browsers, and the URL standard\n // all agree). Strip them for the prefix matching below so the\n // regexes deal with bare hex hextets.\n const rawHost = url.hostname;\n const host = rawHost.startsWith('[') && rawHost.endsWith(']') ? rawHost.slice(1, -1) : rawHost;\n\n // IPv6 loopback.\n if (host === '::1') {\n emitPrivateWarning(rawHost);\n return;\n }\n\n // Localhost name (and `*.localhost` per RFC 6761) — silent.\n if (host === 'localhost' || host.endsWith('.localhost')) return;\n\n // IPv6 link-local (fe80::/10) — first 10 bits are 1111 1110 10.\n // The leading hextet `fexx` ranges from fe80 to febf. The textual\n // prefix is therefore `fe` followed by one of `8`, `9`, `a`, `b`,\n // followed by any hex digit, followed by either `:` (short form,\n // e.g. `fe80::1`) or another hex digit then `:` (full form,\n // e.g. `fe80:0:…`). Match case-insensitively.\n if (/^fe[89ab][0-9a-f]{0,2}:/i.test(host)) {\n emitLinkLocalWarning(rawHost);\n return;\n }\n\n // IPv6 unique-local (fc00::/7) — first 7 bits are 1111 110, so any\n // address whose leading hextet starts with `fc` or `fd`. Allow up\n // to two more hex digits before the `:` separator (covers `fc::`,\n // `fc1::`, `fcab::`, `fd00::`, etc.).\n if (/^f[cd][0-9a-f]{0,2}:/i.test(host)) {\n emitPrivateWarning(rawHost);\n return;\n }\n\n // IPv4 octet match — bounded 0..255 so `999.999.999.999` doesn't\n // silently match. `URL` accepts these strings as hostnames, so the\n // bound matters.\n const ipv4 =\n /^(25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]?\\d)\\.(25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]?\\d)\\.(25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]?\\d)\\.(25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]?\\d)$/.exec(\n host\n );\n if (!ipv4) return; // hostname (no DNS check — see jsdoc)\n const [, a, b] = ipv4;\n const oct1 = Number.parseInt(a!, 10);\n const oct2 = Number.parseInt(b!, 10);\n if (oct1 === 127) return; // loopback — expected for `127.0.0.1` etc.\n if (oct1 === 10) {\n emitPrivateWarning(host);\n return;\n }\n if (oct1 === 172 && oct2 >= 16 && oct2 <= 31) {\n emitPrivateWarning(host);\n return;\n }\n if (oct1 === 192 && oct2 === 168) {\n emitPrivateWarning(host);\n return;\n }\n if (oct1 === 169 && oct2 === 254) {\n emitLinkLocalWarning(host);\n return;\n }\n}\n\n/**\n * Pure predicate version of {@link warnIfPrivateAddress} — same set\n * of literal-IP shapes (loopback, link-local, RFC1918 private,\n * IPv6 ::1 / fe80::/10 / fc00::/7), but returns a boolean instead of\n * writing to stderr. Used by the `--refuse-private-addresses` policy\n * to hard-fail the run; the warn-only path stays as before.\n *\n * Hostnames that aren't literal IPs are treated as \"not private\"\n * (same documented limitation as the warning function — no DNS).\n */\nexport function isPrivateAddress(url: URL): boolean {\n const rawHost = url.hostname;\n const host = rawHost.startsWith('[') && rawHost.endsWith(']') ? rawHost.slice(1, -1) : rawHost;\n\n if (host === '::1') return true;\n if (host === 'localhost' || host.endsWith('.localhost')) return true;\n if (/^fe[89ab][0-9a-f]{0,2}:/i.test(host)) return true;\n if (/^f[cd][0-9a-f]{0,2}:/i.test(host)) return true;\n\n const ipv4 =\n /^(25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]?\\d)\\.(25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]?\\d)\\.(25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]?\\d)\\.(25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]?\\d)$/.exec(\n host\n );\n if (!ipv4) return false;\n const oct1 = Number.parseInt(ipv4[1]!, 10);\n const oct2 = Number.parseInt(ipv4[2]!, 10);\n if (oct1 === 127) return true; // loopback\n if (oct1 === 10) return true;\n if (oct1 === 172 && oct2 >= 16 && oct2 <= 31) return true;\n if (oct1 === 192 && oct2 === 168) return true;\n if (oct1 === 169 && oct2 === 254) return true; // link-local\n return false;\n}\n\nfunction emitLinkLocalWarning(host: string): void {\n // Worth a louder warning since link-local is the textbook SSRF\n // target — AWS / GCP / Azure metadata services live here.\n process.stderr.write(\n `\\u26a0 --target points at a link-local address (${host}). This range hosts cloud metadata services. Verify the target is intentional.\\n`\n );\n}\n\nfunction emitPrivateWarning(host: string): void {\n process.stderr.write(\n `\\u26a0 --target points at a private address (${host}). Verify this is your dev tunnel and not an internal service.\\n`\n );\n}\n\nasync function readBody(file: string | undefined): Promise<string> {\n if (file !== undefined && file !== '-') {\n let raw: string;\n try {\n raw = await readFile(file, 'utf8');\n } catch (cause) {\n throw new CliError(`Failed to read ${file}.`, {\n code: 'CONFIGURATION',\n recoverable: true,\n suggestion: 'Verify the path exists and is readable.',\n cause,\n });\n }\n // Strip the BOM here too — the file branch is the more common\n // case when operators are testing with captured payloads dropped\n // by Notepad or VSCode-with-utf8bom.\n const { stripBom } = await import('../../utils/stdin.js');\n return stripBom(raw);\n }\n // Stdin.\n const chunks: Buffer[] = [];\n for await (const chunk of process.stdin) {\n if (typeof chunk === 'string') chunks.push(Buffer.from(chunk));\n else if (chunk instanceof Buffer) chunks.push(chunk);\n else chunks.push(Buffer.from(chunk as Uint8Array));\n }\n if (chunks.length === 0) {\n throw new CliValidationError(\n 'No input received on stdin.',\n 'Pass a file path or pipe a webhook body into stdin.'\n );\n }\n // Strip a leading UTF-8 BOM. Notepad / VSCode-with-utf8bom and many\n // Windows tools prepend U+FEFF; without the strip the receiver\n // would HMAC-verify a body that starts with the BOM, but its own\n // JSON.parse would fail (BOM isn't valid leading whitespace in\n // JSON). Strip on read so the signature matches the parsed body.\n const { stripBom } = await import('../../utils/stdin.js');\n return stripBom(Buffer.concat(chunks).toString('utf8'));\n}\n\n/**\n * Build the Stripe-compatible signature header. Format is exactly\n * `t=<unix-ms>,v1=<hex-hmac-sha256>` — same shape the SDK's\n * `parseSignatureHeader` accepts. We replicate the math here rather\n * than reaching into the SDK's private helpers (which aren't\n * exported); the impl is small and the algorithm is stable.\n */\nexport function computeSignatureHeader(body: string, secret: string, timestampMs: number): string {\n const payload = `${String(timestampMs)}.${body}`;\n const hex = createHmac('sha256', secret).update(payload).digest('hex');\n return `t=${String(timestampMs)},v1=${hex}`;\n}\n\ntype DeliveryResult = {\n readonly status: number;\n readonly ok: boolean;\n readonly body: string;\n readonly durationMs: number;\n};\n\nasync function deliver(args: {\n readonly targetUrl: string;\n readonly body: string;\n readonly headerName: string;\n readonly headerValue: string;\n readonly timeoutMs: number;\n}): Promise<DeliveryResult> {\n const start = Date.now();\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), args.timeoutMs);\n\n // The CLI is a published-to-npm public package that intentionally\n // has zero `@kashdao` runtime deps beyond `@kashdao/sdk` and\n // `@kashdao/protocol-sdk` (enforced by sync-to-public-mirror.ts).\n // Pulling in `@kashdao/http-client` would break that invariant.\n // The webhook replay is to a USER-supplied URL (already SSRF-guarded\n // by `--refuse-private-addresses` upstream of this call); the call\n // is one-shot, has a caller-set timeout via AbortController, and\n // propagates failures explicitly via `DeliveryResult`.\n try {\n const response = await /* eslint-disable-line @kashdao/no-bare-fetch */ fetch(args.targetUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n [args.headerName]: args.headerValue,\n },\n body: args.body,\n signal: controller.signal,\n });\n const responseBody = await response.text();\n return {\n status: response.status,\n ok: response.ok,\n body: responseBody,\n durationMs: Date.now() - start,\n };\n } catch (cause) {\n // Timeout detection. `AbortError` covers most cases, but Node's\n // fetch can surface a header-stage timeout via `cause.cause` with\n // an UND_ERR_HEADERS_TIMEOUT-style code in which `name` is not\n // `AbortError`. The AbortController is the authoritative signal:\n // if we triggered it, this is a timeout regardless of the error's\n // visible shape.\n const wasAborted = controller.signal.aborted;\n const looksLikeAbort = cause instanceof Error && cause.name === 'AbortError';\n if (wasAborted || looksLikeAbort) {\n throw new CliError(`Delivery timed out after ${String(args.timeoutMs)}ms.`, {\n code: 'TIMEOUT',\n recoverable: true,\n suggestion: 'Raise --timeout-ms or check the target endpoint is responsive.',\n cause,\n });\n }\n throw new CliError(\n `Delivery failed: ${cause instanceof Error ? cause.message : String(cause)}`,\n {\n code: 'NETWORK',\n recoverable: true,\n suggestion: 'Verify the target URL is reachable from this machine.',\n cause,\n }\n );\n } finally {\n clearTimeout(timer);\n }\n}\n","/**\n * `kash webhooks rotate-secret` — rotate the signing secret for the\n * authenticating API key. The plaintext secret is returned ONCE.\n *\n * This is a destructive operation: the current secret is invalidated\n * immediately on success. Interactive callers (TTY, not --json, not\n * --quiet) get a confirmation prompt to prevent one-keystroke prod\n * webhook breakage. CI / scripted callers can bypass with `--yes`,\n * which is also what `--json --quiet` implies (no TTY for prompting,\n * caller knows what it's doing).\n */\n\nimport readline from 'node:readline/promises';\n\nimport { Command } from 'commander';\n\nimport { toCliError } from '../../errors.js';\nimport { buildClient } from '../../utils/client.js';\nimport { formatDate } from '../../utils/formatting.js';\nimport { readGlobals } from '../../utils/global-options.js';\nimport { log, print, printJson, style } from '../../utils/output.js';\n\ntype RotateOptions = {\n yes?: boolean;\n};\n\nexport const rotateSecretCommand = new Command('rotate-secret')\n .description('Rotate the webhook signing secret for the current API key.')\n .option('-y, --yes', 'skip the interactive confirmation (required for --json --quiet)')\n .addHelpText(\n 'after',\n `\nExamples:\n $ kash webhooks rotate-secret # interactive confirm\n $ kash webhooks rotate-secret --yes # skip confirm\n $ kash webhooks rotate-secret --yes --json --quiet | jq -r '.secret' > new-secret.txt\n\nNotes:\n This invalidates your current webhook secret immediately. Receivers\n that haven't been updated will start failing signature verification\n on the next event. Have your secret-store update plan ready before\n confirming.\n`\n )\n .action(async (options: RotateOptions, cmd: Command) => {\n const globals = readGlobals(cmd);\n\n const interactive = process.stdin.isTTY && !globals.quiet && !globals.json;\n const confirmed = options.yes === true || !interactive ? true : await confirm();\n if (!confirmed) {\n log.info('Aborted.');\n return;\n }\n\n let secret;\n try {\n const { client } = await buildClient({ requireAuth: true, globals });\n secret = await client.webhooks.rotateSecret();\n } catch (cause) {\n throw toCliError(cause);\n }\n\n if (globals.json) {\n printJson(secret);\n return;\n }\n\n log.warn('Webhook secret rotated. The new secret is shown ONCE.');\n print('');\n print(` ${style.bold('Secret')}: ${secret.secret}`);\n print(` ${style.dim('Rotated at ')}: ${formatDate(secret.rotatedAt)}`);\n print(` ${style.dim('Previous valid until')}: ${formatDate(secret.previousRetainedUntil)}`);\n print('');\n log.info('Store this secret in your webhook receiver immediately.');\n });\n\nasync function confirm(): Promise<boolean> {\n const rl = readline.createInterface({ input: process.stdin, output: process.stderr });\n try {\n const answer = await rl.question(\n 'This will invalidate your current webhook secret. Continue? [y/N] '\n );\n return answer.trim().toLowerCase().startsWith('y');\n } finally {\n rl.close();\n }\n}\n","/**\n * `kash webhooks verify` — offline HMAC-SHA256 signature verification.\n *\n * Wraps the SDK's `webhooks.verifySignature` so an operator\n * debugging webhook delivery can ad-hoc verify a captured payload\n * against the signing secret. Useful when:\n *\n * - A webhook receiver rejected an event and the operator wants to\n * confirm whether the signature was actually valid.\n * - An agent pipes a captured webhook through `kash webhooks\n * verify --quiet --json` to check authenticity before acting.\n *\n * Reads the body from `--body-file <path>` (recommended — preserves\n * exact bytes) or `--body <string>` (convenient for short payloads).\n * Reads the secret from `--secret <value>`, `--secret-file <path>`,\n * or `KASH_WEBHOOK_SECRET` env. The signature comes from the captured\n * `X-Kash-Signature` header value.\n */\n\nimport { readFile } from 'node:fs/promises';\n\nimport { Command } from 'commander';\n\nimport { CliValidationError, toCliError } from '../../errors.js';\nimport { buildClient } from '../../utils/client.js';\nimport { parsePositiveInt, readGlobals } from '../../utils/global-options.js';\nimport { log, print, printJson, style } from '../../utils/output.js';\n\ntype VerifyOptions = {\n signature: string;\n body?: string;\n bodyFile?: string;\n secret?: string;\n secretFile?: string;\n toleranceMs?: string;\n};\n\nexport const verifyWebhookCommand = new Command('verify')\n .description('Verify a captured X-Kash-Signature against the raw body and secret.')\n .requiredOption('-s, --signature <header>', 'the X-Kash-Signature header value (e.g. \"t=…,v1=…\")')\n .option('--body <string>', 'raw request body as a string (use --body-file for binary-safe input)')\n .option('--body-file <path>', 'path to a file containing the raw request body')\n .option('--secret <value>', 'webhook signing secret (overrides KASH_WEBHOOK_SECRET)')\n .option(\n '--secret-file <path>',\n 'read secret from a file (preferred over --secret on shared shells)'\n )\n .option(\n '--tolerance-ms <n>',\n 'replay-window tolerance in milliseconds (default 300000 = 5 minutes)'\n )\n .addHelpText(\n 'after',\n `\nExamples:\n $ kash webhooks verify --signature \"t=…,v1=…\" --body-file event.json\n $ kash webhooks verify -s \"$SIG\" --body \"$BODY\" --secret-file ~/.kash/webhook.secret --json\n`\n )\n .action(async (options: VerifyOptions, cmd: Command) => {\n const globals = readGlobals(cmd);\n\n if (options.body !== undefined && options.bodyFile !== undefined) {\n throw new CliValidationError('Pass exactly one of --body or --body-file.', undefined, 'body');\n }\n if (options.body === undefined && options.bodyFile === undefined) {\n throw new CliValidationError('--body or --body-file is required.', undefined, 'body');\n }\n if (options.secret !== undefined && options.secretFile !== undefined) {\n throw new CliValidationError(\n 'Pass at most one of --secret or --secret-file.',\n undefined,\n 'secret'\n );\n }\n\n const body =\n options.body !== undefined\n ? options.body\n : await readFileChecked(options.bodyFile!, 'body-file');\n const secret = await resolveSecret(options);\n const toleranceMs =\n options.toleranceMs === undefined\n ? undefined\n : parsePositiveInt(options.toleranceMs, 'tolerance-ms');\n\n let result;\n try {\n const { client } = await buildClient({ globals });\n result = await client.webhooks.verifySignature(body, options.signature, secret, {\n ...(toleranceMs === undefined ? {} : { toleranceMs }),\n });\n } catch (cause) {\n throw toCliError(cause);\n }\n\n if (globals.json) {\n printJson(result);\n return;\n }\n if (result.valid) {\n print(`${style.success('✓')} Signature is valid.`);\n } else {\n log.error(`Signature is INVALID: ${result.reason}`);\n }\n });\n\nasync function readFileChecked(path: string, flag: string): Promise<string> {\n try {\n return await readFile(path, 'utf8');\n } catch (cause) {\n throw new CliValidationError(\n `Failed to read ${flag} ${path}: ${(cause as Error).message}`,\n 'Verify the path exists and is readable.',\n flag\n );\n }\n}\n\nasync function resolveSecret(options: VerifyOptions): Promise<string> {\n if (options.secret !== undefined) return options.secret;\n if (options.secretFile !== undefined) {\n const raw = await readFileChecked(options.secretFile, 'secret-file');\n // Strip a trailing newline that text editors append; secrets\n // never include leading/trailing whitespace.\n return raw.trim();\n }\n const fromEnv = process.env['KASH_WEBHOOK_SECRET'];\n if (fromEnv !== undefined && fromEnv.length > 0) return fromEnv;\n throw new CliValidationError(\n 'Webhook secret not provided.',\n 'Pass --secret-file (preferred), --secret, or set KASH_WEBHOOK_SECRET.',\n 'secret'\n );\n}\n"],"mappings":"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","names":["Command","Command","Command","Command","Command","stripBom","Command","Command","readFile","Command","Command","resolveSecret","readFile","Command"]}
|
|
@@ -2,16 +2,16 @@
|
|
|
2
2
|
import {
|
|
3
3
|
parsePositiveInt,
|
|
4
4
|
readGlobals
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-AROIP4EQ.js";
|
|
6
6
|
import {
|
|
7
7
|
log,
|
|
8
8
|
style
|
|
9
|
-
} from "./chunk-
|
|
10
|
-
import "./chunk-
|
|
9
|
+
} from "./chunk-2HFKULGG.js";
|
|
10
|
+
import "./chunk-VFBQVUL4.js";
|
|
11
11
|
import {
|
|
12
12
|
CliError,
|
|
13
13
|
toCliError
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-WL7XUYPH.js";
|
|
15
15
|
|
|
16
16
|
// src/commands/with-retry.ts
|
|
17
17
|
import { spawn } from "child_process";
|
|
@@ -123,7 +123,13 @@ async function runOnce(args) {
|
|
|
123
123
|
const node = process.execPath;
|
|
124
124
|
const entry = process.argv[1];
|
|
125
125
|
if (!entry) {
|
|
126
|
-
reject(
|
|
126
|
+
reject(
|
|
127
|
+
new CliError("with-retry: cannot determine kash entry path.", {
|
|
128
|
+
code: "CONFIGURATION",
|
|
129
|
+
recoverable: false,
|
|
130
|
+
suggestion: "Invoke `kash` directly rather than through a wrapper that clears argv[1]."
|
|
131
|
+
})
|
|
132
|
+
);
|
|
127
133
|
return;
|
|
128
134
|
}
|
|
129
135
|
const child = spawn(node, [entry, ...args], {
|
|
@@ -220,4 +226,4 @@ export {
|
|
|
220
226
|
parseErrorEnvelope,
|
|
221
227
|
withRetryCommand
|
|
222
228
|
};
|
|
223
|
-
//# sourceMappingURL=with-retry-
|
|
229
|
+
//# sourceMappingURL=with-retry-DYSHNLG7.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/with-retry.ts"],"sourcesContent":["/**\n * `kash with-retry [opts] -- <command> [args...]` — wrap any kash\n * command in a retry loop driven by the structured error envelope.\n *\n * **Why a built-in.** The `examples/trade-replay.sh` recipe shows the\n * pattern, but every agent / CI script ends up reimplementing it\n * slightly differently. Promoting it to a first-class command means:\n * - Idempotency-key handling is consistent (the inner command's\n * own --auto-idempotency-key / --idempotency-key still applies;\n * retries are at the CLI-invocation level so the inner command's\n * guarantees stay intact).\n * - `recoverable`/`retryAfterMs` from the error envelope drive the\n * wait — same logic that powers the SDK's own retries, surfaced\n * to scripts.\n *\n * **Mechanics.** Spawns the same kash binary as a child process,\n * forwards stdout/stderr live (so the user sees progress), and\n * captures stderr to parse the `CliErrorEnvelope` when the inner\n * command fails. If the inner command emitted `--json`, we get\n * structured `code` / `recoverable` / `retryAfterMs` and use them.\n * Otherwise we fall back to a fixed exponential schedule.\n *\n * Bare success (exit 0) returns immediately. Non-recoverable failure\n * also returns immediately — there's no point retrying an\n * `INVALID_INPUT` or `AUTH_REQUIRED`. Recoverable failure\n * (`RATE_LIMITED`, `NETWORK`, `TIMEOUT`, `MAINTENANCE`, `SERVER_ERROR`)\n * triggers the next attempt up to `--max-attempts`.\n */\n\nimport { spawn } from 'node:child_process';\n\nimport { Command } from 'commander';\n\nimport { CliError, toCliError } from '../errors.js';\nimport { parsePositiveInt, readGlobals } from '../utils/global-options.js';\nimport { log, style } from '../utils/output.js';\n\ntype WithRetryOptions = {\n maxAttempts?: string;\n initialDelayMs?: string;\n maxDelayMs?: string;\n retryWithoutJson?: boolean;\n};\n\n/** Codes whose envelopes are worth retrying. Mirrors the SDK's policy. */\nconst RETRYABLE_CODES: ReadonlySet<string> = new Set([\n 'RATE_LIMITED',\n 'NETWORK',\n 'TIMEOUT',\n 'MAINTENANCE',\n 'SERVER_ERROR',\n]);\n\n/** Codes that are explicitly NOT worth retrying — fail fast. */\nconst TERMINAL_CODES: ReadonlySet<string> = new Set([\n 'INVALID_INPUT',\n 'AUTH_REQUIRED',\n 'INSUFFICIENT_SCOPE',\n 'NOT_FOUND',\n 'CONFLICT',\n 'CONFIGURATION',\n 'DIRECT_CONFIG_MISSING',\n 'SIGNER_FAILED',\n]);\n\nexport const withRetryCommand = new Command('with-retry')\n .description('Re-run a kash command on recoverable failures (rate limits, transient errors).')\n .option('--max-attempts <n>', 'maximum total attempts including the first (default 5)', '5')\n .option(\n '--initial-delay-ms <n>',\n 'starting backoff when the envelope has no retryAfterMs (default 1000)',\n '1000'\n )\n .option('--max-delay-ms <n>', 'cap on the per-attempt wait (default 30000)', '30000')\n .option(\n '--retry-without-json',\n 'retry even when the inner command did not emit --json (no envelope to discriminate ' +\n 'terminal from transient — opt in if you trust the inner command failed transiently)'\n )\n // Allow unknown options to flow through as positional args so the\n // wrapped command's flags don't get parsed by Commander as our own.\n .allowUnknownOption()\n .argument('<command...>', 'kash command + args; pass after `--` to disambiguate')\n .addHelpText(\n 'after',\n `\nExamples:\n $ kash with-retry --max-attempts 5 -- markets list --status ACTIVE --json\n $ kash with-retry -- trade buy <id> --outcome 0 --amount 10 \\\\\n --auto-idempotency-key --json --quiet\n $ kash with-retry --max-attempts 3 -- health --json --quiet\n\nNotes:\n - The wrapped command MUST come after \\`--\\` so its flags don't collide\n with with-retry's own.\n - When the wrapped command emits --json, the error envelope's\n \\`code\\` / \\`recoverable\\` / \\`retryAfterMs\\` drive the retry. Recoverable\n codes (RATE_LIMITED, NETWORK, TIMEOUT, MAINTENANCE, SERVER_ERROR)\n retry; terminal codes (INVALID_INPUT, AUTH_REQUIRED, NOT_FOUND, …)\n fail fast.\n - Without --json on the inner command, we DO NOT retry by default —\n no envelope means we can't tell INVALID_INPUT from a transient\n NETWORK error, and retrying validation 5× wastes time. Pass\n --retry-without-json to force exponential backoff regardless.\n`\n )\n .action(async (commandArgs: string[], options: WithRetryOptions, cmd: Command) => {\n const globals = readGlobals(cmd);\n\n const maxAttempts = options.maxAttempts\n ? parsePositiveInt(options.maxAttempts, 'max-attempts')\n : 5;\n const initialDelay = options.initialDelayMs\n ? parsePositiveInt(options.initialDelayMs, 'initial-delay-ms')\n : 1_000;\n const maxDelay = options.maxDelayMs\n ? parsePositiveInt(options.maxDelayMs, 'max-delay-ms')\n : 30_000;\n\n if (commandArgs.length === 0) {\n throw new CliError('with-retry needs a command to run.', {\n code: 'INVALID_INPUT',\n recoverable: true,\n suggestion:\n 'Pass the kash command after `--`, e.g. `kash with-retry -- health --json --quiet`.',\n });\n }\n\n try {\n await runWithRetry(commandArgs, {\n maxAttempts,\n initialDelay,\n maxDelay,\n humanMode: !globals.json,\n retryWithoutJson: options.retryWithoutJson === true,\n });\n } catch (cause) {\n throw toCliError(cause);\n }\n });\n\ntype RunOptions = {\n readonly maxAttempts: number;\n readonly initialDelay: number;\n readonly maxDelay: number;\n readonly humanMode: boolean;\n readonly retryWithoutJson: boolean;\n};\n\nasync function runWithRetry(args: readonly string[], opts: RunOptions): Promise<void> {\n for (let attempt = 1; attempt <= opts.maxAttempts; attempt++) {\n const result = await runOnce(args);\n\n if (result.exitCode === 0) {\n // Success — replay captured stdout (stderr was already streamed\n // live by runOnce). The inner command's JSON / table output is\n // what the user wants on the success path.\n if (result.stdout) process.stdout.write(result.stdout);\n process.exit(0);\n }\n\n // Non-zero exit — the JSON error envelope (if --json was set on\n // the inner command) lives on stdout. Fall back to stderr for\n // commands that route errors that way.\n const envelope = parseErrorEnvelope(result.stdout) ?? parseErrorEnvelope(result.stderr);\n const code = envelope?.error.code;\n const recoverable = decideRetry(code, envelope?.error.recoverable, {\n retryWithoutJson: opts.retryWithoutJson,\n });\n\n if (!recoverable || attempt === opts.maxAttempts) {\n // Final attempt failed (or non-recoverable). Replay captured\n // stdout — that's where the JSON envelope lives in --json mode\n // and any successful prefix lives in human mode. Stderr already\n // streamed live during the attempt, so don't double-print it.\n if (result.stdout) process.stdout.write(result.stdout);\n if (opts.humanMode) {\n const reason = !recoverable ? 'non-recoverable error' : 'max attempts exhausted';\n log.error(`with-retry: ${reason} after attempt ${String(attempt)} (${code ?? 'unknown'}).`);\n }\n process.exit(result.exitCode);\n }\n\n // Recoverable — compute wait and retry. Drop the inner output\n // for failed-but-retried attempts so the user only sees the\n // final outcome.\n const wait = computeWait(envelope?.error.retryAfterMs, attempt, opts);\n if (opts.humanMode) {\n process.stderr.write(\n `${style.dim(\n `with-retry: attempt ${String(attempt)}/${String(opts.maxAttempts)} failed (${code ?? 'unknown'}); retrying in ${String(wait)}ms…`\n )}\\n`\n );\n }\n await sleep(wait);\n }\n}\n\ntype RunResult = {\n readonly exitCode: number;\n readonly stdout: string;\n readonly stderr: string;\n};\n\n/**\n * Spawn the kash CLI as a child process. Captures both stdout and\n * stderr — stdout because that's where the JSON error envelope lives\n * (via `printJson`); stderr because human-mode error rendering goes\n * there. We replay the captured streams to the user only after the\n * retry decision is made, to avoid showing transient failures.\n */\nasync function runOnce(args: readonly string[]): Promise<RunResult> {\n return await new Promise((resolve, reject) => {\n // process.argv[1] points at `dist/index.js` (or the published\n // bin's entry). Re-invoking via `process.execPath` avoids relying\n // on the shell `kash` lookup, which may not be on PATH for the\n // child (e.g. when `kash` was invoked via npx).\n const node = process.execPath;\n const entry = process.argv[1];\n if (!entry) {\n reject(\n new CliError('with-retry: cannot determine kash entry path.', {\n code: 'CONFIGURATION',\n recoverable: false,\n suggestion: 'Invoke `kash` directly rather than through a wrapper that clears argv[1].',\n })\n );\n return;\n }\n\n const child = spawn(node, [entry, ...args], {\n // stdin: 'ignore' (NOT 'inherit'). With inherit, an inner\n // command that prompts for input (`kash auth set-key` without\n // --from-stdin, an interactive `kash setup`) would silently\n // hang on retry — the prompt waits for input but the parent's\n // stdin has already been consumed (or is non-interactive in\n // the CI case). A retry loop should never be holding an\n // interactive prompt; if the inner command needs stdin, the\n // user runs it directly. `kash with-retry` is for commands\n // that read auth/config from env or pre-set state.\n // stdout + stderr stay piped so we can capture and re-emit.\n stdio: ['ignore', 'pipe', 'pipe'],\n // Inherit env so the child sees KASH_API_KEY, KASH_PROFILE, etc.\n env: process.env,\n });\n\n let stdout = '';\n child.stdout.on('data', (chunk: Buffer) => {\n stdout += chunk.toString('utf8');\n });\n\n let stderr = '';\n child.stderr.on('data', (chunk: Buffer) => {\n const text = chunk.toString('utf8');\n stderr += text;\n // Stream stderr through live too — the inner command's progress\n // logs (spinners, retries) shouldn't get held back. The final\n // envelope at the very end is what we'll re-parse.\n process.stderr.write(text);\n });\n\n child.on('error', (err) => reject(err));\n child.on('exit', (exitCode, signal) => {\n // A signal-killed child gets exitCode null. Map to a non-zero\n // sentinel so the retry loop treats it as a failure.\n const code = exitCode ?? (signal === null ? 1 : 128);\n resolve({ exitCode: code, stdout, stderr });\n });\n });\n}\n\n/**\n * Look for a `CliErrorEnvelope` in the captured stderr. Robust to\n * non-JSON stderr (e.g. interleaved log lines) — scans for the last\n * line that parses as JSON with the `{ ok: false, error: { code } }`\n * shape.\n */\nexport function parseErrorEnvelope(\n stderr: string\n): { error: { code: string; recoverable?: boolean; retryAfterMs?: number } } | undefined {\n // Search from the end backwards — the envelope is emitted last.\n const lines = stderr.split('\\n').filter((l) => l.trim().startsWith('{'));\n for (let i = lines.length - 1; i >= 0; i--) {\n try {\n const parsed = JSON.parse(lines[i]!) as unknown;\n if (\n typeof parsed === 'object' &&\n parsed !== null &&\n 'error' in parsed &&\n typeof (parsed as { error?: unknown }).error === 'object'\n ) {\n const error = (\n parsed as { error: { code?: unknown; recoverable?: unknown; retryAfterMs?: unknown } }\n ).error;\n if (typeof error.code === 'string') {\n return {\n error: {\n code: error.code,\n ...(typeof error.recoverable === 'boolean' ? { recoverable: error.recoverable } : {}),\n ...(typeof error.retryAfterMs === 'number'\n ? { retryAfterMs: error.retryAfterMs }\n : {}),\n },\n };\n }\n }\n } catch {\n // Not JSON — skip.\n }\n }\n // The envelope is also commonly pretty-printed across multiple lines.\n // Try parsing the entire stderr as JSON if the multi-line scan fails.\n const trimmed = stderr.trim();\n if (trimmed.startsWith('{') && trimmed.endsWith('}')) {\n try {\n const parsed = JSON.parse(trimmed) as {\n error?: { code?: unknown; recoverable?: unknown; retryAfterMs?: unknown };\n };\n if (parsed.error && typeof parsed.error.code === 'string') {\n return {\n error: {\n code: parsed.error.code,\n ...(typeof parsed.error.recoverable === 'boolean'\n ? { recoverable: parsed.error.recoverable }\n : {}),\n ...(typeof parsed.error.retryAfterMs === 'number'\n ? { retryAfterMs: parsed.error.retryAfterMs }\n : {}),\n },\n };\n }\n } catch {\n // ignore\n }\n }\n return undefined;\n}\n\n/**\n * Decide whether to retry based on the envelope. Sources of truth, in\n * order:\n * 1. Explicit allow-list of retryable codes — wins.\n * 2. Explicit deny-list of terminal codes — fails fast.\n * 3. The envelope's own `recoverable: boolean` — fallback for codes\n * we don't have a strong opinion about.\n * 4. No envelope at all (non-JSON inner command, or stdout that\n * doesn't parse) — DO NOT retry by default. Without an envelope\n * we can't tell INVALID_INPUT (terminal) from a transient\n * NETWORK timeout, and retrying validation errors 5× wastes\n * time and confuses operators. Users who want unconditional\n * retries can pass `--retry-without-json`.\n *\n * The \"no-envelope = no-retry\" default is the safe one. The previous\n * behaviour (assume-retryable) caused `with-retry -- trade buy <bad-id>\n * --outcome 0 --amount 10` to retry 5× on INVALID_INPUT — observable\n * confusion in the wild.\n */\nexport function decideRetry(\n code: string | undefined,\n envelopeRecoverable: boolean | undefined,\n options: { retryWithoutJson: boolean } = { retryWithoutJson: false }\n): boolean {\n if (code === undefined) {\n // No envelope. Caller's policy wins.\n return options.retryWithoutJson;\n }\n if (RETRYABLE_CODES.has(code)) return true;\n if (TERMINAL_CODES.has(code)) return false;\n return envelopeRecoverable ?? true;\n}\n\n/**\n * Wait time for the next attempt. Server-driven `retryAfterMs` wins;\n * otherwise exponential (initial * 2^(attempt-1), capped at maxDelay).\n */\nexport function computeWait(\n retryAfterMs: number | undefined,\n attempt: number,\n opts: { initialDelay: number; maxDelay: number }\n): number {\n if (retryAfterMs !== undefined && retryAfterMs > 0) {\n return Math.min(retryAfterMs, opts.maxDelay);\n }\n const exp = opts.initialDelay * 2 ** (attempt - 1);\n return Math.min(exp, opts.maxDelay);\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kashdao/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Official non-custodial command-line interface for the Kash prediction-market protocol. Wraps @kashdao/sdk (Kash-orchestrated mode) and @kashdao/protocol-sdk (self-orchestrated mode).",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
@@ -35,8 +35,8 @@
|
|
|
35
35
|
"viem": "^2.45.0",
|
|
36
36
|
"zod": "^3.25.76",
|
|
37
37
|
"zod-to-json-schema": "^3.25.0",
|
|
38
|
-
"@kashdao/
|
|
39
|
-
"@kashdao/sdk": "0.
|
|
38
|
+
"@kashdao/sdk": "^0.2.1",
|
|
39
|
+
"@kashdao/protocol-sdk": "^0.3.2"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@types/node": "^25.0.3",
|
|
@@ -45,9 +45,9 @@
|
|
|
45
45
|
"tsx": "^4.21.0",
|
|
46
46
|
"typescript": "^5.9.3",
|
|
47
47
|
"vitest": "^4.0.16",
|
|
48
|
-
"@kashdao/vitest-config": "1.1.0",
|
|
49
48
|
"@kashdao/config-typescript": "1.0.0",
|
|
50
|
-
"@kashdao/eslint-config": "1.0.0"
|
|
49
|
+
"@kashdao/eslint-config": "1.0.0",
|
|
50
|
+
"@kashdao/vitest-config": "1.1.0"
|
|
51
51
|
},
|
|
52
52
|
"engines": {
|
|
53
53
|
"node": ">=22.0.0"
|
|
@@ -1 +0,0 @@
|
|
|
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":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/utils/trade-input.ts"],"sourcesContent":["/**\n * Shared input parsers for every trade-execution command.\n *\n * Three modes share these primitives:\n *\n * - SA mode (`kash protocol trade …`, `kash protocol userop build …`)\n * — converts to atomic units (bigint) for the protocol-sdk.\n * - EOA mode (`kash eoa trade …`) — same atomic-unit conversion.\n * - Public-API mode (`kash trade buy/sell`) — ships the raw string\n * to the hosted-API SDK, so it uses the shape validators\n * (`validateUsdcDecimalShape`, `parseOutcomeIndex`) rather than\n * the atomic-unit converters.\n *\n * Centralising the parsers eliminates three sources of drift:\n *\n * 1. **Validation messages** — the same flag should produce the\n * same error wording regardless of mode.\n * 2. **Bounds** — slippage caps, outcome-index ceilings, USDC\n * decimal precision must be identical across modes (otherwise\n * operators get different errors for the same value).\n * 3. **Real-money correctness** — these inputs feed the SDK's\n * atomic-unit math (or the API's server-side conversion); one\n * drift between modes would silently change a trade's effective\n * amount.\n */\n\nimport { CliValidationError } from '../errors.js';\n\n/** Standard 0x-prefixed 40-char EVM address. */\nexport const HEX_ADDRESS_REGEX = /^0x[0-9a-fA-F]{40}$/;\n/** 32-byte hash (UserOp hashes, tx hashes). */\nexport const HEX_HASH_REGEX = /^0x[0-9a-fA-F]{64}$/;\n/**\n * Stringified non-negative bigint (canonical decimal). Used by Zod\n * envelopes that must round-trip a `bigint` field through JSON without\n * losing precision — the wire shape is \"every digit, no scientific\n * notation, no leading sign\". Single source of truth so the SA-mode\n * direct-client envelopes (`OnChainBalanceSchema`, `MarketStateSchema`,\n * `QuoteSchema`) can't drift on what counts as a valid encoding.\n */\nexport const BIGINT_STRING_REGEX = /^\\d+$/;\n/**\n * Human USDC decimal: whole digits with up to 6 fractional digits.\n * Backs both the `validateUsdcDecimalShape` runtime check and the\n * `kash trade buy/sell --dry-run --json` envelope schema, so the\n * runtime parser and the JSON contract can't disagree on what\n * counts as a valid amount.\n */\nexport const USDC_DECIMAL_REGEX = /^\\d+(\\.\\d{1,6})?$/;\n\n/** Default slippage tolerance in basis points (50 = 0.5%). */\nexport const DEFAULT_SLIPPAGE_BPS = 50;\n/** ERC-20's `MAX_UINT256` — sentinel for \"infinite\" approval. */\nexport const MAX_UINT256 = 2n ** 256n - 1n;\n\n/**\n * Validate a market / spender / account address. Throws\n * `INVALID_INPUT` with the supplied field name (so the structured\n * error envelope's `check_input` action points at the right flag).\n */\nexport function validateAddress(value: string, field: string): `0x${string}` {\n if (!HEX_ADDRESS_REGEX.test(value)) {\n throw new CliValidationError(\n `<${field}> must be a 0x-prefixed 40-char hex address.`,\n `Got \"${value}\".`,\n field\n );\n }\n return value as `0x${string}`;\n}\n\n/**\n * Optional variant of {@link validateAddress}. Returns `undefined`\n * when the input is `undefined` (the common \"default to active\n * profile's account\" pattern); otherwise validates and returns the\n * narrowed type.\n */\nexport function validateAddressOptional(\n value: string | undefined,\n field: string\n): `0x${string}` | undefined {\n return value === undefined ? undefined : validateAddress(value, field);\n}\n\n/**\n * Strict-shape regex for unsigned integer flag values. Bare digits\n * only (with optional leading `+`). Anything else — `1.5`, `1e3`,\n * `0x10`, ` 42`, `42 `, `1 2` — must be rejected.\n *\n * Plain `Number.parseInt('1.5', 10)` returns `1` and\n * `Number.parseInt('1e3', 10)` returns `1` too. Both are silent\n * precision losses on inputs that look like real-money typos:\n * - `--outcome 1.5` would buy outcome 1 (wrong outcome).\n * - `--slippage-bps 1e3` would use 1 bps instead of 1000 (10%\n * slippage clipped to 0.01% — catastrophic).\n *\n * Every flag-parser in this module that consumes integers MUST\n * gate its `Number.parseInt` call on this regex.\n */\nconst UNSIGNED_INT_REGEX = /^\\+?\\d+$/;\n\n/**\n * Outcome index in `[0, 7]` (Pythagorean AMM ceiling — markets carry\n * 2-8 outcomes). Throws `INVALID_INPUT` on missing or out-of-range.\n */\nexport function parseOutcomeIndex(raw: string | undefined): number {\n if (raw === undefined) {\n throw new CliValidationError('--outcome is required.', undefined, 'outcome');\n }\n if (!UNSIGNED_INT_REGEX.test(raw)) {\n throw new CliValidationError(\n '--outcome must be an integer between 0 and 7.',\n `Got \"${raw}\". Pass digits only — no decimals (\\`1.5\\` would silently round to 1) or scientific notation.`,\n 'outcome'\n );\n }\n const n = Number.parseInt(raw, 10);\n if (!Number.isFinite(n) || n < 0 || n > 7) {\n throw new CliValidationError(\n '--outcome must be an integer between 0 and 7.',\n 'Markets carry 2-8 outcomes; the index is 0-based.',\n 'outcome'\n );\n }\n return n;\n}\n\n/**\n * `--side` parser shared by `kash protocol quote` and `kash eoa trade`.\n * Returns the canonical uppercase form (`'BUY' | 'SELL'`) the SDKs\n * expect. Case-insensitive on input.\n *\n * The lowercase variant (`'buy' | 'sell'`) used by the public-API\n * predictions feed (`kash markets predictions --side buy`) lives at\n * its sole call site — it has different semantics (filter rather\n * than execute) and routes through a separate API endpoint.\n */\nexport function parseUppercaseSide(raw: string, name = 'side'): 'BUY' | 'SELL' {\n const upper = raw.toUpperCase();\n if (upper !== 'BUY' && upper !== 'SELL') {\n throw new CliValidationError(\n `--${name} must be one of: BUY, SELL (case-insensitive).`,\n `Got \"${raw}\".`,\n name\n );\n }\n return upper;\n}\n\n/**\n * One-shot trade-execution side. SA mode (`kash protocol trade …`)\n * and EOA mode (`kash eoa trade …`) share this enum — `close` is the\n * \"sell my entire balance for outcome N\" sugar.\n */\nexport type TradeOpSide = 'buy' | 'sell' | 'close';\n\n/**\n * Slippage in basis points (0..10000 → 0%..100%).\n *\n * Strict integer shape: `parseInt('1e3', 10)` returns 1, which would\n * silently clip 10% slippage to 0.01% (catastrophic for any real\n * trade). The shape check rejects any non-bare-integer before the\n * range check runs.\n */\nexport function parseSlippageBps(raw: string): number {\n if (!UNSIGNED_INT_REGEX.test(raw)) {\n throw new CliValidationError(\n '--slippage-bps must be an integer between 0 and 10000.',\n `Got \"${raw}\". Pass digits only — \\`1e3\\` would silently parse as 1 (0.01%) instead of 1000 (10%).`,\n 'slippage-bps'\n );\n }\n const n = Number.parseInt(raw, 10);\n if (!Number.isFinite(n) || n < 0 || n > 10_000) {\n throw new CliValidationError(\n '--slippage-bps must be an integer between 0 and 10000.',\n `Got \"${raw}\". 50 = 0.5%, 100 = 1%, 10000 = 100%.`,\n 'slippage-bps'\n );\n }\n return n;\n}\n\n/**\n * Unix-seconds deadline as a bigint. Caps the value at\n * `Number.MAX_SAFE_INTEGER` to catch the common ms-vs-sec confusion\n * (a JS `Date.now()` value passed verbatim would overflow into the\n * year 50000+ AD, which the AMM treats as \"no deadline\" and is\n * indistinguishable from the user's intent).\n */\nexport function parseDeadlineSec(raw: string): bigint {\n let parsed: bigint;\n try {\n parsed = BigInt(raw);\n } catch {\n throw new CliValidationError(\n '--deadline-sec must be a unix timestamp in seconds.',\n `Got \"${raw}\".`,\n 'deadline-sec'\n );\n }\n if (parsed <= 0n) {\n throw new CliValidationError(\n '--deadline-sec must be positive.',\n `Got \"${raw}\".`,\n 'deadline-sec'\n );\n }\n // Sanity check: the largest value that fits in a JS safe integer is\n // ~9e15. Anything above that is almost certainly the user passing\n // milliseconds instead of seconds, and `Number(parsed)` would lose\n // precision in downstream code.\n if (parsed > BigInt(Number.MAX_SAFE_INTEGER)) {\n throw new CliValidationError(\n '--deadline-sec is impossibly far in the future.',\n `Got ${String(parsed)}. Did you pass milliseconds by mistake? Convert to seconds first.`,\n 'deadline-sec'\n );\n }\n return parsed;\n}\n\n/**\n * Validate that a string is a well-formed USDC decimal (whole or up\n * to 6 fractional digits). Used by call sites that need the\n * shape check but pass the raw string downstream rather than\n * converting to atomic units (e.g. `kash trade buy/sell` ships the\n * decimal verbatim to the public-API SDK, which handles atomic\n * conversion server-side).\n */\nexport function validateUsdcDecimalShape(decimal: string, name: string): void {\n if (!USDC_DECIMAL_REGEX.test(decimal)) {\n throw new CliValidationError(\n `--${name} must be a USDC decimal with up to 6 fractional digits.`,\n `Got \"${decimal}\". USDC accepts up to 6 fractional digits (e.g. \"10\", \"12.50\", \"0.000001\").`,\n name\n );\n }\n}\n\n/**\n * Generic partial-completion hash extractor. SA-mode and EOA-mode\n * trade runners catch SDK errors that may carry an already-landed\n * hash (`userOpHash` for SA, `transactionHash` for EOA) in the\n * cause's `context` field — we surface that hash so the operator\n * can resume rather than pay for a duplicate sign/submit cycle.\n *\n * **Discrimination matters here.** Multiple SDK error classes\n * populate `context.<hash>` with the same shape, so we narrow on\n * the SDK's own `code` enum first:\n *\n * - SA mode: `code === 'BUNDLER_RECEIPT_TIMEOUT'` (genuine partial\n * completion — submit succeeded, wait timed out). Other SA\n * errors like `KashSignerError` (`SIGNER_SIGN_FAILED`) ALSO\n * carry `context.userOpHash` but happen pre-submit, so reporting\n * them as partial completions would be actively misleading.\n * - EOA mode: `code === 'WAIT_RECEIPT_FAILED'` (post-submit wait\n * timeout). Other `KashChainError` variants like\n * `CHAIN_RPC_FAILED` carry no transactionHash and would fail\n * the `contextKey` lookup anyway, but the code-narrow catches\n * them earlier.\n *\n * The `cause` argument is `unknown` because it crosses the SDK ↔\n * CLI boundary; reads are defensive at every step.\n */\nexport function extractPartialHash(\n cause: unknown,\n options: { code: string; contextKey: 'userOpHash' | 'transactionHash' }\n): `0x${string}` | undefined {\n if (cause === null || typeof cause !== 'object') return undefined;\n const code = (cause as { code?: unknown }).code;\n if (code !== options.code) return undefined;\n const ctx = (cause as { context?: unknown }).context;\n if (ctx === null || typeof ctx !== 'object') return undefined;\n const hash = (ctx as Record<string, unknown>)[options.contextKey];\n return typeof hash === 'string' && HEX_HASH_REGEX.test(hash)\n ? (hash as `0x${string}`)\n : undefined;\n}\n\n/**\n * Convert a USDC decimal string (`\"10\"`, `\"12.50\"`, `\"0.000001\"`)\n * to the 6-decimal atomic representation the AMM expects.\n *\n * Rejects strings with more than 6 fractional digits — silently\n * truncating could lose precision the user expected to preserve.\n */\nexport function decimalToAtomicUsdc(decimal: string, name: string): bigint {\n validateUsdcDecimalShape(decimal, name);\n const [whole, frac = ''] = decimal.split('.');\n const fracPadded = frac.padEnd(6, '0');\n return BigInt(`${whole}${fracPadded}`);\n}\n\n/**\n * Convert a token decimal string (`\"1.5\"`, `\"0.0001\"`) to WAD-18\n * atomic units. Outcome tokens use 18-decimal precision throughout\n * the protocol.\n */\nexport function decimalToAtomicWad(decimal: string, name: string): bigint {\n if (!/^\\d+(\\.\\d{1,18})?$/.test(decimal)) {\n throw new CliValidationError(\n `--${name} must be a token decimal with up to 18 fractional digits.`,\n `Got \"${decimal}\". Outcome tokens use WAD-18 precision.`,\n name\n );\n }\n const [whole, frac = ''] = decimal.split('.');\n const fracPadded = frac.padEnd(18, '0');\n return BigInt(`${whole}${fracPadded}`);\n}\n"],"mappings":"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","names":[]}
|
package/dist/chunk-KAM6KOFD.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/utils/custom-chain.ts","../src/utils/signer-key.ts"],"sourcesContent":["/**\n * Translate the CLI's profile-level `customChain` config into the\n * `@kashdao/protocol-sdk` `CustomChain` shape (viem chain + protocol\n * addresses + optional smart-account factory config).\n *\n * The CLI never asks the user to hand-construct a viem Chain object —\n * we synthesise one from the profile's `defaultChainId + rpcUrl +\n * customChain.name`, matching what the\n * `packages/protocol-sdk/examples/local-anvil/` quickstarts do inline.\n *\n * This is the only place in the CLI that imports `viem.defineChain`.\n * The dynamic import keeps viem off the cold-start path for non-direct\n * commands (markets list / trade buy / portfolio / health all skip\n * this module).\n *\n * @module utils/custom-chain\n */\n\nimport { CliError, EXIT_CODES } from '../errors.js';\n\nimport type { CliConfig } from './config-store.js';\nimport type { CustomChain } from '@kashdao/protocol-sdk';\n\nexport type ResolveCustomChainOptions = {\n /** The profile's `customChain` block — required, validated by Zod upstream. */\n readonly customChain: NonNullable<CliConfig['customChain']>;\n /**\n * `defaultChainId` from the profile. Required at the call site (the\n * caller raises `DIRECT_CONFIG_MISSING` if missing) — we accept it\n * non-optional here so this helper is total.\n */\n readonly chainId: number;\n /**\n * RPC URL used to construct the viem chain's `rpcUrls.default.http`.\n * Same URL the CLI passes as the SDK's `rpc` field — duplicated\n * because the viem chain object carries its own RPC array for any\n * downstream callers that read from `chain.rpcUrls` rather than the\n * `transport`.\n */\n readonly rpc: string;\n};\n\n/**\n * Materialise a CustomChain. Use it like:\n *\n * ```ts\n * const customChain = await resolveCliCustomChain({ customChain, chainId, rpc });\n * createEoaClient({ ..., customChain });\n * ```\n *\n * The viem chain is given `Ether` as the native currency by default —\n * which is correct for every chain the protocol-sdk supports today\n * (Base + Anvil derivatives). If we ever launch on a chain whose\n * native currency isn't ETH-shaped, surface that as a separate\n * `customChain.nativeCurrency` config field; for now the constant\n * keeps the config minimal.\n */\nexport async function resolveCliCustomChain(opts: ResolveCustomChainOptions): Promise<CustomChain> {\n const { customChain, chainId, rpc } = opts;\n\n // Required-at-use validation. The persisted schema marks every field\n // optional so `kash config set customChain.<leaf>` can build the\n // object up one leaf at a time. The SDK's CustomChain shape requires\n // `name + addresses.factory + addresses.usdc`. We surface the\n // missing field here, where the consumer just tried to run an\n // `eoa`/`protocol` command, with the exact `kash config set` line\n // they need to fix it.\n if (customChain.name === undefined) {\n throw missingCustomChainField(\n 'customChain.name',\n '`kash config set customChain.name \"Anvil local\"` (or whatever label you want surfaced on client.addresses.name)'\n );\n }\n if (customChain.factoryAddress === undefined) {\n throw missingCustomChainField(\n 'customChain.factoryAddress',\n '`kash config set customChain.factoryAddress 0x…` (the deployed Kash factory address on this chain)'\n );\n }\n if (customChain.usdcAddress === undefined) {\n throw missingCustomChainField(\n 'customChain.usdcAddress',\n '`kash config set customChain.usdcAddress 0x…` (the deployed USDC token address on this chain)'\n );\n }\n\n // Smart-account triple is all-or-nothing — surfacing each missing\n // leaf separately is more actionable than \"smartAccount is incomplete\".\n let smartAccount: CustomChain['smartAccount'];\n if (customChain.smartAccount !== undefined) {\n const sa = customChain.smartAccount;\n if (\n sa.factoryAddress !== undefined ||\n sa.implementationAddress !== undefined ||\n sa.entryPointAddress !== undefined\n ) {\n if (sa.factoryAddress === undefined) {\n throw missingCustomChainField(\n 'customChain.smartAccount.factoryAddress',\n '`kash config set customChain.smartAccount.factoryAddress 0x…`'\n );\n }\n if (sa.implementationAddress === undefined) {\n throw missingCustomChainField(\n 'customChain.smartAccount.implementationAddress',\n '`kash config set customChain.smartAccount.implementationAddress 0x…`'\n );\n }\n if (sa.entryPointAddress === undefined) {\n throw missingCustomChainField(\n 'customChain.smartAccount.entryPointAddress',\n '`kash config set customChain.smartAccount.entryPointAddress 0x…`'\n );\n }\n smartAccount = {\n factoryAddress: sa.factoryAddress as `0x${string}`,\n implementationAddress: sa.implementationAddress as `0x${string}`,\n entryPointAddress: sa.entryPointAddress as `0x${string}`,\n };\n }\n }\n\n // Lazy-load viem so this module's import doesn't drag viem onto the\n // non-direct command paths. Same firewall pattern as the\n // `eoa-client.ts` / `direct-client.ts` `await import('@kashdao/protocol-sdk')`.\n const { defineChain } = await import('viem');\n\n return {\n name: customChain.name,\n viemChain: defineChain({\n id: chainId,\n name: customChain.name,\n nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 },\n rpcUrls: { default: { http: [rpc] } },\n }),\n addresses: {\n factory: customChain.factoryAddress as `0x${string}`,\n usdc: customChain.usdcAddress as `0x${string}`,\n ...(customChain.oracleAddress === undefined\n ? {}\n : { oracle: customChain.oracleAddress as `0x${string}` }),\n ...(customChain.vaultAddress === undefined\n ? {}\n : { vault: customChain.vaultAddress as `0x${string}` }),\n ...(customChain.tokens1155Address === undefined\n ? {}\n : { tokens1155: customChain.tokens1155Address as `0x${string}` }),\n ...(customChain.paramRegistryAddress === undefined\n ? {}\n : { paramRegistry: customChain.paramRegistryAddress as `0x${string}` }),\n },\n ...(smartAccount === undefined ? {} : { smartAccount }),\n };\n}\n\nfunction missingCustomChainField(field: string, hint: string): CliError {\n return new CliError(`Custom-chain config missing: ${field}.`, {\n code: 'DIRECT_CONFIG_MISSING',\n exitCode: EXIT_CODES.GENERIC,\n suggestion: `Set ${field} via ${hint}.`,\n actions: [\n {\n type: 'check_input',\n field,\n description: hint,\n },\n ],\n });\n}\n","/**\n * Shared `signerKeyRef` resolution for direct-mode + EOA-mode clients.\n *\n * Both modes pull the EOA's private key from the same place:\n *\n * - `file:<absolute-path>` — read a 0x-prefixed hex key from disk.\n * On POSIX, the file mode is checked and\n * a warning is emitted (but the load is\n * not refused) if the file is readable\n * by group or other — the same discipline\n * as ssh / aws-cli warning on\n * `~/.ssh/id_rsa` permissions, but\n * non-fatal because a CI runner may\n * legitimately stage keys with broader\n * modes inside an ephemeral container.\n * - `env:<NAME>` — read a 0x-prefixed hex key from a\n * process env var. (No mode check\n * applies — the OS does not expose\n * env-var ACLs.)\n *\n * The CLI never persists raw keys (the config schema only stores the\n * reference, not the key itself). This helper validates the format\n * and resolves the raw bytes; the caller wraps them in the right\n * adapter for its mode (`viemAccountSigner` for SA UserOps,\n * `viemAccountEoaSigner` for vanilla EIP-1559 txs).\n *\n * Centralised so the SA and EOA paths can never drift on the\n * file/env reading, hex validation, or error wording — drift here is\n * a real-money risk.\n */\n\nimport { readFile, stat } from 'node:fs/promises';\n\nimport { CliError } from '../errors.js';\n\nimport { log } from './output.js';\n\n/** 0x-prefixed 32-byte hex private key. */\nexport type RawPrivateKey = `0x${string}`;\n\n/** Loosely-typed regex for a private key — not a cryptographic check. */\nconst PRIVATE_KEY_REGEX = /^0x[0-9a-fA-F]{64}$/;\n\n/**\n * Resolve a `signerKeyRef` to its raw 0x-prefixed hex private key.\n * Throws `CliError(SIGNER_FAILED)` for every failure mode (missing\n * scheme, missing file/env, malformed key) so callers get a uniform\n * structured envelope.\n */\nexport async function loadRawPrivateKey(ref: string | undefined): Promise<RawPrivateKey> {\n if (!ref) {\n throw new CliError('signerKeyRef is required.', {\n code: 'SIGNER_FAILED',\n suggestion:\n '`kash config set signerKeyRef file:<path>` or `env:<NAME>` (CLI never persists raw keys).',\n });\n }\n\n const [scheme, ...rest] = ref.split(':');\n const target = rest.join(':');\n\n let rawKey: string;\n if (scheme === 'file') {\n if (target === '') {\n throw new CliError('signerKeyRef: file path is empty.', {\n code: 'SIGNER_FAILED',\n suggestion: 'Use `signerKeyRef = file:<absolute-path>` with a path to the private key.',\n });\n }\n try {\n rawKey = (await readFile(target, 'utf8')).trim();\n } catch (cause) {\n throw new CliError(`signerKeyRef: failed to read ${target}.`, {\n code: 'SIGNER_FAILED',\n suggestion: 'Verify the file exists, is readable, and contains a 0x-prefixed hex key.',\n cause,\n });\n }\n await warnIfWorldReadable(target);\n } else if (scheme === 'env') {\n if (target === '') {\n throw new CliError('signerKeyRef: env variable name is empty.', {\n code: 'SIGNER_FAILED',\n suggestion: 'Use `signerKeyRef = env:<NAME>` referencing an environment variable.',\n });\n }\n const fromEnv = process.env[target];\n if (!fromEnv) {\n throw new CliError(`signerKeyRef: environment variable ${target} is not set or empty.`, {\n code: 'SIGNER_FAILED',\n suggestion: `Set ${target} to a 0x-prefixed hex private key, or rotate to a file:<path> reference.`,\n });\n }\n rawKey = fromEnv.trim();\n } else {\n throw new CliError(`signerKeyRef: unknown scheme \"${scheme ?? ''}\".`, {\n code: 'SIGNER_FAILED',\n suggestion: 'Supported schemes: `file:<path>`, `env:<NAME>`.',\n });\n }\n\n if (!PRIVATE_KEY_REGEX.test(rawKey)) {\n throw new CliError('signerKeyRef target is not a 0x-prefixed 32-byte hex private key.', {\n code: 'SIGNER_FAILED',\n suggestion: 'The key must be exactly 0x followed by 64 hex characters (32 bytes).',\n });\n }\n\n return rawKey as RawPrivateKey;\n}\n\n/**\n * POSIX-only: warn (don't refuse) if the key file is readable by\n * group or other. Mirrors ssh / aws-cli's \"you should `chmod 600`\"\n * banner on `~/.ssh/id_rsa`. Skipped on Windows (no-op) because\n * Windows uses ACLs rather than POSIX modes — `mode & 0o077` is\n * meaningless. Skipped on stat failure because we just successfully\n * read the file; the stat is purely advisory.\n */\nasync function warnIfWorldReadable(path: string): Promise<void> {\n if (process.platform === 'win32') return;\n let mode: number;\n try {\n const info = await stat(path);\n mode = info.mode;\n } catch {\n return; // advisory only — we already loaded the key successfully\n }\n // Lower 9 bits are rwxrwxrwx. Group + other read bits are 0o044.\n // Lower 6 bits (group + other rwx) are 0o077 — anything set in\n // those positions means somebody other than the owner can touch\n // the key.\n if ((mode & 0o077) !== 0) {\n log.warn(\n `signerKeyRef file mode is 0${(mode & 0o777).toString(8)} (group/other readable). ` +\n `Run \\`chmod 600 ${path}\\` to restrict access. (warning only — load succeeded.)`\n );\n }\n}\n"],"mappings":"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","names":[]}
|