@kidd-cli/core 0.3.0 → 0.4.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.
- package/README.md +2 -3
- package/dist/index.d.ts +7 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +228 -39
- package/dist/index.js.map +1 -1
- package/dist/middleware/auth.d.ts +15 -3
- package/dist/middleware/auth.d.ts.map +1 -1
- package/dist/middleware/auth.js +45 -6
- package/dist/middleware/auth.js.map +1 -1
- package/dist/middleware/http.d.ts +1 -1
- package/dist/{types-CTvrsrnD.d.ts → types-U73X_oQ_.d.ts} +59 -9
- package/dist/{types-CTvrsrnD.d.ts.map → types-U73X_oQ_.d.ts.map} +1 -1
- package/package.json +3 -3
|
@@ -1 +1 @@
|
|
|
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":"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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 ValidateCredential,\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 readonly validate?: ValidateCredential\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, validate } = 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 activeValidate = resolveLoginValidate(loginOptions, validate)\n const [validationError, validatedCredential] = await runValidation(activeValidate, resolved)\n\n if (validationError) {\n return [validationError, null] as const\n }\n\n const store = createStore({ dirName: `.${cliName}` })\n const [saveError] = store.save(DEFAULT_AUTH_FILENAME, validatedCredential)\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(validatedCredential)\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/**\n * Resolve the active validate callback for a login attempt.\n *\n * Returns the override from login options when provided,\n * otherwise falls back to the configured validate callback.\n *\n * @private\n * @param loginOptions - Optional login overrides.\n * @param configured - The default configured validate callback.\n * @returns The validate callback to use, or undefined.\n */\nfunction resolveLoginValidate(\n loginOptions: LoginOptions | undefined,\n configured: ValidateCredential | undefined\n): ValidateCredential | undefined {\n if (loginOptions !== undefined && loginOptions.validate !== undefined) {\n return loginOptions.validate\n }\n\n return configured\n}\n\n/**\n * Run the validate callback against a resolved credential.\n *\n * When no validate callback is provided, returns the credential as-is.\n * When validation fails, returns the error Result.\n * When validation succeeds, returns the (possibly transformed) credential.\n *\n * @private\n * @param validateFn - The validate callback, or undefined.\n * @param credential - The resolved credential to validate.\n * @returns A Result with the validated credential or an AuthError.\n */\nasync function runValidation(\n validateFn: ValidateCredential | undefined,\n credential: AuthCredential\n): AsyncResult<AuthCredential, AuthError> {\n if (validateFn === undefined) {\n return ok(credential)\n }\n\n const [validationError, validatedCredential] = await validateFn(credential)\n\n if (validationError) {\n return authError({\n message: validationError.message,\n type: 'validation_failed',\n })\n }\n\n return ok(validatedCredential)\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, validate } = 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 validate,\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":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as CliLogger } from "./logger-9j49T5da.js";
|
|
2
|
-
import { Tagged } from "@kidd-cli/utils/tag";
|
|
3
2
|
import { z } from "zod";
|
|
3
|
+
import { Tagged } from "@kidd-cli/utils/tag";
|
|
4
4
|
|
|
5
5
|
//#region src/context/types.d.ts
|
|
6
6
|
/**
|
|
@@ -343,6 +343,30 @@ type InferArgs<TDef extends ArgsDef> = TDef extends z.ZodObject<z.ZodRawShape> ?
|
|
|
343
343
|
*/
|
|
344
344
|
type HandlerFn<TArgs extends AnyRecord = AnyRecord, TConfig extends AnyRecord = AnyRecord, TVars = {}> = (ctx: Context<TArgs, TConfig> & Readonly<TVars>) => Promise<void> | void;
|
|
345
345
|
/**
|
|
346
|
+
* Structured configuration for a command's subcommands.
|
|
347
|
+
*
|
|
348
|
+
* Groups the command source (inline map or directory path) alongside display
|
|
349
|
+
* ordering into a single cohesive object.
|
|
350
|
+
*/
|
|
351
|
+
interface CommandsConfig {
|
|
352
|
+
/**
|
|
353
|
+
* Display order for subcommands.
|
|
354
|
+
* Subcommands listed appear first in the specified order; omitted subcommands
|
|
355
|
+
* fall back to alphabetical sort.
|
|
356
|
+
*/
|
|
357
|
+
readonly order?: readonly string[];
|
|
358
|
+
/**
|
|
359
|
+
* Directory path to autoload subcommand files from.
|
|
360
|
+
* Mutually exclusive with `commands` within this config object.
|
|
361
|
+
*/
|
|
362
|
+
readonly path?: string;
|
|
363
|
+
/**
|
|
364
|
+
* Inline subcommand map or a promise from `autoload()`.
|
|
365
|
+
* Mutually exclusive with `path` within this config object.
|
|
366
|
+
*/
|
|
367
|
+
readonly commands?: CommandMap | Promise<CommandMap>;
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
346
370
|
* Options passed to `command()`.
|
|
347
371
|
*
|
|
348
372
|
* @typeParam TArgsDef - Arg definitions type.
|
|
@@ -363,9 +387,10 @@ interface CommandDef<TArgsDef extends ArgsDef = ArgsDef, TConfig extends AnyReco
|
|
|
363
387
|
*/
|
|
364
388
|
middleware?: TMiddleware;
|
|
365
389
|
/**
|
|
366
|
-
* Nested subcommands — a static map
|
|
390
|
+
* Nested subcommands — a static map, a promise from `autoload()`, or a
|
|
391
|
+
* structured {@link CommandsConfig} grouping the source with display order.
|
|
367
392
|
*/
|
|
368
|
-
commands?: CommandMap | Promise<CommandMap
|
|
393
|
+
commands?: CommandMap | Promise<CommandMap> | CommandsConfig;
|
|
369
394
|
/**
|
|
370
395
|
* The command handler.
|
|
371
396
|
*/
|
|
@@ -379,6 +404,7 @@ type Command<TArgsDef extends ArgsDef = ArgsDef, TConfig extends AnyRecord = Any
|
|
|
379
404
|
readonly args?: TArgsDef;
|
|
380
405
|
readonly middleware?: TMiddleware;
|
|
381
406
|
readonly commands?: CommandMap | Promise<CommandMap>;
|
|
407
|
+
readonly order?: readonly string[];
|
|
382
408
|
readonly handler?: HandlerFn<TArgsDef extends z.ZodObject<z.ZodRawShape> ? z.infer<TArgsDef> : InferArgs<TArgsDef & ArgsDef>, TConfig, InferVariables<TMiddleware>>;
|
|
383
409
|
}, "Command">;
|
|
384
410
|
/**
|
|
@@ -410,6 +436,21 @@ interface CliConfigOptions<TSchema extends z.ZodType = z.ZodType> {
|
|
|
410
436
|
name?: string;
|
|
411
437
|
}
|
|
412
438
|
/**
|
|
439
|
+
* Help output customization options for the CLI.
|
|
440
|
+
*/
|
|
441
|
+
interface CliHelpOptions {
|
|
442
|
+
/**
|
|
443
|
+
* Header text displayed above help output when the CLI is invoked
|
|
444
|
+
* without a command. Not shown on `--help`.
|
|
445
|
+
*/
|
|
446
|
+
readonly header?: string;
|
|
447
|
+
/**
|
|
448
|
+
* Footer text displayed below help output (e.g., docs URL, bug report link).
|
|
449
|
+
* Shown on all help output.
|
|
450
|
+
*/
|
|
451
|
+
readonly footer?: string;
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
413
454
|
* Options passed to `cli()`.
|
|
414
455
|
*/
|
|
415
456
|
interface CliOptions<TSchema extends z.ZodType = z.ZodType> {
|
|
@@ -419,8 +460,12 @@ interface CliOptions<TSchema extends z.ZodType = z.ZodType> {
|
|
|
419
460
|
name: string;
|
|
420
461
|
/**
|
|
421
462
|
* CLI version. Enables `--version` flag.
|
|
463
|
+
*
|
|
464
|
+
* When omitted, falls back to the compile-time `__KIDD_VERSION__` constant
|
|
465
|
+
* injected by the kidd bundler. An error is raised at startup if neither
|
|
466
|
+
* an explicit version nor `__KIDD_VERSION__` is available.
|
|
422
467
|
*/
|
|
423
|
-
version
|
|
468
|
+
version?: string;
|
|
424
469
|
/**
|
|
425
470
|
* Human-readable description shown in help text.
|
|
426
471
|
*/
|
|
@@ -437,11 +482,16 @@ interface CliOptions<TSchema extends z.ZodType = z.ZodType> {
|
|
|
437
482
|
* Override the commands source. When omitted, `cli()` loads `kidd.config.ts`
|
|
438
483
|
* and autoloads from its `commands` field (falling back to `'./commands'`).
|
|
439
484
|
*
|
|
440
|
-
* Accepts a directory path string, a static {@link CommandMap},
|
|
441
|
-
* `Promise<CommandMap
|
|
485
|
+
* Accepts a directory path string, a static {@link CommandMap}, a
|
|
486
|
+
* `Promise<CommandMap>`, or a structured {@link CommandsConfig} grouping
|
|
487
|
+
* the source with display ordering.
|
|
488
|
+
*/
|
|
489
|
+
commands?: string | CommandMap | Promise<CommandMap> | CommandsConfig;
|
|
490
|
+
/**
|
|
491
|
+
* Help output customization (header, footer).
|
|
442
492
|
*/
|
|
443
|
-
|
|
493
|
+
help?: CliHelpOptions;
|
|
444
494
|
}
|
|
445
495
|
//#endregion
|
|
446
|
-
export {
|
|
447
|
-
//# sourceMappingURL=types-
|
|
496
|
+
export { Command as a, CommandsConfig as c, MiddlewareEnv as d, MiddlewareFn as f, CliOptions as i, InferVariables as l, AutoloadOptions as n, CommandDef as o, Context as p, CliHelpOptions as r, CommandMap as s, ArgsDef as t, Middleware as u };
|
|
497
|
+
//# sourceMappingURL=types-U73X_oQ_.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types-
|
|
1
|
+
{"version":3,"file":"types-U73X_oQ_.d.ts","names":[],"sources":["../src/context/types.ts","../src/types.ts"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kidd-cli/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "An opinionated CLI framework for Node.js",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -62,8 +62,8 @@
|
|
|
62
62
|
"yaml": "^2.8.2",
|
|
63
63
|
"yargs": "^18.0.0",
|
|
64
64
|
"zod": "^4.3.6",
|
|
65
|
-
"@kidd-cli/config": "0.1.
|
|
66
|
-
"@kidd-cli/utils": "0.1.
|
|
65
|
+
"@kidd-cli/config": "0.1.4",
|
|
66
|
+
"@kidd-cli/utils": "0.1.4"
|
|
67
67
|
},
|
|
68
68
|
"devDependencies": {
|
|
69
69
|
"@types/node": "^25.4.0",
|