@kidd-cli/core 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/dist/{config-Db_sjFU-.js → config-D8e5qxLp.js} +5 -17
  2. package/dist/config-D8e5qxLp.js.map +1 -0
  3. package/dist/{create-store-D-fQpCql.js → create-store-OHdkm_Yt.js} +3 -4
  4. package/dist/{create-store-D-fQpCql.js.map → create-store-OHdkm_Yt.js.map} +1 -1
  5. package/dist/index.d.ts +31 -2
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +75 -40
  8. package/dist/index.js.map +1 -1
  9. package/dist/lib/config.js +3 -4
  10. package/dist/lib/logger.d.ts +1 -1
  11. package/dist/lib/logger.js +1 -2
  12. package/dist/lib/logger.js.map +1 -1
  13. package/dist/lib/project.d.ts +1 -1
  14. package/dist/lib/project.d.ts.map +1 -1
  15. package/dist/lib/project.js +2 -3
  16. package/dist/lib/store.d.ts +1 -1
  17. package/dist/lib/store.js +3 -4
  18. package/dist/{logger-BkQQej8h.d.ts → logger-9j49T5da.d.ts} +1 -1
  19. package/dist/{logger-BkQQej8h.d.ts.map → logger-9j49T5da.d.ts.map} +1 -1
  20. package/dist/middleware/auth.d.ts +68 -40
  21. package/dist/middleware/auth.d.ts.map +1 -1
  22. package/dist/middleware/auth.js +245 -230
  23. package/dist/middleware/auth.js.map +1 -1
  24. package/dist/middleware/http.d.ts +1 -1
  25. package/dist/middleware/http.js +163 -4
  26. package/dist/middleware/http.js.map +1 -1
  27. package/dist/{middleware-BFBKNSPQ.js → middleware-BWnPSRWR.js} +2 -4
  28. package/dist/{middleware-BFBKNSPQ.js.map → middleware-BWnPSRWR.js.map} +1 -1
  29. package/dist/{project-DuXgjaa_.js → project-D0g84bZY.js} +4 -8
  30. package/dist/project-D0g84bZY.js.map +1 -0
  31. package/dist/{types-BaZ5WqVM.d.ts → types-CTvrsrnD.d.ts} +3 -3
  32. package/dist/types-CTvrsrnD.d.ts.map +1 -0
  33. package/dist/{types-C0CYivzY.d.ts → types-D-BxshYM.d.ts} +1 -1
  34. package/dist/{types-C0CYivzY.d.ts.map → types-D-BxshYM.d.ts.map} +1 -1
  35. package/package.json +7 -7
  36. package/dist/config-Db_sjFU-.js.map +0 -1
  37. package/dist/create-http-client-tZJWlWp1.js +0 -165
  38. package/dist/create-http-client-tZJWlWp1.js.map +0 -1
  39. package/dist/project-DuXgjaa_.js.map +0 -1
  40. package/dist/types-BaZ5WqVM.d.ts.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"auth.js","names":["match","match","match","attempt","match"],"sources":["../../src/middleware/http/build-auth-headers.ts","../../src/middleware/auth/constants.ts","../../src/middleware/auth/credential.ts","../../src/middleware/auth/oauth-server.ts","../../src/middleware/auth/strategies/device-code.ts","../../src/middleware/auth/strategies/dotenv.ts","../../src/middleware/auth/strategies/env.ts","../../src/middleware/auth/schema.ts","../../src/middleware/auth/strategies/file.ts","../../src/middleware/auth/strategies/oauth.ts","../../src/middleware/auth/strategies/token.ts","../../src/middleware/auth/chain.ts","../../src/middleware/auth/context.ts","../../src/middleware/auth/auth.ts"],"sourcesContent":["/**\n * Convert auth credentials into HTTP headers.\n *\n * Uses exhaustive pattern matching to map each credential variant to\n * the appropriate header format.\n *\n * @module\n */\n\nimport { Buffer } from 'node:buffer'\n\nimport { match } from 'ts-pattern'\n\nimport type { AuthCredential } from '../auth/types.js'\n\n/**\n * Convert an auth credential into HTTP headers.\n *\n * @param credential - The credential to convert.\n * @returns A record of header name to header value.\n */\nexport function buildAuthHeaders(credential: AuthCredential): Readonly<Record<string, string>> {\n return match(credential)\n .with({ type: 'bearer' }, (c) => ({\n Authorization: `Bearer ${c.token}`,\n }))\n .with({ type: 'basic' }, (c) => ({\n Authorization: `Basic ${Buffer.from(`${c.username}:${c.password}`).toString('base64')}`,\n }))\n .with({ type: 'api-key' }, (c) => ({\n [c.headerName]: c.key,\n }))\n .with({ type: 'custom' }, (c) => ({ ...c.headers }))\n .exhaustive()\n}\n","/**\n * Default filename for file-based credential storage.\n */\nexport const DEFAULT_AUTH_FILENAME = 'auth.json' as const\n\n/**\n * Suffix appended to the derived token environment variable name.\n */\nexport const TOKEN_VAR_SUFFIX = '_TOKEN' as const\n\n/**\n * Default port for the local OAuth callback server (`0` = ephemeral).\n */\nexport const DEFAULT_OAUTH_PORT = 0\n\n/**\n * Default callback path for the local OAuth server.\n */\nexport const DEFAULT_OAUTH_CALLBACK_PATH = '/callback'\n\n/**\n * Default timeout for the OAuth PKCE flow in milliseconds (2 minutes).\n */\nexport const DEFAULT_OAUTH_TIMEOUT = 120_000\n\n/**\n * Default poll interval for the device code flow in milliseconds (5 seconds).\n */\nexport const DEFAULT_DEVICE_CODE_POLL_INTERVAL = 5000\n\n/**\n * Default timeout for the device code flow in milliseconds (5 minutes).\n */\nexport const DEFAULT_DEVICE_CODE_TIMEOUT = 300_000\n\n/**\n * Derive the default environment variable name from a CLI name.\n *\n * Converts kebab-case to SCREAMING_SNAKE_CASE and appends `_TOKEN`.\n * Example: `my-app` → `MY_APP_TOKEN`\n *\n * @param cliName - The CLI name.\n * @returns The derived environment variable name.\n */\nexport function deriveTokenVar(cliName: string): string {\n return `${cliName.replaceAll('-', '_').toUpperCase()}${TOKEN_VAR_SUFFIX}`\n}\n","import type { BearerCredential } from './types.js'\n\n/**\n * Check whether a token string is a non-empty, non-whitespace value.\n *\n * Acts as a type guard: when it returns true, TypeScript narrows the\n * token to `string`. Consolidates the repeated `!token || token.trim() === ''`\n * guard found across strategy resolvers.\n *\n * @param token - The token string to check.\n * @returns True when the token is a non-empty string.\n */\nexport function isValidToken(token: string | undefined | null): token is string {\n if (!token) {\n return false\n }\n\n if (token.trim() === '') {\n return false\n }\n\n return true\n}\n\n/**\n * Construct a bearer credential from a raw token string.\n *\n * @param token - The access token value.\n * @returns A BearerCredential with `type: 'bearer'`.\n */\nexport function createBearerCredential(token: string): BearerCredential {\n return { token, type: 'bearer' }\n}\n\n/**\n * POST form-encoded parameters to a URL.\n *\n * Wraps the duplicated `fetch` call with `Content-Type: application/x-www-form-urlencoded`\n * found in the OAuth and device code strategies. Returns null on network or\n * request failure instead of throwing.\n *\n * @param url - The endpoint URL.\n * @param params - The URL-encoded form parameters.\n * @param signal - Optional AbortSignal for timeout/cancellation.\n * @returns The fetch Response on success, null on failure.\n */\nexport async function postFormEncoded(\n url: string,\n params: URLSearchParams,\n signal?: AbortSignal\n): Promise<Response | null> {\n try {\n return await fetch(url, {\n body: params.toString(),\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n method: 'POST',\n signal,\n })\n } catch {\n return null\n }\n}\n","/**\n * Shared utilities for OAuth-based auth resolvers.\n *\n * Extracted from the local HTTP server, browser-launch, and\n * lifecycle patterns shared by the PKCE and device-code flows.\n *\n * @module\n */\n\nimport { execFile } from 'node:child_process'\nimport { createServer } from 'node:http'\nimport type { IncomingMessage, Server, ServerResponse } from 'node:http'\nimport type { Socket } from 'node:net'\nimport { platform } from 'node:os'\n\nimport { match } from 'ts-pattern'\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst CLOSE_PAGE_HTML = [\n '<!DOCTYPE html>',\n '<html>',\n '<body><p>Authentication complete. You can close this tab.</p></body>',\n '</html>',\n].join('\\n')\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/**\n * A deferred promise with an externally accessible resolve function.\n */\nexport interface Deferred<T> {\n readonly promise: Promise<T>\n readonly resolve: (value: T) => void\n}\n\n/**\n * A clearable timeout that does not keep the event loop alive after cancellation.\n */\nexport interface Timeout {\n readonly promise: Promise<void>\n readonly clear: () => void\n}\n\n/**\n * Result of starting a local HTTP server with request handling.\n */\nexport interface LocalServerHandle {\n readonly port: Promise<number | null>\n readonly server: Server\n readonly sockets: Set<Socket>\n}\n\n// ---------------------------------------------------------------------------\n// Exported functions\n// ---------------------------------------------------------------------------\n\n/**\n * Create a deferred promise with externally accessible resolve.\n *\n * Uses a mutable state container to capture the promise resolver --\n * this is an intentional exception to immutability rules because the\n * Promise constructor API requires synchronous resolver capture.\n *\n * @returns A deferred object with promise and resolve.\n */\nexport function createDeferred<T>(): Deferred<T> {\n const state: { resolve: ((value: T) => void) | null } = { resolve: null }\n\n const promise = new Promise<T>((resolve) => {\n state.resolve = resolve\n })\n\n return {\n promise,\n resolve: (value: T): void => {\n if (state.resolve) {\n state.resolve(value)\n }\n },\n }\n}\n\n/**\n * Create a clearable timeout.\n *\n * Returns a promise that resolves after `ms` milliseconds and a `clear`\n * function that cancels the timer so it does not hold the event loop open.\n *\n * Uses a mutable state container to capture the timer id -- this is an\n * intentional exception to immutability rules because `setTimeout`\n * returns an opaque handle that must be stored for later cancellation.\n *\n * @param ms - Duration in milliseconds.\n * @returns A Timeout with `promise` and `clear`.\n */\nexport function createTimeout(ms: number): Timeout {\n const state: { id: ReturnType<typeof setTimeout> | null } = { id: null }\n\n const promise = new Promise<void>((resolve) => {\n state.id = setTimeout(resolve, ms)\n })\n\n return {\n clear: (): void => {\n if (state.id !== null) {\n clearTimeout(state.id)\n state.id = null\n }\n },\n promise,\n }\n}\n\n/**\n * Track socket connections on a server so they can be destroyed on close.\n *\n * Mutates the provided socket set -- this is an intentional exception to\n * immutability rules because the HTTP server API is inherently stateful.\n *\n * @param server - The HTTP server.\n * @param sockets - The set to track sockets in.\n */\nexport function trackConnections(server: Server, sockets: Set<Socket>): void {\n server.on('connection', (socket: Socket) => {\n sockets.add(socket)\n socket.on('close', () => {\n sockets.delete(socket)\n })\n })\n}\n\n/**\n * Close a server and destroy all active connections immediately.\n *\n * `server.close()` only stops accepting new connections -- existing\n * keep-alive connections hold the event loop open. This helper\n * destroys every tracked socket so the process can exit cleanly.\n *\n * @param server - The HTTP server to close.\n * @param sockets - The set of tracked sockets.\n */\nexport function destroyServer(server: Server, sockets: Set<Socket>): void {\n server.close()\n Array.from(sockets, (socket) => socket.destroy())\n sockets.clear()\n}\n\n/**\n * Send an HTML success page and end the response.\n *\n * @param res - The server response object.\n */\nexport function sendSuccessPage(res: ServerResponse): void {\n res.writeHead(200, { 'Content-Type': 'text/html' })\n res.end(CLOSE_PAGE_HTML)\n}\n\n/**\n * Check whether a URL is safe for use as an OAuth endpoint.\n *\n * Requires HTTPS for all URLs except loopback addresses, where\n * HTTP is permitted per RFC 8252 §8.3 (native app redirect URIs).\n *\n * @param url - The URL string to validate.\n * @returns True when the URL uses HTTPS or HTTP on a loopback address.\n */\nexport function isSecureAuthUrl(url: string): boolean {\n try {\n const parsed = new URL(url)\n\n if (parsed.protocol === 'https:') {\n return true\n }\n\n if (parsed.protocol !== 'http:') {\n return false\n }\n\n return isLoopbackHost(parsed.hostname)\n } catch {\n return false\n }\n}\n\n/**\n * Open a URL in the user's default browser using a platform-specific command.\n *\n * Validates that the URL uses the HTTP or HTTPS protocol before opening\n * to prevent dangerous schemes like `javascript:` or `data:`. Silently\n * returns if the URL is invalid.\n *\n * On Windows, `start` is a `cmd.exe` built-in -- not a standalone executable --\n * so it must be invoked via `cmd /c start \"\" <url>`. The empty string argument\n * prevents `cmd` from interpreting the URL as a window title.\n *\n * @param url - The URL to open (must use http: or https: protocol).\n */\nexport function openBrowser(url: string): void {\n if (!isHttpUrl(url)) {\n return\n }\n\n const { command, args } = match(platform())\n .with('darwin', () => ({ args: [url], command: 'open' }))\n .with('win32', () => ({ args: ['/c', 'start', '', escapeCmdMeta(url)], command: 'cmd' }))\n .otherwise(() => ({ args: [url], command: 'xdg-open' }))\n\n const child = execFile(command, args)\n child.on('error', () => undefined)\n}\n\n/**\n * Start a local HTTP server on `127.0.0.1` with socket tracking.\n *\n * Returns a handle containing the server, tracked sockets, and a port\n * promise that resolves once the server is listening.\n *\n * @param options - Server configuration.\n * @returns A LocalServerHandle with port, server, and sockets.\n */\nexport function startLocalServer(options: {\n readonly port: number\n readonly onRequest: (req: IncomingMessage, res: ServerResponse) => void\n}): LocalServerHandle {\n const portDeferred = createDeferred<number | null>()\n\n // Mutable socket set required for resource cleanup.\n // Server API is stateful -- tracking sockets is the only way to destroy keep-alive connections.\n const sockets = new Set<Socket>()\n\n const server = createServer(options.onRequest)\n\n trackConnections(server, sockets)\n\n server.on('error', () => {\n destroyServer(server, sockets)\n portDeferred.resolve(null)\n })\n\n server.listen(options.port, '127.0.0.1', () => {\n const addr = server.address()\n\n if (addr === null || typeof addr === 'string') {\n destroyServer(server, sockets)\n portDeferred.resolve(null)\n return\n }\n\n portDeferred.resolve(addr.port)\n })\n\n return {\n port: portDeferred.promise,\n server,\n sockets,\n }\n}\n\n// ---------------------------------------------------------------------------\n// Private helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Check whether a URL uses the HTTP or HTTPS protocol.\n *\n * Rejects dangerous schemes like `javascript:`, `data:`, and `file:`\n * to prevent browser-based attacks when opening untrusted URLs.\n *\n * @private\n * @param url - The URL string to validate.\n * @returns True when the URL uses http: or https: protocol.\n */\nfunction isHttpUrl(url: string): boolean {\n try {\n const parsed = new URL(url)\n return parsed.protocol === 'https:' || parsed.protocol === 'http:'\n } catch {\n return false\n }\n}\n\n/**\n * Check whether a hostname is a loopback address.\n *\n * RFC 8252 §8.3 permits HTTP for loopback interfaces during\n * native app authorization flows.\n *\n * @private\n * @param hostname - The hostname to check.\n * @returns True when the hostname is a loopback address.\n */\nfunction isLoopbackHost(hostname: string): boolean {\n return hostname === '127.0.0.1' || hostname === '[::1]' || hostname === 'localhost'\n}\n\n/**\n * Escape `cmd.exe` metacharacters in a URL string.\n *\n * Characters like `&`, `|`, `<`, `>`, and `^` are interpreted as\n * command separators or redirectors by `cmd.exe`. Prefixing each\n * with `^` neutralises the special meaning.\n *\n * @private\n * @param url - The URL to escape.\n * @returns The escaped URL string.\n */\nfunction escapeCmdMeta(url: string): string {\n return url.replaceAll(/[&|<>^]/g, '^$&')\n}\n","/**\n * OAuth 2.0 Device Authorization Grant resolver (RFC 8628).\n *\n * Requests a device code, displays the verification URL and user code,\n * and polls the token endpoint until the user completes authorization\n * or the flow times out.\n *\n * @module\n */\n\nimport { match } from 'ts-pattern'\n\nimport type { Prompts } from '@/context/types.js'\n\nimport { createBearerCredential, postFormEncoded } from '../credential.js'\nimport { isSecureAuthUrl, openBrowser } from '../oauth-server.js'\nimport type { AuthCredential } from '../types.js'\n\n/**\n * RFC 8628 slow_down backoff increment in milliseconds.\n */\nconst SLOW_DOWN_INCREMENT = 5000\n\n/**\n * RFC 8628 device code grant type URN.\n */\nconst DEVICE_CODE_GRANT_TYPE = 'urn:ietf:params:oauth:grant-type:device_code'\n\n/**\n * Resolve a bearer credential via OAuth 2.0 Device Authorization Grant.\n *\n * 1. POSTs to the device authorization endpoint to obtain a device code\n * 2. Displays the verification URL and user code via prompts\n * 3. Optionally opens the verification URL in the browser\n * 4. Polls the token endpoint until authorization completes or times out\n *\n * @param options - Device code flow configuration.\n * @returns A bearer credential on success, null on failure or timeout.\n */\nexport async function resolveFromDeviceCode(options: {\n readonly clientId: string\n readonly deviceAuthUrl: string\n readonly tokenUrl: string\n readonly scopes: readonly string[]\n readonly pollInterval: number\n readonly timeout: number\n readonly prompts: Prompts\n readonly openBrowserOnStart?: boolean\n}): Promise<AuthCredential | null> {\n if (!isSecureAuthUrl(options.deviceAuthUrl)) {\n return null\n }\n\n if (!isSecureAuthUrl(options.tokenUrl)) {\n return null\n }\n\n const deadline = Date.now() + options.timeout\n const signal = AbortSignal.timeout(options.timeout)\n\n const authResponse = await requestDeviceAuth({\n clientId: options.clientId,\n deviceAuthUrl: options.deviceAuthUrl,\n scopes: options.scopes,\n signal,\n })\n\n if (!authResponse) {\n return null\n }\n\n await displayUserCode(options.prompts, authResponse.verificationUri, authResponse.userCode)\n\n if (options.openBrowserOnStart !== false) {\n openBrowser(authResponse.verificationUri)\n }\n\n const interval = resolveInterval(authResponse.interval, options.pollInterval)\n\n return pollForToken({\n clientId: options.clientId,\n deadline,\n deviceCode: authResponse.deviceCode,\n interval,\n signal,\n tokenUrl: options.tokenUrl,\n })\n}\n\n// ---------------------------------------------------------------------------\n// Private types\n// ---------------------------------------------------------------------------\n\n/**\n * Parsed response from the device authorization endpoint.\n *\n * @private\n */\ninterface DeviceAuthResponse {\n readonly deviceCode: string\n readonly userCode: string\n readonly verificationUri: string\n readonly interval: number | null\n}\n\n// ---------------------------------------------------------------------------\n// Private helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Request a device code from the authorization server.\n *\n * @private\n * @param options - Device auth request parameters.\n * @returns The parsed device auth response, or null on failure.\n */\nasync function requestDeviceAuth(options: {\n readonly clientId: string\n readonly deviceAuthUrl: string\n readonly scopes: readonly string[]\n readonly signal?: AbortSignal\n}): Promise<DeviceAuthResponse | null> {\n const body = new URLSearchParams({ client_id: options.clientId })\n\n if (options.scopes.length > 0) {\n body.set('scope', options.scopes.join(' '))\n }\n\n const response = await postFormEncoded(options.deviceAuthUrl, body, options.signal)\n\n if (!response) {\n return null\n }\n\n if (!response.ok) {\n return null\n }\n\n try {\n const data: unknown = await response.json()\n\n return parseDeviceAuthResponse(data)\n } catch {\n return null\n }\n}\n\n/**\n * Parse a device authorization response body.\n *\n * @private\n * @param data - The raw response data.\n * @returns The parsed response, or null if required fields are missing.\n */\nfunction parseDeviceAuthResponse(data: unknown): DeviceAuthResponse | null {\n if (typeof data !== 'object' || data === null) {\n return null\n }\n\n const record = data as Record<string, unknown>\n\n if (typeof record.device_code !== 'string' || record.device_code === '') {\n return null\n }\n\n if (typeof record.user_code !== 'string' || record.user_code === '') {\n return null\n }\n\n if (typeof record.verification_uri !== 'string' || record.verification_uri === '') {\n return null\n }\n\n const interval = resolveServerInterval(record.interval)\n\n return {\n deviceCode: record.device_code,\n interval,\n userCode: record.user_code,\n verificationUri: record.verification_uri,\n }\n}\n\n/**\n * Display the verification URL and user code to the user.\n *\n * Uses `prompts.text()` to show the information and wait for\n * the user to press Enter to acknowledge.\n *\n * @private\n * @param prompts - The prompts instance.\n * @param verificationUri - The URL the user should visit.\n * @param userCode - The code the user should enter.\n */\nasync function displayUserCode(\n prompts: Prompts,\n verificationUri: string,\n userCode: string\n): Promise<void> {\n try {\n await prompts.text({\n defaultValue: '',\n message: `Open ${verificationUri} and enter code: ${userCode} (press Enter to continue)`,\n })\n } catch {\n // User cancelled -- continue anyway, polling will handle timeout\n }\n}\n\n/**\n * Resolve the poll interval, preferring server-provided value.\n *\n * @private\n * @param serverInterval - The interval from the server response (in ms), or null.\n * @param configInterval - The configured default interval.\n * @returns The resolved interval in milliseconds.\n */\nfunction resolveInterval(serverInterval: number | null, configInterval: number): number {\n if (serverInterval !== null) {\n return serverInterval\n }\n\n return configInterval\n}\n\n/**\n * Poll the token endpoint for an access token using recursive tail-call style.\n *\n * Handles RFC 8628 error codes:\n * - `authorization_pending` -- continue polling\n * - `slow_down` -- increase interval by 5 seconds, continue\n * - `expired_token` -- return null\n * - `access_denied` -- return null\n *\n * @private\n * @param options - Polling parameters.\n * @returns A bearer credential on success, null on failure or timeout.\n */\nasync function pollForToken(options: {\n readonly tokenUrl: string\n readonly deviceCode: string\n readonly clientId: string\n readonly interval: number\n readonly deadline: number\n readonly signal?: AbortSignal\n}): Promise<AuthCredential | null> {\n if (Date.now() >= options.deadline) {\n return null\n }\n\n await sleep(options.interval)\n\n if (Date.now() >= options.deadline) {\n return null\n }\n\n const result = await requestToken({\n clientId: options.clientId,\n deviceCode: options.deviceCode,\n signal: options.signal,\n tokenUrl: options.tokenUrl,\n })\n\n return match(result)\n .with({ status: 'success' }, (r) => r.credential)\n .with({ status: 'pending' }, () => pollForToken(options))\n .with({ status: 'slow_down' }, () =>\n pollForToken({\n ...options,\n interval: options.interval + SLOW_DOWN_INCREMENT,\n })\n )\n .with({ status: 'denied' }, () => null)\n .with({ status: 'expired' }, () => null)\n .with({ status: 'error' }, () => null)\n .exhaustive()\n}\n\n/**\n * Convert a server-provided interval value to milliseconds.\n *\n * @private\n * @param value - The raw interval value from the server response.\n * @returns The interval in milliseconds, or null if not a number.\n */\nfunction resolveServerInterval(value: unknown): number | null {\n if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0) {\n return null\n }\n\n return Math.max(1000, Math.min(value * 1000, 60_000))\n}\n\n/**\n * Sleep for a given duration.\n *\n * @private\n * @param ms - Duration in milliseconds.\n * @returns A promise that resolves after the delay.\n */\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => {\n setTimeout(resolve, ms)\n })\n}\n\n// ---------------------------------------------------------------------------\n// Token request types and helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Discriminated union of token request outcomes.\n *\n * @private\n */\ntype TokenRequestResult =\n | { readonly status: 'success'; readonly credential: AuthCredential }\n | { readonly status: 'pending' }\n | { readonly status: 'slow_down' }\n | { readonly status: 'denied' }\n | { readonly status: 'expired' }\n | { readonly status: 'error' }\n\n/**\n * Request an access token from the token endpoint.\n *\n * @private\n * @param options - Token request parameters.\n * @returns A discriminated result indicating the outcome.\n */\nasync function requestToken(options: {\n readonly tokenUrl: string\n readonly deviceCode: string\n readonly clientId: string\n readonly signal?: AbortSignal\n}): Promise<TokenRequestResult> {\n const body = new URLSearchParams({\n client_id: options.clientId,\n device_code: options.deviceCode,\n grant_type: DEVICE_CODE_GRANT_TYPE,\n })\n\n const response = await postFormEncoded(options.tokenUrl, body, options.signal)\n\n if (!response) {\n return { status: 'error' }\n }\n\n try {\n const data: unknown = await response.json()\n\n if (typeof data !== 'object' || data === null) {\n return { status: 'error' }\n }\n\n const record = data as Record<string, unknown>\n\n if (response.ok && typeof record.access_token === 'string' && record.access_token !== '') {\n if (typeof record.token_type === 'string' && record.token_type.toLowerCase() !== 'bearer') {\n return { status: 'error' }\n }\n\n return { credential: createBearerCredential(record.access_token), status: 'success' }\n }\n\n if (typeof record.error !== 'string') {\n return { status: 'error' }\n }\n\n return match(record.error)\n .with('authorization_pending', (): TokenRequestResult => ({ status: 'pending' }))\n .with('slow_down', (): TokenRequestResult => ({ status: 'slow_down' }))\n .with('expired_token', (): TokenRequestResult => ({ status: 'expired' }))\n .with('access_denied', (): TokenRequestResult => ({ status: 'denied' }))\n .otherwise((): TokenRequestResult => ({ status: 'error' }))\n } catch {\n return { status: 'error' }\n }\n}\n","import { readFileSync } from 'node:fs'\n\nimport { parse } from 'dotenv'\nimport { attempt } from 'es-toolkit'\n\nimport { createBearerCredential, isValidToken } from '../credential.js'\nimport type { AuthCredential } from '../types.js'\n\n/**\n * Resolve a bearer credential from a `.env` file without mutating `process.env`.\n *\n * Reads the file and parses it with `dotenv.parse`. If the target variable\n * is present, returns a bearer credential. Otherwise returns null.\n *\n * Skips a separate existence check to avoid a TOCTOU race — if the file\n * does not exist, `readFileSync` throws and `attempt` captures the error.\n *\n * @param options - Options with the env variable name and file path.\n * @returns A bearer credential if found, null otherwise.\n */\nexport function resolveFromDotenv(options: {\n readonly tokenVar: string\n readonly path: string\n}): AuthCredential | null {\n const [readError, content] = attempt(() => readFileSync(options.path, 'utf8'))\n\n if (readError || content === null) {\n return null\n }\n\n const parsed = parse(content)\n const token = parsed[options.tokenVar]\n\n if (!isValidToken(token)) {\n return null\n }\n\n return createBearerCredential(token)\n}\n","import { createBearerCredential, isValidToken } from '../credential.js'\nimport type { AuthCredential } from '../types.js'\n\n/**\n * Resolve a bearer credential from a process environment variable.\n *\n * @param options - Options containing the environment variable name.\n * @returns A bearer credential if the variable is set, null otherwise.\n */\nexport function resolveFromEnv(options: { readonly tokenVar: string }): AuthCredential | null {\n const token = process.env[options.tokenVar]\n\n if (!isValidToken(token)) {\n return null\n }\n\n return createBearerCredential(token)\n}\n","import { z } from 'zod'\n\n/**\n * Zod schema for bearer credentials.\n */\nexport const bearerCredentialSchema = z.object({\n token: z.string().min(1),\n type: z.literal('bearer'),\n})\n\n/**\n * Zod schema for basic auth credentials.\n */\nexport const basicCredentialSchema = z.object({\n password: z.string().min(1),\n type: z.literal('basic'),\n username: z.string().min(1),\n})\n\n/**\n * Zod schema for API key credentials.\n */\nexport const apiKeyCredentialSchema = z.object({\n headerName: z.string().min(1),\n key: z.string().min(1),\n type: z.literal('api-key'),\n})\n\n/**\n * Zod schema for custom header credentials.\n */\nexport const customCredentialSchema = z.object({\n headers: z.record(z.string(), z.string()),\n type: z.literal('custom'),\n})\n\n/**\n * Zod discriminated union schema for validating auth.json credential payloads.\n * Validates against all four credential types using the `type` field as discriminator.\n */\nexport const authCredentialSchema = z.discriminatedUnion('type', [\n bearerCredentialSchema,\n basicCredentialSchema,\n apiKeyCredentialSchema,\n customCredentialSchema,\n])\n","import { createStore } from '@/lib/store/create-store.js'\n\nimport { authCredentialSchema } from '../schema.js'\nimport type { AuthCredential } from '../types.js'\n\n/**\n * Resolve credentials from a JSON file on disk.\n *\n * Uses the file-backed store with local-then-global resolution to find\n * the credentials file, then validates its contents against the auth\n * credential schema.\n *\n * @param options - Options with the filename and directory name.\n * @returns A validated auth credential, or null if not found or invalid.\n */\nexport function resolveFromFile(options: {\n readonly filename: string\n readonly dirName: string\n}): AuthCredential | null {\n const store = createStore({ dirName: options.dirName })\n const data = store.load(options.filename)\n\n if (data === null) {\n return null\n }\n\n const result = authCredentialSchema.safeParse(data)\n\n if (!result.success) {\n return null\n }\n\n return result.data\n}\n","/**\n * OAuth 2.0 Authorization Code + PKCE resolver (RFC 7636 + RFC 8252).\n *\n * Opens the user's browser to the authorization URL with a PKCE challenge,\n * listens for a GET redirect with an authorization code on a local server,\n * and exchanges the code at the token endpoint with the code verifier.\n *\n * @module\n */\n\nimport { createHash, randomBytes } from 'node:crypto'\nimport type { IncomingMessage, ServerResponse } from 'node:http'\n\nimport { createBearerCredential, postFormEncoded } from '../credential.js'\nimport {\n createDeferred,\n createTimeout,\n destroyServer,\n isSecureAuthUrl,\n openBrowser,\n sendSuccessPage,\n startLocalServer,\n} from '../oauth-server.js'\nimport type { AuthCredential } from '../types.js'\n\n/**\n * Resolve a bearer credential via OAuth 2.0 Authorization Code + PKCE.\n *\n * 1. Generates a `code_verifier` and derives the `code_challenge`\n * 2. Starts a local HTTP server on `127.0.0.1`\n * 3. Opens the browser to the authorization URL with PKCE params\n * 4. Receives the authorization code via GET redirect\n * 5. Exchanges the code at the token endpoint with the verifier\n * 6. Returns the access token as a bearer credential\n *\n * @param options - PKCE flow configuration.\n * @returns A bearer credential on success, null on failure or timeout.\n */\nexport async function resolveFromOAuth(options: {\n readonly clientId: string\n readonly authUrl: string\n readonly tokenUrl: string\n readonly scopes: readonly string[]\n readonly port: number\n readonly callbackPath: string\n readonly timeout: number\n}): Promise<AuthCredential | null> {\n if (!isSecureAuthUrl(options.authUrl)) {\n return null\n }\n\n if (!isSecureAuthUrl(options.tokenUrl)) {\n return null\n }\n\n const codeVerifier = generateCodeVerifier()\n const codeChallenge = deriveCodeChallenge(codeVerifier)\n const state = randomBytes(32).toString('hex')\n\n const timeout = createTimeout(options.timeout)\n const codeDeferred = createDeferred<string | null>()\n\n const handle = startLocalServer({\n onRequest: (req, res) => {\n handleCallback(req, res, options.callbackPath, state, codeDeferred.resolve)\n },\n port: options.port,\n })\n\n const serverPort = await handle.port\n\n if (serverPort === null) {\n timeout.clear()\n return null\n }\n\n const redirectUri = `http://127.0.0.1:${String(serverPort)}${options.callbackPath}`\n\n const fullAuthUrl = buildAuthUrl({\n authUrl: options.authUrl,\n clientId: options.clientId,\n codeChallenge,\n redirectUri,\n scopes: options.scopes,\n state,\n })\n\n openBrowser(fullAuthUrl)\n\n const timeoutPromise = timeout.promise.then((): null => {\n codeDeferred.resolve(null)\n destroyServer(handle.server, handle.sockets)\n return null\n })\n\n const code = await Promise.race([codeDeferred.promise, timeoutPromise])\n\n timeout.clear()\n\n if (!code) {\n destroyServer(handle.server, handle.sockets)\n return null\n }\n\n destroyServer(handle.server, handle.sockets)\n\n const token = await exchangeCodeForToken({\n clientId: options.clientId,\n code,\n codeVerifier,\n redirectUri,\n tokenUrl: options.tokenUrl,\n })\n\n return token\n}\n\n// ---------------------------------------------------------------------------\n// Private helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Generate a cryptographically random code verifier for PKCE.\n *\n * @private\n * @returns A base64url-encoded random string.\n */\nfunction generateCodeVerifier(): string {\n return randomBytes(32).toString('base64url')\n}\n\n/**\n * Derive a S256 code challenge from a code verifier.\n *\n * @private\n * @param verifier - The code verifier string.\n * @returns The base64url-encoded SHA-256 hash.\n */\nfunction deriveCodeChallenge(verifier: string): string {\n return createHash('sha256').update(verifier).digest('base64url')\n}\n\n/**\n * Build the full authorization URL with PKCE query parameters.\n *\n * @private\n * @param options - Authorization URL components.\n * @returns The complete authorization URL string.\n */\nfunction buildAuthUrl(options: {\n readonly authUrl: string\n readonly clientId: string\n readonly redirectUri: string\n readonly codeChallenge: string\n readonly state: string\n readonly scopes: readonly string[]\n}): string {\n const url = new URL(options.authUrl)\n url.searchParams.set('response_type', 'code')\n url.searchParams.set('client_id', options.clientId)\n url.searchParams.set('redirect_uri', options.redirectUri)\n url.searchParams.set('code_challenge', options.codeChallenge)\n url.searchParams.set('code_challenge_method', 'S256')\n url.searchParams.set('state', options.state)\n\n if (options.scopes.length > 0) {\n url.searchParams.set('scope', options.scopes.join(' '))\n }\n\n return url.toString()\n}\n\n/**\n * Handle an incoming HTTP request on the callback server.\n *\n * Accepts GET requests to the callback path with `code` and `state`\n * query parameters. Validates the state nonce and resolves the\n * authorization code.\n *\n * @private\n * @param req - The incoming HTTP request.\n * @param res - The server response.\n * @param callbackPath - The expected callback path.\n * @param expectedState - The state nonce to validate.\n * @param resolve - Callback to deliver the authorization code.\n */\nfunction handleCallback(\n req: IncomingMessage,\n res: ServerResponse,\n callbackPath: string,\n expectedState: string,\n resolve: (value: string | null) => void\n): void {\n const result = extractCodeFromUrl(req.url, callbackPath, expectedState)\n\n if (!result.ok) {\n res.writeHead(400)\n res.end()\n\n if (result.isOAuthError) {\n resolve(null)\n }\n\n return\n }\n\n sendSuccessPage(res)\n resolve(result.code)\n}\n\n/**\n * Result of extracting an authorization code from a callback URL.\n *\n * @private\n */\ntype ExtractCodeResult =\n | { readonly ok: true; readonly code: string }\n | { readonly ok: false; readonly isOAuthError: boolean }\n\n/**\n * Extract an authorization code from a request URL.\n *\n * Validates that the request path matches the callback path,\n * the `state` parameter matches the expected nonce, and a\n * `code` parameter is present. Detects OAuth error responses\n * (e.g. `?error=access_denied`) and flags them so the caller\n * can resolve immediately instead of waiting for the timeout.\n *\n * @private\n * @param reqUrl - The raw request URL string.\n * @param callbackPath - The expected callback path.\n * @param expectedState - The state nonce to validate.\n * @returns An extraction result with the code or error flag.\n */\nfunction extractCodeFromUrl(\n reqUrl: string | undefined,\n callbackPath: string,\n expectedState: string\n): ExtractCodeResult {\n const url = new URL(reqUrl ?? '/', 'http://localhost')\n\n if (url.pathname !== callbackPath) {\n return { isOAuthError: false, ok: false }\n }\n\n const state = url.searchParams.get('state')\n\n if (state !== expectedState) {\n return { isOAuthError: false, ok: false }\n }\n\n const error = url.searchParams.get('error')\n\n if (error) {\n return { isOAuthError: true, ok: false }\n }\n\n const code = url.searchParams.get('code')\n\n if (!code) {\n return { isOAuthError: false, ok: false }\n }\n\n return { code, ok: true }\n}\n\n/**\n * Exchange an authorization code for an access token at the token endpoint.\n *\n * Sends a POST request with `application/x-www-form-urlencoded` body\n * containing the authorization code, redirect URI, client ID, and\n * PKCE code verifier.\n *\n * @private\n * @param options - Token exchange parameters.\n * @returns A bearer credential on success, null on failure.\n */\nasync function exchangeCodeForToken(options: {\n readonly tokenUrl: string\n readonly code: string\n readonly redirectUri: string\n readonly clientId: string\n readonly codeVerifier: string\n}): Promise<AuthCredential | null> {\n const body = new URLSearchParams({\n client_id: options.clientId,\n code: options.code,\n code_verifier: options.codeVerifier,\n grant_type: 'authorization_code',\n redirect_uri: options.redirectUri,\n })\n\n const response = await postFormEncoded(options.tokenUrl, body)\n\n if (!response) {\n return null\n }\n\n if (!response.ok) {\n return null\n }\n\n try {\n const data: unknown = await response.json()\n\n if (typeof data !== 'object' || data === null) {\n return null\n }\n\n const record = data as Record<string, unknown>\n\n if (typeof record.access_token !== 'string' || record.access_token === '') {\n return null\n }\n\n if (typeof record.token_type === 'string' && record.token_type.toLowerCase() !== 'bearer') {\n return null\n }\n\n return createBearerCredential(record.access_token)\n } catch {\n return null\n }\n}\n","import type { Prompts } from '@/context/types.js'\n\nimport { createBearerCredential, isValidToken } from '../credential.js'\nimport type { AuthCredential } from '../types.js'\n\n/**\n * Resolve a bearer credential by interactively prompting the user.\n *\n * Uses `prompts.password()` to ask for an API key or token. Returns\n * null if the user cancels the prompt or provides an empty value.\n *\n * Should be placed last in the resolver chain as a fallback.\n *\n * @param options - Options with the prompt message and prompts instance.\n * @returns A bearer credential on input, null on cancellation.\n */\nexport async function resolveFromToken(options: {\n readonly message: string\n readonly prompts: Prompts\n}): Promise<AuthCredential | null> {\n try {\n const token = await options.prompts.password({ message: options.message })\n\n if (!isValidToken(token)) {\n return null\n }\n\n return createBearerCredential(token)\n } catch {\n return null\n }\n}\n","import { join } from 'node:path'\n\nimport { match } from 'ts-pattern'\n\nimport type { Prompts } from '@/context/types.js'\n\nimport {\n DEFAULT_AUTH_FILENAME,\n DEFAULT_DEVICE_CODE_POLL_INTERVAL,\n DEFAULT_DEVICE_CODE_TIMEOUT,\n DEFAULT_OAUTH_CALLBACK_PATH,\n DEFAULT_OAUTH_PORT,\n DEFAULT_OAUTH_TIMEOUT,\n deriveTokenVar,\n} from './constants.js'\nimport { resolveFromDeviceCode } from './strategies/device-code.js'\nimport { resolveFromDotenv } from './strategies/dotenv.js'\nimport { resolveFromEnv } from './strategies/env.js'\nimport { resolveFromFile } from './strategies/file.js'\nimport { resolveFromOAuth } from './strategies/oauth.js'\nimport { resolveFromToken } from './strategies/token.js'\nimport type { AuthCredential, ResolverConfig } from './types.js'\n\nconst DEFAULT_PROMPT_MESSAGE = 'Enter your API key'\n\n/**\n * Chain credential resolvers, returning the first non-null result.\n *\n * Walks the resolver list in order, dispatching each config to the\n * appropriate resolver function via pattern matching. Short-circuits\n * on the first successful resolution.\n *\n * @param options - Options with resolvers, CLI name, and prompts instance.\n * @returns The first resolved credential, or null if all resolvers fail.\n */\nexport async function runStrategyChain(options: {\n readonly resolvers: readonly ResolverConfig[]\n readonly cliName: string\n readonly prompts: Prompts\n}): Promise<AuthCredential | null> {\n const defaultTokenVar = deriveTokenVar(options.cliName)\n\n return tryResolvers(options.resolvers, 0, defaultTokenVar, options)\n}\n\n/**\n * Return the given value when defined, otherwise the fallback.\n *\n * @param value - The optional value.\n * @param fallback - The default value.\n * @returns The resolved value.\n */\nexport function withDefault<T>(value: T | undefined, fallback: T): T {\n if (value !== undefined) {\n return value\n }\n return fallback\n}\n\n// ---------------------------------------------------------------------------\n// Private helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Recursively try resolvers until one returns a credential or the list is exhausted.\n *\n * @private\n * @param configs - The resolver configs.\n * @param index - The current index.\n * @param defaultTokenVar - The derived default token env var name.\n * @param context - The resolve options for prompts access.\n * @returns The first resolved credential, or null.\n */\nasync function tryResolvers(\n configs: readonly ResolverConfig[],\n index: number,\n defaultTokenVar: string,\n context: {\n readonly cliName: string\n readonly prompts: Prompts\n }\n): Promise<AuthCredential | null> {\n if (index >= configs.length) {\n return null\n }\n\n const config = configs[index]\n\n if (config === undefined) {\n return null\n }\n\n const credential = await dispatchResolver(config, defaultTokenVar, context)\n\n if (credential) {\n return credential\n }\n\n return tryResolvers(configs, index + 1, defaultTokenVar, context)\n}\n\n/**\n * Dispatch a single resolver config to its implementation.\n *\n * @private\n * @param config - The resolver config to dispatch.\n * @param defaultTokenVar - The derived default token env var name.\n * @param context - The resolve options for prompts access.\n * @returns The resolved credential, or null.\n */\nasync function dispatchResolver(\n config: ResolverConfig,\n defaultTokenVar: string,\n context: {\n readonly cliName: string\n readonly prompts: Prompts\n }\n): Promise<AuthCredential | null> {\n return match(config)\n .with({ source: 'env' }, (c): AuthCredential | null =>\n resolveFromEnv({\n tokenVar: withDefault(c.tokenVar, defaultTokenVar),\n })\n )\n .with({ source: 'dotenv' }, (c): AuthCredential | null =>\n resolveFromDotenv({\n path: withDefault(c.path, join(process.cwd(), '.env')),\n tokenVar: withDefault(c.tokenVar, defaultTokenVar),\n })\n )\n .with({ source: 'file' }, (c): AuthCredential | null =>\n resolveFromFile({\n dirName: withDefault(c.dirName, `.${context.cliName}`),\n filename: withDefault(c.filename, DEFAULT_AUTH_FILENAME),\n })\n )\n .with(\n { source: 'oauth' },\n (c): Promise<AuthCredential | null> =>\n resolveFromOAuth({\n authUrl: c.authUrl,\n callbackPath: withDefault(c.callbackPath, DEFAULT_OAUTH_CALLBACK_PATH),\n clientId: c.clientId,\n port: withDefault(c.port, DEFAULT_OAUTH_PORT),\n scopes: withDefault(c.scopes, []),\n timeout: withDefault(c.timeout, DEFAULT_OAUTH_TIMEOUT),\n tokenUrl: c.tokenUrl,\n })\n )\n .with(\n { source: 'device-code' },\n (c): Promise<AuthCredential | null> =>\n resolveFromDeviceCode({\n clientId: c.clientId,\n deviceAuthUrl: c.deviceAuthUrl,\n openBrowserOnStart: withDefault(c.openBrowser, true),\n pollInterval: withDefault(c.pollInterval, DEFAULT_DEVICE_CODE_POLL_INTERVAL),\n prompts: context.prompts,\n scopes: withDefault(c.scopes, []),\n timeout: withDefault(c.timeout, DEFAULT_DEVICE_CODE_TIMEOUT),\n tokenUrl: c.tokenUrl,\n })\n )\n .with(\n { source: 'token' },\n (c): Promise<AuthCredential | null> =>\n resolveFromToken({\n message: withDefault(c.message, DEFAULT_PROMPT_MESSAGE),\n prompts: context.prompts,\n })\n )\n .with({ source: 'custom' }, (c): Promise<AuthCredential | null> | AuthCredential | null =>\n c.resolver()\n )\n .exhaustive()\n}\n","/**\n * Factory for the {@link AuthContext} object decorated onto `ctx.auth`.\n *\n * Closes over the middleware's resolver config, CLI name, prompts, and\n * a credential resolver function so that `login()` can run\n * interactive resolvers and persist the result.\n *\n * @module\n */\n\nimport type { AsyncResult, Result } from '@kidd-cli/utils/fp'\nimport { ok } from '@kidd-cli/utils/fp'\n\nimport type { Prompts } from '@/context/types.js'\nimport { createStore } from '@/lib/store/create-store.js'\n\nimport { runStrategyChain } from './chain.js'\nimport { DEFAULT_AUTH_FILENAME } from './constants.js'\nimport type { AuthContext, AuthCredential, AuthError, ResolverConfig } from './types.js'\n\n/**\n * Options for {@link createAuthContext}.\n */\nexport interface CreateAuthContextOptions {\n readonly resolvers: readonly ResolverConfig[]\n readonly cliName: string\n readonly prompts: Prompts\n readonly resolveCredential: () => AuthCredential | null\n}\n\n/**\n * Create an {@link AuthContext} value for `ctx.auth`.\n *\n * No credential data is stored on the returned object. `credential()`\n * resolves passively on every call, `authenticated()` checks existence,\n * `login()` runs the configured interactive resolvers, saves the\n * credential to the global file store, and `logout()` removes it.\n *\n * @param options - Factory options.\n * @returns An AuthContext instance.\n */\nexport function createAuthContext(options: CreateAuthContextOptions): AuthContext {\n const { resolvers, cliName, prompts, resolveCredential } = options\n\n /**\n * Resolve the current credential from passive sources (file, env).\n *\n * @private\n * @returns The credential, or null when none exists.\n */\n function credential(): AuthCredential | null {\n return resolveCredential()\n }\n\n /**\n * Check whether a credential is available from passive sources.\n *\n * @private\n * @returns True when a credential exists.\n */\n function authenticated(): boolean {\n return resolveCredential() !== null\n }\n\n /**\n * Run configured resolvers interactively and persist the credential.\n *\n * @private\n * @returns A Result with the credential on success or an AuthError on failure.\n */\n // TODO: support targeted resolver selection, e.g. ctx.auth.login({ source: 'token' })\n // to let callers skip to a specific resolver instead of walking the full chain.\n async function login(): AsyncResult<AuthCredential, AuthError> {\n const resolved = await runStrategyChain({ cliName, prompts, resolvers })\n\n if (resolved === null) {\n return authError({\n message: 'No credential resolved from any source',\n type: 'no_credential',\n })\n }\n\n const store = createStore({ dirName: `.${cliName}` })\n const [saveError] = store.save(DEFAULT_AUTH_FILENAME, resolved)\n\n if (saveError) {\n return authError({\n message: `Failed to save credential: ${saveError.message}`,\n type: 'save_failed',\n })\n }\n\n return ok(resolved)\n }\n\n /**\n * Remove the stored credential from disk.\n *\n * @private\n * @returns A Result with the removed file path on success or an AuthError on failure.\n */\n async function logout(): AsyncResult<string, AuthError> {\n const store = createStore({ dirName: `.${cliName}` })\n const [removeError, filePath] = store.remove(DEFAULT_AUTH_FILENAME)\n\n if (removeError) {\n return authError({\n message: `Failed to remove credential: ${removeError.message}`,\n type: 'remove_failed',\n })\n }\n\n return ok(filePath)\n }\n\n return { authenticated, credential, login, logout }\n}\n\n// ---------------------------------------------------------------------------\n// Private helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Construct a failure Result tuple with an {@link AuthError}.\n *\n * @private\n * @param error - The auth error.\n * @returns A Result tuple `[AuthError, null]`.\n */\nfunction authError(error: AuthError): Result<never, AuthError> {\n return [error, null] as const\n}\n","/**\n * Auth middleware factory with resolver builder functions.\n *\n * Decorates `ctx.auth` with functions to resolve credentials on demand\n * and run interactive authentication. Also supports creating authenticated\n * HTTP clients via the `http` option.\n *\n * @module\n */\n\nimport { join } from 'node:path'\n\nimport { decorateContext } from '@/context/decorate.js'\nimport { middleware } from '@/middleware.js'\nimport type { Middleware } from '@/types.js'\n\nimport { buildAuthHeaders } from '../http/build-auth-headers.js'\nimport { createHttpClient } from '../http/create-http-client.js'\nimport { withDefault } from './chain.js'\nimport { DEFAULT_AUTH_FILENAME, deriveTokenVar } from './constants.js'\nimport { createAuthContext } from './context.js'\nimport { resolveFromDotenv } from './strategies/dotenv.js'\nimport { resolveFromEnv } from './strategies/env.js'\nimport { resolveFromFile } from './strategies/file.js'\nimport type {\n AuthCredential,\n AuthHttpOptions,\n AuthOptions,\n CustomResolverFn,\n CustomSourceConfig,\n DeviceCodeResolverOptions,\n DeviceCodeSourceConfig,\n DotenvResolverOptions,\n DotenvSourceConfig,\n EnvResolverOptions,\n EnvSourceConfig,\n FileResolverOptions,\n FileSourceConfig,\n OAuthResolverOptions,\n OAuthSourceConfig,\n ResolverConfig,\n TokenResolverOptions,\n TokenSourceConfig,\n} from './types.js'\n\n/**\n * Auth factory interface — callable as a middleware factory and as a\n * namespace for resolver builder functions.\n */\nexport interface AuthFactory {\n (options: AuthOptions): Middleware\n readonly env: (options?: EnvResolverOptions) => EnvSourceConfig\n readonly dotenv: (options?: DotenvResolverOptions) => DotenvSourceConfig\n readonly file: (options?: FileResolverOptions) => FileSourceConfig\n readonly oauth: (options: OAuthResolverOptions) => OAuthSourceConfig\n readonly deviceCode: (options: DeviceCodeResolverOptions) => DeviceCodeSourceConfig\n readonly token: (options?: TokenResolverOptions) => TokenSourceConfig\n readonly apiKey: (options?: TokenResolverOptions) => TokenSourceConfig\n readonly custom: (resolver: CustomResolverFn) => CustomSourceConfig\n}\n\n/**\n * Create an auth middleware that decorates `ctx.auth`.\n *\n * No credential data is stored on the context. `ctx.auth.credential()`\n * resolves passively from three sources on every call:\n * 1. File — `~/.cli-name/auth.json`\n * 2. Dotenv — `.env` file (when configured)\n * 3. Env — `CLI_NAME_TOKEN`\n *\n * Interactive resolvers (OAuth, prompt, custom) only run when the\n * command handler explicitly calls `ctx.auth.login()`.\n *\n * When `options.http` is provided, the middleware also creates HTTP\n * client(s) with automatic credential header injection and decorates\n * them onto `ctx[namespace]`.\n *\n * @param options - Auth middleware configuration.\n * @returns A Middleware that decorates ctx.auth (and optionally HTTP clients).\n */\nfunction createAuth(options: AuthOptions): Middleware {\n const { resolvers } = options\n\n return middleware((ctx, next) => {\n const cliName = ctx.meta.name\n\n const authContext = createAuthContext({\n cliName,\n prompts: ctx.prompts,\n resolveCredential: () => resolveStoredCredential(cliName, resolvers),\n resolvers,\n })\n\n decorateContext(ctx, 'auth', authContext)\n\n if (options.http !== undefined) {\n const httpConfigs = normalizeHttpOptions(options.http)\n\n httpConfigs.reduce((context, httpConfig) => {\n const client = createHttpClient({\n baseUrl: httpConfig.baseUrl,\n defaultHeaders: httpConfig.headers,\n resolveHeaders: () => credentialToHeaders(authContext.credential()),\n })\n\n return decorateContext(context, httpConfig.namespace, client)\n }, ctx)\n }\n\n return next()\n })\n}\n\n/**\n * Auth middleware factory with resolver builder methods.\n *\n * Use as `auth({ resolvers: [...] })` to create middleware, or use\n * the builder methods (`auth.env()`, `auth.oauth()`, etc.) to construct\n * resolver configs with a cleaner API.\n */\nexport const auth: AuthFactory = Object.assign(createAuth, {\n apiKey: buildToken,\n custom: buildCustom,\n deviceCode: buildDeviceCode,\n dotenv: buildDotenv,\n env: buildEnv,\n file: buildFile,\n oauth: buildOAuth,\n token: buildToken,\n})\n\n// ---------------------------------------------------------------------------\n// Resolver builders\n// ---------------------------------------------------------------------------\n\n/**\n * Build an env resolver config.\n *\n * @private\n * @param options - Optional env resolver options.\n * @returns An EnvSourceConfig with `source: 'env'`.\n */\nfunction buildEnv(options?: EnvResolverOptions): EnvSourceConfig {\n return { source: 'env' as const, ...options }\n}\n\n/**\n * Build a dotenv resolver config.\n *\n * @private\n * @param options - Optional dotenv resolver options.\n * @returns A DotenvSourceConfig with `source: 'dotenv'`.\n */\nfunction buildDotenv(options?: DotenvResolverOptions): DotenvSourceConfig {\n return { source: 'dotenv' as const, ...options }\n}\n\n/**\n * Build a file resolver config.\n *\n * @private\n * @param options - Optional file resolver options.\n * @returns A FileSourceConfig with `source: 'file'`.\n */\nfunction buildFile(options?: FileResolverOptions): FileSourceConfig {\n return { source: 'file' as const, ...options }\n}\n\n/**\n * Build an OAuth resolver config.\n *\n * @private\n * @param options - OAuth resolver options (clientId, authUrl, tokenUrl required).\n * @returns An OAuthSourceConfig with `source: 'oauth'`.\n */\nfunction buildOAuth(options: OAuthResolverOptions): OAuthSourceConfig {\n return { source: 'oauth' as const, ...options }\n}\n\n/**\n * Build a device code resolver config.\n *\n * @private\n * @param options - Device code resolver options (clientId, deviceAuthUrl, tokenUrl required).\n * @returns A DeviceCodeSourceConfig with `source: 'device-code'`.\n */\nfunction buildDeviceCode(options: DeviceCodeResolverOptions): DeviceCodeSourceConfig {\n return { source: 'device-code' as const, ...options }\n}\n\n/**\n * Build a token resolver config.\n *\n * Prompts the user for a token interactively. Aliased as `auth.apiKey()`.\n *\n * @private\n * @param options - Optional token resolver options.\n * @returns A TokenSourceConfig with `source: 'token'`.\n */\nfunction buildToken(options?: TokenResolverOptions): TokenSourceConfig {\n return { source: 'token' as const, ...options }\n}\n\n/**\n * Build a custom resolver config from a resolver function.\n *\n * @private\n * @param resolver - The custom resolver function.\n * @returns A CustomSourceConfig with `source: 'custom'`.\n */\nfunction buildCustom(resolver: CustomResolverFn): CustomSourceConfig {\n return { resolver, source: 'custom' as const }\n}\n\n// ---------------------------------------------------------------------------\n// Private helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Normalize the `http` option into an array of configs.\n *\n * @private\n * @param http - A single config or array of configs.\n * @returns An array of AuthHttpOptions.\n */\nfunction normalizeHttpOptions(\n http: AuthHttpOptions | readonly AuthHttpOptions[]\n): readonly AuthHttpOptions[] {\n if ('baseUrl' in http) {\n return [http]\n }\n\n return http\n}\n\n/**\n * Convert a credential into auth headers, returning an empty record\n * when no credential is available.\n *\n * @private\n * @param credential - The credential or null.\n * @returns A record of auth headers.\n */\nfunction credentialToHeaders(\n credential: AuthCredential | null\n): Readonly<Record<string, string>> {\n if (credential === null) {\n return {}\n }\n\n return buildAuthHeaders(credential)\n}\n\n/**\n * Attempt to resolve a credential from stored (non-interactive) sources.\n *\n * Checks the file store first, then dotenv, then falls back to the\n * environment variable. Scans the resolver list for `file`, `dotenv`,\n * and `env` source configs to respect user-configured overrides\n * (e.g. a custom `tokenVar`, `dirName`, or dotenv `path`).\n *\n * @private\n * @param cliName - The CLI name, used to derive paths and env var names.\n * @param resolvers - The configured resolver list for extracting overrides.\n * @returns The resolved credential, or null.\n */\nfunction resolveStoredCredential(\n cliName: string,\n resolvers: readonly ResolverConfig[]\n): AuthCredential | null {\n const fileConfig = findResolverBySource(resolvers, 'file')\n const dotenvConfig = findResolverBySource(resolvers, 'dotenv')\n const envConfig = findResolverBySource(resolvers, 'env')\n const defaultTokenVar = deriveTokenVar(cliName)\n\n const fromFile = resolveFromFile({\n dirName: withDefault(extractProp(fileConfig, 'dirName'), `.${cliName}`),\n filename: withDefault(extractProp(fileConfig, 'filename'), DEFAULT_AUTH_FILENAME),\n })\n\n if (fromFile) {\n return fromFile\n }\n\n if (dotenvConfig !== undefined) {\n const fromDotenv = resolveFromDotenv({\n path: withDefault(extractProp(dotenvConfig, 'path'), join(process.cwd(), '.env')),\n tokenVar: withDefault(extractProp(dotenvConfig, 'tokenVar'), defaultTokenVar),\n })\n\n if (fromDotenv) {\n return fromDotenv\n }\n }\n\n return resolveFromEnv({\n tokenVar: withDefault(extractProp(envConfig, 'tokenVar'), defaultTokenVar),\n })\n}\n\n/**\n * Find the first resolver config matching a given source type.\n *\n * @private\n * @param resolvers - The resolver config list.\n * @param source - The source type to find.\n * @returns The matching config, or undefined.\n */\nfunction findResolverBySource<TSource extends ResolverConfig['source']>(\n resolvers: readonly ResolverConfig[],\n source: TSource\n): Extract<ResolverConfig, { readonly source: TSource }> | undefined {\n return resolvers.find(\n (r): r is Extract<ResolverConfig, { readonly source: TSource }> => r.source === source\n )\n}\n\n/**\n * Safely extract a property from an optional config object.\n *\n * Returns the property value when the config is defined, or undefined\n * when the config itself is undefined.\n *\n * @private\n * @param config - The config object, or undefined.\n * @param key - The property key to extract.\n * @returns The property value, or undefined.\n */\nfunction extractProp<TConfig extends object, TKey extends keyof TConfig>(\n config: TConfig | undefined,\n key: TKey\n): TConfig[TKey] | undefined {\n if (config === undefined) {\n return undefined\n }\n\n return config[key]\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"auth.js","names":["attempt","match","match"],"sources":["../../src/middleware/auth/constants.ts","../../src/middleware/auth/credential.ts","../../src/middleware/auth/oauth-server.ts","../../src/middleware/auth/strategies/device-code.ts","../../src/middleware/auth/strategies/dotenv.ts","../../src/middleware/auth/strategies/env.ts","../../src/middleware/auth/schema.ts","../../src/middleware/auth/strategies/file.ts","../../src/middleware/auth/strategies/oauth.ts","../../src/middleware/auth/strategies/token.ts","../../src/middleware/auth/chain.ts","../../src/middleware/auth/context.ts","../../src/middleware/http/build-auth-headers.ts","../../src/middleware/auth/headers.ts","../../src/middleware/auth/require.ts","../../src/middleware/auth/auth.ts"],"sourcesContent":["/**\n * Default filename for file-based credential storage.\n */\nexport const DEFAULT_AUTH_FILENAME = 'auth.json' as const\n\n/**\n * Suffix appended to the derived token environment variable name.\n */\nexport const TOKEN_VAR_SUFFIX = '_TOKEN' as const\n\n/**\n * Default port for the local OAuth callback server (`0` = ephemeral).\n */\nexport const DEFAULT_OAUTH_PORT = 0\n\n/**\n * Default callback path for the local OAuth server.\n */\nexport const DEFAULT_OAUTH_CALLBACK_PATH = '/callback'\n\n/**\n * Default timeout for the OAuth PKCE flow in milliseconds (2 minutes).\n */\nexport const DEFAULT_OAUTH_TIMEOUT = 120_000\n\n/**\n * Default poll interval for the device code flow in milliseconds (5 seconds).\n */\nexport const DEFAULT_DEVICE_CODE_POLL_INTERVAL = 5000\n\n/**\n * Default timeout for the device code flow in milliseconds (5 minutes).\n */\nexport const DEFAULT_DEVICE_CODE_TIMEOUT = 300_000\n\n/**\n * Derive the default environment variable name from a CLI name.\n *\n * Converts kebab-case to SCREAMING_SNAKE_CASE and appends `_TOKEN`.\n * Example: `my-app` → `MY_APP_TOKEN`\n *\n * @param cliName - The CLI name.\n * @returns The derived environment variable name.\n */\nexport function deriveTokenVar(cliName: string): string {\n return `${cliName.replaceAll('-', '_').toUpperCase()}${TOKEN_VAR_SUFFIX}`\n}\n","import { attemptAsync } from '@kidd-cli/utils/fp'\n\nimport type { BearerCredential } from './types.js'\n\n/**\n * Check whether a token string is a non-empty, non-whitespace value.\n *\n * Acts as a type guard: when it returns true, TypeScript narrows the\n * token to `string`. Consolidates the repeated `!token || token.trim() === ''`\n * guard found across strategy resolvers.\n *\n * @param token - The token string to check.\n * @returns True when the token is a non-empty string.\n */\nexport function isValidToken(token: string | undefined | null): token is string {\n if (!token) {\n return false\n }\n\n if (token.trim() === '') {\n return false\n }\n\n return true\n}\n\n/**\n * Construct a bearer credential from a raw token string.\n *\n * @param token - The access token value.\n * @returns A BearerCredential with `type: 'bearer'`.\n */\nexport function createBearerCredential(token: string): BearerCredential {\n return { token, type: 'bearer' }\n}\n\n/**\n * POST form-encoded parameters to a URL.\n *\n * Wraps the duplicated `fetch` call with `Content-Type: application/x-www-form-urlencoded`\n * found in the OAuth and device code strategies. Returns null on network or\n * request failure instead of throwing.\n *\n * @param url - The endpoint URL.\n * @param params - The URL-encoded form parameters.\n * @param signal - Optional AbortSignal for timeout/cancellation.\n * @returns The fetch Response on success, null on failure.\n */\nexport async function postFormEncoded(\n url: string,\n params: URLSearchParams,\n signal?: AbortSignal\n): Promise<Response | null> {\n const [fetchError, response] = await attemptAsync(() =>\n fetch(url, {\n body: params.toString(),\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n method: 'POST',\n signal,\n })\n )\n\n if (fetchError) {\n return null\n }\n\n return response\n}\n","/**\n * Shared utilities for OAuth-based auth resolvers.\n *\n * Extracted from the local HTTP server, browser-launch, and\n * lifecycle patterns shared by the PKCE and device-code flows.\n *\n * @module\n */\n\nimport { execFile } from 'node:child_process'\nimport { createServer } from 'node:http'\nimport type { IncomingMessage, Server, ServerResponse } from 'node:http'\nimport type { Socket } from 'node:net'\nimport { platform } from 'node:os'\n\nimport { attempt, match } from '@kidd-cli/utils/fp'\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst CLOSE_PAGE_HTML = [\n '<!DOCTYPE html>',\n '<html>',\n '<body><p>Authentication complete. You can close this tab.</p></body>',\n '</html>',\n].join('\\n')\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/**\n * A deferred promise with an externally accessible resolve function.\n */\nexport interface Deferred<T> {\n readonly promise: Promise<T>\n readonly resolve: (value: T) => void\n}\n\n/**\n * A clearable timeout that does not keep the event loop alive after cancellation.\n */\nexport interface Timeout {\n readonly promise: Promise<void>\n readonly clear: () => void\n}\n\n/**\n * Result of starting a local HTTP server with request handling.\n */\nexport interface LocalServerHandle {\n readonly port: Promise<number | null>\n readonly server: Server\n readonly sockets: Set<Socket>\n}\n\n// ---------------------------------------------------------------------------\n// Exported functions\n// ---------------------------------------------------------------------------\n\n/**\n * Create a deferred promise with externally accessible resolve.\n *\n * Uses a mutable state container to capture the promise resolver --\n * this is an intentional exception to immutability rules because the\n * Promise constructor API requires synchronous resolver capture.\n *\n * @returns A deferred object with promise and resolve.\n */\nexport function createDeferred<T>(): Deferred<T> {\n const state: { resolve: ((value: T) => void) | null } = { resolve: null }\n\n const promise = new Promise<T>((resolve) => {\n state.resolve = resolve\n })\n\n return {\n promise,\n resolve: (value: T): void => {\n if (state.resolve) {\n state.resolve(value)\n }\n },\n }\n}\n\n/**\n * Create a clearable timeout.\n *\n * Returns a promise that resolves after `ms` milliseconds and a `clear`\n * function that cancels the timer so it does not hold the event loop open.\n *\n * Uses a mutable state container to capture the timer id -- this is an\n * intentional exception to immutability rules because `setTimeout`\n * returns an opaque handle that must be stored for later cancellation.\n *\n * @param ms - Duration in milliseconds.\n * @returns A Timeout with `promise` and `clear`.\n */\nexport function createTimeout(ms: number): Timeout {\n const state: { id: ReturnType<typeof setTimeout> | null } = { id: null }\n\n const promise = new Promise<void>((resolve) => {\n state.id = setTimeout(resolve, ms)\n })\n\n return {\n clear: (): void => {\n if (state.id !== null) {\n clearTimeout(state.id)\n state.id = null\n }\n },\n promise,\n }\n}\n\n/**\n * Track socket connections on a server so they can be destroyed on close.\n *\n * Mutates the provided socket set -- this is an intentional exception to\n * immutability rules because the HTTP server API is inherently stateful.\n *\n * @param server - The HTTP server.\n * @param sockets - The set to track sockets in.\n */\nexport function trackConnections(server: Server, sockets: Set<Socket>): void {\n server.on('connection', (socket: Socket) => {\n sockets.add(socket)\n socket.on('close', () => {\n sockets.delete(socket)\n })\n })\n}\n\n/**\n * Close a server and destroy all active connections immediately.\n *\n * `server.close()` only stops accepting new connections -- existing\n * keep-alive connections hold the event loop open. This helper\n * destroys every tracked socket so the process can exit cleanly.\n *\n * @param server - The HTTP server to close.\n * @param sockets - The set of tracked sockets.\n */\nexport function destroyServer(server: Server, sockets: Set<Socket>): void {\n server.close()\n Array.from(sockets, (socket) => socket.destroy())\n sockets.clear()\n}\n\n/**\n * Send an HTML success page and end the response.\n *\n * @param res - The server response object.\n */\nexport function sendSuccessPage(res: ServerResponse): void {\n res.writeHead(200, { 'Content-Type': 'text/html' })\n res.end(CLOSE_PAGE_HTML)\n}\n\n/**\n * Check whether a URL is safe for use as an OAuth endpoint.\n *\n * Requires HTTPS for all URLs except loopback addresses, where\n * HTTP is permitted per RFC 8252 §8.3 (native app redirect URIs).\n *\n * @param url - The URL string to validate.\n * @returns True when the URL uses HTTPS or HTTP on a loopback address.\n */\nexport function isSecureAuthUrl(url: string): boolean {\n const [error, parsed] = attempt(() => new URL(url))\n\n if (error || !parsed) {\n return false\n }\n\n if (parsed.protocol === 'https:') {\n return true\n }\n\n if (parsed.protocol !== 'http:') {\n return false\n }\n\n return isLoopbackHost(parsed.hostname)\n}\n\n/**\n * Open a URL in the user's default browser using a platform-specific command.\n *\n * Validates that the URL uses the HTTP or HTTPS protocol before opening\n * to prevent dangerous schemes like `javascript:` or `data:`. Silently\n * returns if the URL is invalid.\n *\n * On Windows, `start` is a `cmd.exe` built-in -- not a standalone executable --\n * so it must be invoked via `cmd /c start \"\" <url>`. The empty string argument\n * prevents `cmd` from interpreting the URL as a window title.\n *\n * @param url - The URL to open (must use http: or https: protocol).\n */\nexport function openBrowser(url: string): void {\n if (!isHttpUrl(url)) {\n return\n }\n\n const { command, args } = match(platform())\n .with('darwin', () => ({ args: [url], command: 'open' }))\n .with('win32', () => ({ args: ['/c', 'start', '', escapeCmdMeta(url)], command: 'cmd' }))\n .otherwise(() => ({ args: [url], command: 'xdg-open' }))\n\n const child = execFile(command, args)\n child.on('error', () => undefined)\n}\n\n/**\n * Start a local HTTP server on `127.0.0.1` with socket tracking.\n *\n * Returns a handle containing the server, tracked sockets, and a port\n * promise that resolves once the server is listening.\n *\n * @param options - Server configuration.\n * @returns A LocalServerHandle with port, server, and sockets.\n */\nexport function startLocalServer(options: {\n readonly port: number\n readonly onRequest: (req: IncomingMessage, res: ServerResponse) => void\n}): LocalServerHandle {\n const portDeferred = createDeferred<number | null>()\n\n // Mutable socket set required for resource cleanup.\n // Server API is stateful -- tracking sockets is the only way to destroy keep-alive connections.\n const sockets = new Set<Socket>()\n\n const server = createServer(options.onRequest)\n\n trackConnections(server, sockets)\n\n server.on('error', () => {\n destroyServer(server, sockets)\n portDeferred.resolve(null)\n })\n\n server.listen(options.port, '127.0.0.1', () => {\n const addr = server.address()\n\n if (addr === null || typeof addr === 'string') {\n destroyServer(server, sockets)\n portDeferred.resolve(null)\n return\n }\n\n portDeferred.resolve(addr.port)\n })\n\n return {\n port: portDeferred.promise,\n server,\n sockets,\n }\n}\n\n// ---------------------------------------------------------------------------\n// Private helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Check whether a URL uses the HTTP or HTTPS protocol.\n *\n * Rejects dangerous schemes like `javascript:`, `data:`, and `file:`\n * to prevent browser-based attacks when opening untrusted URLs.\n *\n * @private\n * @param url - The URL string to validate.\n * @returns True when the URL uses http: or https: protocol.\n */\nfunction isHttpUrl(url: string): boolean {\n const [error, parsed] = attempt(() => new URL(url))\n\n if (error || !parsed) {\n return false\n }\n\n return parsed.protocol === 'https:' || parsed.protocol === 'http:'\n}\n\n/**\n * Check whether a hostname is a loopback address.\n *\n * RFC 8252 §8.3 permits HTTP for loopback interfaces during\n * native app authorization flows.\n *\n * @private\n * @param hostname - The hostname to check.\n * @returns True when the hostname is a loopback address.\n */\nfunction isLoopbackHost(hostname: string): boolean {\n return hostname === '127.0.0.1' || hostname === '[::1]' || hostname === 'localhost'\n}\n\n/**\n * Escape `cmd.exe` metacharacters in a URL string.\n *\n * Characters like `&`, `|`, `<`, `>`, and `^` are interpreted as\n * command separators or redirectors by `cmd.exe`. Prefixing each\n * with `^` neutralises the special meaning.\n *\n * @private\n * @param url - The URL to escape.\n * @returns The escaped URL string.\n */\nfunction escapeCmdMeta(url: string): string {\n return url.replaceAll(/[&|<>^]/g, '^$&')\n}\n","/**\n * OAuth 2.0 Device Authorization Grant resolver (RFC 8628).\n *\n * Requests a device code, displays the verification URL and user code,\n * and polls the token endpoint until the user completes authorization\n * or the flow times out.\n *\n * @module\n */\n\nimport { attemptAsync, isPlainObject, match } from '@kidd-cli/utils/fp'\n\nimport type { Prompts } from '@/context/types.js'\n\nimport { createBearerCredential, postFormEncoded } from '../credential.js'\nimport { isSecureAuthUrl, openBrowser } from '../oauth-server.js'\nimport type { AuthCredential } from '../types.js'\n\n/**\n * RFC 8628 slow_down backoff increment in milliseconds.\n */\nconst SLOW_DOWN_INCREMENT = 5000\n\n/**\n * RFC 8628 device code grant type URN.\n */\nconst DEVICE_CODE_GRANT_TYPE = 'urn:ietf:params:oauth:grant-type:device_code'\n\n/**\n * Resolve a bearer credential via OAuth 2.0 Device Authorization Grant.\n *\n * 1. POSTs to the device authorization endpoint to obtain a device code\n * 2. Displays the verification URL and user code via prompts\n * 3. Optionally opens the verification URL in the browser\n * 4. Polls the token endpoint until authorization completes or times out\n *\n * @param options - Device code flow configuration.\n * @returns A bearer credential on success, null on failure or timeout.\n */\nexport async function resolveFromDeviceCode(options: {\n readonly clientId: string\n readonly deviceAuthUrl: string\n readonly tokenUrl: string\n readonly scopes: readonly string[]\n readonly pollInterval: number\n readonly timeout: number\n readonly prompts: Prompts\n readonly openBrowserOnStart?: boolean\n}): Promise<AuthCredential | null> {\n if (!isSecureAuthUrl(options.deviceAuthUrl)) {\n return null\n }\n\n if (!isSecureAuthUrl(options.tokenUrl)) {\n return null\n }\n\n const deadline = Date.now() + options.timeout\n const signal = AbortSignal.timeout(options.timeout)\n\n const authResponse = await requestDeviceAuth({\n clientId: options.clientId,\n deviceAuthUrl: options.deviceAuthUrl,\n scopes: options.scopes,\n signal,\n })\n\n if (!authResponse) {\n return null\n }\n\n await displayUserCode(options.prompts, authResponse.verificationUri, authResponse.userCode)\n\n if (options.openBrowserOnStart !== false) {\n openBrowser(authResponse.verificationUri)\n }\n\n const interval = resolveInterval(authResponse.interval, options.pollInterval)\n\n return pollForToken({\n clientId: options.clientId,\n deadline,\n deviceCode: authResponse.deviceCode,\n interval,\n signal,\n tokenUrl: options.tokenUrl,\n })\n}\n\n// ---------------------------------------------------------------------------\n// Private types\n// ---------------------------------------------------------------------------\n\n/**\n * Parsed response from the device authorization endpoint.\n *\n * @private\n */\ninterface DeviceAuthResponse {\n readonly deviceCode: string\n readonly userCode: string\n readonly verificationUri: string\n readonly interval: number | null\n}\n\n// ---------------------------------------------------------------------------\n// Private helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Request a device code from the authorization server.\n *\n * @private\n * @param options - Device auth request parameters.\n * @returns The parsed device auth response, or null on failure.\n */\nasync function requestDeviceAuth(options: {\n readonly clientId: string\n readonly deviceAuthUrl: string\n readonly scopes: readonly string[]\n readonly signal?: AbortSignal\n}): Promise<DeviceAuthResponse | null> {\n const body = new URLSearchParams({ client_id: options.clientId })\n\n if (options.scopes.length > 0) {\n body.set('scope', options.scopes.join(' '))\n }\n\n const response = await postFormEncoded(options.deviceAuthUrl, body, options.signal)\n\n if (!response) {\n return null\n }\n\n if (!response.ok) {\n return null\n }\n\n const [parseError, data] = await attemptAsync((): Promise<unknown> => response.json())\n\n if (parseError) {\n return null\n }\n\n return parseDeviceAuthResponse(data)\n}\n\n/**\n * Parse a device authorization response body.\n *\n * @private\n * @param data - The raw response data.\n * @returns The parsed response, or null if required fields are missing.\n */\nfunction parseDeviceAuthResponse(data: unknown): DeviceAuthResponse | null {\n if (!isPlainObject(data)) {\n return null\n }\n\n if (typeof data.device_code !== 'string' || data.device_code === '') {\n return null\n }\n\n if (typeof data.user_code !== 'string' || data.user_code === '') {\n return null\n }\n\n if (typeof data.verification_uri !== 'string' || data.verification_uri === '') {\n return null\n }\n\n const interval = resolveServerInterval(data.interval)\n\n return {\n deviceCode: data.device_code,\n interval,\n userCode: data.user_code,\n verificationUri: data.verification_uri,\n }\n}\n\n/**\n * Display the verification URL and user code to the user.\n *\n * Uses `prompts.text()` to show the information and wait for\n * the user to press Enter to acknowledge.\n *\n * @private\n * @param prompts - The prompts instance.\n * @param verificationUri - The URL the user should visit.\n * @param userCode - The code the user should enter.\n */\nasync function displayUserCode(\n prompts: Prompts,\n verificationUri: string,\n userCode: string\n): Promise<void> {\n // User cancellation is non-fatal — polling will handle timeout\n await attemptAsync(() =>\n prompts.text({\n defaultValue: '',\n message: `Open ${verificationUri} and enter code: ${userCode} (press Enter to continue)`,\n })\n )\n}\n\n/**\n * Resolve the poll interval, preferring server-provided value.\n *\n * @private\n * @param serverInterval - The interval from the server response (in ms), or null.\n * @param configInterval - The configured default interval.\n * @returns The resolved interval in milliseconds.\n */\nfunction resolveInterval(serverInterval: number | null, configInterval: number): number {\n if (serverInterval !== null) {\n return serverInterval\n }\n\n return configInterval\n}\n\n/**\n * Poll the token endpoint for an access token using recursive tail-call style.\n *\n * Handles RFC 8628 error codes:\n * - `authorization_pending` -- continue polling\n * - `slow_down` -- increase interval by 5 seconds, continue\n * - `expired_token` -- return null\n * - `access_denied` -- return null\n *\n * @private\n * @param options - Polling parameters.\n * @returns A bearer credential on success, null on failure or timeout.\n */\nasync function pollForToken(options: {\n readonly tokenUrl: string\n readonly deviceCode: string\n readonly clientId: string\n readonly interval: number\n readonly deadline: number\n readonly signal?: AbortSignal\n}): Promise<AuthCredential | null> {\n if (Date.now() >= options.deadline) {\n return null\n }\n\n await sleep(options.interval)\n\n if (Date.now() >= options.deadline) {\n return null\n }\n\n const result = await requestToken({\n clientId: options.clientId,\n deviceCode: options.deviceCode,\n signal: options.signal,\n tokenUrl: options.tokenUrl,\n })\n\n return match(result)\n .with({ status: 'success' }, (r) => r.credential)\n .with({ status: 'pending' }, () => pollForToken(options))\n .with({ status: 'slow_down' }, () =>\n pollForToken({\n ...options,\n interval: options.interval + SLOW_DOWN_INCREMENT,\n })\n )\n .with({ status: 'denied' }, () => null)\n .with({ status: 'expired' }, () => null)\n .with({ status: 'error' }, () => null)\n .exhaustive()\n}\n\n/**\n * Convert a server-provided interval value to milliseconds.\n *\n * @private\n * @param value - The raw interval value from the server response.\n * @returns The interval in milliseconds, or null if not a number.\n */\nfunction resolveServerInterval(value: unknown): number | null {\n if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0) {\n return null\n }\n\n return Math.max(1000, Math.min(value * 1000, 60_000))\n}\n\n/**\n * Sleep for a given duration.\n *\n * @private\n * @param ms - Duration in milliseconds.\n * @returns A promise that resolves after the delay.\n */\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => {\n setTimeout(resolve, ms)\n })\n}\n\n// ---------------------------------------------------------------------------\n// Token request types and helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Discriminated union of token request outcomes.\n *\n * @private\n */\ntype TokenRequestResult =\n | { readonly status: 'success'; readonly credential: AuthCredential }\n | { readonly status: 'pending' }\n | { readonly status: 'slow_down' }\n | { readonly status: 'denied' }\n | { readonly status: 'expired' }\n | { readonly status: 'error' }\n\n/**\n * Request an access token from the token endpoint.\n *\n * @private\n * @param options - Token request parameters.\n * @returns A discriminated result indicating the outcome.\n */\nasync function requestToken(options: {\n readonly tokenUrl: string\n readonly deviceCode: string\n readonly clientId: string\n readonly signal?: AbortSignal\n}): Promise<TokenRequestResult> {\n const body = new URLSearchParams({\n client_id: options.clientId,\n device_code: options.deviceCode,\n grant_type: DEVICE_CODE_GRANT_TYPE,\n })\n\n const response = await postFormEncoded(options.tokenUrl, body, options.signal)\n\n if (!response) {\n return { status: 'error' }\n }\n\n const [parseError, data] = await attemptAsync((): Promise<unknown> => response.json())\n\n if (parseError) {\n return { status: 'error' }\n }\n\n if (!isPlainObject(data)) {\n return { status: 'error' }\n }\n\n if (response.ok && typeof data.access_token === 'string' && data.access_token !== '') {\n if (typeof data.token_type === 'string' && data.token_type.toLowerCase() !== 'bearer') {\n return { status: 'error' }\n }\n\n return { credential: createBearerCredential(data.access_token), status: 'success' }\n }\n\n if (typeof data.error !== 'string') {\n return { status: 'error' }\n }\n\n return match(data.error)\n .with('authorization_pending', (): TokenRequestResult => ({ status: 'pending' }))\n .with('slow_down', (): TokenRequestResult => ({ status: 'slow_down' }))\n .with('expired_token', (): TokenRequestResult => ({ status: 'expired' }))\n .with('access_denied', (): TokenRequestResult => ({ status: 'denied' }))\n .otherwise((): TokenRequestResult => ({ status: 'error' }))\n}\n","import { readFileSync } from 'node:fs'\n\nimport { parse } from 'dotenv'\nimport { attempt } from 'es-toolkit'\n\nimport { createBearerCredential, isValidToken } from '../credential.js'\nimport type { AuthCredential } from '../types.js'\n\n/**\n * Resolve a bearer credential from a `.env` file without mutating `process.env`.\n *\n * Reads the file and parses it with `dotenv.parse`. If the target variable\n * is present, returns a bearer credential. Otherwise returns null.\n *\n * Skips a separate existence check to avoid a TOCTOU race — if the file\n * does not exist, `readFileSync` throws and `attempt` captures the error.\n *\n * @param options - Options with the env variable name and file path.\n * @returns A bearer credential if found, null otherwise.\n */\nexport function resolveFromDotenv(options: {\n readonly tokenVar: string\n readonly path: string\n}): AuthCredential | null {\n const [readError, content] = attempt(() => readFileSync(options.path, 'utf8'))\n\n if (readError || content === null) {\n return null\n }\n\n const parsed = parse(content)\n const token = parsed[options.tokenVar]\n\n if (!isValidToken(token)) {\n return null\n }\n\n return createBearerCredential(token)\n}\n","import { createBearerCredential, isValidToken } from '../credential.js'\nimport type { AuthCredential } from '../types.js'\n\n/**\n * Resolve a bearer credential from a process environment variable.\n *\n * @param options - Options containing the environment variable name.\n * @returns A bearer credential if the variable is set, null otherwise.\n */\nexport function resolveFromEnv(options: { readonly tokenVar: string }): AuthCredential | null {\n const token = process.env[options.tokenVar]\n\n if (!isValidToken(token)) {\n return null\n }\n\n return createBearerCredential(token)\n}\n","import { z } from 'zod'\n\n/**\n * Zod schema for bearer credentials.\n */\nexport const bearerCredentialSchema = z.object({\n token: z.string().min(1),\n type: z.literal('bearer'),\n})\n\n/**\n * Zod schema for basic auth credentials.\n */\nexport const basicCredentialSchema = z.object({\n password: z.string().min(1),\n type: z.literal('basic'),\n username: z.string().min(1),\n})\n\n/**\n * Zod schema for API key credentials.\n */\nexport const apiKeyCredentialSchema = z.object({\n headerName: z.string().min(1),\n key: z.string().min(1),\n type: z.literal('api-key'),\n})\n\n/**\n * Zod schema for custom header credentials.\n */\nexport const customCredentialSchema = z.object({\n headers: z.record(z.string(), z.string()),\n type: z.literal('custom'),\n})\n\n/**\n * Zod discriminated union schema for validating auth.json credential payloads.\n * Validates against all four credential types using the `type` field as discriminator.\n */\nexport const authCredentialSchema = z.discriminatedUnion('type', [\n bearerCredentialSchema,\n basicCredentialSchema,\n apiKeyCredentialSchema,\n customCredentialSchema,\n])\n","import { createStore } from '@/lib/store/create-store.js'\n\nimport { authCredentialSchema } from '../schema.js'\nimport type { AuthCredential } from '../types.js'\n\n/**\n * Resolve credentials from a JSON file on disk.\n *\n * Uses the file-backed store with local-then-global resolution to find\n * the credentials file, then validates its contents against the auth\n * credential schema.\n *\n * @param options - Options with the filename and directory name.\n * @returns A validated auth credential, or null if not found or invalid.\n */\nexport function resolveFromFile(options: {\n readonly filename: string\n readonly dirName: string\n}): AuthCredential | null {\n const store = createStore({ dirName: options.dirName })\n const data = store.load(options.filename)\n\n if (data === null) {\n return null\n }\n\n const result = authCredentialSchema.safeParse(data)\n\n if (!result.success) {\n return null\n }\n\n return result.data\n}\n","/**\n * OAuth 2.0 Authorization Code + PKCE resolver (RFC 7636 + RFC 8252).\n *\n * Opens the user's browser to the authorization URL with a PKCE challenge,\n * listens for a GET redirect with an authorization code on a local server,\n * and exchanges the code at the token endpoint with the code verifier.\n *\n * @module\n */\n\nimport { createHash, randomBytes } from 'node:crypto'\nimport type { IncomingMessage, ServerResponse } from 'node:http'\n\nimport { attemptAsync, isPlainObject } from '@kidd-cli/utils/fp'\n\nimport { createBearerCredential, postFormEncoded } from '../credential.js'\nimport {\n createDeferred,\n createTimeout,\n destroyServer,\n isSecureAuthUrl,\n openBrowser,\n sendSuccessPage,\n startLocalServer,\n} from '../oauth-server.js'\nimport type { AuthCredential } from '../types.js'\n\n/**\n * Resolve a bearer credential via OAuth 2.0 Authorization Code + PKCE.\n *\n * 1. Generates a `code_verifier` and derives the `code_challenge`\n * 2. Starts a local HTTP server on `127.0.0.1`\n * 3. Opens the browser to the authorization URL with PKCE params\n * 4. Receives the authorization code via GET redirect\n * 5. Exchanges the code at the token endpoint with the verifier\n * 6. Returns the access token as a bearer credential\n *\n * @param options - PKCE flow configuration.\n * @returns A bearer credential on success, null on failure or timeout.\n */\nexport async function resolveFromOAuth(options: {\n readonly clientId: string\n readonly authUrl: string\n readonly tokenUrl: string\n readonly scopes: readonly string[]\n readonly port: number\n readonly callbackPath: string\n readonly timeout: number\n}): Promise<AuthCredential | null> {\n if (!isSecureAuthUrl(options.authUrl)) {\n return null\n }\n\n if (!isSecureAuthUrl(options.tokenUrl)) {\n return null\n }\n\n const codeVerifier = generateCodeVerifier()\n const codeChallenge = deriveCodeChallenge(codeVerifier)\n const state = randomBytes(32).toString('hex')\n\n const timeout = createTimeout(options.timeout)\n const codeDeferred = createDeferred<string | null>()\n\n const handle = startLocalServer({\n onRequest: (req, res) => {\n handleCallback(req, res, options.callbackPath, state, codeDeferred.resolve)\n },\n port: options.port,\n })\n\n const serverPort = await handle.port\n\n if (serverPort === null) {\n timeout.clear()\n return null\n }\n\n const redirectUri = `http://127.0.0.1:${String(serverPort)}${options.callbackPath}`\n\n const fullAuthUrl = buildAuthUrl({\n authUrl: options.authUrl,\n clientId: options.clientId,\n codeChallenge,\n redirectUri,\n scopes: options.scopes,\n state,\n })\n\n openBrowser(fullAuthUrl)\n\n const timeoutPromise = timeout.promise.then((): null => {\n codeDeferred.resolve(null)\n destroyServer(handle.server, handle.sockets)\n return null\n })\n\n const code = await Promise.race([codeDeferred.promise, timeoutPromise])\n\n timeout.clear()\n\n if (!code) {\n destroyServer(handle.server, handle.sockets)\n return null\n }\n\n destroyServer(handle.server, handle.sockets)\n\n const token = await exchangeCodeForToken({\n clientId: options.clientId,\n code,\n codeVerifier,\n redirectUri,\n tokenUrl: options.tokenUrl,\n })\n\n return token\n}\n\n// ---------------------------------------------------------------------------\n// Private helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Generate a cryptographically random code verifier for PKCE.\n *\n * @private\n * @returns A base64url-encoded random string.\n */\nfunction generateCodeVerifier(): string {\n return randomBytes(32).toString('base64url')\n}\n\n/**\n * Derive a S256 code challenge from a code verifier.\n *\n * @private\n * @param verifier - The code verifier string.\n * @returns The base64url-encoded SHA-256 hash.\n */\nfunction deriveCodeChallenge(verifier: string): string {\n return createHash('sha256').update(verifier).digest('base64url')\n}\n\n/**\n * Build the full authorization URL with PKCE query parameters.\n *\n * @private\n * @param options - Authorization URL components.\n * @returns The complete authorization URL string.\n */\nfunction buildAuthUrl(options: {\n readonly authUrl: string\n readonly clientId: string\n readonly redirectUri: string\n readonly codeChallenge: string\n readonly state: string\n readonly scopes: readonly string[]\n}): string {\n const url = new URL(options.authUrl)\n url.searchParams.set('response_type', 'code')\n url.searchParams.set('client_id', options.clientId)\n url.searchParams.set('redirect_uri', options.redirectUri)\n url.searchParams.set('code_challenge', options.codeChallenge)\n url.searchParams.set('code_challenge_method', 'S256')\n url.searchParams.set('state', options.state)\n\n if (options.scopes.length > 0) {\n url.searchParams.set('scope', options.scopes.join(' '))\n }\n\n return url.toString()\n}\n\n/**\n * Handle an incoming HTTP request on the callback server.\n *\n * Accepts GET requests to the callback path with `code` and `state`\n * query parameters. Validates the state nonce and resolves the\n * authorization code.\n *\n * @private\n * @param req - The incoming HTTP request.\n * @param res - The server response.\n * @param callbackPath - The expected callback path.\n * @param expectedState - The state nonce to validate.\n * @param resolve - Callback to deliver the authorization code.\n */\nfunction handleCallback(\n req: IncomingMessage,\n res: ServerResponse,\n callbackPath: string,\n expectedState: string,\n resolve: (value: string | null) => void\n): void {\n const result = extractCodeFromUrl(req.url, callbackPath, expectedState)\n\n if (!result.ok) {\n res.writeHead(400)\n res.end()\n\n if (result.isOAuthError) {\n resolve(null)\n }\n\n return\n }\n\n sendSuccessPage(res)\n resolve(result.code)\n}\n\n/**\n * Result of extracting an authorization code from a callback URL.\n *\n * @private\n */\ntype ExtractCodeResult =\n | { readonly ok: true; readonly code: string }\n | { readonly ok: false; readonly isOAuthError: boolean }\n\n/**\n * Extract an authorization code from a request URL.\n *\n * Validates that the request path matches the callback path,\n * the `state` parameter matches the expected nonce, and a\n * `code` parameter is present. Detects OAuth error responses\n * (e.g. `?error=access_denied`) and flags them so the caller\n * can resolve immediately instead of waiting for the timeout.\n *\n * @private\n * @param reqUrl - The raw request URL string.\n * @param callbackPath - The expected callback path.\n * @param expectedState - The state nonce to validate.\n * @returns An extraction result with the code or error flag.\n */\nfunction extractCodeFromUrl(\n reqUrl: string | undefined,\n callbackPath: string,\n expectedState: string\n): ExtractCodeResult {\n const url = new URL(reqUrl ?? '/', 'http://localhost')\n\n if (url.pathname !== callbackPath) {\n return { isOAuthError: false, ok: false }\n }\n\n const state = url.searchParams.get('state')\n\n if (state !== expectedState) {\n return { isOAuthError: false, ok: false }\n }\n\n const error = url.searchParams.get('error')\n\n if (error) {\n return { isOAuthError: true, ok: false }\n }\n\n const code = url.searchParams.get('code')\n\n if (!code) {\n return { isOAuthError: false, ok: false }\n }\n\n return { code, ok: true }\n}\n\n/**\n * Exchange an authorization code for an access token at the token endpoint.\n *\n * Sends a POST request with `application/x-www-form-urlencoded` body\n * containing the authorization code, redirect URI, client ID, and\n * PKCE code verifier.\n *\n * @private\n * @param options - Token exchange parameters.\n * @returns A bearer credential on success, null on failure.\n */\nasync function exchangeCodeForToken(options: {\n readonly tokenUrl: string\n readonly code: string\n readonly redirectUri: string\n readonly clientId: string\n readonly codeVerifier: string\n}): Promise<AuthCredential | null> {\n const body = new URLSearchParams({\n client_id: options.clientId,\n code: options.code,\n code_verifier: options.codeVerifier,\n grant_type: 'authorization_code',\n redirect_uri: options.redirectUri,\n })\n\n const response = await postFormEncoded(options.tokenUrl, body)\n\n if (!response) {\n return null\n }\n\n if (!response.ok) {\n return null\n }\n\n const [parseError, data] = await attemptAsync((): Promise<unknown> => response.json())\n\n if (parseError) {\n return null\n }\n\n if (!isPlainObject(data)) {\n return null\n }\n\n if (typeof data.access_token !== 'string' || data.access_token === '') {\n return null\n }\n\n if (typeof data.token_type === 'string' && data.token_type.toLowerCase() !== 'bearer') {\n return null\n }\n\n return createBearerCredential(data.access_token)\n}\n","import { attemptAsync } from '@kidd-cli/utils/fp'\n\nimport type { Prompts } from '@/context/types.js'\n\nimport { createBearerCredential, isValidToken } from '../credential.js'\nimport type { AuthCredential } from '../types.js'\n\n/**\n * Resolve a bearer credential by interactively prompting the user.\n *\n * Uses `prompts.password()` to ask for an API key or token. Returns\n * null if the user cancels the prompt or provides an empty value.\n *\n * Should be placed last in the resolver chain as a fallback.\n *\n * @param options - Options with the prompt message and prompts instance.\n * @returns A bearer credential on input, null on cancellation.\n */\nexport async function resolveFromToken(options: {\n readonly message: string\n readonly prompts: Prompts\n}): Promise<AuthCredential | null> {\n const [promptError, token] = await attemptAsync(() =>\n options.prompts.password({ message: options.message })\n )\n\n if (promptError) {\n return null\n }\n\n if (!isValidToken(token)) {\n return null\n }\n\n return createBearerCredential(token)\n}\n","import { join } from 'node:path'\n\nimport { match } from 'ts-pattern'\n\nimport type { Prompts } from '@/context/types.js'\n\nimport {\n DEFAULT_AUTH_FILENAME,\n DEFAULT_DEVICE_CODE_POLL_INTERVAL,\n DEFAULT_DEVICE_CODE_TIMEOUT,\n DEFAULT_OAUTH_CALLBACK_PATH,\n DEFAULT_OAUTH_PORT,\n DEFAULT_OAUTH_TIMEOUT,\n deriveTokenVar,\n} from './constants.js'\nimport { resolveFromDeviceCode } from './strategies/device-code.js'\nimport { resolveFromDotenv } from './strategies/dotenv.js'\nimport { resolveFromEnv } from './strategies/env.js'\nimport { resolveFromFile } from './strategies/file.js'\nimport { resolveFromOAuth } from './strategies/oauth.js'\nimport { resolveFromToken } from './strategies/token.js'\nimport type { AuthCredential, StrategyConfig } from './types.js'\n\nconst DEFAULT_PROMPT_MESSAGE = 'Enter your API key'\n\n/**\n * Chain credential strategies, returning the first non-null result.\n *\n * Walks the strategy list in order, dispatching each config to the\n * appropriate strategy function via pattern matching. Short-circuits\n * on the first successful resolution.\n *\n * @param options - Options with strategies, CLI name, and prompts instance.\n * @returns The first resolved credential, or null if all strategies fail.\n */\nexport async function runStrategyChain(options: {\n readonly strategies: readonly StrategyConfig[]\n readonly cliName: string\n readonly prompts: Prompts\n}): Promise<AuthCredential | null> {\n const defaultTokenVar = deriveTokenVar(options.cliName)\n\n return tryStrategies(options.strategies, 0, defaultTokenVar, options)\n}\n\n/**\n * Return the given value when defined, otherwise the fallback.\n *\n * @param value - The optional value.\n * @param fallback - The default value.\n * @returns The resolved value.\n */\nexport function withDefault<T>(value: T | undefined, fallback: T): T {\n if (value !== undefined) {\n return value\n }\n return fallback\n}\n\n// ---------------------------------------------------------------------------\n// Private helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Recursively try strategies until one returns a credential or the list is exhausted.\n *\n * @private\n * @param configs - The strategy configs.\n * @param index - The current index.\n * @param defaultTokenVar - The derived default token env var name.\n * @param context - The resolve options for prompts access.\n * @returns The first resolved credential, or null.\n */\nasync function tryStrategies(\n configs: readonly StrategyConfig[],\n index: number,\n defaultTokenVar: string,\n context: {\n readonly cliName: string\n readonly prompts: Prompts\n }\n): Promise<AuthCredential | null> {\n if (index >= configs.length) {\n return null\n }\n\n const config = configs[index]\n\n if (config === undefined) {\n return null\n }\n\n const credential = await dispatchStrategy(config, defaultTokenVar, context)\n\n if (credential) {\n return credential\n }\n\n return tryStrategies(configs, index + 1, defaultTokenVar, context)\n}\n\n/**\n * Dispatch a single strategy config to its implementation.\n *\n * @private\n * @param config - The strategy config to dispatch.\n * @param defaultTokenVar - The derived default token env var name.\n * @param context - The resolve options for prompts access.\n * @returns The resolved credential, or null.\n */\nasync function dispatchStrategy(\n config: StrategyConfig,\n defaultTokenVar: string,\n context: {\n readonly cliName: string\n readonly prompts: Prompts\n }\n): Promise<AuthCredential | null> {\n return match(config)\n .with({ source: 'env' }, (c): AuthCredential | null =>\n resolveFromEnv({\n tokenVar: withDefault(c.tokenVar, defaultTokenVar),\n })\n )\n .with({ source: 'dotenv' }, (c): AuthCredential | null =>\n resolveFromDotenv({\n path: withDefault(c.path, join(process.cwd(), '.env')),\n tokenVar: withDefault(c.tokenVar, defaultTokenVar),\n })\n )\n .with({ source: 'file' }, (c): AuthCredential | null =>\n resolveFromFile({\n dirName: withDefault(c.dirName, `.${context.cliName}`),\n filename: withDefault(c.filename, DEFAULT_AUTH_FILENAME),\n })\n )\n .with(\n { source: 'oauth' },\n (c): Promise<AuthCredential | null> =>\n resolveFromOAuth({\n authUrl: c.authUrl,\n callbackPath: withDefault(c.callbackPath, DEFAULT_OAUTH_CALLBACK_PATH),\n clientId: c.clientId,\n port: withDefault(c.port, DEFAULT_OAUTH_PORT),\n scopes: withDefault(c.scopes, []),\n timeout: withDefault(c.timeout, DEFAULT_OAUTH_TIMEOUT),\n tokenUrl: c.tokenUrl,\n })\n )\n .with(\n { source: 'device-code' },\n (c): Promise<AuthCredential | null> =>\n resolveFromDeviceCode({\n clientId: c.clientId,\n deviceAuthUrl: c.deviceAuthUrl,\n openBrowserOnStart: withDefault(c.openBrowser, true),\n pollInterval: withDefault(c.pollInterval, DEFAULT_DEVICE_CODE_POLL_INTERVAL),\n prompts: context.prompts,\n scopes: withDefault(c.scopes, []),\n timeout: withDefault(c.timeout, DEFAULT_DEVICE_CODE_TIMEOUT),\n tokenUrl: c.tokenUrl,\n })\n )\n .with(\n { source: 'token' },\n (c): Promise<AuthCredential | null> =>\n resolveFromToken({\n message: withDefault(c.message, DEFAULT_PROMPT_MESSAGE),\n prompts: context.prompts,\n })\n )\n .with({ source: 'custom' }, (c): Promise<AuthCredential | null> | AuthCredential | null =>\n c.resolver()\n )\n .exhaustive()\n}\n","/**\n * Factory for the {@link AuthContext} object decorated onto `ctx.auth`.\n *\n * Closes over the middleware's strategy config, CLI name, prompts, and\n * a credential resolver function so that `login()` can run\n * interactive strategies and persist the result.\n *\n * @module\n */\n\nimport type { AsyncResult, Result } from '@kidd-cli/utils/fp'\nimport { ok } from '@kidd-cli/utils/fp'\n\nimport type { Prompts } from '@/context/types.js'\nimport { createStore } from '@/lib/store/create-store.js'\n\nimport { runStrategyChain } from './chain.js'\nimport { DEFAULT_AUTH_FILENAME } from './constants.js'\nimport type {\n AuthContext,\n AuthCredential,\n AuthError,\n LoginOptions,\n StrategyConfig,\n} from './types.js'\n\n/**\n * Options for {@link createAuthContext}.\n */\nexport interface CreateAuthContextOptions {\n readonly strategies: readonly StrategyConfig[]\n readonly cliName: string\n readonly prompts: Prompts\n readonly resolveCredential: () => AuthCredential | null\n}\n\n/**\n * Create an {@link AuthContext} value for `ctx.auth`.\n *\n * No credential data is stored on the returned object. `credential()`\n * resolves passively on every call, `authenticated()` checks existence,\n * `login()` runs the configured interactive strategies, saves the\n * credential to the global file store, and `logout()` removes it.\n *\n * @param options - Factory options.\n * @returns An AuthContext instance.\n */\nexport function createAuthContext(options: CreateAuthContextOptions): AuthContext {\n const { strategies, cliName, prompts, resolveCredential } = options\n\n /**\n * Resolve the current credential from passive sources (file, env).\n *\n * @private\n * @returns The credential, or null when none exists.\n */\n function credential(): AuthCredential | null {\n return resolveCredential()\n }\n\n /**\n * Check whether a credential is available from passive sources.\n *\n * @private\n * @returns True when a credential exists.\n */\n function authenticated(): boolean {\n return resolveCredential() !== null\n }\n\n /**\n * Run configured strategies interactively and persist the credential.\n *\n * When `loginOptions.strategies` is provided, those strategies are used\n * instead of the default configured list.\n *\n * @private\n * @param loginOptions - Optional overrides for the login attempt.\n * @returns A Result with the credential on success or an AuthError on failure.\n */\n async function login(loginOptions?: LoginOptions): AsyncResult<AuthCredential, AuthError> {\n const activeStrategies = resolveLoginStrategies(loginOptions, strategies)\n\n const resolved = await runStrategyChain({\n cliName,\n prompts,\n strategies: activeStrategies,\n })\n\n if (resolved === null) {\n return authError({\n message: 'No credential resolved from any source',\n type: 'no_credential',\n })\n }\n\n const store = createStore({ dirName: `.${cliName}` })\n const [saveError] = store.save(DEFAULT_AUTH_FILENAME, resolved)\n\n if (saveError) {\n return authError({\n message: `Failed to save credential: ${saveError.message}`,\n type: 'save_failed',\n })\n }\n\n return ok(resolved)\n }\n\n /**\n * Remove the stored credential from disk.\n *\n * @private\n * @returns A Result with the removed file path on success or an AuthError on failure.\n */\n async function logout(): AsyncResult<string, AuthError> {\n const store = createStore({ dirName: `.${cliName}` })\n const [removeError, filePath] = store.remove(DEFAULT_AUTH_FILENAME)\n\n if (removeError) {\n return authError({\n message: `Failed to remove credential: ${removeError.message}`,\n type: 'remove_failed',\n })\n }\n\n return ok(filePath)\n }\n\n return { authenticated, credential, login, logout }\n}\n\n// ---------------------------------------------------------------------------\n// Private helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Construct a failure Result tuple with an {@link AuthError}.\n *\n * @private\n * @param error - The auth error.\n * @returns A Result tuple `[AuthError, null]`.\n */\nfunction authError(error: AuthError): Result<never, AuthError> {\n return [error, null] as const\n}\n\n/**\n * Resolve the active strategies for a login attempt.\n *\n * Returns the override strategies from login options when provided,\n * otherwise falls back to the configured strategies.\n *\n * @private\n * @param loginOptions - Optional login overrides.\n * @param configured - The default configured strategies.\n * @returns The strategies to use for the login attempt.\n */\nfunction resolveLoginStrategies(\n loginOptions: LoginOptions | undefined,\n configured: readonly StrategyConfig[]\n): readonly StrategyConfig[] {\n if (loginOptions !== undefined && loginOptions.strategies !== undefined) {\n return loginOptions.strategies\n }\n\n return configured\n}\n","/**\n * Convert auth credentials into HTTP headers.\n *\n * Uses exhaustive pattern matching to map each credential variant to\n * the appropriate header format.\n *\n * @module\n */\n\nimport { Buffer } from 'node:buffer'\n\nimport { match } from 'ts-pattern'\n\nimport type { AuthCredential } from '../auth/types.js'\n\n/**\n * Convert an auth credential into HTTP headers.\n *\n * @param credential - The credential to convert.\n * @returns A record of header name to header value.\n */\nexport function buildAuthHeaders(credential: AuthCredential): Readonly<Record<string, string>> {\n return match(credential)\n .with({ type: 'bearer' }, (c) => ({\n Authorization: `Bearer ${c.token}`,\n }))\n .with({ type: 'basic' }, (c) => ({\n Authorization: `Basic ${Buffer.from(`${c.username}:${c.password}`).toString('base64')}`,\n }))\n .with({ type: 'api-key' }, (c) => ({\n [c.headerName]: c.key,\n }))\n .with({ type: 'custom' }, (c) => ({ ...c.headers }))\n .exhaustive()\n}\n","/**\n * Factory for a header-resolver function that reads credentials from `ctx.auth`.\n *\n * @module\n */\n\nimport type { Context } from '@/context/types.js'\n\nimport { buildAuthHeaders } from '../http/build-auth-headers.js'\nimport type { AuthContext } from './types.js'\n\n/**\n * Create a function that resolves auth credentials from `ctx.auth` into HTTP headers.\n *\n * The returned function reads `ctx.auth.credential()` and converts the credential\n * into the appropriate header format using `buildAuthHeaders()`. Returns an empty\n * record when no auth middleware is present or no credential exists.\n *\n * @returns A function that takes a Context and returns auth headers.\n */\nexport function createAuthHeaders(): (ctx: Context) => Readonly<Record<string, string>> {\n return function resolveHeaders(ctx: Context): Readonly<Record<string, string>> {\n if (!('auth' in ctx)) {\n return {}\n }\n\n const authCtx = (ctx as Context & { readonly auth: AuthContext }).auth\n const credential = authCtx.credential()\n\n if (credential === null) {\n return {}\n }\n\n return buildAuthHeaders(credential)\n }\n}\n","/**\n * Enforcement gate middleware that requires authentication.\n *\n * @module\n */\n\nimport { middleware } from '@/middleware.js'\nimport type { Middleware } from '@/types.js'\n\nconst DEFAULT_MESSAGE = 'Authentication required.'\n\n/**\n * Options for {@link createAuthRequire}.\n */\nexport interface AuthRequireOptions {\n readonly message?: string\n}\n\n/**\n * Create an enforcement middleware that gates on authentication.\n *\n * When `ctx.auth.authenticated()` returns true, the middleware calls\n * `next()`. When not authenticated, it calls `ctx.fail()` with the\n * provided (or default) message. When `ctx.auth` is absent (auth\n * middleware not configured), it calls `ctx.fail()` with an\n * `AUTH_MIDDLEWARE_MISSING` code.\n *\n * @param options - Optional configuration for the require gate.\n * @returns A Middleware that enforces authentication.\n */\nexport function createAuthRequire(options?: AuthRequireOptions): Middleware {\n const message = resolveMessage(options)\n\n return middleware((ctx, next) => {\n if (!hasProperty(ctx, 'auth')) {\n ctx.fail('auth.require() must run after auth() middleware', {\n code: 'AUTH_MIDDLEWARE_MISSING',\n })\n }\n\n if (!ctx.auth.authenticated()) {\n ctx.fail(message, { code: 'AUTH_REQUIRED' })\n }\n\n return next()\n })\n}\n\n// ---------------------------------------------------------------------------\n// Private helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Runtime property check that avoids TypeScript's `in` narrowing.\n *\n * The `Context` interface declares `auth` via module augmentation,\n * so `!('auth' in ctx)` narrows to `never`. This function uses an\n * `unknown` cast to bypass the narrowing and perform a pure runtime\n * check.\n *\n * @private\n * @param obj - The object to inspect.\n * @param key - The property name to check.\n * @returns True when the property exists on the object.\n */\nfunction hasProperty(obj: unknown, key: string): boolean {\n return typeof obj === 'object' && obj !== null && key in obj\n}\n\n/**\n * Resolve the failure message from optional require options.\n *\n * @private\n * @param options - Optional require gate options.\n * @returns The resolved message string.\n */\nfunction resolveMessage(options: AuthRequireOptions | undefined): string {\n if (options !== undefined && options.message !== undefined) {\n return options.message\n }\n\n return DEFAULT_MESSAGE\n}\n","/**\n * Auth middleware factory with strategy builder functions.\n *\n * Decorates `ctx.auth` with functions to resolve credentials on demand\n * and run interactive authentication.\n *\n * @module\n */\n\nimport { join } from 'node:path'\n\nimport { decorateContext } from '@/context/decorate.js'\nimport type { Context } from '@/context/types.js'\nimport { middleware } from '@/middleware.js'\nimport type { Middleware } from '@/types.js'\n\nimport { withDefault } from './chain.js'\nimport { DEFAULT_AUTH_FILENAME, deriveTokenVar } from './constants.js'\nimport { createAuthContext } from './context.js'\nimport { createAuthHeaders } from './headers.js'\nimport { createAuthRequire } from './require.js'\nimport type { AuthRequireOptions } from './require.js'\nimport { resolveFromDotenv } from './strategies/dotenv.js'\nimport { resolveFromEnv } from './strategies/env.js'\nimport { resolveFromFile } from './strategies/file.js'\nimport type {\n AuthCredential,\n AuthOptions,\n CustomSourceConfig,\n CustomStrategyFn,\n DeviceCodeSourceConfig,\n DeviceCodeStrategyOptions,\n DotenvSourceConfig,\n DotenvStrategyOptions,\n EnvSourceConfig,\n EnvStrategyOptions,\n FileSourceConfig,\n FileStrategyOptions,\n OAuthSourceConfig,\n OAuthStrategyOptions,\n StrategyConfig,\n TokenSourceConfig,\n TokenStrategyOptions,\n} from './types.js'\n\n/**\n * Auth factory interface — callable as a middleware factory and as a\n * namespace for strategy builder functions.\n */\nexport interface AuthFactory {\n (options: AuthOptions): Middleware\n readonly env: (options?: EnvStrategyOptions) => EnvSourceConfig\n readonly dotenv: (options?: DotenvStrategyOptions) => DotenvSourceConfig\n readonly file: (options?: FileStrategyOptions) => FileSourceConfig\n readonly oauth: (options: OAuthStrategyOptions) => OAuthSourceConfig\n readonly deviceCode: (options: DeviceCodeStrategyOptions) => DeviceCodeSourceConfig\n readonly token: (options?: TokenStrategyOptions) => TokenSourceConfig\n readonly apiKey: (options?: TokenStrategyOptions) => TokenSourceConfig\n readonly custom: (fn: CustomStrategyFn) => CustomSourceConfig\n readonly headers: () => (ctx: Context) => Readonly<Record<string, string>>\n readonly require: (options?: AuthRequireOptions) => Middleware\n}\n\n/**\n * Create an auth middleware that decorates `ctx.auth`.\n *\n * No credential data is stored on the context. `ctx.auth.credential()`\n * resolves passively from three sources on every call:\n * 1. File — `~/.cli-name/auth.json`\n * 2. Dotenv — `.env` file (when configured)\n * 3. Env — `CLI_NAME_TOKEN`\n *\n * Interactive strategies (OAuth, device-code, token, custom) only run when the\n * command handler explicitly calls `ctx.auth.login()`.\n *\n * @param options - Auth middleware configuration.\n * @returns A Middleware that decorates ctx.auth.\n */\nfunction createAuth(options: AuthOptions): Middleware {\n const { strategies } = options\n\n return middleware((ctx, next) => {\n const cliName = ctx.meta.name\n\n const authContext = createAuthContext({\n cliName,\n prompts: ctx.prompts,\n resolveCredential: () => resolveStoredCredential(cliName, strategies),\n strategies,\n })\n\n decorateContext(ctx, 'auth', authContext)\n\n return next()\n })\n}\n\n/**\n * Auth middleware factory with strategy builder methods.\n *\n * Use as `auth({ strategies: [...] })` to create middleware, or use\n * the builder methods (`auth.env()`, `auth.oauth()`, etc.) to construct\n * strategy configs with a cleaner API.\n */\nexport const auth: AuthFactory = Object.assign(createAuth, {\n apiKey: buildToken,\n custom: buildCustom,\n deviceCode: buildDeviceCode,\n dotenv: buildDotenv,\n env: buildEnv,\n file: buildFile,\n headers: createAuthHeaders,\n oauth: buildOAuth,\n require: createAuthRequire,\n token: buildToken,\n})\n\n// ---------------------------------------------------------------------------\n// Strategy builders\n// ---------------------------------------------------------------------------\n\n/**\n * Build an env strategy config.\n *\n * @private\n * @param options - Optional env strategy options.\n * @returns An EnvSourceConfig with `source: 'env'`.\n */\nfunction buildEnv(options?: EnvStrategyOptions): EnvSourceConfig {\n return { ...options, source: 'env' as const }\n}\n\n/**\n * Build a dotenv strategy config.\n *\n * @private\n * @param options - Optional dotenv strategy options.\n * @returns A DotenvSourceConfig with `source: 'dotenv'`.\n */\nfunction buildDotenv(options?: DotenvStrategyOptions): DotenvSourceConfig {\n return { ...options, source: 'dotenv' as const }\n}\n\n/**\n * Build a file strategy config.\n *\n * @private\n * @param options - Optional file strategy options.\n * @returns A FileSourceConfig with `source: 'file'`.\n */\nfunction buildFile(options?: FileStrategyOptions): FileSourceConfig {\n return { ...options, source: 'file' as const }\n}\n\n/**\n * Build an OAuth strategy config.\n *\n * @private\n * @param options - OAuth strategy options (clientId, authUrl, tokenUrl required).\n * @returns An OAuthSourceConfig with `source: 'oauth'`.\n */\nfunction buildOAuth(options: OAuthStrategyOptions): OAuthSourceConfig {\n return { ...options, source: 'oauth' as const }\n}\n\n/**\n * Build a device code strategy config.\n *\n * @private\n * @param options - Device code strategy options (clientId, deviceAuthUrl, tokenUrl required).\n * @returns A DeviceCodeSourceConfig with `source: 'device-code'`.\n */\nfunction buildDeviceCode(options: DeviceCodeStrategyOptions): DeviceCodeSourceConfig {\n return { ...options, source: 'device-code' as const }\n}\n\n/**\n * Build a token strategy config.\n *\n * Prompts the user for a token interactively. Aliased as `auth.apiKey()`.\n *\n * @private\n * @param options - Optional token strategy options.\n * @returns A TokenSourceConfig with `source: 'token'`.\n */\nfunction buildToken(options?: TokenStrategyOptions): TokenSourceConfig {\n return { ...options, source: 'token' as const }\n}\n\n/**\n * Build a custom strategy config from a strategy function.\n *\n * @private\n * @param fn - The custom strategy function.\n * @returns A CustomSourceConfig with `source: 'custom'`.\n */\nfunction buildCustom(fn: CustomStrategyFn): CustomSourceConfig {\n return { resolver: fn, source: 'custom' as const }\n}\n\n// ---------------------------------------------------------------------------\n// Private helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Attempt to resolve a credential from stored (non-interactive) sources.\n *\n * Checks the file store first, then dotenv, then falls back to the\n * environment variable. Scans the strategy list for `file`, `dotenv`,\n * and `env` source configs to respect user-configured overrides\n * (e.g. a custom `tokenVar`, `dirName`, or dotenv `path`).\n *\n * @private\n * @param cliName - The CLI name, used to derive paths and env var names.\n * @param strategies - The configured strategy list for extracting overrides.\n * @returns The resolved credential, or null.\n */\nfunction resolveStoredCredential(\n cliName: string,\n strategies: readonly StrategyConfig[]\n): AuthCredential | null {\n const fileConfig = findStrategyBySource(strategies, 'file')\n const dotenvConfig = findStrategyBySource(strategies, 'dotenv')\n const envConfig = findStrategyBySource(strategies, 'env')\n const defaultTokenVar = deriveTokenVar(cliName)\n\n const fromFile = resolveFromFile({\n dirName: withDefault(extractProp(fileConfig, 'dirName'), `.${cliName}`),\n filename: withDefault(extractProp(fileConfig, 'filename'), DEFAULT_AUTH_FILENAME),\n })\n\n if (fromFile) {\n return fromFile\n }\n\n if (dotenvConfig !== undefined) {\n const fromDotenv = resolveFromDotenv({\n path: withDefault(extractProp(dotenvConfig, 'path'), join(process.cwd(), '.env')),\n tokenVar: withDefault(extractProp(dotenvConfig, 'tokenVar'), defaultTokenVar),\n })\n\n if (fromDotenv) {\n return fromDotenv\n }\n }\n\n return resolveFromEnv({\n tokenVar: withDefault(extractProp(envConfig, 'tokenVar'), defaultTokenVar),\n })\n}\n\n/**\n * Find the first strategy config matching a given source type.\n *\n * @private\n * @param strategies - The strategy config list.\n * @param source - The source type to find.\n * @returns The matching config, or undefined.\n */\nfunction findStrategyBySource<TSource extends StrategyConfig['source']>(\n strategies: readonly StrategyConfig[],\n source: TSource\n): Extract<StrategyConfig, { readonly source: TSource }> | undefined {\n return strategies.find(\n (r): r is Extract<StrategyConfig, { readonly source: TSource }> => r.source === source\n )\n}\n\n/**\n * Safely extract a property from an optional config object.\n *\n * Returns the property value when the config is defined, or undefined\n * when the config itself is undefined.\n *\n * @private\n * @param config - The config object, or undefined.\n * @param key - The property key to extract.\n * @returns The property value, or undefined.\n */\nfunction extractProp<TConfig extends object, TKey extends keyof TConfig>(\n config: TConfig | undefined,\n key: TKey\n): TConfig[TKey] | undefined {\n if (config === undefined) {\n return undefined\n }\n\n return config[key]\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAGA,MAAa,wBAAwB;;;;AAKrC,MAAa,mBAAmB;;;;AAUhC,MAAa,8BAA8B;;;;AAK3C,MAAa,wBAAwB;;;;AAKrC,MAAa,oCAAoC;;;;AAKjD,MAAa,8BAA8B;;;;;;;;;;AAW3C,SAAgB,eAAe,SAAyB;AACtD,QAAO,GAAG,QAAQ,WAAW,KAAK,IAAI,CAAC,aAAa,GAAG;;;;;;;;;;;;;;AC/BzD,SAAgB,aAAa,OAAmD;AAC9E,KAAI,CAAC,MACH,QAAO;AAGT,KAAI,MAAM,MAAM,KAAK,GACnB,QAAO;AAGT,QAAO;;;;;;;;AAST,SAAgB,uBAAuB,OAAiC;AACtE,QAAO;EAAE;EAAO,MAAM;EAAU;;;;;;;;;;;;;;AAelC,eAAsB,gBACpB,KACA,QACA,QAC0B;CAC1B,MAAM,CAAC,YAAY,YAAY,MAAM,mBACnC,MAAM,KAAK;EACT,MAAM,OAAO,UAAU;EACvB,SAAS,EAAE,gBAAgB,qCAAqC;EAChE,QAAQ;EACR;EACD,CAAC,CACH;AAED,KAAI,WACF,QAAO;AAGT,QAAO;;;;;;;;;;;;AC7CT,MAAM,kBAAkB;CACtB;CACA;CACA;CACA;CACD,CAAC,KAAK,KAAK;;;;;;;;;;AA4CZ,SAAgB,iBAAiC;CAC/C,MAAM,QAAkD,EAAE,SAAS,MAAM;AAMzE,QAAO;EACL,SALc,IAAI,SAAY,YAAY;AAC1C,SAAM,UAAU;IAChB;EAIA,UAAU,UAAmB;AAC3B,OAAI,MAAM,QACR,OAAM,QAAQ,MAAM;;EAGzB;;;;;;;;;;;;;;;AAgBH,SAAgB,cAAc,IAAqB;CACjD,MAAM,QAAsD,EAAE,IAAI,MAAM;AAMxE,QAAO;EACL,aAAmB;AACjB,OAAI,MAAM,OAAO,MAAM;AACrB,iBAAa,MAAM,GAAG;AACtB,UAAM,KAAK;;;EAGf,SAXc,IAAI,SAAe,YAAY;AAC7C,SAAM,KAAK,WAAW,SAAS,GAAG;IAClC;EAUD;;;;;;;;;;;AAYH,SAAgB,iBAAiB,QAAgB,SAA4B;AAC3E,QAAO,GAAG,eAAe,WAAmB;AAC1C,UAAQ,IAAI,OAAO;AACnB,SAAO,GAAG,eAAe;AACvB,WAAQ,OAAO,OAAO;IACtB;GACF;;;;;;;;;;;;AAaJ,SAAgB,cAAc,QAAgB,SAA4B;AACxE,QAAO,OAAO;AACd,OAAM,KAAK,UAAU,WAAW,OAAO,SAAS,CAAC;AACjD,SAAQ,OAAO;;;;;;;AAQjB,SAAgB,gBAAgB,KAA2B;AACzD,KAAI,UAAU,KAAK,EAAE,gBAAgB,aAAa,CAAC;AACnD,KAAI,IAAI,gBAAgB;;;;;;;;;;;AAY1B,SAAgB,gBAAgB,KAAsB;CACpD,MAAM,CAAC,OAAO,UAAU,cAAc,IAAI,IAAI,IAAI,CAAC;AAEnD,KAAI,SAAS,CAAC,OACZ,QAAO;AAGT,KAAI,OAAO,aAAa,SACtB,QAAO;AAGT,KAAI,OAAO,aAAa,QACtB,QAAO;AAGT,QAAO,eAAe,OAAO,SAAS;;;;;;;;;;;;;;;AAgBxC,SAAgB,YAAY,KAAmB;AAC7C,KAAI,CAAC,UAAU,IAAI,CACjB;CAGF,MAAM,EAAE,SAAS,SAAS,MAAM,UAAU,CAAC,CACxC,KAAK,iBAAiB;EAAE,MAAM,CAAC,IAAI;EAAE,SAAS;EAAQ,EAAE,CACxD,KAAK,gBAAgB;EAAE,MAAM;GAAC;GAAM;GAAS;GAAI,cAAc,IAAI;GAAC;EAAE,SAAS;EAAO,EAAE,CACxF,iBAAiB;EAAE,MAAM,CAAC,IAAI;EAAE,SAAS;EAAY,EAAE;AAE5C,UAAS,SAAS,KAAK,CAC/B,GAAG,eAAe,KAAA,EAAU;;;;;;;;;;;AAYpC,SAAgB,iBAAiB,SAGX;CACpB,MAAM,eAAe,gBAA+B;CAIpD,MAAM,0BAAU,IAAI,KAAa;CAEjC,MAAM,SAAS,aAAa,QAAQ,UAAU;AAE9C,kBAAiB,QAAQ,QAAQ;AAEjC,QAAO,GAAG,eAAe;AACvB,gBAAc,QAAQ,QAAQ;AAC9B,eAAa,QAAQ,KAAK;GAC1B;AAEF,QAAO,OAAO,QAAQ,MAAM,mBAAmB;EAC7C,MAAM,OAAO,OAAO,SAAS;AAE7B,MAAI,SAAS,QAAQ,OAAO,SAAS,UAAU;AAC7C,iBAAc,QAAQ,QAAQ;AAC9B,gBAAa,QAAQ,KAAK;AAC1B;;AAGF,eAAa,QAAQ,KAAK,KAAK;GAC/B;AAEF,QAAO;EACL,MAAM,aAAa;EACnB;EACA;EACD;;;;;;;;;;;;AAiBH,SAAS,UAAU,KAAsB;CACvC,MAAM,CAAC,OAAO,UAAU,cAAc,IAAI,IAAI,IAAI,CAAC;AAEnD,KAAI,SAAS,CAAC,OACZ,QAAO;AAGT,QAAO,OAAO,aAAa,YAAY,OAAO,aAAa;;;;;;;;;;;;AAa7D,SAAS,eAAe,UAA2B;AACjD,QAAO,aAAa,eAAe,aAAa,WAAW,aAAa;;;;;;;;;;;;;AAc1E,SAAS,cAAc,KAAqB;AAC1C,QAAO,IAAI,WAAW,YAAY,MAAM;;;;;;;;;;;;;;;;ACpS1C,MAAM,sBAAsB;;;;AAK5B,MAAM,yBAAyB;;;;;;;;;;;;AAa/B,eAAsB,sBAAsB,SAST;AACjC,KAAI,CAAC,gBAAgB,QAAQ,cAAc,CACzC,QAAO;AAGT,KAAI,CAAC,gBAAgB,QAAQ,SAAS,CACpC,QAAO;CAGT,MAAM,WAAW,KAAK,KAAK,GAAG,QAAQ;CACtC,MAAM,SAAS,YAAY,QAAQ,QAAQ,QAAQ;CAEnD,MAAM,eAAe,MAAM,kBAAkB;EAC3C,UAAU,QAAQ;EAClB,eAAe,QAAQ;EACvB,QAAQ,QAAQ;EAChB;EACD,CAAC;AAEF,KAAI,CAAC,aACH,QAAO;AAGT,OAAM,gBAAgB,QAAQ,SAAS,aAAa,iBAAiB,aAAa,SAAS;AAE3F,KAAI,QAAQ,uBAAuB,MACjC,aAAY,aAAa,gBAAgB;CAG3C,MAAM,WAAW,gBAAgB,aAAa,UAAU,QAAQ,aAAa;AAE7E,QAAO,aAAa;EAClB,UAAU,QAAQ;EAClB;EACA,YAAY,aAAa;EACzB;EACA;EACA,UAAU,QAAQ;EACnB,CAAC;;;;;;;;;AA8BJ,eAAe,kBAAkB,SAKM;CACrC,MAAM,OAAO,IAAI,gBAAgB,EAAE,WAAW,QAAQ,UAAU,CAAC;AAEjE,KAAI,QAAQ,OAAO,SAAS,EAC1B,MAAK,IAAI,SAAS,QAAQ,OAAO,KAAK,IAAI,CAAC;CAG7C,MAAM,WAAW,MAAM,gBAAgB,QAAQ,eAAe,MAAM,QAAQ,OAAO;AAEnF,KAAI,CAAC,SACH,QAAO;AAGT,KAAI,CAAC,SAAS,GACZ,QAAO;CAGT,MAAM,CAAC,YAAY,QAAQ,MAAM,mBAAqC,SAAS,MAAM,CAAC;AAEtF,KAAI,WACF,QAAO;AAGT,QAAO,wBAAwB,KAAK;;;;;;;;;AAUtC,SAAS,wBAAwB,MAA0C;AACzE,KAAI,CAAC,cAAc,KAAK,CACtB,QAAO;AAGT,KAAI,OAAO,KAAK,gBAAgB,YAAY,KAAK,gBAAgB,GAC/D,QAAO;AAGT,KAAI,OAAO,KAAK,cAAc,YAAY,KAAK,cAAc,GAC3D,QAAO;AAGT,KAAI,OAAO,KAAK,qBAAqB,YAAY,KAAK,qBAAqB,GACzE,QAAO;CAGT,MAAM,WAAW,sBAAsB,KAAK,SAAS;AAErD,QAAO;EACL,YAAY,KAAK;EACjB;EACA,UAAU,KAAK;EACf,iBAAiB,KAAK;EACvB;;;;;;;;;;;;;AAcH,eAAe,gBACb,SACA,iBACA,UACe;AAEf,OAAM,mBACJ,QAAQ,KAAK;EACX,cAAc;EACd,SAAS,QAAQ,gBAAgB,mBAAmB,SAAS;EAC9D,CAAC,CACH;;;;;;;;;;AAWH,SAAS,gBAAgB,gBAA+B,gBAAgC;AACtF,KAAI,mBAAmB,KACrB,QAAO;AAGT,QAAO;;;;;;;;;;;;;;;AAgBT,eAAe,aAAa,SAOO;AACjC,KAAI,KAAK,KAAK,IAAI,QAAQ,SACxB,QAAO;AAGT,OAAM,MAAM,QAAQ,SAAS;AAE7B,KAAI,KAAK,KAAK,IAAI,QAAQ,SACxB,QAAO;AAUT,QAAO,MAPQ,MAAM,aAAa;EAChC,UAAU,QAAQ;EAClB,YAAY,QAAQ;EACpB,QAAQ,QAAQ;EAChB,UAAU,QAAQ;EACnB,CAAC,CAEkB,CACjB,KAAK,EAAE,QAAQ,WAAW,GAAG,MAAM,EAAE,WAAW,CAChD,KAAK,EAAE,QAAQ,WAAW,QAAQ,aAAa,QAAQ,CAAC,CACxD,KAAK,EAAE,QAAQ,aAAa,QAC3B,aAAa;EACX,GAAG;EACH,UAAU,QAAQ,WAAW;EAC9B,CAAC,CACH,CACA,KAAK,EAAE,QAAQ,UAAU,QAAQ,KAAK,CACtC,KAAK,EAAE,QAAQ,WAAW,QAAQ,KAAK,CACvC,KAAK,EAAE,QAAQ,SAAS,QAAQ,KAAK,CACrC,YAAY;;;;;;;;;AAUjB,SAAS,sBAAsB,OAA+B;AAC5D,KAAI,OAAO,UAAU,YAAY,CAAC,OAAO,SAAS,MAAM,IAAI,SAAS,EACnE,QAAO;AAGT,QAAO,KAAK,IAAI,KAAM,KAAK,IAAI,QAAQ,KAAM,IAAO,CAAC;;;;;;;;;AAUvD,SAAS,MAAM,IAA2B;AACxC,QAAO,IAAI,SAAS,YAAY;AAC9B,aAAW,SAAS,GAAG;GACvB;;;;;;;;;AA2BJ,eAAe,aAAa,SAKI;CAC9B,MAAM,OAAO,IAAI,gBAAgB;EAC/B,WAAW,QAAQ;EACnB,aAAa,QAAQ;EACrB,YAAY;EACb,CAAC;CAEF,MAAM,WAAW,MAAM,gBAAgB,QAAQ,UAAU,MAAM,QAAQ,OAAO;AAE9E,KAAI,CAAC,SACH,QAAO,EAAE,QAAQ,SAAS;CAG5B,MAAM,CAAC,YAAY,QAAQ,MAAM,mBAAqC,SAAS,MAAM,CAAC;AAEtF,KAAI,WACF,QAAO,EAAE,QAAQ,SAAS;AAG5B,KAAI,CAAC,cAAc,KAAK,CACtB,QAAO,EAAE,QAAQ,SAAS;AAG5B,KAAI,SAAS,MAAM,OAAO,KAAK,iBAAiB,YAAY,KAAK,iBAAiB,IAAI;AACpF,MAAI,OAAO,KAAK,eAAe,YAAY,KAAK,WAAW,aAAa,KAAK,SAC3E,QAAO,EAAE,QAAQ,SAAS;AAG5B,SAAO;GAAE,YAAY,uBAAuB,KAAK,aAAa;GAAE,QAAQ;GAAW;;AAGrF,KAAI,OAAO,KAAK,UAAU,SACxB,QAAO,EAAE,QAAQ,SAAS;AAG5B,QAAO,MAAM,KAAK,MAAM,CACrB,KAAK,gCAAoD,EAAE,QAAQ,WAAW,EAAE,CAChF,KAAK,oBAAwC,EAAE,QAAQ,aAAa,EAAE,CACtE,KAAK,wBAA4C,EAAE,QAAQ,WAAW,EAAE,CACxE,KAAK,wBAA4C,EAAE,QAAQ,UAAU,EAAE,CACvE,iBAAqC,EAAE,QAAQ,SAAS,EAAE;;;;;;;;;;;;;;;;AChW/D,SAAgB,kBAAkB,SAGR;CACxB,MAAM,CAAC,WAAW,WAAWA,gBAAc,aAAa,QAAQ,MAAM,OAAO,CAAC;AAE9E,KAAI,aAAa,YAAY,KAC3B,QAAO;CAIT,MAAM,QADS,MAAM,QAAQ,CACR,QAAQ;AAE7B,KAAI,CAAC,aAAa,MAAM,CACtB,QAAO;AAGT,QAAO,uBAAuB,MAAM;;;;;;;;;;AC5BtC,SAAgB,eAAe,SAA+D;CAC5F,MAAM,QAAQ,QAAQ,IAAI,QAAQ;AAElC,KAAI,CAAC,aAAa,MAAM,CACtB,QAAO;AAGT,QAAO,uBAAuB,MAAM;;;;;;;ACXtC,MAAa,yBAAyB,EAAE,OAAO;CAC7C,OAAO,EAAE,QAAQ,CAAC,IAAI,EAAE;CACxB,MAAM,EAAE,QAAQ,SAAS;CAC1B,CAAC;;;;AAKF,MAAa,wBAAwB,EAAE,OAAO;CAC5C,UAAU,EAAE,QAAQ,CAAC,IAAI,EAAE;CAC3B,MAAM,EAAE,QAAQ,QAAQ;CACxB,UAAU,EAAE,QAAQ,CAAC,IAAI,EAAE;CAC5B,CAAC;;;;AAKF,MAAa,yBAAyB,EAAE,OAAO;CAC7C,YAAY,EAAE,QAAQ,CAAC,IAAI,EAAE;CAC7B,KAAK,EAAE,QAAQ,CAAC,IAAI,EAAE;CACtB,MAAM,EAAE,QAAQ,UAAU;CAC3B,CAAC;;;;AAKF,MAAa,yBAAyB,EAAE,OAAO;CAC7C,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,QAAQ,CAAC;CACzC,MAAM,EAAE,QAAQ,SAAS;CAC1B,CAAC;;;;;AAMF,MAAa,uBAAuB,EAAE,mBAAmB,QAAQ;CAC/D;CACA;CACA;CACA;CACD,CAAC;;;;;;;;;;;;;AC9BF,SAAgB,gBAAgB,SAGN;CAExB,MAAM,OADQ,YAAY,EAAE,SAAS,QAAQ,SAAS,CAAC,CACpC,KAAK,QAAQ,SAAS;AAEzC,KAAI,SAAS,KACX,QAAO;CAGT,MAAM,SAAS,qBAAqB,UAAU,KAAK;AAEnD,KAAI,CAAC,OAAO,QACV,QAAO;AAGT,QAAO,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;ACQhB,eAAsB,iBAAiB,SAQJ;AACjC,KAAI,CAAC,gBAAgB,QAAQ,QAAQ,CACnC,QAAO;AAGT,KAAI,CAAC,gBAAgB,QAAQ,SAAS,CACpC,QAAO;CAGT,MAAM,eAAe,sBAAsB;CAC3C,MAAM,gBAAgB,oBAAoB,aAAa;CACvD,MAAM,QAAQ,YAAY,GAAG,CAAC,SAAS,MAAM;CAE7C,MAAM,UAAU,cAAc,QAAQ,QAAQ;CAC9C,MAAM,eAAe,gBAA+B;CAEpD,MAAM,SAAS,iBAAiB;EAC9B,YAAY,KAAK,QAAQ;AACvB,kBAAe,KAAK,KAAK,QAAQ,cAAc,OAAO,aAAa,QAAQ;;EAE7E,MAAM,QAAQ;EACf,CAAC;CAEF,MAAM,aAAa,MAAM,OAAO;AAEhC,KAAI,eAAe,MAAM;AACvB,UAAQ,OAAO;AACf,SAAO;;CAGT,MAAM,cAAc,oBAAoB,OAAO,WAAW,GAAG,QAAQ;AAWrE,aAToB,aAAa;EAC/B,SAAS,QAAQ;EACjB,UAAU,QAAQ;EAClB;EACA;EACA,QAAQ,QAAQ;EAChB;EACD,CAAC,CAEsB;CAExB,MAAM,iBAAiB,QAAQ,QAAQ,WAAiB;AACtD,eAAa,QAAQ,KAAK;AAC1B,gBAAc,OAAO,QAAQ,OAAO,QAAQ;AAC5C,SAAO;GACP;CAEF,MAAM,OAAO,MAAM,QAAQ,KAAK,CAAC,aAAa,SAAS,eAAe,CAAC;AAEvE,SAAQ,OAAO;AAEf,KAAI,CAAC,MAAM;AACT,gBAAc,OAAO,QAAQ,OAAO,QAAQ;AAC5C,SAAO;;AAGT,eAAc,OAAO,QAAQ,OAAO,QAAQ;AAU5C,QARc,MAAM,qBAAqB;EACvC,UAAU,QAAQ;EAClB;EACA;EACA;EACA,UAAU,QAAQ;EACnB,CAAC;;;;;;;;AAeJ,SAAS,uBAA+B;AACtC,QAAO,YAAY,GAAG,CAAC,SAAS,YAAY;;;;;;;;;AAU9C,SAAS,oBAAoB,UAA0B;AACrD,QAAO,WAAW,SAAS,CAAC,OAAO,SAAS,CAAC,OAAO,YAAY;;;;;;;;;AAUlE,SAAS,aAAa,SAOX;CACT,MAAM,MAAM,IAAI,IAAI,QAAQ,QAAQ;AACpC,KAAI,aAAa,IAAI,iBAAiB,OAAO;AAC7C,KAAI,aAAa,IAAI,aAAa,QAAQ,SAAS;AACnD,KAAI,aAAa,IAAI,gBAAgB,QAAQ,YAAY;AACzD,KAAI,aAAa,IAAI,kBAAkB,QAAQ,cAAc;AAC7D,KAAI,aAAa,IAAI,yBAAyB,OAAO;AACrD,KAAI,aAAa,IAAI,SAAS,QAAQ,MAAM;AAE5C,KAAI,QAAQ,OAAO,SAAS,EAC1B,KAAI,aAAa,IAAI,SAAS,QAAQ,OAAO,KAAK,IAAI,CAAC;AAGzD,QAAO,IAAI,UAAU;;;;;;;;;;;;;;;;AAiBvB,SAAS,eACP,KACA,KACA,cACA,eACA,SACM;CACN,MAAM,SAAS,mBAAmB,IAAI,KAAK,cAAc,cAAc;AAEvE,KAAI,CAAC,OAAO,IAAI;AACd,MAAI,UAAU,IAAI;AAClB,MAAI,KAAK;AAET,MAAI,OAAO,aACT,SAAQ,KAAK;AAGf;;AAGF,iBAAgB,IAAI;AACpB,SAAQ,OAAO,KAAK;;;;;;;;;;;;;;;;;AA2BtB,SAAS,mBACP,QACA,cACA,eACmB;CACnB,MAAM,MAAM,IAAI,IAAI,UAAU,KAAK,mBAAmB;AAEtD,KAAI,IAAI,aAAa,aACnB,QAAO;EAAE,cAAc;EAAO,IAAI;EAAO;AAK3C,KAFc,IAAI,aAAa,IAAI,QAAQ,KAE7B,cACZ,QAAO;EAAE,cAAc;EAAO,IAAI;EAAO;AAK3C,KAFc,IAAI,aAAa,IAAI,QAAQ,CAGzC,QAAO;EAAE,cAAc;EAAM,IAAI;EAAO;CAG1C,MAAM,OAAO,IAAI,aAAa,IAAI,OAAO;AAEzC,KAAI,CAAC,KACH,QAAO;EAAE,cAAc;EAAO,IAAI;EAAO;AAG3C,QAAO;EAAE;EAAM,IAAI;EAAM;;;;;;;;;;;;;AAc3B,eAAe,qBAAqB,SAMD;CACjC,MAAM,OAAO,IAAI,gBAAgB;EAC/B,WAAW,QAAQ;EACnB,MAAM,QAAQ;EACd,eAAe,QAAQ;EACvB,YAAY;EACZ,cAAc,QAAQ;EACvB,CAAC;CAEF,MAAM,WAAW,MAAM,gBAAgB,QAAQ,UAAU,KAAK;AAE9D,KAAI,CAAC,SACH,QAAO;AAGT,KAAI,CAAC,SAAS,GACZ,QAAO;CAGT,MAAM,CAAC,YAAY,QAAQ,MAAM,mBAAqC,SAAS,MAAM,CAAC;AAEtF,KAAI,WACF,QAAO;AAGT,KAAI,CAAC,cAAc,KAAK,CACtB,QAAO;AAGT,KAAI,OAAO,KAAK,iBAAiB,YAAY,KAAK,iBAAiB,GACjE,QAAO;AAGT,KAAI,OAAO,KAAK,eAAe,YAAY,KAAK,WAAW,aAAa,KAAK,SAC3E,QAAO;AAGT,QAAO,uBAAuB,KAAK,aAAa;;;;;;;;;;;;;;;AChTlD,eAAsB,iBAAiB,SAGJ;CACjC,MAAM,CAAC,aAAa,SAAS,MAAM,mBACjC,QAAQ,QAAQ,SAAS,EAAE,SAAS,QAAQ,SAAS,CAAC,CACvD;AAED,KAAI,YACF,QAAO;AAGT,KAAI,CAAC,aAAa,MAAM,CACtB,QAAO;AAGT,QAAO,uBAAuB,MAAM;;;;ACXtC,MAAM,yBAAyB;;;;;;;;;;;AAY/B,eAAsB,iBAAiB,SAIJ;CACjC,MAAM,kBAAkB,eAAe,QAAQ,QAAQ;AAEvD,QAAO,cAAc,QAAQ,YAAY,GAAG,iBAAiB,QAAQ;;;;;;;;;AAUvE,SAAgB,YAAe,OAAsB,UAAgB;AACnE,KAAI,UAAU,KAAA,EACZ,QAAO;AAET,QAAO;;;;;;;;;;;;AAiBT,eAAe,cACb,SACA,OACA,iBACA,SAIgC;AAChC,KAAI,SAAS,QAAQ,OACnB,QAAO;CAGT,MAAM,SAAS,QAAQ;AAEvB,KAAI,WAAW,KAAA,EACb,QAAO;CAGT,MAAM,aAAa,MAAM,iBAAiB,QAAQ,iBAAiB,QAAQ;AAE3E,KAAI,WACF,QAAO;AAGT,QAAO,cAAc,SAAS,QAAQ,GAAG,iBAAiB,QAAQ;;;;;;;;;;;AAYpE,eAAe,iBACb,QACA,iBACA,SAIgC;AAChC,QAAOC,QAAM,OAAO,CACjB,KAAK,EAAE,QAAQ,OAAO,GAAG,MACxB,eAAe,EACb,UAAU,YAAY,EAAE,UAAU,gBAAgB,EACnD,CAAC,CACH,CACA,KAAK,EAAE,QAAQ,UAAU,GAAG,MAC3B,kBAAkB;EAChB,MAAM,YAAY,EAAE,MAAM,KAAK,QAAQ,KAAK,EAAE,OAAO,CAAC;EACtD,UAAU,YAAY,EAAE,UAAU,gBAAgB;EACnD,CAAC,CACH,CACA,KAAK,EAAE,QAAQ,QAAQ,GAAG,MACzB,gBAAgB;EACd,SAAS,YAAY,EAAE,SAAS,IAAI,QAAQ,UAAU;EACtD,UAAU,YAAY,EAAE,UAAU,sBAAsB;EACzD,CAAC,CACH,CACA,KACC,EAAE,QAAQ,SAAS,GAClB,MACC,iBAAiB;EACf,SAAS,EAAE;EACX,cAAc,YAAY,EAAE,cAAc,4BAA4B;EACtE,UAAU,EAAE;EACZ,MAAM,YAAY,EAAE,MAAA,EAAyB;EAC7C,QAAQ,YAAY,EAAE,QAAQ,EAAE,CAAC;EACjC,SAAS,YAAY,EAAE,SAAS,sBAAsB;EACtD,UAAU,EAAE;EACb,CAAC,CACL,CACA,KACC,EAAE,QAAQ,eAAe,GACxB,MACC,sBAAsB;EACpB,UAAU,EAAE;EACZ,eAAe,EAAE;EACjB,oBAAoB,YAAY,EAAE,aAAa,KAAK;EACpD,cAAc,YAAY,EAAE,cAAc,kCAAkC;EAC5E,SAAS,QAAQ;EACjB,QAAQ,YAAY,EAAE,QAAQ,EAAE,CAAC;EACjC,SAAS,YAAY,EAAE,SAAS,4BAA4B;EAC5D,UAAU,EAAE;EACb,CAAC,CACL,CACA,KACC,EAAE,QAAQ,SAAS,GAClB,MACC,iBAAiB;EACf,SAAS,YAAY,EAAE,SAAS,uBAAuB;EACvD,SAAS,QAAQ;EAClB,CAAC,CACL,CACA,KAAK,EAAE,QAAQ,UAAU,GAAG,MAC3B,EAAE,UAAU,CACb,CACA,YAAY;;;;;;;;;;;;;;;AC/HjB,SAAgB,kBAAkB,SAAgD;CAChF,MAAM,EAAE,YAAY,SAAS,SAAS,sBAAsB;;;;;;;CAQ5D,SAAS,aAAoC;AAC3C,SAAO,mBAAmB;;;;;;;;CAS5B,SAAS,gBAAyB;AAChC,SAAO,mBAAmB,KAAK;;;;;;;;;;;;CAajC,eAAe,MAAM,cAAqE;EAGxF,MAAM,WAAW,MAAM,iBAAiB;GACtC;GACA;GACA,YALuB,uBAAuB,cAAc,WAAW;GAMxE,CAAC;AAEF,MAAI,aAAa,KACf,QAAO,UAAU;GACf,SAAS;GACT,MAAM;GACP,CAAC;EAIJ,MAAM,CAAC,aADO,YAAY,EAAE,SAAS,IAAI,WAAW,CAAC,CAC3B,KAAK,uBAAuB,SAAS;AAE/D,MAAI,UACF,QAAO,UAAU;GACf,SAAS,8BAA8B,UAAU;GACjD,MAAM;GACP,CAAC;AAGJ,SAAO,GAAG,SAAS;;;;;;;;CASrB,eAAe,SAAyC;EAEtD,MAAM,CAAC,aAAa,YADN,YAAY,EAAE,SAAS,IAAI,WAAW,CAAC,CACf,OAAO,sBAAsB;AAEnE,MAAI,YACF,QAAO,UAAU;GACf,SAAS,gCAAgC,YAAY;GACrD,MAAM;GACP,CAAC;AAGJ,SAAO,GAAG,SAAS;;AAGrB,QAAO;EAAE;EAAe;EAAY;EAAO;EAAQ;;;;;;;;;AAcrD,SAAS,UAAU,OAA4C;AAC7D,QAAO,CAAC,OAAO,KAAK;;;;;;;;;;;;;AActB,SAAS,uBACP,cACA,YAC2B;AAC3B,KAAI,iBAAiB,KAAA,KAAa,aAAa,eAAe,KAAA,EAC5D,QAAO,aAAa;AAGtB,QAAO;;;;;;;;;;;;;;;;;;ACjJT,SAAgB,iBAAiB,YAA8D;AAC7F,QAAOC,QAAM,WAAW,CACrB,KAAK,EAAE,MAAM,UAAU,GAAG,OAAO,EAChC,eAAe,UAAU,EAAE,SAC5B,EAAE,CACF,KAAK,EAAE,MAAM,SAAS,GAAG,OAAO,EAC/B,eAAe,SAAS,OAAO,KAAK,GAAG,EAAE,SAAS,GAAG,EAAE,WAAW,CAAC,SAAS,SAAS,IACtF,EAAE,CACF,KAAK,EAAE,MAAM,WAAW,GAAG,OAAO,GAChC,EAAE,aAAa,EAAE,KACnB,EAAE,CACF,KAAK,EAAE,MAAM,UAAU,GAAG,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,CACnD,YAAY;;;;;;;;;;;;;ACbjB,SAAgB,oBAAwE;AACtF,QAAO,SAAS,eAAe,KAAgD;AAC7E,MAAI,EAAE,UAAU,KACd,QAAO,EAAE;EAIX,MAAM,aADW,IAAiD,KACvC,YAAY;AAEvC,MAAI,eAAe,KACjB,QAAO,EAAE;AAGX,SAAO,iBAAiB,WAAW;;;;;;;;;;ACxBvC,MAAM,kBAAkB;;;;;;;;;;;;;AAqBxB,SAAgB,kBAAkB,SAA0C;CAC1E,MAAM,UAAU,eAAe,QAAQ;AAEvC,QAAO,YAAY,KAAK,SAAS;AAC/B,MAAI,CAAC,YAAY,KAAK,OAAO,CAC3B,KAAI,KAAK,mDAAmD,EAC1D,MAAM,2BACP,CAAC;AAGJ,MAAI,CAAC,IAAI,KAAK,eAAe,CAC3B,KAAI,KAAK,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAG9C,SAAO,MAAM;GACb;;;;;;;;;;;;;;;AAoBJ,SAAS,YAAY,KAAc,KAAsB;AACvD,QAAO,OAAO,QAAQ,YAAY,QAAQ,QAAQ,OAAO;;;;;;;;;AAU3D,SAAS,eAAe,SAAiD;AACvE,KAAI,YAAY,KAAA,KAAa,QAAQ,YAAY,KAAA,EAC/C,QAAO,QAAQ;AAGjB,QAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;ACHT,SAAS,WAAW,SAAkC;CACpD,MAAM,EAAE,eAAe;AAEvB,QAAO,YAAY,KAAK,SAAS;EAC/B,MAAM,UAAU,IAAI,KAAK;AASzB,kBAAgB,KAAK,QAPD,kBAAkB;GACpC;GACA,SAAS,IAAI;GACb,yBAAyB,wBAAwB,SAAS,WAAW;GACrE;GACD,CAAC,CAEuC;AAEzC,SAAO,MAAM;GACb;;;;;;;;;AAUJ,MAAa,OAAoB,OAAO,OAAO,YAAY;CACzD,QAAQ;CACR,QAAQ;CACR,YAAY;CACZ,QAAQ;CACR,KAAK;CACL,MAAM;CACN,SAAS;CACT,OAAO;CACP,SAAS;CACT,OAAO;CACR,CAAC;;;;;;;;AAaF,SAAS,SAAS,SAA+C;AAC/D,QAAO;EAAE,GAAG;EAAS,QAAQ;EAAgB;;;;;;;;;AAU/C,SAAS,YAAY,SAAqD;AACxE,QAAO;EAAE,GAAG;EAAS,QAAQ;EAAmB;;;;;;;;;AAUlD,SAAS,UAAU,SAAiD;AAClE,QAAO;EAAE,GAAG;EAAS,QAAQ;EAAiB;;;;;;;;;AAUhD,SAAS,WAAW,SAAkD;AACpE,QAAO;EAAE,GAAG;EAAS,QAAQ;EAAkB;;;;;;;;;AAUjD,SAAS,gBAAgB,SAA4D;AACnF,QAAO;EAAE,GAAG;EAAS,QAAQ;EAAwB;;;;;;;;;;;AAYvD,SAAS,WAAW,SAAmD;AACrE,QAAO;EAAE,GAAG;EAAS,QAAQ;EAAkB;;;;;;;;;AAUjD,SAAS,YAAY,IAA0C;AAC7D,QAAO;EAAE,UAAU;EAAI,QAAQ;EAAmB;;;;;;;;;;;;;;;AAoBpD,SAAS,wBACP,SACA,YACuB;CACvB,MAAM,aAAa,qBAAqB,YAAY,OAAO;CAC3D,MAAM,eAAe,qBAAqB,YAAY,SAAS;CAC/D,MAAM,YAAY,qBAAqB,YAAY,MAAM;CACzD,MAAM,kBAAkB,eAAe,QAAQ;CAE/C,MAAM,WAAW,gBAAgB;EAC/B,SAAS,YAAY,YAAY,YAAY,UAAU,EAAE,IAAI,UAAU;EACvE,UAAU,YAAY,YAAY,YAAY,WAAW,EAAE,sBAAsB;EAClF,CAAC;AAEF,KAAI,SACF,QAAO;AAGT,KAAI,iBAAiB,KAAA,GAAW;EAC9B,MAAM,aAAa,kBAAkB;GACnC,MAAM,YAAY,YAAY,cAAc,OAAO,EAAE,KAAK,QAAQ,KAAK,EAAE,OAAO,CAAC;GACjF,UAAU,YAAY,YAAY,cAAc,WAAW,EAAE,gBAAgB;GAC9E,CAAC;AAEF,MAAI,WACF,QAAO;;AAIX,QAAO,eAAe,EACpB,UAAU,YAAY,YAAY,WAAW,WAAW,EAAE,gBAAgB,EAC3E,CAAC;;;;;;;;;;AAWJ,SAAS,qBACP,YACA,QACmE;AACnE,QAAO,WAAW,MACf,MAAkE,EAAE,WAAW,OACjF;;;;;;;;;;;;;AAcH,SAAS,YACP,QACA,KAC2B;AAC3B,KAAI,WAAW,KAAA,EACb;AAGF,QAAO,OAAO"}
@@ -1,4 +1,4 @@
1
- import { s as Middleware, u as Context } from "../types-BaZ5WqVM.js";
1
+ import { c as Middleware, d as Context } from "../types-CTvrsrnD.js";
2
2
 
3
3
  //#region src/middleware/http/types.d.ts
4
4
  /**
@@ -1,6 +1,165 @@
1
- import { n as decorateContext, t as middleware } from "../middleware-BFBKNSPQ.js";
2
- import { t as createHttpClient } from "../create-http-client-tZJWlWp1.js";
3
-
1
+ import { n as decorateContext, t as middleware } from "../middleware-BWnPSRWR.js";
2
+ import { attemptAsync } from "@kidd-cli/utils/fp";
3
+ //#region src/middleware/http/create-http-client.ts
4
+ /**
5
+ * Typed HTTP client factory.
6
+ *
7
+ * Creates a closure-based {@link HttpClient} with pre-configured base URL
8
+ * and default headers. All methods delegate to a shared request executor.
9
+ *
10
+ * @module
11
+ */
12
+ /**
13
+ * Create a typed HTTP client with pre-configured base URL and headers.
14
+ *
15
+ * @param options - Client configuration.
16
+ * @returns An HttpClient instance.
17
+ */
18
+ function createHttpClient(options) {
19
+ const { baseUrl, defaultHeaders, resolveHeaders } = options;
20
+ return {
21
+ delete: (path, requestOptions) => executeRequest(baseUrl, "DELETE", path, defaultHeaders, resolveHeaders, requestOptions),
22
+ get: (path, requestOptions) => executeRequest(baseUrl, "GET", path, defaultHeaders, resolveHeaders, requestOptions),
23
+ patch: (path, requestOptions) => executeRequest(baseUrl, "PATCH", path, defaultHeaders, resolveHeaders, requestOptions),
24
+ post: (path, requestOptions) => executeRequest(baseUrl, "POST", path, defaultHeaders, resolveHeaders, requestOptions),
25
+ put: (path, requestOptions) => executeRequest(baseUrl, "PUT", path, defaultHeaders, resolveHeaders, requestOptions)
26
+ };
27
+ }
28
+ /**
29
+ * Build the full URL from base, path, and optional query params.
30
+ *
31
+ * @private
32
+ * @param baseUrl - The base URL.
33
+ * @param path - The request path.
34
+ * @param params - Optional query parameters.
35
+ * @returns The fully qualified URL string.
36
+ */
37
+ function buildUrl(baseUrl, path, params) {
38
+ const url = new URL(path, baseUrl);
39
+ if (params !== void 0) url.search = new URLSearchParams(params).toString();
40
+ return url.toString();
41
+ }
42
+ /**
43
+ * Merge default, dynamic, and per-request headers into a single record.
44
+ *
45
+ * Per-request headers take highest priority, then dynamic headers,
46
+ * then default headers.
47
+ *
48
+ * @private
49
+ * @param defaultHeaders - Optional default headers.
50
+ * @param dynamicHeaders - Optional dynamically resolved headers.
51
+ * @param requestHeaders - Optional per-request headers.
52
+ * @returns The merged headers record.
53
+ */
54
+ function mergeHeaders(defaultHeaders, dynamicHeaders, requestHeaders) {
55
+ return {
56
+ ...defaultHeaders,
57
+ ...dynamicHeaders,
58
+ ...requestHeaders
59
+ };
60
+ }
61
+ /**
62
+ * Normalize optional request options into a concrete object with safe defaults.
63
+ *
64
+ * When `options` is `undefined`, returns an empty object so callers can use
65
+ * direct property access without additional nil checks.
66
+ *
67
+ * @private
68
+ * @param options - Optional per-request options.
69
+ * @returns The resolved options object.
70
+ */
71
+ function resolveRequestOptions(options) {
72
+ if (options !== void 0) return options;
73
+ return {};
74
+ }
75
+ /**
76
+ * Invoke the dynamic header resolver if provided.
77
+ *
78
+ * @private
79
+ * @param resolveHeaders - Optional function to resolve dynamic headers.
80
+ * @returns The resolved headers record, or undefined.
81
+ */
82
+ function resolveDynamicHeaders(resolveHeaders) {
83
+ if (resolveHeaders !== void 0) return resolveHeaders();
84
+ }
85
+ /**
86
+ * Resolve the serialized body string and content-type header mutation.
87
+ *
88
+ * @private
89
+ * @param options - Optional per-request options.
90
+ * @returns The serialized body string or undefined.
91
+ */
92
+ function resolveBody(options) {
93
+ if (options !== void 0 && options.body !== void 0) return JSON.stringify(options.body);
94
+ }
95
+ /**
96
+ * Build the fetch init options from resolved values.
97
+ *
98
+ * @private
99
+ * @param method - The HTTP method.
100
+ * @param headers - The merged headers.
101
+ * @param body - The serialized body or undefined.
102
+ * @param signal - The abort signal or undefined.
103
+ * @returns The RequestInit for fetch.
104
+ */
105
+ function buildFetchInit(method, headers, body, signal) {
106
+ if (body !== void 0) return {
107
+ body,
108
+ headers: {
109
+ ...headers,
110
+ "Content-Type": "application/json"
111
+ },
112
+ method,
113
+ signal
114
+ };
115
+ return {
116
+ headers,
117
+ method,
118
+ signal
119
+ };
120
+ }
121
+ /**
122
+ * Execute an HTTP request and wrap the response.
123
+ *
124
+ * @private
125
+ * @param baseUrl - The base URL.
126
+ * @param method - The HTTP method.
127
+ * @param path - The request path.
128
+ * @param defaultHeaders - Optional default headers.
129
+ * @param resolveHeaders - Optional function to resolve dynamic headers per-request.
130
+ * @param options - Optional per-request options.
131
+ * @returns A typed response wrapper.
132
+ */
133
+ async function executeRequest(baseUrl, method, path, defaultHeaders, resolveHeaders, options) {
134
+ const resolved = resolveRequestOptions(options);
135
+ const url = buildUrl(baseUrl, path, resolved.params);
136
+ const init = buildFetchInit(method, mergeHeaders(defaultHeaders, resolveDynamicHeaders(resolveHeaders), resolved.headers), resolveBody(options), resolved.signal);
137
+ const response = await fetch(url, init);
138
+ return {
139
+ data: await parseResponseBody(response),
140
+ headers: response.headers,
141
+ ok: response.ok,
142
+ raw: response,
143
+ status: response.status
144
+ };
145
+ }
146
+ /**
147
+ * Parse the response body as JSON, returning null on failure.
148
+ *
149
+ * Wraps `response.json()` with `attemptAsync` so malformed API
150
+ * responses do not crash the command. Returns `null as TResponse`
151
+ * when parsing fails.
152
+ *
153
+ * @private
154
+ * @param response - The fetch Response.
155
+ * @returns The parsed body or null.
156
+ */
157
+ async function parseResponseBody(response) {
158
+ const [error, data] = await attemptAsync(() => response.json());
159
+ if (error) return null;
160
+ return data;
161
+ }
162
+ //#endregion
4
163
  //#region src/middleware/http/http.ts
5
164
  /**
6
165
  * HTTP client middleware factory.
@@ -49,7 +208,7 @@ function resolveHeaders(ctx, headers) {
49
208
  if (typeof headers === "function") return headers(ctx);
50
209
  return headers;
51
210
  }
52
-
53
211
  //#endregion
54
212
  export { createHttpClient, http };
213
+
55
214
  //# sourceMappingURL=http.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"http.js","names":[],"sources":["../../src/middleware/http/http.ts"],"sourcesContent":["/**\n * HTTP client middleware factory.\n *\n * Creates a middleware that decorates the context with a typed\n * {@link HttpClient} bound to a base URL and optional headers.\n *\n * This middleware is fully decoupled from auth. For automatic credential\n * injection, use `auth({ http: { ... } })` instead.\n *\n * @module\n */\n\nimport { decorateContext } from '@/context/decorate.js'\nimport type { Context } from '@/context/types.js'\nimport { middleware } from '@/middleware.js'\nimport type { Middleware } from '@/types.js'\n\nimport { createHttpClient } from './create-http-client.js'\nimport type { HttpOptions } from './types.js'\n\n/**\n * Create an HTTP client middleware that decorates the context\n * with a typed client.\n *\n * Resolves headers from the `headers` option (static record or function),\n * builds a typed {@link HttpClient}, and attaches it to `ctx[namespace]`.\n *\n * @param options - HTTP middleware configuration.\n * @returns A Middleware that adds an HttpClient to ctx[namespace].\n */\nexport function http(options: HttpOptions): Middleware {\n const { namespace, baseUrl, headers } = options\n\n return middleware((ctx, next) => {\n const resolvedHeaders = resolveHeaders(ctx, headers)\n\n const client = createHttpClient({\n baseUrl,\n defaultHeaders: resolvedHeaders,\n })\n\n decorateContext(ctx, namespace, client)\n\n return next()\n })\n}\n\n// ---------------------------------------------------------------------------\n// Private helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve headers from the options value.\n *\n * Calls the function form with ctx when provided, returns static headers\n * directly, or returns undefined when no headers are configured.\n *\n * @private\n * @param ctx - The context object.\n * @param headers - The headers option (static, function, or undefined).\n * @returns The resolved headers record or undefined.\n */\nfunction resolveHeaders(\n ctx: Context,\n headers: HttpOptions['headers']\n): Readonly<Record<string, string>> | undefined {\n if (headers === undefined) {\n return undefined\n }\n\n if (typeof headers === 'function') {\n return headers(ctx)\n }\n\n return headers\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AA8BA,SAAgB,KAAK,SAAkC;CACrD,MAAM,EAAE,WAAW,SAAS,YAAY;AAExC,QAAO,YAAY,KAAK,SAAS;AAQ/B,kBAAgB,KAAK,WALN,iBAAiB;GAC9B;GACA,gBAJsB,eAAe,KAAK,QAAQ;GAKnD,CAAC,CAEqC;AAEvC,SAAO,MAAM;GACb;;;;;;;;;;;;;AAkBJ,SAAS,eACP,KACA,SAC8C;AAC9C,KAAI,YAAY,OACd;AAGF,KAAI,OAAO,YAAY,WACrB,QAAO,QAAQ,IAAI;AAGrB,QAAO"}
1
+ {"version":3,"file":"http.js","names":[],"sources":["../../src/middleware/http/create-http-client.ts","../../src/middleware/http/http.ts"],"sourcesContent":["/**\n * Typed HTTP client factory.\n *\n * Creates a closure-based {@link HttpClient} with pre-configured base URL\n * and default headers. All methods delegate to a shared request executor.\n *\n * @module\n */\n\nimport { attemptAsync } from '@kidd-cli/utils/fp'\n\nimport type { HttpClient, RequestOptions, TypedResponse } from './types.js'\n\n/**\n * Options for creating an HTTP client.\n */\ninterface CreateHttpClientOptions {\n readonly baseUrl: string\n readonly defaultHeaders?: Readonly<Record<string, string>>\n readonly resolveHeaders?: () => Readonly<Record<string, string>>\n}\n\n/**\n * Create a typed HTTP client with pre-configured base URL and headers.\n *\n * @param options - Client configuration.\n * @returns An HttpClient instance.\n */\nexport function createHttpClient(options: CreateHttpClientOptions): HttpClient {\n const { baseUrl, defaultHeaders, resolveHeaders } = options\n\n return {\n delete: <TResponse = unknown>(path: string, requestOptions?: RequestOptions) =>\n executeRequest<TResponse>(\n baseUrl,\n 'DELETE',\n path,\n defaultHeaders,\n resolveHeaders,\n requestOptions\n ),\n\n get: <TResponse = unknown>(path: string, requestOptions?: RequestOptions) =>\n executeRequest<TResponse>(\n baseUrl,\n 'GET',\n path,\n defaultHeaders,\n resolveHeaders,\n requestOptions\n ),\n\n patch: <TResponse = unknown, TBody = unknown>(\n path: string,\n requestOptions?: RequestOptions<TBody>\n ) =>\n executeRequest<TResponse>(\n baseUrl,\n 'PATCH',\n path,\n defaultHeaders,\n resolveHeaders,\n requestOptions\n ),\n\n post: <TResponse = unknown, TBody = unknown>(\n path: string,\n requestOptions?: RequestOptions<TBody>\n ) =>\n executeRequest<TResponse>(\n baseUrl,\n 'POST',\n path,\n defaultHeaders,\n resolveHeaders,\n requestOptions\n ),\n\n put: <TResponse = unknown, TBody = unknown>(\n path: string,\n requestOptions?: RequestOptions<TBody>\n ) =>\n executeRequest<TResponse>(\n baseUrl,\n 'PUT',\n path,\n defaultHeaders,\n resolveHeaders,\n requestOptions\n ),\n }\n}\n\n// ---------------------------------------------------------------------------\n// Private helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Build the full URL from base, path, and optional query params.\n *\n * @private\n * @param baseUrl - The base URL.\n * @param path - The request path.\n * @param params - Optional query parameters.\n * @returns The fully qualified URL string.\n */\nfunction buildUrl(\n baseUrl: string,\n path: string,\n params: Readonly<Record<string, string>> | undefined\n): string {\n const url = new URL(path, baseUrl)\n\n if (params !== undefined) {\n const searchParams = new URLSearchParams(params)\n url.search = searchParams.toString()\n }\n\n return url.toString()\n}\n\n/**\n * Merge default, dynamic, and per-request headers into a single record.\n *\n * Per-request headers take highest priority, then dynamic headers,\n * then default headers.\n *\n * @private\n * @param defaultHeaders - Optional default headers.\n * @param dynamicHeaders - Optional dynamically resolved headers.\n * @param requestHeaders - Optional per-request headers.\n * @returns The merged headers record.\n */\nfunction mergeHeaders(\n defaultHeaders: Readonly<Record<string, string>> | undefined,\n dynamicHeaders: Readonly<Record<string, string>> | undefined,\n requestHeaders: Readonly<Record<string, string>> | undefined\n): Readonly<Record<string, string>> {\n return {\n ...defaultHeaders,\n ...dynamicHeaders,\n ...requestHeaders,\n }\n}\n\n/**\n * Normalize optional request options into a concrete object with safe defaults.\n *\n * When `options` is `undefined`, returns an empty object so callers can use\n * direct property access without additional nil checks.\n *\n * @private\n * @param options - Optional per-request options.\n * @returns The resolved options object.\n */\nfunction resolveRequestOptions(options: RequestOptions | undefined): RequestOptions {\n if (options !== undefined) {\n return options\n }\n\n return {}\n}\n\n/**\n * Invoke the dynamic header resolver if provided.\n *\n * @private\n * @param resolveHeaders - Optional function to resolve dynamic headers.\n * @returns The resolved headers record, or undefined.\n */\nfunction resolveDynamicHeaders(\n resolveHeaders: (() => Readonly<Record<string, string>>) | undefined\n): Readonly<Record<string, string>> | undefined {\n if (resolveHeaders !== undefined) {\n return resolveHeaders()\n }\n\n return undefined\n}\n\n/**\n * Resolve the serialized body string and content-type header mutation.\n *\n * @private\n * @param options - Optional per-request options.\n * @returns The serialized body string or undefined.\n */\nfunction resolveBody(options: RequestOptions | undefined): string | undefined {\n if (options !== undefined && options.body !== undefined) {\n return JSON.stringify(options.body)\n }\n\n return undefined\n}\n\n/**\n * Build the fetch init options from resolved values.\n *\n * @private\n * @param method - The HTTP method.\n * @param headers - The merged headers.\n * @param body - The serialized body or undefined.\n * @param signal - The abort signal or undefined.\n * @returns The RequestInit for fetch.\n */\nfunction buildFetchInit(\n method: string,\n headers: Readonly<Record<string, string>>,\n body: string | undefined,\n signal: AbortSignal | undefined\n): RequestInit {\n if (body !== undefined) {\n return {\n body,\n headers: { ...headers, 'Content-Type': 'application/json' },\n method,\n signal,\n }\n }\n\n return {\n headers,\n method,\n signal,\n }\n}\n\n/**\n * Execute an HTTP request and wrap the response.\n *\n * @private\n * @param baseUrl - The base URL.\n * @param method - The HTTP method.\n * @param path - The request path.\n * @param defaultHeaders - Optional default headers.\n * @param resolveHeaders - Optional function to resolve dynamic headers per-request.\n * @param options - Optional per-request options.\n * @returns A typed response wrapper.\n */\nasync function executeRequest<TResponse>(\n baseUrl: string,\n method: string,\n path: string,\n defaultHeaders: Readonly<Record<string, string>> | undefined,\n resolveHeaders: (() => Readonly<Record<string, string>>) | undefined,\n options: RequestOptions | undefined\n): Promise<TypedResponse<TResponse>> {\n const resolved = resolveRequestOptions(options)\n const url = buildUrl(baseUrl, path, resolved.params)\n const dynamicHeaders = resolveDynamicHeaders(resolveHeaders)\n const headers = mergeHeaders(defaultHeaders, dynamicHeaders, resolved.headers)\n const body = resolveBody(options)\n const init = buildFetchInit(method, headers, body, resolved.signal)\n\n const response = await fetch(url, init)\n const data = await parseResponseBody<TResponse>(response)\n\n return {\n data,\n headers: response.headers,\n ok: response.ok,\n raw: response,\n status: response.status,\n }\n}\n\n/**\n * Parse the response body as JSON, returning null on failure.\n *\n * Wraps `response.json()` with `attemptAsync` so malformed API\n * responses do not crash the command. Returns `null as TResponse`\n * when parsing fails.\n *\n * @private\n * @param response - The fetch Response.\n * @returns The parsed body or null.\n */\nasync function parseResponseBody<TResponse>(response: Response): Promise<TResponse> {\n // Accepted exception: generic TResponse cannot be inferred from response.json().\n // The `as` casts bridge the generic HTTP client boundary. Callers provide TResponse\n // At the call site and accept the runtime type risk.\n const [error, data] = await attemptAsync(() => response.json() as Promise<TResponse>)\n\n if (error) {\n return null as TResponse\n }\n\n return data as TResponse\n}\n","/**\n * HTTP client middleware factory.\n *\n * Creates a middleware that decorates the context with a typed\n * {@link HttpClient} bound to a base URL and optional headers.\n *\n * This middleware is fully decoupled from auth. For automatic credential\n * injection, use `auth({ http: { ... } })` instead.\n *\n * @module\n */\n\nimport { decorateContext } from '@/context/decorate.js'\nimport type { Context } from '@/context/types.js'\nimport { middleware } from '@/middleware.js'\nimport type { Middleware } from '@/types.js'\n\nimport { createHttpClient } from './create-http-client.js'\nimport type { HttpOptions } from './types.js'\n\n/**\n * Create an HTTP client middleware that decorates the context\n * with a typed client.\n *\n * Resolves headers from the `headers` option (static record or function),\n * builds a typed {@link HttpClient}, and attaches it to `ctx[namespace]`.\n *\n * @param options - HTTP middleware configuration.\n * @returns A Middleware that adds an HttpClient to ctx[namespace].\n */\nexport function http(options: HttpOptions): Middleware {\n const { namespace, baseUrl, headers } = options\n\n return middleware((ctx, next) => {\n const resolvedHeaders = resolveHeaders(ctx, headers)\n\n const client = createHttpClient({\n baseUrl,\n defaultHeaders: resolvedHeaders,\n })\n\n decorateContext(ctx, namespace, client)\n\n return next()\n })\n}\n\n// ---------------------------------------------------------------------------\n// Private helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve headers from the options value.\n *\n * Calls the function form with ctx when provided, returns static headers\n * directly, or returns undefined when no headers are configured.\n *\n * @private\n * @param ctx - The context object.\n * @param headers - The headers option (static, function, or undefined).\n * @returns The resolved headers record or undefined.\n */\nfunction resolveHeaders(\n ctx: Context,\n headers: HttpOptions['headers']\n): Readonly<Record<string, string>> | undefined {\n if (headers === undefined) {\n return undefined\n }\n\n if (typeof headers === 'function') {\n return headers(ctx)\n }\n\n return headers\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AA4BA,SAAgB,iBAAiB,SAA8C;CAC7E,MAAM,EAAE,SAAS,gBAAgB,mBAAmB;AAEpD,QAAO;EACL,SAA8B,MAAc,mBAC1C,eACE,SACA,UACA,MACA,gBACA,gBACA,eACD;EAEH,MAA2B,MAAc,mBACvC,eACE,SACA,OACA,MACA,gBACA,gBACA,eACD;EAEH,QACE,MACA,mBAEA,eACE,SACA,SACA,MACA,gBACA,gBACA,eACD;EAEH,OACE,MACA,mBAEA,eACE,SACA,QACA,MACA,gBACA,gBACA,eACD;EAEH,MACE,MACA,mBAEA,eACE,SACA,OACA,MACA,gBACA,gBACA,eACD;EACJ;;;;;;;;;;;AAgBH,SAAS,SACP,SACA,MACA,QACQ;CACR,MAAM,MAAM,IAAI,IAAI,MAAM,QAAQ;AAElC,KAAI,WAAW,KAAA,EAEb,KAAI,SADiB,IAAI,gBAAgB,OAAO,CACtB,UAAU;AAGtC,QAAO,IAAI,UAAU;;;;;;;;;;;;;;AAevB,SAAS,aACP,gBACA,gBACA,gBACkC;AAClC,QAAO;EACL,GAAG;EACH,GAAG;EACH,GAAG;EACJ;;;;;;;;;;;;AAaH,SAAS,sBAAsB,SAAqD;AAClF,KAAI,YAAY,KAAA,EACd,QAAO;AAGT,QAAO,EAAE;;;;;;;;;AAUX,SAAS,sBACP,gBAC8C;AAC9C,KAAI,mBAAmB,KAAA,EACrB,QAAO,gBAAgB;;;;;;;;;AAa3B,SAAS,YAAY,SAAyD;AAC5E,KAAI,YAAY,KAAA,KAAa,QAAQ,SAAS,KAAA,EAC5C,QAAO,KAAK,UAAU,QAAQ,KAAK;;;;;;;;;;;;AAgBvC,SAAS,eACP,QACA,SACA,MACA,QACa;AACb,KAAI,SAAS,KAAA,EACX,QAAO;EACL;EACA,SAAS;GAAE,GAAG;GAAS,gBAAgB;GAAoB;EAC3D;EACA;EACD;AAGH,QAAO;EACL;EACA;EACA;EACD;;;;;;;;;;;;;;AAeH,eAAe,eACb,SACA,QACA,MACA,gBACA,gBACA,SACmC;CACnC,MAAM,WAAW,sBAAsB,QAAQ;CAC/C,MAAM,MAAM,SAAS,SAAS,MAAM,SAAS,OAAO;CAIpD,MAAM,OAAO,eAAe,QAFZ,aAAa,gBADN,sBAAsB,eAAe,EACC,SAAS,QAAQ,EACjE,YAAY,QAAQ,EACkB,SAAS,OAAO;CAEnE,MAAM,WAAW,MAAM,MAAM,KAAK,KAAK;AAGvC,QAAO;EACL,MAHW,MAAM,kBAA6B,SAAS;EAIvD,SAAS,SAAS;EAClB,IAAI,SAAS;EACb,KAAK;EACL,QAAQ,SAAS;EAClB;;;;;;;;;;;;;AAcH,eAAe,kBAA6B,UAAwC;CAIlF,MAAM,CAAC,OAAO,QAAQ,MAAM,mBAAmB,SAAS,MAAM,CAAuB;AAErF,KAAI,MACF,QAAO;AAGT,QAAO;;;;;;;;;;;;;;;;;;;;;;;;;ACjQT,SAAgB,KAAK,SAAkC;CACrD,MAAM,EAAE,WAAW,SAAS,YAAY;AAExC,QAAO,YAAY,KAAK,SAAS;AAQ/B,kBAAgB,KAAK,WALN,iBAAiB;GAC9B;GACA,gBAJsB,eAAe,KAAK,QAAQ;GAKnD,CAAC,CAEqC;AAEvC,SAAO,MAAM;GACb;;;;;;;;;;;;;AAkBJ,SAAS,eACP,KACA,SAC8C;AAC9C,KAAI,YAAY,KAAA,EACd;AAGF,KAAI,OAAO,YAAY,WACrB,QAAO,QAAQ,IAAI;AAGrB,QAAO"}
@@ -1,5 +1,4 @@
1
1
  import { withTag } from "@kidd-cli/utils/tag";
2
-
3
2
  //#region src/context/decorate.ts
4
3
  /**
5
4
  * Add a typed, immutable property to a context instance.
@@ -36,7 +35,6 @@ function decorateContext(ctx, key, value) {
36
35
  });
37
36
  return ctx;
38
37
  }
39
-
40
38
  //#endregion
41
39
  //#region src/middleware.ts
42
40
  /**
@@ -59,7 +57,7 @@ function decorateContext(ctx, key, value) {
59
57
  function middleware(handler) {
60
58
  return withTag({ handler }, "Middleware");
61
59
  }
62
-
63
60
  //#endregion
64
61
  export { decorateContext as n, middleware as t };
65
- //# sourceMappingURL=middleware-BFBKNSPQ.js.map
62
+
63
+ //# sourceMappingURL=middleware-BWnPSRWR.js.map