@kidd-cli/core 0.1.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/dist/{config-BvGapuFJ.js → config-D8e5qxLp.js} +63 -80
  2. package/dist/config-D8e5qxLp.js.map +1 -0
  3. package/dist/{create-store-BQUX0tAn.js → create-store-OHdkm_Yt.js} +33 -6
  4. package/dist/create-store-OHdkm_Yt.js.map +1 -0
  5. package/dist/index.d.ts +50 -6
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +90 -54
  8. package/dist/index.js.map +1 -1
  9. package/dist/lib/config.js +3 -4
  10. package/dist/lib/logger.d.ts +1 -1
  11. package/dist/lib/logger.js +1 -2
  12. package/dist/lib/logger.js.map +1 -1
  13. package/dist/lib/project.d.ts +1 -1
  14. package/dist/lib/project.d.ts.map +1 -1
  15. package/dist/lib/project.js +2 -3
  16. package/dist/lib/store.d.ts +2 -1
  17. package/dist/lib/store.d.ts.map +1 -1
  18. package/dist/lib/store.js +3 -4
  19. package/dist/{logger-BkQQej8h.d.ts → logger-9j49T5da.d.ts} +1 -1
  20. package/dist/{logger-BkQQej8h.d.ts.map → logger-9j49T5da.d.ts.map} +1 -1
  21. package/dist/middleware/auth.d.ts +250 -13
  22. package/dist/middleware/auth.d.ts.map +1 -1
  23. package/dist/middleware/auth.js +1008 -428
  24. package/dist/middleware/auth.js.map +1 -1
  25. package/dist/middleware/http.d.ts +10 -16
  26. package/dist/middleware/http.d.ts.map +1 -1
  27. package/dist/middleware/http.js +53 -94
  28. package/dist/middleware/http.js.map +1 -1
  29. package/dist/{middleware-D3psyhYo.js → middleware-BWnPSRWR.js} +14 -5
  30. package/dist/{middleware-D3psyhYo.js.map → middleware-BWnPSRWR.js.map} +1 -1
  31. package/dist/{project-NPtYX2ZX.js → project-D0g84bZY.js} +22 -23
  32. package/dist/project-D0g84bZY.js.map +1 -0
  33. package/dist/{types-kjpRau0U.d.ts → types-CTvrsrnD.d.ts} +79 -14
  34. package/dist/types-CTvrsrnD.d.ts.map +1 -0
  35. package/dist/{types-Cz9h927W.d.ts → types-D-BxshYM.d.ts} +1 -1
  36. package/dist/{types-Cz9h927W.d.ts.map → types-D-BxshYM.d.ts.map} +1 -1
  37. package/package.json +9 -16
  38. package/dist/config-BvGapuFJ.js.map +0 -1
  39. package/dist/create-store-BQUX0tAn.js.map +0 -1
  40. package/dist/lib/output.d.ts +0 -62
  41. package/dist/lib/output.d.ts.map +0 -1
  42. package/dist/lib/output.js +0 -276
  43. package/dist/lib/output.js.map +0 -1
  44. package/dist/lib/prompts.d.ts +0 -24
  45. package/dist/lib/prompts.d.ts.map +0 -1
  46. package/dist/lib/prompts.js +0 -3
  47. package/dist/project-NPtYX2ZX.js.map +0 -1
  48. package/dist/prompts-lLfUSgd6.js +0 -63
  49. package/dist/prompts-lLfUSgd6.js.map +0 -1
  50. package/dist/types-CqKJhsYk.d.ts +0 -135
  51. package/dist/types-CqKJhsYk.d.ts.map +0 -1
  52. package/dist/types-DFtYg5uZ.d.ts +0 -26
  53. package/dist/types-DFtYg5uZ.d.ts.map +0 -1
  54. package/dist/types-kjpRau0U.d.ts.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"auth.js","names":["attempt","match","match"],"sources":["../../src/middleware/auth/constants.ts","../../src/middleware/auth/resolve-dotenv.ts","../../src/middleware/auth/resolve-env.ts","../../src/middleware/auth/schema.ts","../../src/middleware/auth/resolve-file.ts","../../src/middleware/auth/resolve-oauth.ts","../../src/middleware/auth/resolve-prompt.ts","../../src/middleware/auth/resolve-credentials.ts","../../src/middleware/auth/create-auth-context.ts","../../src/middleware/auth/auth.ts"],"sourcesContent":["/**\n * Default store key used by the auth middleware to store credentials.\n */\nexport const DEFAULT_AUTH_STORE_KEY = 'auth' as const\n\n/**\n * Default filename for file-based credential storage.\n */\nexport const DEFAULT_AUTH_FILENAME = 'auth.json' as const\n\n/**\n * Suffix appended to the derived token environment variable name.\n */\nexport const TOKEN_VAR_SUFFIX = '_TOKEN' as const\n\n/**\n * 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 { readFileSync } from 'node:fs'\n\nimport { parse } from 'dotenv'\nimport { attempt } from 'es-toolkit'\n\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 (!token) {\n return null\n }\n\n return { token, type: 'bearer' }\n}\n","import 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 (!token) {\n return null\n }\n\n return { token, type: 'bearer' }\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","import { execFile } from 'node:child_process'\nimport { randomBytes } from 'node:crypto'\nimport { createServer } from 'node:http'\nimport type { IncomingMessage, Server, ServerResponse } from 'node:http'\nimport type { Socket } from 'node:net'\nimport { platform } from 'node:os'\n\nimport { match } from 'ts-pattern'\n\nimport type { AuthCredential } from './types.js'\n\n/**\n * Maximum request body size in bytes (16 KB).\n *\n * Limits memory consumption from the local OAuth callback server\n * to prevent resource exhaustion from oversized payloads.\n *\n * @private\n */\nconst MAX_BODY_BYTES = 16_384\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 * Resolve a bearer credential via an OAuth browser flow.\n *\n * Starts a minimal HTTP server on a local port, opens the user's browser\n * to the auth URL with a callback parameter, and waits for the token\n * to arrive via POST body.\n *\n * Only POST requests with a JSON body containing a `token` field are\n * accepted. Query-string tokens are rejected to avoid leaking credentials\n * in server logs, browser history, and referrer headers.\n *\n * @param options - OAuth flow configuration.\n * @returns A bearer credential on success, null on timeout.\n */\nexport async function resolveFromOAuth(options: {\n readonly authUrl: string\n readonly port: number\n readonly callbackPath: string\n readonly timeout: number\n}): Promise<AuthCredential | null> {\n const controller = new AbortController()\n const state = randomBytes(32).toString('hex')\n\n const timeout = createTimeout(options.timeout)\n\n const tokenPromise = listenForToken({\n callbackPath: options.callbackPath,\n port: options.port,\n signal: controller.signal,\n state,\n })\n\n const timeoutPromise = timeout.promise.then((): null => {\n controller.abort()\n return null\n })\n\n const serverPort = await getServerPort(tokenPromise)\n\n if (serverPort === null) {\n controller.abort()\n timeout.clear()\n return null\n }\n\n const callbackUrl = `http://127.0.0.1:${String(serverPort)}${options.callbackPath}`\n const fullAuthUrl = `${options.authUrl}?callback_url=${encodeURIComponent(callbackUrl)}&state=${encodeURIComponent(state)}`\n openBrowser(fullAuthUrl)\n\n const result = await Promise.race([tokenPromise.result, timeoutPromise])\n\n timeout.clear()\n controller.abort()\n\n return result\n}\n\n// ---------------------------------------------------------------------------\n// Private helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Token listener result with port information.\n *\n * @private\n */\ninterface TokenListener {\n readonly port: Promise<number | null>\n readonly result: Promise<AuthCredential | null>\n}\n\n/**\n * Start an HTTP server that listens for an OAuth callback token.\n *\n * The server accepts POST requests with a JSON body `{ \"token\": \"...\" }`\n * on the configured callback path. All other requests receive a 400.\n *\n * @private\n * @param options - Listener configuration.\n * @returns A TokenListener with port and result promises.\n */\nfunction listenForToken(options: {\n readonly callbackPath: string\n readonly port: number\n readonly signal: AbortSignal\n readonly state: string\n}): TokenListener {\n const portResolvers = createDeferred<number | null>()\n const resultResolvers = createDeferred<AuthCredential | 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((req, res) => {\n extractTokenFromBody(req, options.callbackPath, options.state, (token) => {\n if (!token) {\n res.writeHead(400)\n res.end()\n return\n }\n\n sendSuccessPage(res)\n destroyServer(server, sockets)\n resultResolvers.resolve({ token, type: 'bearer' })\n })\n })\n\n trackConnections(server, sockets)\n\n server.on('error', () => {\n destroyServer(server, sockets)\n portResolvers.resolve(null)\n resultResolvers.resolve(null)\n })\n\n options.signal.addEventListener('abort', () => {\n destroyServer(server, sockets)\n resultResolvers.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 portResolvers.resolve(null)\n resultResolvers.resolve(null)\n return\n }\n\n portResolvers.resolve(addr.port)\n })\n\n return {\n port: portResolvers.promise,\n result: resultResolvers.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 * @private\n * @param server - The HTTP server.\n * @param sockets - The set to track sockets in.\n */\nfunction 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 * @private\n * @param server - The HTTP server to close.\n * @param sockets - The set of tracked sockets.\n */\nfunction destroyServer(server: Server, sockets: Set<Socket>): void {\n server.close()\n Array.from(sockets, (socket) => socket.destroy())\n sockets.clear()\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 * @private\n * @returns A deferred object with promise and resolve.\n */\nfunction createDeferred<T>(): {\n readonly promise: Promise<T>\n readonly resolve: (value: T) => void\n} {\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 * Clearable timeout that does not keep the event loop alive after cancellation.\n *\n * @private\n */\ninterface Timeout {\n readonly promise: Promise<void>\n readonly clear: () => void\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 * @private\n * @param ms - Duration in milliseconds.\n * @returns A Timeout with `promise` and `clear`.\n */\nfunction 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 * Get the server port from a token listener.\n *\n * @private\n * @param listener - The token listener.\n * @returns The port number, or null if the server failed to start.\n */\nasync function getServerPort(listener: TokenListener): Promise<number | null> {\n return listener.port\n}\n\n/**\n * Extract a token from the POST body of an incoming HTTP request.\n *\n * Only POST requests to the callback path with `application/json`\n * Content-Type and a JSON body containing `token` and matching `state`\n * fields are accepted. Query-string tokens are intentionally rejected\n * to prevent credential leakage through browser history, server logs,\n * and referrer headers.\n *\n * The `Content-Type` check prevents CORS-safelisted simple requests\n * (which skip preflight) from delivering forged payloads — `text/plain`\n * is safelisted, but `application/json` is not (Fetch Standard §2.2.2).\n *\n * Body size is capped at {@link MAX_BODY_BYTES} to prevent resource\n * exhaustion from oversized payloads.\n *\n * @private\n * @param req - The incoming request.\n * @param callbackPath - The expected callback path.\n * @param expectedState - The state nonce to validate against.\n * @param callback - Called with the extracted token or null.\n */\nfunction extractTokenFromBody(\n req: IncomingMessage,\n callbackPath: string,\n expectedState: string,\n callback: (token: string | null) => void\n): void {\n const reqUrl = new URL(req.url ?? '/', 'http://localhost')\n\n if (reqUrl.pathname !== callbackPath) {\n callback(null)\n return\n }\n\n if (req.method !== 'POST') {\n callback(null)\n return\n }\n\n const contentType = req.headers['content-type'] ?? ''\n\n if (!contentType.startsWith('application/json')) {\n callback(null)\n return\n }\n\n const chunks: Buffer[] = []\n\n // Mutable byte counter — streams must be checked incrementally\n // Before the full payload is buffered to prevent resource exhaustion.\n const received = { bytes: 0 }\n\n req.on('data', (chunk: Buffer) => {\n received.bytes += chunk.length\n\n if (received.bytes > MAX_BODY_BYTES) {\n req.destroy()\n callback(null)\n return\n }\n\n chunks.push(chunk)\n })\n\n req.on('end', () => {\n const body = Buffer.concat(chunks).toString('utf8')\n\n const token = parseTokenFromJson(body, expectedState)\n callback(token)\n })\n\n req.on('error', () => {\n callback(null)\n })\n}\n\n/**\n * Parse a token string from a JSON body and validate the state nonce.\n *\n * Expects `{ \"token\": \"<value>\", \"state\": \"<value>\" }`. Returns null\n * for invalid JSON, missing/empty token fields, or mismatched state.\n *\n * @private\n * @param body - The raw request body string.\n * @param expectedState - The state nonce that must match.\n * @returns The token string or null.\n */\nfunction parseTokenFromJson(body: string, expectedState: string): string | null {\n try {\n const parsed: unknown = JSON.parse(body)\n\n if (typeof parsed !== 'object' || parsed === null) {\n return null\n }\n\n const record = parsed as Record<string, unknown>\n\n if (typeof record.token !== 'string' || record.token === '') {\n return null\n }\n\n if (record.state !== expectedState) {\n return null\n }\n\n return record.token\n } catch {\n return null\n }\n}\n\n/**\n * Send an HTML success page and end the response.\n *\n * @private\n * @param res - The server response object.\n */\nfunction sendSuccessPage(res: ServerResponse): void {\n res.writeHead(200, { 'Content-Type': 'text/html' })\n res.end(CLOSE_PAGE_HTML)\n}\n\n/**\n * Open a URL in the user's default browser using a platform-specific command.\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 * @private\n * @param url - The URL to open.\n */\nfunction openBrowser(url: string): void {\n const { command, args } = match(platform())\n .with('darwin', () => ({ args: [url], command: 'open' }))\n .with('win32', () => ({ args: ['/c', 'start', '', url], command: 'cmd' }))\n .otherwise(() => ({ args: [url], command: 'xdg-open' }))\n execFile(command, args)\n}\n","import type { Prompts } from '@/context/types.js'\n\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 resolveFromPrompt(options: {\n readonly message: string\n readonly prompts: Prompts\n}): Promise<AuthCredential | null> {\n try {\n const token = await options.prompts.password({ message: options.message })\n\n if (!token) {\n return null\n }\n\n return { token, type: 'bearer' }\n } catch {\n return null\n }\n}\n","import { join } from 'node:path'\n\nimport { match } from 'ts-pattern'\n\nimport type { Prompts } from '@/context/types.js'\n\nimport { DEFAULT_AUTH_FILENAME, deriveTokenVar } from './constants.js'\nimport { resolveFromDotenv } from './resolve-dotenv.js'\nimport { resolveFromEnv } from './resolve-env.js'\nimport { resolveFromFile } from './resolve-file.js'\nimport { resolveFromOAuth } from './resolve-oauth.js'\nimport { resolveFromPrompt } from './resolve-prompt.js'\nimport type { AuthCredential, ResolverConfig } from './types.js'\n\nconst DEFAULT_OAUTH_PORT = 0\nconst DEFAULT_OAUTH_CALLBACK_PATH = '/callback'\nconst DEFAULT_OAUTH_TIMEOUT = 120_000\nconst DEFAULT_PROMPT_MESSAGE = 'Enter your API key'\n\n/**\n * Chain credential resolvers, returning the first non-null result.\n *\n * Walks the resolver list in order, dispatching each config to the\n * appropriate resolver function via pattern matching. Short-circuits\n * on the first successful resolution.\n *\n * @param options - Options with resolvers, CLI name, and prompts instance.\n * @returns The first resolved credential, or null if all resolvers fail.\n */\nexport async function resolveCredentials(options: {\n readonly resolvers: readonly ResolverConfig[]\n readonly cliName: string\n readonly prompts: Prompts\n}): Promise<AuthCredential | null> {\n const defaultTokenVar = deriveTokenVar(options.cliName)\n\n return tryResolvers(options.resolvers, 0, defaultTokenVar, options)\n}\n\n// ---------------------------------------------------------------------------\n// Private helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Recursively try resolvers until one returns a credential or the list is exhausted.\n *\n * @private\n * @param configs - The resolver configs.\n * @param index - The current index.\n * @param defaultTokenVar - The derived default token env var name.\n * @param context - The resolve options for prompts access.\n * @returns The first resolved credential, or null.\n */\nasync function tryResolvers(\n configs: readonly ResolverConfig[],\n index: number,\n defaultTokenVar: string,\n context: {\n readonly cliName: string\n readonly prompts: Prompts\n }\n): Promise<AuthCredential | null> {\n if (index >= configs.length) {\n return null\n }\n\n const config = configs[index]\n\n if (config === undefined) {\n return null\n }\n\n const credential = await dispatchResolver(config, defaultTokenVar, context)\n\n if (credential) {\n return credential\n }\n\n return tryResolvers(configs, index + 1, defaultTokenVar, context)\n}\n\n/**\n * Dispatch a single resolver config to its implementation.\n *\n * @private\n * @param config - The resolver config to dispatch.\n * @param defaultTokenVar - The derived default token env var name.\n * @param context - The resolve options for prompts access.\n * @returns The resolved credential, or null.\n */\nasync function dispatchResolver(\n config: ResolverConfig,\n defaultTokenVar: string,\n context: {\n readonly cliName: string\n readonly prompts: Prompts\n }\n): Promise<AuthCredential | null> {\n return match(config)\n .with({ source: 'env' }, (c): AuthCredential | null =>\n resolveFromEnv({\n tokenVar: resolveOptionalString(c.tokenVar, defaultTokenVar),\n })\n )\n .with({ source: 'dotenv' }, (c): AuthCredential | null =>\n resolveFromDotenv({\n path: resolveOptionalString(c.path, join(process.cwd(), '.env')),\n tokenVar: resolveOptionalString(c.tokenVar, defaultTokenVar),\n })\n )\n .with({ source: 'file' }, (c): AuthCredential | null =>\n resolveFromFile({\n dirName: resolveOptionalString(c.dirName, `.${context.cliName}`),\n filename: resolveOptionalString(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: resolveOptionalString(c.callbackPath, DEFAULT_OAUTH_CALLBACK_PATH),\n port: resolveOptionalNumber(c.port, DEFAULT_OAUTH_PORT),\n timeout: resolveOptionalNumber(c.timeout, DEFAULT_OAUTH_TIMEOUT),\n })\n )\n .with(\n { source: 'prompt' },\n (c): Promise<AuthCredential | null> =>\n resolveFromPrompt({\n message: resolveOptionalString(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/**\n * Resolve an optional string value, falling back to a default.\n *\n * @private\n * @param value - The optional value.\n * @param fallback - The default value.\n * @returns The resolved string.\n */\nfunction resolveOptionalString(value: string | undefined, fallback: string): string {\n if (value !== undefined) {\n return value\n }\n return fallback\n}\n\n/**\n * Resolve an optional number value, falling back to a default.\n *\n * @private\n * @param value - The optional value.\n * @param fallback - The default value.\n * @returns The resolved number.\n */\nfunction resolveOptionalNumber(value: number | undefined, fallback: number): number {\n if (value !== undefined) {\n return value\n }\n return fallback\n}\n","/**\n * Factory for the {@link AuthContext} object decorated onto `ctx.auth`.\n *\n * Closes over the middleware's resolver config, CLI name, prompts, and\n * a credential resolver function so that `authenticate()` can run\n * interactive resolvers and persist the result.\n *\n * @module\n */\n\nimport type { AsyncResult, Result } from '@kidd-cli/utils/fp'\nimport { ok } from '@kidd-cli/utils/fp'\n\nimport type { Prompts } from '@/context/types.js'\nimport { createStore } from '@/lib/store/create-store.js'\n\nimport { DEFAULT_AUTH_FILENAME } from './constants.js'\nimport { resolveCredentials } from './resolve-credentials.js'\nimport type { AuthContext, AuthCredential, LoginError, ResolverConfig } from './types.js'\n\n/**\n * Options for {@link createAuthContext}.\n */\nexport interface CreateAuthContextOptions {\n readonly resolvers: readonly ResolverConfig[]\n readonly cliName: string\n readonly prompts: Prompts\n readonly resolveCredential: () => AuthCredential | null\n}\n\n/**\n * Create an {@link AuthContext} value for `ctx.auth`.\n *\n * No credential data is stored on the returned object. `credential()`\n * resolves passively on every call, `authenticated()` checks existence,\n * and `authenticate()` runs the configured interactive resolvers, saves\n * the credential to the global file store, and returns a Result.\n *\n * @param options - Factory options.\n * @returns An AuthContext instance.\n */\nexport function createAuthContext(options: CreateAuthContextOptions): AuthContext {\n const { resolvers, cliName, prompts, resolveCredential } = options\n\n /**\n * Resolve the current credential from passive sources (file, env).\n *\n * @private\n * @returns The credential, or null when none exists.\n */\n function credential(): AuthCredential | null {\n return resolveCredential()\n }\n\n /**\n * Check whether a credential is available from passive sources.\n *\n * @private\n * @returns True when a credential exists.\n */\n function authenticated(): boolean {\n return resolveCredential() !== null\n }\n\n /**\n * Run configured resolvers interactively and persist the credential.\n *\n * @private\n * @returns A Result with the credential on success or a LoginError on failure.\n */\n async function authenticate(): AsyncResult<AuthCredential, LoginError> {\n const resolved = await resolveCredentials({ cliName, prompts, resolvers })\n\n if (resolved === null) {\n return loginError({\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 loginError({\n message: `Failed to save credential: ${saveError.message}`,\n type: 'save_failed',\n })\n }\n\n return ok(resolved)\n }\n\n return { authenticate, authenticated, credential }\n}\n\n// ---------------------------------------------------------------------------\n// Private helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Construct a failure Result tuple with a {@link LoginError}.\n *\n * @private\n * @param error - The login error.\n * @returns A Result tuple `[LoginError, null]`.\n */\nfunction loginError(error: LoginError): Result<never, LoginError> {\n return [error, null] as const\n}\n","/**\n * Auth middleware factory.\n *\n * Decorates `ctx.auth` with functions to resolve credentials on demand\n * and run interactive authentication.\n *\n * @module\n */\n\nimport { decorateContext } from '@/context/decorate.js'\nimport { middleware } from '@/middleware.js'\nimport type { Middleware } from '@/types.js'\n\nimport { DEFAULT_AUTH_FILENAME, deriveTokenVar } from './constants.js'\nimport { createAuthContext } from './create-auth-context.js'\nimport { resolveFromEnv } from './resolve-env.js'\nimport { resolveFromFile } from './resolve-file.js'\nimport type { AuthCredential, AuthOptions, ResolverConfig } from './types.js'\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 two sources on every call:\n * 1. File — `~/.cli-name/auth.json`\n * 2. Env — `CLI_NAME_TOKEN`\n *\n * Interactive resolvers (OAuth, prompt, custom) only run when the\n * command handler explicitly calls `ctx.auth.authenticate()`.\n *\n * @param options - Auth middleware configuration.\n * @returns A Middleware that decorates ctx.auth.\n */\nexport function auth(options: AuthOptions): Middleware {\n const { resolvers } = options\n\n return middleware((ctx, next) => {\n const cliName = ctx.meta.name\n\n const authContext = createAuthContext({\n cliName,\n prompts: ctx.prompts,\n resolveCredential: () => resolvePassive(cliName, resolvers),\n resolvers,\n })\n\n decorateContext(ctx, 'auth', authContext)\n\n return next()\n })\n}\n\n// ---------------------------------------------------------------------------\n// Private helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Attempt to resolve a credential from passive (non-interactive) sources.\n *\n * Checks the file store first, then falls back to the environment variable.\n * Scans the resolver list for `env` and `file` source configs to respect\n * user-configured overrides (e.g. a custom `tokenVar` or `dirName`).\n *\n * @private\n * @param cliName - The CLI name, used to derive paths and env var names.\n * @param resolvers - The configured resolver list for extracting overrides.\n * @returns The resolved credential, or null.\n */\nfunction resolvePassive(\n cliName: string,\n resolvers: readonly ResolverConfig[]\n): AuthCredential | null {\n const fileConfig = findResolverBySource(resolvers, 'file')\n const envConfig = findResolverBySource(resolvers, 'env')\n\n const fromFile = resolveFromFile({\n dirName: resolveFileDir(fileConfig, cliName),\n filename: resolveFileFilename(fileConfig),\n })\n\n if (fromFile) {\n return fromFile\n }\n\n return resolveFromEnv({\n tokenVar: resolveEnvTokenVar(envConfig, cliName),\n })\n}\n\n/**\n * Find the first resolver config matching a given source type.\n *\n * @private\n * @param resolvers - The resolver config list.\n * @param source - The source type to find.\n * @returns The matching config, or undefined.\n */\nfunction findResolverBySource<TSource extends ResolverConfig['source']>(\n resolvers: readonly ResolverConfig[],\n source: TSource\n): Extract<ResolverConfig, { readonly source: TSource }> | undefined {\n return resolvers.find(\n (r): r is Extract<ResolverConfig, { readonly source: TSource }> => r.source === source\n )\n}\n\n/**\n * Resolve the file store directory name from a file resolver config.\n *\n * @private\n * @param config - The file resolver config, or undefined.\n * @param cliName - The CLI name for deriving the default.\n * @returns The directory name.\n */\nfunction resolveFileDir(\n config: Extract<ResolverConfig, { readonly source: 'file' }> | undefined,\n cliName: string\n): string {\n if (config !== undefined && config.dirName !== undefined) {\n return config.dirName\n }\n\n return `.${cliName}`\n}\n\n/**\n * Resolve the file store filename from a file resolver config.\n *\n * @private\n * @param config - The file resolver config, or undefined.\n * @returns The filename.\n */\nfunction resolveFileFilename(\n config: Extract<ResolverConfig, { readonly source: 'file' }> | undefined\n): string {\n if (config !== undefined && config.filename !== undefined) {\n return config.filename\n }\n\n return DEFAULT_AUTH_FILENAME\n}\n\n/**\n * Resolve the environment variable name from an env resolver config.\n *\n * @private\n * @param config - The env resolver config, or undefined.\n * @param cliName - The CLI name for deriving the default.\n * @returns The token variable name.\n */\nfunction resolveEnvTokenVar(\n config: Extract<ResolverConfig, { readonly source: 'env' }> | undefined,\n cliName: string\n): string {\n if (config !== undefined && config.tokenVar !== undefined) {\n return config.tokenVar\n }\n\n return deriveTokenVar(cliName)\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"auth.js","names":["attempt","match","match"],"sources":["../../src/middleware/auth/constants.ts","../../src/middleware/auth/credential.ts","../../src/middleware/auth/oauth-server.ts","../../src/middleware/auth/strategies/device-code.ts","../../src/middleware/auth/strategies/dotenv.ts","../../src/middleware/auth/strategies/env.ts","../../src/middleware/auth/schema.ts","../../src/middleware/auth/strategies/file.ts","../../src/middleware/auth/strategies/oauth.ts","../../src/middleware/auth/strategies/token.ts","../../src/middleware/auth/chain.ts","../../src/middleware/auth/context.ts","../../src/middleware/http/build-auth-headers.ts","../../src/middleware/auth/headers.ts","../../src/middleware/auth/require.ts","../../src/middleware/auth/auth.ts"],"sourcesContent":["/**\n * Default filename for file-based credential storage.\n */\nexport const DEFAULT_AUTH_FILENAME = 'auth.json' as const\n\n/**\n * Suffix appended to the derived token environment variable name.\n */\nexport const TOKEN_VAR_SUFFIX = '_TOKEN' as const\n\n/**\n * Default port for the local OAuth callback server (`0` = ephemeral).\n */\nexport const DEFAULT_OAUTH_PORT = 0\n\n/**\n * Default callback path for the local OAuth server.\n */\nexport const DEFAULT_OAUTH_CALLBACK_PATH = '/callback'\n\n/**\n * Default timeout for the OAuth PKCE flow in milliseconds (2 minutes).\n */\nexport const DEFAULT_OAUTH_TIMEOUT = 120_000\n\n/**\n * Default poll interval for the device code flow in milliseconds (5 seconds).\n */\nexport const DEFAULT_DEVICE_CODE_POLL_INTERVAL = 5000\n\n/**\n * Default timeout for the device code flow in milliseconds (5 minutes).\n */\nexport const DEFAULT_DEVICE_CODE_TIMEOUT = 300_000\n\n/**\n * Derive the default environment variable name from a CLI name.\n *\n * Converts kebab-case to SCREAMING_SNAKE_CASE and appends `_TOKEN`.\n * Example: `my-app` → `MY_APP_TOKEN`\n *\n * @param cliName - The CLI name.\n * @returns The derived environment variable name.\n */\nexport function deriveTokenVar(cliName: string): string {\n return `${cliName.replaceAll('-', '_').toUpperCase()}${TOKEN_VAR_SUFFIX}`\n}\n","import { attemptAsync } from '@kidd-cli/utils/fp'\n\nimport type { BearerCredential } from './types.js'\n\n/**\n * Check whether a token string is a non-empty, non-whitespace value.\n *\n * Acts as a type guard: when it returns true, TypeScript narrows the\n * token to `string`. Consolidates the repeated `!token || token.trim() === ''`\n * guard found across strategy resolvers.\n *\n * @param token - The token string to check.\n * @returns True when the token is a non-empty string.\n */\nexport function isValidToken(token: string | undefined | null): token is string {\n if (!token) {\n return false\n }\n\n if (token.trim() === '') {\n return false\n }\n\n return true\n}\n\n/**\n * Construct a bearer credential from a raw token string.\n *\n * @param token - The access token value.\n * @returns A BearerCredential with `type: 'bearer'`.\n */\nexport function createBearerCredential(token: string): BearerCredential {\n return { token, type: 'bearer' }\n}\n\n/**\n * POST form-encoded parameters to a URL.\n *\n * Wraps the duplicated `fetch` call with `Content-Type: application/x-www-form-urlencoded`\n * found in the OAuth and device code strategies. Returns null on network or\n * request failure instead of throwing.\n *\n * @param url - The endpoint URL.\n * @param params - The URL-encoded form parameters.\n * @param signal - Optional AbortSignal for timeout/cancellation.\n * @returns The fetch Response on success, null on failure.\n */\nexport async function postFormEncoded(\n url: string,\n params: URLSearchParams,\n signal?: AbortSignal\n): Promise<Response | null> {\n const [fetchError, response] = await attemptAsync(() =>\n fetch(url, {\n body: params.toString(),\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n method: 'POST',\n signal,\n })\n )\n\n if (fetchError) {\n return null\n }\n\n return response\n}\n","/**\n * Shared utilities for OAuth-based auth resolvers.\n *\n * Extracted from the local HTTP server, browser-launch, and\n * lifecycle patterns shared by the PKCE and device-code flows.\n *\n * @module\n */\n\nimport { execFile } from 'node:child_process'\nimport { createServer } from 'node:http'\nimport type { IncomingMessage, Server, ServerResponse } from 'node:http'\nimport type { Socket } from 'node:net'\nimport { platform } from 'node:os'\n\nimport { attempt, match } from '@kidd-cli/utils/fp'\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst CLOSE_PAGE_HTML = [\n '<!DOCTYPE html>',\n '<html>',\n '<body><p>Authentication complete. You can close this tab.</p></body>',\n '</html>',\n].join('\\n')\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/**\n * A deferred promise with an externally accessible resolve function.\n */\nexport interface Deferred<T> {\n readonly promise: Promise<T>\n readonly resolve: (value: T) => void\n}\n\n/**\n * A clearable timeout that does not keep the event loop alive after cancellation.\n */\nexport interface Timeout {\n readonly promise: Promise<void>\n readonly clear: () => void\n}\n\n/**\n * Result of starting a local HTTP server with request handling.\n */\nexport interface LocalServerHandle {\n readonly port: Promise<number | null>\n readonly server: Server\n readonly sockets: Set<Socket>\n}\n\n// ---------------------------------------------------------------------------\n// Exported functions\n// ---------------------------------------------------------------------------\n\n/**\n * Create a deferred promise with externally accessible resolve.\n *\n * Uses a mutable state container to capture the promise resolver --\n * this is an intentional exception to immutability rules because the\n * Promise constructor API requires synchronous resolver capture.\n *\n * @returns A deferred object with promise and resolve.\n */\nexport function createDeferred<T>(): Deferred<T> {\n const state: { resolve: ((value: T) => void) | null } = { resolve: null }\n\n const promise = new Promise<T>((resolve) => {\n state.resolve = resolve\n })\n\n return {\n promise,\n resolve: (value: T): void => {\n if (state.resolve) {\n state.resolve(value)\n }\n },\n }\n}\n\n/**\n * Create a clearable timeout.\n *\n * Returns a promise that resolves after `ms` milliseconds and a `clear`\n * function that cancels the timer so it does not hold the event loop open.\n *\n * Uses a mutable state container to capture the timer id -- this is an\n * intentional exception to immutability rules because `setTimeout`\n * returns an opaque handle that must be stored for later cancellation.\n *\n * @param ms - Duration in milliseconds.\n * @returns A Timeout with `promise` and `clear`.\n */\nexport function createTimeout(ms: number): Timeout {\n const state: { id: ReturnType<typeof setTimeout> | null } = { id: null }\n\n const promise = new Promise<void>((resolve) => {\n state.id = setTimeout(resolve, ms)\n })\n\n return {\n clear: (): void => {\n if (state.id !== null) {\n clearTimeout(state.id)\n state.id = null\n }\n },\n promise,\n }\n}\n\n/**\n * Track socket connections on a server so they can be destroyed on close.\n *\n * Mutates the provided socket set -- this is an intentional exception to\n * immutability rules because the HTTP server API is inherently stateful.\n *\n * @param server - The HTTP server.\n * @param sockets - The set to track sockets in.\n */\nexport function trackConnections(server: Server, sockets: Set<Socket>): void {\n server.on('connection', (socket: Socket) => {\n sockets.add(socket)\n socket.on('close', () => {\n sockets.delete(socket)\n })\n })\n}\n\n/**\n * Close a server and destroy all active connections immediately.\n *\n * `server.close()` only stops accepting new connections -- existing\n * keep-alive connections hold the event loop open. This helper\n * destroys every tracked socket so the process can exit cleanly.\n *\n * @param server - The HTTP server to close.\n * @param sockets - The set of tracked sockets.\n */\nexport function destroyServer(server: Server, sockets: Set<Socket>): void {\n server.close()\n Array.from(sockets, (socket) => socket.destroy())\n sockets.clear()\n}\n\n/**\n * Send an HTML success page and end the response.\n *\n * @param res - The server response object.\n */\nexport function sendSuccessPage(res: ServerResponse): void {\n res.writeHead(200, { 'Content-Type': 'text/html' })\n res.end(CLOSE_PAGE_HTML)\n}\n\n/**\n * Check whether a URL is safe for use as an OAuth endpoint.\n *\n * Requires HTTPS for all URLs except loopback addresses, where\n * HTTP is permitted per RFC 8252 §8.3 (native app redirect URIs).\n *\n * @param url - The URL string to validate.\n * @returns True when the URL uses HTTPS or HTTP on a loopback address.\n */\nexport function isSecureAuthUrl(url: string): boolean {\n const [error, parsed] = attempt(() => new URL(url))\n\n if (error || !parsed) {\n return false\n }\n\n if (parsed.protocol === 'https:') {\n return true\n }\n\n if (parsed.protocol !== 'http:') {\n return false\n }\n\n return isLoopbackHost(parsed.hostname)\n}\n\n/**\n * Open a URL in the user's default browser using a platform-specific command.\n *\n * Validates that the URL uses the HTTP or HTTPS protocol before opening\n * to prevent dangerous schemes like `javascript:` or `data:`. Silently\n * returns if the URL is invalid.\n *\n * On Windows, `start` is a `cmd.exe` built-in -- not a standalone executable --\n * so it must be invoked via `cmd /c start \"\" <url>`. The empty string argument\n * prevents `cmd` from interpreting the URL as a window title.\n *\n * @param url - The URL to open (must use http: or https: protocol).\n */\nexport function openBrowser(url: string): void {\n if (!isHttpUrl(url)) {\n return\n }\n\n const { command, args } = match(platform())\n .with('darwin', () => ({ args: [url], command: 'open' }))\n .with('win32', () => ({ args: ['/c', 'start', '', escapeCmdMeta(url)], command: 'cmd' }))\n .otherwise(() => ({ args: [url], command: 'xdg-open' }))\n\n const child = execFile(command, args)\n child.on('error', () => undefined)\n}\n\n/**\n * Start a local HTTP server on `127.0.0.1` with socket tracking.\n *\n * Returns a handle containing the server, tracked sockets, and a port\n * promise that resolves once the server is listening.\n *\n * @param options - Server configuration.\n * @returns A LocalServerHandle with port, server, and sockets.\n */\nexport function startLocalServer(options: {\n readonly port: number\n readonly onRequest: (req: IncomingMessage, res: ServerResponse) => void\n}): LocalServerHandle {\n const portDeferred = createDeferred<number | null>()\n\n // Mutable socket set required for resource cleanup.\n // Server API is stateful -- tracking sockets is the only way to destroy keep-alive connections.\n const sockets = new Set<Socket>()\n\n const server = createServer(options.onRequest)\n\n trackConnections(server, sockets)\n\n server.on('error', () => {\n destroyServer(server, sockets)\n portDeferred.resolve(null)\n })\n\n server.listen(options.port, '127.0.0.1', () => {\n const addr = server.address()\n\n if (addr === null || typeof addr === 'string') {\n destroyServer(server, sockets)\n portDeferred.resolve(null)\n return\n }\n\n portDeferred.resolve(addr.port)\n })\n\n return {\n port: portDeferred.promise,\n server,\n sockets,\n }\n}\n\n// ---------------------------------------------------------------------------\n// Private helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Check whether a URL uses the HTTP or HTTPS protocol.\n *\n * Rejects dangerous schemes like `javascript:`, `data:`, and `file:`\n * to prevent browser-based attacks when opening untrusted URLs.\n *\n * @private\n * @param url - The URL string to validate.\n * @returns True when the URL uses http: or https: protocol.\n */\nfunction isHttpUrl(url: string): boolean {\n const [error, parsed] = attempt(() => new URL(url))\n\n if (error || !parsed) {\n return false\n }\n\n return parsed.protocol === 'https:' || parsed.protocol === 'http:'\n}\n\n/**\n * Check whether a hostname is a loopback address.\n *\n * RFC 8252 §8.3 permits HTTP for loopback interfaces during\n * native app authorization flows.\n *\n * @private\n * @param hostname - The hostname to check.\n * @returns True when the hostname is a loopback address.\n */\nfunction isLoopbackHost(hostname: string): boolean {\n return hostname === '127.0.0.1' || hostname === '[::1]' || hostname === 'localhost'\n}\n\n/**\n * Escape `cmd.exe` metacharacters in a URL string.\n *\n * Characters like `&`, `|`, `<`, `>`, and `^` are interpreted as\n * command separators or redirectors by `cmd.exe`. Prefixing each\n * with `^` neutralises the special meaning.\n *\n * @private\n * @param url - The URL to escape.\n * @returns The escaped URL string.\n */\nfunction escapeCmdMeta(url: string): string {\n return url.replaceAll(/[&|<>^]/g, '^$&')\n}\n","/**\n * OAuth 2.0 Device Authorization Grant resolver (RFC 8628).\n *\n * Requests a device code, displays the verification URL and user code,\n * and polls the token endpoint until the user completes authorization\n * or the flow times out.\n *\n * @module\n */\n\nimport { attemptAsync, isPlainObject, match } from '@kidd-cli/utils/fp'\n\nimport type { Prompts } from '@/context/types.js'\n\nimport { createBearerCredential, postFormEncoded } from '../credential.js'\nimport { isSecureAuthUrl, openBrowser } from '../oauth-server.js'\nimport type { AuthCredential } from '../types.js'\n\n/**\n * RFC 8628 slow_down backoff increment in milliseconds.\n */\nconst SLOW_DOWN_INCREMENT = 5000\n\n/**\n * RFC 8628 device code grant type URN.\n */\nconst DEVICE_CODE_GRANT_TYPE = 'urn:ietf:params:oauth:grant-type:device_code'\n\n/**\n * Resolve a bearer credential via OAuth 2.0 Device Authorization Grant.\n *\n * 1. POSTs to the device authorization endpoint to obtain a device code\n * 2. Displays the verification URL and user code via prompts\n * 3. Optionally opens the verification URL in the browser\n * 4. Polls the token endpoint until authorization completes or times out\n *\n * @param options - Device code flow configuration.\n * @returns A bearer credential on success, null on failure or timeout.\n */\nexport async function resolveFromDeviceCode(options: {\n readonly clientId: string\n readonly deviceAuthUrl: string\n readonly tokenUrl: string\n readonly scopes: readonly string[]\n readonly pollInterval: number\n readonly timeout: number\n readonly prompts: Prompts\n readonly openBrowserOnStart?: boolean\n}): Promise<AuthCredential | null> {\n if (!isSecureAuthUrl(options.deviceAuthUrl)) {\n return null\n }\n\n if (!isSecureAuthUrl(options.tokenUrl)) {\n return null\n }\n\n const deadline = Date.now() + options.timeout\n const signal = AbortSignal.timeout(options.timeout)\n\n const authResponse = await requestDeviceAuth({\n clientId: options.clientId,\n deviceAuthUrl: options.deviceAuthUrl,\n scopes: options.scopes,\n signal,\n })\n\n if (!authResponse) {\n return null\n }\n\n await displayUserCode(options.prompts, authResponse.verificationUri, authResponse.userCode)\n\n if (options.openBrowserOnStart !== false) {\n openBrowser(authResponse.verificationUri)\n }\n\n const interval = resolveInterval(authResponse.interval, options.pollInterval)\n\n return pollForToken({\n clientId: options.clientId,\n deadline,\n deviceCode: authResponse.deviceCode,\n interval,\n signal,\n tokenUrl: options.tokenUrl,\n })\n}\n\n// ---------------------------------------------------------------------------\n// Private types\n// ---------------------------------------------------------------------------\n\n/**\n * Parsed response from the device authorization endpoint.\n *\n * @private\n */\ninterface DeviceAuthResponse {\n readonly deviceCode: string\n readonly userCode: string\n readonly verificationUri: string\n readonly interval: number | null\n}\n\n// ---------------------------------------------------------------------------\n// Private helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Request a device code from the authorization server.\n *\n * @private\n * @param options - Device auth request parameters.\n * @returns The parsed device auth response, or null on failure.\n */\nasync function requestDeviceAuth(options: {\n readonly clientId: string\n readonly deviceAuthUrl: string\n readonly scopes: readonly string[]\n readonly signal?: AbortSignal\n}): Promise<DeviceAuthResponse | null> {\n const body = new URLSearchParams({ client_id: options.clientId })\n\n if (options.scopes.length > 0) {\n body.set('scope', options.scopes.join(' '))\n }\n\n const response = await postFormEncoded(options.deviceAuthUrl, body, options.signal)\n\n if (!response) {\n return null\n }\n\n if (!response.ok) {\n return null\n }\n\n const [parseError, data] = await attemptAsync((): Promise<unknown> => response.json())\n\n if (parseError) {\n return null\n }\n\n return parseDeviceAuthResponse(data)\n}\n\n/**\n * Parse a device authorization response body.\n *\n * @private\n * @param data - The raw response data.\n * @returns The parsed response, or null if required fields are missing.\n */\nfunction parseDeviceAuthResponse(data: unknown): DeviceAuthResponse | null {\n if (!isPlainObject(data)) {\n return null\n }\n\n if (typeof data.device_code !== 'string' || data.device_code === '') {\n return null\n }\n\n if (typeof data.user_code !== 'string' || data.user_code === '') {\n return null\n }\n\n if (typeof data.verification_uri !== 'string' || data.verification_uri === '') {\n return null\n }\n\n const interval = resolveServerInterval(data.interval)\n\n return {\n deviceCode: data.device_code,\n interval,\n userCode: data.user_code,\n verificationUri: data.verification_uri,\n }\n}\n\n/**\n * Display the verification URL and user code to the user.\n *\n * Uses `prompts.text()` to show the information and wait for\n * the user to press Enter to acknowledge.\n *\n * @private\n * @param prompts - The prompts instance.\n * @param verificationUri - The URL the user should visit.\n * @param userCode - The code the user should enter.\n */\nasync function displayUserCode(\n prompts: Prompts,\n verificationUri: string,\n userCode: string\n): Promise<void> {\n // User cancellation is non-fatal — polling will handle timeout\n await attemptAsync(() =>\n prompts.text({\n defaultValue: '',\n message: `Open ${verificationUri} and enter code: ${userCode} (press Enter to continue)`,\n })\n )\n}\n\n/**\n * Resolve the poll interval, preferring server-provided value.\n *\n * @private\n * @param serverInterval - The interval from the server response (in ms), or null.\n * @param configInterval - The configured default interval.\n * @returns The resolved interval in milliseconds.\n */\nfunction resolveInterval(serverInterval: number | null, configInterval: number): number {\n if (serverInterval !== null) {\n return serverInterval\n }\n\n return configInterval\n}\n\n/**\n * Poll the token endpoint for an access token using recursive tail-call style.\n *\n * Handles RFC 8628 error codes:\n * - `authorization_pending` -- continue polling\n * - `slow_down` -- increase interval by 5 seconds, continue\n * - `expired_token` -- return null\n * - `access_denied` -- return null\n *\n * @private\n * @param options - Polling parameters.\n * @returns A bearer credential on success, null on failure or timeout.\n */\nasync function pollForToken(options: {\n readonly tokenUrl: string\n readonly deviceCode: string\n readonly clientId: string\n readonly interval: number\n readonly deadline: number\n readonly signal?: AbortSignal\n}): Promise<AuthCredential | null> {\n if (Date.now() >= options.deadline) {\n return null\n }\n\n await sleep(options.interval)\n\n if (Date.now() >= options.deadline) {\n return null\n }\n\n const result = await requestToken({\n clientId: options.clientId,\n deviceCode: options.deviceCode,\n signal: options.signal,\n tokenUrl: options.tokenUrl,\n })\n\n return match(result)\n .with({ status: 'success' }, (r) => r.credential)\n .with({ status: 'pending' }, () => pollForToken(options))\n .with({ status: 'slow_down' }, () =>\n pollForToken({\n ...options,\n interval: options.interval + SLOW_DOWN_INCREMENT,\n })\n )\n .with({ status: 'denied' }, () => null)\n .with({ status: 'expired' }, () => null)\n .with({ status: 'error' }, () => null)\n .exhaustive()\n}\n\n/**\n * Convert a server-provided interval value to milliseconds.\n *\n * @private\n * @param value - The raw interval value from the server response.\n * @returns The interval in milliseconds, or null if not a number.\n */\nfunction resolveServerInterval(value: unknown): number | null {\n if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0) {\n return null\n }\n\n return Math.max(1000, Math.min(value * 1000, 60_000))\n}\n\n/**\n * Sleep for a given duration.\n *\n * @private\n * @param ms - Duration in milliseconds.\n * @returns A promise that resolves after the delay.\n */\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => {\n setTimeout(resolve, ms)\n })\n}\n\n// ---------------------------------------------------------------------------\n// Token request types and helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Discriminated union of token request outcomes.\n *\n * @private\n */\ntype TokenRequestResult =\n | { readonly status: 'success'; readonly credential: AuthCredential }\n | { readonly status: 'pending' }\n | { readonly status: 'slow_down' }\n | { readonly status: 'denied' }\n | { readonly status: 'expired' }\n | { readonly status: 'error' }\n\n/**\n * Request an access token from the token endpoint.\n *\n * @private\n * @param options - Token request parameters.\n * @returns A discriminated result indicating the outcome.\n */\nasync function requestToken(options: {\n readonly tokenUrl: string\n readonly deviceCode: string\n readonly clientId: string\n readonly signal?: AbortSignal\n}): Promise<TokenRequestResult> {\n const body = new URLSearchParams({\n client_id: options.clientId,\n device_code: options.deviceCode,\n grant_type: DEVICE_CODE_GRANT_TYPE,\n })\n\n const response = await postFormEncoded(options.tokenUrl, body, options.signal)\n\n if (!response) {\n return { status: 'error' }\n }\n\n const [parseError, data] = await attemptAsync((): Promise<unknown> => response.json())\n\n if (parseError) {\n return { status: 'error' }\n }\n\n if (!isPlainObject(data)) {\n return { status: 'error' }\n }\n\n if (response.ok && typeof data.access_token === 'string' && data.access_token !== '') {\n if (typeof data.token_type === 'string' && data.token_type.toLowerCase() !== 'bearer') {\n return { status: 'error' }\n }\n\n return { credential: createBearerCredential(data.access_token), status: 'success' }\n }\n\n if (typeof data.error !== 'string') {\n return { status: 'error' }\n }\n\n return match(data.error)\n .with('authorization_pending', (): TokenRequestResult => ({ status: 'pending' }))\n .with('slow_down', (): TokenRequestResult => ({ status: 'slow_down' }))\n .with('expired_token', (): TokenRequestResult => ({ status: 'expired' }))\n .with('access_denied', (): TokenRequestResult => ({ status: 'denied' }))\n .otherwise((): TokenRequestResult => ({ status: 'error' }))\n}\n","import { readFileSync } from 'node:fs'\n\nimport { parse } from 'dotenv'\nimport { attempt } from 'es-toolkit'\n\nimport { createBearerCredential, isValidToken } from '../credential.js'\nimport type { AuthCredential } from '../types.js'\n\n/**\n * Resolve a bearer credential from a `.env` file without mutating `process.env`.\n *\n * Reads the file and parses it with `dotenv.parse`. If the target variable\n * is present, returns a bearer credential. Otherwise returns null.\n *\n * Skips a separate existence check to avoid a TOCTOU race — if the file\n * does not exist, `readFileSync` throws and `attempt` captures the error.\n *\n * @param options - Options with the env variable name and file path.\n * @returns A bearer credential if found, null otherwise.\n */\nexport function resolveFromDotenv(options: {\n readonly tokenVar: string\n readonly path: string\n}): AuthCredential | null {\n const [readError, content] = attempt(() => readFileSync(options.path, 'utf8'))\n\n if (readError || content === null) {\n return null\n }\n\n const parsed = parse(content)\n const token = parsed[options.tokenVar]\n\n if (!isValidToken(token)) {\n return null\n }\n\n return createBearerCredential(token)\n}\n","import { createBearerCredential, isValidToken } from '../credential.js'\nimport type { AuthCredential } from '../types.js'\n\n/**\n * Resolve a bearer credential from a process environment variable.\n *\n * @param options - Options containing the environment variable name.\n * @returns A bearer credential if the variable is set, null otherwise.\n */\nexport function resolveFromEnv(options: { readonly tokenVar: string }): AuthCredential | null {\n const token = process.env[options.tokenVar]\n\n if (!isValidToken(token)) {\n return null\n }\n\n return createBearerCredential(token)\n}\n","import { z } from 'zod'\n\n/**\n * Zod schema for bearer credentials.\n */\nexport const bearerCredentialSchema = z.object({\n token: z.string().min(1),\n type: z.literal('bearer'),\n})\n\n/**\n * Zod schema for basic auth credentials.\n */\nexport const basicCredentialSchema = z.object({\n password: z.string().min(1),\n type: z.literal('basic'),\n username: z.string().min(1),\n})\n\n/**\n * Zod schema for API key credentials.\n */\nexport const apiKeyCredentialSchema = z.object({\n headerName: z.string().min(1),\n key: z.string().min(1),\n type: z.literal('api-key'),\n})\n\n/**\n * Zod schema for custom header credentials.\n */\nexport const customCredentialSchema = z.object({\n headers: z.record(z.string(), z.string()),\n type: z.literal('custom'),\n})\n\n/**\n * Zod discriminated union schema for validating auth.json credential payloads.\n * Validates against all four credential types using the `type` field as discriminator.\n */\nexport const authCredentialSchema = z.discriminatedUnion('type', [\n bearerCredentialSchema,\n basicCredentialSchema,\n apiKeyCredentialSchema,\n customCredentialSchema,\n])\n","import { createStore } from '@/lib/store/create-store.js'\n\nimport { authCredentialSchema } from '../schema.js'\nimport type { AuthCredential } from '../types.js'\n\n/**\n * Resolve credentials from a JSON file on disk.\n *\n * Uses the file-backed store with local-then-global resolution to find\n * the credentials file, then validates its contents against the auth\n * credential schema.\n *\n * @param options - Options with the filename and directory name.\n * @returns A validated auth credential, or null if not found or invalid.\n */\nexport function resolveFromFile(options: {\n readonly filename: string\n readonly dirName: string\n}): AuthCredential | null {\n const store = createStore({ dirName: options.dirName })\n const data = store.load(options.filename)\n\n if (data === null) {\n return null\n }\n\n const result = authCredentialSchema.safeParse(data)\n\n if (!result.success) {\n return null\n }\n\n return result.data\n}\n","/**\n * OAuth 2.0 Authorization Code + PKCE resolver (RFC 7636 + RFC 8252).\n *\n * Opens the user's browser to the authorization URL with a PKCE challenge,\n * listens for a GET redirect with an authorization code on a local server,\n * and exchanges the code at the token endpoint with the code verifier.\n *\n * @module\n */\n\nimport { createHash, randomBytes } from 'node:crypto'\nimport type { IncomingMessage, ServerResponse } from 'node:http'\n\nimport { attemptAsync, isPlainObject } from '@kidd-cli/utils/fp'\n\nimport { createBearerCredential, postFormEncoded } from '../credential.js'\nimport {\n createDeferred,\n createTimeout,\n destroyServer,\n isSecureAuthUrl,\n openBrowser,\n sendSuccessPage,\n startLocalServer,\n} from '../oauth-server.js'\nimport type { AuthCredential } from '../types.js'\n\n/**\n * Resolve a bearer credential via OAuth 2.0 Authorization Code + PKCE.\n *\n * 1. Generates a `code_verifier` and derives the `code_challenge`\n * 2. Starts a local HTTP server on `127.0.0.1`\n * 3. Opens the browser to the authorization URL with PKCE params\n * 4. Receives the authorization code via GET redirect\n * 5. Exchanges the code at the token endpoint with the verifier\n * 6. Returns the access token as a bearer credential\n *\n * @param options - PKCE flow configuration.\n * @returns A bearer credential on success, null on failure or timeout.\n */\nexport async function resolveFromOAuth(options: {\n readonly clientId: string\n readonly authUrl: string\n readonly tokenUrl: string\n readonly scopes: readonly string[]\n readonly port: number\n readonly callbackPath: string\n readonly timeout: number\n}): Promise<AuthCredential | null> {\n if (!isSecureAuthUrl(options.authUrl)) {\n return null\n }\n\n if (!isSecureAuthUrl(options.tokenUrl)) {\n return null\n }\n\n const codeVerifier = generateCodeVerifier()\n const codeChallenge = deriveCodeChallenge(codeVerifier)\n const state = randomBytes(32).toString('hex')\n\n const timeout = createTimeout(options.timeout)\n const codeDeferred = createDeferred<string | null>()\n\n const handle = startLocalServer({\n onRequest: (req, res) => {\n handleCallback(req, res, options.callbackPath, state, codeDeferred.resolve)\n },\n port: options.port,\n })\n\n const serverPort = await handle.port\n\n if (serverPort === null) {\n timeout.clear()\n return null\n }\n\n const redirectUri = `http://127.0.0.1:${String(serverPort)}${options.callbackPath}`\n\n const fullAuthUrl = buildAuthUrl({\n authUrl: options.authUrl,\n clientId: options.clientId,\n codeChallenge,\n redirectUri,\n scopes: options.scopes,\n state,\n })\n\n openBrowser(fullAuthUrl)\n\n const timeoutPromise = timeout.promise.then((): null => {\n codeDeferred.resolve(null)\n destroyServer(handle.server, handle.sockets)\n return null\n })\n\n const code = await Promise.race([codeDeferred.promise, timeoutPromise])\n\n timeout.clear()\n\n if (!code) {\n destroyServer(handle.server, handle.sockets)\n return null\n }\n\n destroyServer(handle.server, handle.sockets)\n\n const token = await exchangeCodeForToken({\n clientId: options.clientId,\n code,\n codeVerifier,\n redirectUri,\n tokenUrl: options.tokenUrl,\n })\n\n return token\n}\n\n// ---------------------------------------------------------------------------\n// Private helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Generate a cryptographically random code verifier for PKCE.\n *\n * @private\n * @returns A base64url-encoded random string.\n */\nfunction generateCodeVerifier(): string {\n return randomBytes(32).toString('base64url')\n}\n\n/**\n * Derive a S256 code challenge from a code verifier.\n *\n * @private\n * @param verifier - The code verifier string.\n * @returns The base64url-encoded SHA-256 hash.\n */\nfunction deriveCodeChallenge(verifier: string): string {\n return createHash('sha256').update(verifier).digest('base64url')\n}\n\n/**\n * Build the full authorization URL with PKCE query parameters.\n *\n * @private\n * @param options - Authorization URL components.\n * @returns The complete authorization URL string.\n */\nfunction buildAuthUrl(options: {\n readonly authUrl: string\n readonly clientId: string\n readonly redirectUri: string\n readonly codeChallenge: string\n readonly state: string\n readonly scopes: readonly string[]\n}): string {\n const url = new URL(options.authUrl)\n url.searchParams.set('response_type', 'code')\n url.searchParams.set('client_id', options.clientId)\n url.searchParams.set('redirect_uri', options.redirectUri)\n url.searchParams.set('code_challenge', options.codeChallenge)\n url.searchParams.set('code_challenge_method', 'S256')\n url.searchParams.set('state', options.state)\n\n if (options.scopes.length > 0) {\n url.searchParams.set('scope', options.scopes.join(' '))\n }\n\n return url.toString()\n}\n\n/**\n * Handle an incoming HTTP request on the callback server.\n *\n * Accepts GET requests to the callback path with `code` and `state`\n * query parameters. Validates the state nonce and resolves the\n * authorization code.\n *\n * @private\n * @param req - The incoming HTTP request.\n * @param res - The server response.\n * @param callbackPath - The expected callback path.\n * @param expectedState - The state nonce to validate.\n * @param resolve - Callback to deliver the authorization code.\n */\nfunction handleCallback(\n req: IncomingMessage,\n res: ServerResponse,\n callbackPath: string,\n expectedState: string,\n resolve: (value: string | null) => void\n): void {\n const result = extractCodeFromUrl(req.url, callbackPath, expectedState)\n\n if (!result.ok) {\n res.writeHead(400)\n res.end()\n\n if (result.isOAuthError) {\n resolve(null)\n }\n\n return\n }\n\n sendSuccessPage(res)\n resolve(result.code)\n}\n\n/**\n * Result of extracting an authorization code from a callback URL.\n *\n * @private\n */\ntype ExtractCodeResult =\n | { readonly ok: true; readonly code: string }\n | { readonly ok: false; readonly isOAuthError: boolean }\n\n/**\n * Extract an authorization code from a request URL.\n *\n * Validates that the request path matches the callback path,\n * the `state` parameter matches the expected nonce, and a\n * `code` parameter is present. Detects OAuth error responses\n * (e.g. `?error=access_denied`) and flags them so the caller\n * can resolve immediately instead of waiting for the timeout.\n *\n * @private\n * @param reqUrl - The raw request URL string.\n * @param callbackPath - The expected callback path.\n * @param expectedState - The state nonce to validate.\n * @returns An extraction result with the code or error flag.\n */\nfunction extractCodeFromUrl(\n reqUrl: string | undefined,\n callbackPath: string,\n expectedState: string\n): ExtractCodeResult {\n const url = new URL(reqUrl ?? '/', 'http://localhost')\n\n if (url.pathname !== callbackPath) {\n return { isOAuthError: false, ok: false }\n }\n\n const state = url.searchParams.get('state')\n\n if (state !== expectedState) {\n return { isOAuthError: false, ok: false }\n }\n\n const error = url.searchParams.get('error')\n\n if (error) {\n return { isOAuthError: true, ok: false }\n }\n\n const code = url.searchParams.get('code')\n\n if (!code) {\n return { isOAuthError: false, ok: false }\n }\n\n return { code, ok: true }\n}\n\n/**\n * Exchange an authorization code for an access token at the token endpoint.\n *\n * Sends a POST request with `application/x-www-form-urlencoded` body\n * containing the authorization code, redirect URI, client ID, and\n * PKCE code verifier.\n *\n * @private\n * @param options - Token exchange parameters.\n * @returns A bearer credential on success, null on failure.\n */\nasync function exchangeCodeForToken(options: {\n readonly tokenUrl: string\n readonly code: string\n readonly redirectUri: string\n readonly clientId: string\n readonly codeVerifier: string\n}): Promise<AuthCredential | null> {\n const body = new URLSearchParams({\n client_id: options.clientId,\n code: options.code,\n code_verifier: options.codeVerifier,\n grant_type: 'authorization_code',\n redirect_uri: options.redirectUri,\n })\n\n const response = await postFormEncoded(options.tokenUrl, body)\n\n if (!response) {\n return null\n }\n\n if (!response.ok) {\n return null\n }\n\n const [parseError, data] = await attemptAsync((): Promise<unknown> => response.json())\n\n if (parseError) {\n return null\n }\n\n if (!isPlainObject(data)) {\n return null\n }\n\n if (typeof data.access_token !== 'string' || data.access_token === '') {\n return null\n }\n\n if (typeof data.token_type === 'string' && data.token_type.toLowerCase() !== 'bearer') {\n return null\n }\n\n return createBearerCredential(data.access_token)\n}\n","import { attemptAsync } from '@kidd-cli/utils/fp'\n\nimport type { Prompts } from '@/context/types.js'\n\nimport { createBearerCredential, isValidToken } from '../credential.js'\nimport type { AuthCredential } from '../types.js'\n\n/**\n * Resolve a bearer credential by interactively prompting the user.\n *\n * Uses `prompts.password()` to ask for an API key or token. Returns\n * null if the user cancels the prompt or provides an empty value.\n *\n * Should be placed last in the resolver chain as a fallback.\n *\n * @param options - Options with the prompt message and prompts instance.\n * @returns A bearer credential on input, null on cancellation.\n */\nexport async function resolveFromToken(options: {\n readonly message: string\n readonly prompts: Prompts\n}): Promise<AuthCredential | null> {\n const [promptError, token] = await attemptAsync(() =>\n options.prompts.password({ message: options.message })\n )\n\n if (promptError) {\n return null\n }\n\n if (!isValidToken(token)) {\n return null\n }\n\n return createBearerCredential(token)\n}\n","import { join } from 'node:path'\n\nimport { match } from 'ts-pattern'\n\nimport type { Prompts } from '@/context/types.js'\n\nimport {\n DEFAULT_AUTH_FILENAME,\n DEFAULT_DEVICE_CODE_POLL_INTERVAL,\n DEFAULT_DEVICE_CODE_TIMEOUT,\n DEFAULT_OAUTH_CALLBACK_PATH,\n DEFAULT_OAUTH_PORT,\n DEFAULT_OAUTH_TIMEOUT,\n deriveTokenVar,\n} from './constants.js'\nimport { resolveFromDeviceCode } from './strategies/device-code.js'\nimport { resolveFromDotenv } from './strategies/dotenv.js'\nimport { resolveFromEnv } from './strategies/env.js'\nimport { resolveFromFile } from './strategies/file.js'\nimport { resolveFromOAuth } from './strategies/oauth.js'\nimport { resolveFromToken } from './strategies/token.js'\nimport type { AuthCredential, StrategyConfig } from './types.js'\n\nconst DEFAULT_PROMPT_MESSAGE = 'Enter your API key'\n\n/**\n * Chain credential strategies, returning the first non-null result.\n *\n * Walks the strategy list in order, dispatching each config to the\n * appropriate strategy function via pattern matching. Short-circuits\n * on the first successful resolution.\n *\n * @param options - Options with strategies, CLI name, and prompts instance.\n * @returns The first resolved credential, or null if all strategies fail.\n */\nexport async function runStrategyChain(options: {\n readonly strategies: readonly StrategyConfig[]\n readonly cliName: string\n readonly prompts: Prompts\n}): Promise<AuthCredential | null> {\n const defaultTokenVar = deriveTokenVar(options.cliName)\n\n return tryStrategies(options.strategies, 0, defaultTokenVar, options)\n}\n\n/**\n * Return the given value when defined, otherwise the fallback.\n *\n * @param value - The optional value.\n * @param fallback - The default value.\n * @returns The resolved value.\n */\nexport function withDefault<T>(value: T | undefined, fallback: T): T {\n if (value !== undefined) {\n return value\n }\n return fallback\n}\n\n// ---------------------------------------------------------------------------\n// Private helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Recursively try strategies until one returns a credential or the list is exhausted.\n *\n * @private\n * @param configs - The strategy configs.\n * @param index - The current index.\n * @param defaultTokenVar - The derived default token env var name.\n * @param context - The resolve options for prompts access.\n * @returns The first resolved credential, or null.\n */\nasync function tryStrategies(\n configs: readonly StrategyConfig[],\n index: number,\n defaultTokenVar: string,\n context: {\n readonly cliName: string\n readonly prompts: Prompts\n }\n): Promise<AuthCredential | null> {\n if (index >= configs.length) {\n return null\n }\n\n const config = configs[index]\n\n if (config === undefined) {\n return null\n }\n\n const credential = await dispatchStrategy(config, defaultTokenVar, context)\n\n if (credential) {\n return credential\n }\n\n return tryStrategies(configs, index + 1, defaultTokenVar, context)\n}\n\n/**\n * Dispatch a single strategy config to its implementation.\n *\n * @private\n * @param config - The strategy config to dispatch.\n * @param defaultTokenVar - The derived default token env var name.\n * @param context - The resolve options for prompts access.\n * @returns The resolved credential, or null.\n */\nasync function dispatchStrategy(\n config: StrategyConfig,\n defaultTokenVar: string,\n context: {\n readonly cliName: string\n readonly prompts: Prompts\n }\n): Promise<AuthCredential | null> {\n return match(config)\n .with({ source: 'env' }, (c): AuthCredential | null =>\n resolveFromEnv({\n tokenVar: withDefault(c.tokenVar, defaultTokenVar),\n })\n )\n .with({ source: 'dotenv' }, (c): AuthCredential | null =>\n resolveFromDotenv({\n path: withDefault(c.path, join(process.cwd(), '.env')),\n tokenVar: withDefault(c.tokenVar, defaultTokenVar),\n })\n )\n .with({ source: 'file' }, (c): AuthCredential | null =>\n resolveFromFile({\n dirName: withDefault(c.dirName, `.${context.cliName}`),\n filename: withDefault(c.filename, DEFAULT_AUTH_FILENAME),\n })\n )\n .with(\n { source: 'oauth' },\n (c): Promise<AuthCredential | null> =>\n resolveFromOAuth({\n authUrl: c.authUrl,\n callbackPath: withDefault(c.callbackPath, DEFAULT_OAUTH_CALLBACK_PATH),\n clientId: c.clientId,\n port: withDefault(c.port, DEFAULT_OAUTH_PORT),\n scopes: withDefault(c.scopes, []),\n timeout: withDefault(c.timeout, DEFAULT_OAUTH_TIMEOUT),\n tokenUrl: c.tokenUrl,\n })\n )\n .with(\n { source: 'device-code' },\n (c): Promise<AuthCredential | null> =>\n resolveFromDeviceCode({\n clientId: c.clientId,\n deviceAuthUrl: c.deviceAuthUrl,\n openBrowserOnStart: withDefault(c.openBrowser, true),\n pollInterval: withDefault(c.pollInterval, DEFAULT_DEVICE_CODE_POLL_INTERVAL),\n prompts: context.prompts,\n scopes: withDefault(c.scopes, []),\n timeout: withDefault(c.timeout, DEFAULT_DEVICE_CODE_TIMEOUT),\n tokenUrl: c.tokenUrl,\n })\n )\n .with(\n { source: 'token' },\n (c): Promise<AuthCredential | null> =>\n resolveFromToken({\n message: withDefault(c.message, DEFAULT_PROMPT_MESSAGE),\n prompts: context.prompts,\n })\n )\n .with({ source: 'custom' }, (c): Promise<AuthCredential | null> | AuthCredential | null =>\n c.resolver()\n )\n .exhaustive()\n}\n","/**\n * Factory for the {@link AuthContext} object decorated onto `ctx.auth`.\n *\n * Closes over the middleware's strategy config, CLI name, prompts, and\n * a credential resolver function so that `login()` can run\n * interactive strategies and persist the result.\n *\n * @module\n */\n\nimport type { AsyncResult, Result } from '@kidd-cli/utils/fp'\nimport { ok } from '@kidd-cli/utils/fp'\n\nimport type { Prompts } from '@/context/types.js'\nimport { createStore } from '@/lib/store/create-store.js'\n\nimport { runStrategyChain } from './chain.js'\nimport { DEFAULT_AUTH_FILENAME } from './constants.js'\nimport type {\n AuthContext,\n AuthCredential,\n AuthError,\n LoginOptions,\n StrategyConfig,\n} from './types.js'\n\n/**\n * Options for {@link createAuthContext}.\n */\nexport interface CreateAuthContextOptions {\n readonly strategies: readonly StrategyConfig[]\n readonly cliName: string\n readonly prompts: Prompts\n readonly resolveCredential: () => AuthCredential | null\n}\n\n/**\n * Create an {@link AuthContext} value for `ctx.auth`.\n *\n * No credential data is stored on the returned object. `credential()`\n * resolves passively on every call, `authenticated()` checks existence,\n * `login()` runs the configured interactive strategies, saves the\n * credential to the global file store, and `logout()` removes it.\n *\n * @param options - Factory options.\n * @returns An AuthContext instance.\n */\nexport function createAuthContext(options: CreateAuthContextOptions): AuthContext {\n const { strategies, cliName, prompts, resolveCredential } = options\n\n /**\n * Resolve the current credential from passive sources (file, env).\n *\n * @private\n * @returns The credential, or null when none exists.\n */\n function credential(): AuthCredential | null {\n return resolveCredential()\n }\n\n /**\n * Check whether a credential is available from passive sources.\n *\n * @private\n * @returns True when a credential exists.\n */\n function authenticated(): boolean {\n return resolveCredential() !== null\n }\n\n /**\n * Run configured strategies interactively and persist the credential.\n *\n * When `loginOptions.strategies` is provided, those strategies are used\n * instead of the default configured list.\n *\n * @private\n * @param loginOptions - Optional overrides for the login attempt.\n * @returns A Result with the credential on success or an AuthError on failure.\n */\n async function login(loginOptions?: LoginOptions): AsyncResult<AuthCredential, AuthError> {\n const activeStrategies = resolveLoginStrategies(loginOptions, strategies)\n\n const resolved = await runStrategyChain({\n cliName,\n prompts,\n strategies: activeStrategies,\n })\n\n if (resolved === null) {\n return authError({\n message: 'No credential resolved from any source',\n type: 'no_credential',\n })\n }\n\n const store = createStore({ dirName: `.${cliName}` })\n const [saveError] = store.save(DEFAULT_AUTH_FILENAME, resolved)\n\n if (saveError) {\n return authError({\n message: `Failed to save credential: ${saveError.message}`,\n type: 'save_failed',\n })\n }\n\n return ok(resolved)\n }\n\n /**\n * Remove the stored credential from disk.\n *\n * @private\n * @returns A Result with the removed file path on success or an AuthError on failure.\n */\n async function logout(): AsyncResult<string, AuthError> {\n const store = createStore({ dirName: `.${cliName}` })\n const [removeError, filePath] = store.remove(DEFAULT_AUTH_FILENAME)\n\n if (removeError) {\n return authError({\n message: `Failed to remove credential: ${removeError.message}`,\n type: 'remove_failed',\n })\n }\n\n return ok(filePath)\n }\n\n return { authenticated, credential, login, logout }\n}\n\n// ---------------------------------------------------------------------------\n// Private helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Construct a failure Result tuple with an {@link AuthError}.\n *\n * @private\n * @param error - The auth error.\n * @returns A Result tuple `[AuthError, null]`.\n */\nfunction authError(error: AuthError): Result<never, AuthError> {\n return [error, null] as const\n}\n\n/**\n * Resolve the active strategies for a login attempt.\n *\n * Returns the override strategies from login options when provided,\n * otherwise falls back to the configured strategies.\n *\n * @private\n * @param loginOptions - Optional login overrides.\n * @param configured - The default configured strategies.\n * @returns The strategies to use for the login attempt.\n */\nfunction resolveLoginStrategies(\n loginOptions: LoginOptions | undefined,\n configured: readonly StrategyConfig[]\n): readonly StrategyConfig[] {\n if (loginOptions !== undefined && loginOptions.strategies !== undefined) {\n return loginOptions.strategies\n }\n\n return configured\n}\n","/**\n * Convert auth credentials into HTTP headers.\n *\n * Uses exhaustive pattern matching to map each credential variant to\n * the appropriate header format.\n *\n * @module\n */\n\nimport { Buffer } from 'node:buffer'\n\nimport { match } from 'ts-pattern'\n\nimport type { AuthCredential } from '../auth/types.js'\n\n/**\n * Convert an auth credential into HTTP headers.\n *\n * @param credential - The credential to convert.\n * @returns A record of header name to header value.\n */\nexport function buildAuthHeaders(credential: AuthCredential): Readonly<Record<string, string>> {\n return match(credential)\n .with({ type: 'bearer' }, (c) => ({\n Authorization: `Bearer ${c.token}`,\n }))\n .with({ type: 'basic' }, (c) => ({\n Authorization: `Basic ${Buffer.from(`${c.username}:${c.password}`).toString('base64')}`,\n }))\n .with({ type: 'api-key' }, (c) => ({\n [c.headerName]: c.key,\n }))\n .with({ type: 'custom' }, (c) => ({ ...c.headers }))\n .exhaustive()\n}\n","/**\n * Factory for a header-resolver function that reads credentials from `ctx.auth`.\n *\n * @module\n */\n\nimport type { Context } from '@/context/types.js'\n\nimport { buildAuthHeaders } from '../http/build-auth-headers.js'\nimport type { AuthContext } from './types.js'\n\n/**\n * Create a function that resolves auth credentials from `ctx.auth` into HTTP headers.\n *\n * The returned function reads `ctx.auth.credential()` and converts the credential\n * into the appropriate header format using `buildAuthHeaders()`. Returns an empty\n * record when no auth middleware is present or no credential exists.\n *\n * @returns A function that takes a Context and returns auth headers.\n */\nexport function createAuthHeaders(): (ctx: Context) => Readonly<Record<string, string>> {\n return function resolveHeaders(ctx: Context): Readonly<Record<string, string>> {\n if (!('auth' in ctx)) {\n return {}\n }\n\n const authCtx = (ctx as Context & { readonly auth: AuthContext }).auth\n const credential = authCtx.credential()\n\n if (credential === null) {\n return {}\n }\n\n return buildAuthHeaders(credential)\n }\n}\n","/**\n * Enforcement gate middleware that requires authentication.\n *\n * @module\n */\n\nimport { middleware } from '@/middleware.js'\nimport type { Middleware } from '@/types.js'\n\nconst DEFAULT_MESSAGE = 'Authentication required.'\n\n/**\n * Options for {@link createAuthRequire}.\n */\nexport interface AuthRequireOptions {\n readonly message?: string\n}\n\n/**\n * Create an enforcement middleware that gates on authentication.\n *\n * When `ctx.auth.authenticated()` returns true, the middleware calls\n * `next()`. When not authenticated, it calls `ctx.fail()` with the\n * provided (or default) message. When `ctx.auth` is absent (auth\n * middleware not configured), it calls `ctx.fail()` with an\n * `AUTH_MIDDLEWARE_MISSING` code.\n *\n * @param options - Optional configuration for the require gate.\n * @returns A Middleware that enforces authentication.\n */\nexport function createAuthRequire(options?: AuthRequireOptions): Middleware {\n const message = resolveMessage(options)\n\n return middleware((ctx, next) => {\n if (!hasProperty(ctx, 'auth')) {\n ctx.fail('auth.require() must run after auth() middleware', {\n code: 'AUTH_MIDDLEWARE_MISSING',\n })\n }\n\n if (!ctx.auth.authenticated()) {\n ctx.fail(message, { code: 'AUTH_REQUIRED' })\n }\n\n return next()\n })\n}\n\n// ---------------------------------------------------------------------------\n// Private helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Runtime property check that avoids TypeScript's `in` narrowing.\n *\n * The `Context` interface declares `auth` via module augmentation,\n * so `!('auth' in ctx)` narrows to `never`. This function uses an\n * `unknown` cast to bypass the narrowing and perform a pure runtime\n * check.\n *\n * @private\n * @param obj - The object to inspect.\n * @param key - The property name to check.\n * @returns True when the property exists on the object.\n */\nfunction hasProperty(obj: unknown, key: string): boolean {\n return typeof obj === 'object' && obj !== null && key in obj\n}\n\n/**\n * Resolve the failure message from optional require options.\n *\n * @private\n * @param options - Optional require gate options.\n * @returns The resolved message string.\n */\nfunction resolveMessage(options: AuthRequireOptions | undefined): string {\n if (options !== undefined && options.message !== undefined) {\n return options.message\n }\n\n return DEFAULT_MESSAGE\n}\n","/**\n * Auth middleware factory with strategy builder functions.\n *\n * Decorates `ctx.auth` with functions to resolve credentials on demand\n * and run interactive authentication.\n *\n * @module\n */\n\nimport { join } from 'node:path'\n\nimport { decorateContext } from '@/context/decorate.js'\nimport type { Context } from '@/context/types.js'\nimport { middleware } from '@/middleware.js'\nimport type { Middleware } from '@/types.js'\n\nimport { withDefault } from './chain.js'\nimport { DEFAULT_AUTH_FILENAME, deriveTokenVar } from './constants.js'\nimport { createAuthContext } from './context.js'\nimport { createAuthHeaders } from './headers.js'\nimport { createAuthRequire } from './require.js'\nimport type { AuthRequireOptions } from './require.js'\nimport { resolveFromDotenv } from './strategies/dotenv.js'\nimport { resolveFromEnv } from './strategies/env.js'\nimport { resolveFromFile } from './strategies/file.js'\nimport type {\n AuthCredential,\n AuthOptions,\n CustomSourceConfig,\n CustomStrategyFn,\n DeviceCodeSourceConfig,\n DeviceCodeStrategyOptions,\n DotenvSourceConfig,\n DotenvStrategyOptions,\n EnvSourceConfig,\n EnvStrategyOptions,\n FileSourceConfig,\n FileStrategyOptions,\n OAuthSourceConfig,\n OAuthStrategyOptions,\n StrategyConfig,\n TokenSourceConfig,\n TokenStrategyOptions,\n} from './types.js'\n\n/**\n * Auth factory interface — callable as a middleware factory and as a\n * namespace for strategy builder functions.\n */\nexport interface AuthFactory {\n (options: AuthOptions): Middleware\n readonly env: (options?: EnvStrategyOptions) => EnvSourceConfig\n readonly dotenv: (options?: DotenvStrategyOptions) => DotenvSourceConfig\n readonly file: (options?: FileStrategyOptions) => FileSourceConfig\n readonly oauth: (options: OAuthStrategyOptions) => OAuthSourceConfig\n readonly deviceCode: (options: DeviceCodeStrategyOptions) => DeviceCodeSourceConfig\n readonly token: (options?: TokenStrategyOptions) => TokenSourceConfig\n readonly apiKey: (options?: TokenStrategyOptions) => TokenSourceConfig\n readonly custom: (fn: CustomStrategyFn) => CustomSourceConfig\n readonly headers: () => (ctx: Context) => Readonly<Record<string, string>>\n readonly require: (options?: AuthRequireOptions) => Middleware\n}\n\n/**\n * Create an auth middleware that decorates `ctx.auth`.\n *\n * No credential data is stored on the context. `ctx.auth.credential()`\n * resolves passively from three sources on every call:\n * 1. File — `~/.cli-name/auth.json`\n * 2. Dotenv — `.env` file (when configured)\n * 3. Env — `CLI_NAME_TOKEN`\n *\n * Interactive strategies (OAuth, device-code, token, custom) only run when the\n * command handler explicitly calls `ctx.auth.login()`.\n *\n * @param options - Auth middleware configuration.\n * @returns A Middleware that decorates ctx.auth.\n */\nfunction createAuth(options: AuthOptions): Middleware {\n const { strategies } = options\n\n return middleware((ctx, next) => {\n const cliName = ctx.meta.name\n\n const authContext = createAuthContext({\n cliName,\n prompts: ctx.prompts,\n resolveCredential: () => resolveStoredCredential(cliName, strategies),\n strategies,\n })\n\n decorateContext(ctx, 'auth', authContext)\n\n return next()\n })\n}\n\n/**\n * Auth middleware factory with strategy builder methods.\n *\n * Use as `auth({ strategies: [...] })` to create middleware, or use\n * the builder methods (`auth.env()`, `auth.oauth()`, etc.) to construct\n * strategy configs with a cleaner API.\n */\nexport const auth: AuthFactory = Object.assign(createAuth, {\n apiKey: buildToken,\n custom: buildCustom,\n deviceCode: buildDeviceCode,\n dotenv: buildDotenv,\n env: buildEnv,\n file: buildFile,\n headers: createAuthHeaders,\n oauth: buildOAuth,\n require: createAuthRequire,\n token: buildToken,\n})\n\n// ---------------------------------------------------------------------------\n// Strategy builders\n// ---------------------------------------------------------------------------\n\n/**\n * Build an env strategy config.\n *\n * @private\n * @param options - Optional env strategy options.\n * @returns An EnvSourceConfig with `source: 'env'`.\n */\nfunction buildEnv(options?: EnvStrategyOptions): EnvSourceConfig {\n return { ...options, source: 'env' as const }\n}\n\n/**\n * Build a dotenv strategy config.\n *\n * @private\n * @param options - Optional dotenv strategy options.\n * @returns A DotenvSourceConfig with `source: 'dotenv'`.\n */\nfunction buildDotenv(options?: DotenvStrategyOptions): DotenvSourceConfig {\n return { ...options, source: 'dotenv' as const }\n}\n\n/**\n * Build a file strategy config.\n *\n * @private\n * @param options - Optional file strategy options.\n * @returns A FileSourceConfig with `source: 'file'`.\n */\nfunction buildFile(options?: FileStrategyOptions): FileSourceConfig {\n return { ...options, source: 'file' as const }\n}\n\n/**\n * Build an OAuth strategy config.\n *\n * @private\n * @param options - OAuth strategy options (clientId, authUrl, tokenUrl required).\n * @returns An OAuthSourceConfig with `source: 'oauth'`.\n */\nfunction buildOAuth(options: OAuthStrategyOptions): OAuthSourceConfig {\n return { ...options, source: 'oauth' as const }\n}\n\n/**\n * Build a device code strategy config.\n *\n * @private\n * @param options - Device code strategy options (clientId, deviceAuthUrl, tokenUrl required).\n * @returns A DeviceCodeSourceConfig with `source: 'device-code'`.\n */\nfunction buildDeviceCode(options: DeviceCodeStrategyOptions): DeviceCodeSourceConfig {\n return { ...options, source: 'device-code' as const }\n}\n\n/**\n * Build a token strategy config.\n *\n * Prompts the user for a token interactively. Aliased as `auth.apiKey()`.\n *\n * @private\n * @param options - Optional token strategy options.\n * @returns A TokenSourceConfig with `source: 'token'`.\n */\nfunction buildToken(options?: TokenStrategyOptions): TokenSourceConfig {\n return { ...options, source: 'token' as const }\n}\n\n/**\n * Build a custom strategy config from a strategy function.\n *\n * @private\n * @param fn - The custom strategy function.\n * @returns A CustomSourceConfig with `source: 'custom'`.\n */\nfunction buildCustom(fn: CustomStrategyFn): CustomSourceConfig {\n return { resolver: fn, source: 'custom' as const }\n}\n\n// ---------------------------------------------------------------------------\n// Private helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Attempt to resolve a credential from stored (non-interactive) sources.\n *\n * Checks the file store first, then dotenv, then falls back to the\n * environment variable. Scans the strategy list for `file`, `dotenv`,\n * and `env` source configs to respect user-configured overrides\n * (e.g. a custom `tokenVar`, `dirName`, or dotenv `path`).\n *\n * @private\n * @param cliName - The CLI name, used to derive paths and env var names.\n * @param strategies - The configured strategy list for extracting overrides.\n * @returns The resolved credential, or null.\n */\nfunction resolveStoredCredential(\n cliName: string,\n strategies: readonly StrategyConfig[]\n): AuthCredential | null {\n const fileConfig = findStrategyBySource(strategies, 'file')\n const dotenvConfig = findStrategyBySource(strategies, 'dotenv')\n const envConfig = findStrategyBySource(strategies, 'env')\n const defaultTokenVar = deriveTokenVar(cliName)\n\n const fromFile = resolveFromFile({\n dirName: withDefault(extractProp(fileConfig, 'dirName'), `.${cliName}`),\n filename: withDefault(extractProp(fileConfig, 'filename'), DEFAULT_AUTH_FILENAME),\n })\n\n if (fromFile) {\n return fromFile\n }\n\n if (dotenvConfig !== undefined) {\n const fromDotenv = resolveFromDotenv({\n path: withDefault(extractProp(dotenvConfig, 'path'), join(process.cwd(), '.env')),\n tokenVar: withDefault(extractProp(dotenvConfig, 'tokenVar'), defaultTokenVar),\n })\n\n if (fromDotenv) {\n return fromDotenv\n }\n }\n\n return resolveFromEnv({\n tokenVar: withDefault(extractProp(envConfig, 'tokenVar'), defaultTokenVar),\n })\n}\n\n/**\n * Find the first strategy config matching a given source type.\n *\n * @private\n * @param strategies - The strategy config list.\n * @param source - The source type to find.\n * @returns The matching config, or undefined.\n */\nfunction findStrategyBySource<TSource extends StrategyConfig['source']>(\n strategies: readonly StrategyConfig[],\n source: TSource\n): Extract<StrategyConfig, { readonly source: TSource }> | undefined {\n return strategies.find(\n (r): r is Extract<StrategyConfig, { readonly source: TSource }> => r.source === source\n )\n}\n\n/**\n * Safely extract a property from an optional config object.\n *\n * Returns the property value when the config is defined, or undefined\n * when the config itself is undefined.\n *\n * @private\n * @param config - The config object, or undefined.\n * @param key - The property key to extract.\n * @returns The property value, or undefined.\n */\nfunction extractProp<TConfig extends object, TKey extends keyof TConfig>(\n config: TConfig | undefined,\n key: TKey\n): TConfig[TKey] | undefined {\n if (config === undefined) {\n return undefined\n }\n\n return config[key]\n}\n"],"mappings":"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"}
@@ -1,16 +1,7 @@
1
- import { s as Middleware } from "../types-kjpRau0U.js";
2
- import { n as AuthCredential } from "../types-CqKJhsYk.js";
1
+ import { c as Middleware, d as Context } from "../types-CTvrsrnD.js";
3
2
 
4
3
  //#region src/middleware/http/types.d.ts
5
4
  /**
6
- * HTTP middleware types.
7
- *
8
- * Defines the typed HTTP client interface, request/response wrappers,
9
- * and configuration options for the {@link http} middleware factory.
10
- *
11
- * @module
12
- */
13
- /**
14
5
  * Typed response wrapper returned by all client methods.
15
6
  *
16
7
  * @typeParam TData - The parsed JSON body type.
@@ -45,11 +36,15 @@ interface HttpClient {
45
36
  }
46
37
  /**
47
38
  * Options for the {@link http} middleware factory.
39
+ *
40
+ * The standalone `http()` middleware does not read from `ctx.auth`.
41
+ * Use `auth({ http: { ... } })` for automatic credential injection,
42
+ * or pass headers directly via the `headers` option.
48
43
  */
49
44
  interface HttpOptions {
50
45
  readonly namespace: string;
51
46
  readonly baseUrl: string;
52
- readonly defaultHeaders?: Readonly<Record<string, string>>;
47
+ readonly headers?: Readonly<Record<string, string>> | ((ctx: Context) => Readonly<Record<string, string>>);
53
48
  }
54
49
  //#endregion
55
50
  //#region src/middleware/http/http.d.ts
@@ -57,9 +52,8 @@ interface HttpOptions {
57
52
  * Create an HTTP client middleware that decorates the context
58
53
  * with a typed client.
59
54
  *
60
- * Reads auth credentials from `ctx.auth.credential()` (set by the auth
61
- * middleware), builds a typed {@link HttpClient}, and attaches it to
62
- * `ctx[namespace]`.
55
+ * Resolves headers from the `headers` option (static record or function),
56
+ * builds a typed {@link HttpClient}, and attaches it to `ctx[namespace]`.
63
57
  *
64
58
  * @param options - HTTP middleware configuration.
65
59
  * @returns A Middleware that adds an HttpClient to ctx[namespace].
@@ -72,11 +66,11 @@ declare function http(options: HttpOptions): Middleware;
72
66
  */
73
67
  interface CreateHttpClientOptions {
74
68
  readonly baseUrl: string;
75
- readonly credential?: AuthCredential;
76
69
  readonly defaultHeaders?: Readonly<Record<string, string>>;
70
+ readonly resolveHeaders?: () => Readonly<Record<string, string>>;
77
71
  }
78
72
  /**
79
- * Create a typed HTTP client with pre-configured base URL, auth, and headers.
73
+ * Create a typed HTTP client with pre-configured base URL and headers.
80
74
  *
81
75
  * @param options - Client configuration.
82
76
  * @returns An HttpClient instance.
@@ -1 +1 @@
1
- {"version":3,"file":"http.d.ts","names":[],"sources":["../../src/middleware/http/types.ts","../../src/middleware/http/http.ts","../../src/middleware/http/create-http-client.ts"],"mappings":";;;;;;;;;AAkBA;;;;;;;;UAAiB,aAAA;EAAA,SACN,IAAA,EAAM,KAAA;EAAA,SACN,MAAA;EAAA,SACA,OAAA,EAAS,OAAA;EAAA,SACT,EAAA;EAAA,SACA,GAAA,EAAK,QAAA;AAAA;;;;;AAYhB;UAAiB,cAAA;EAAA,SACN,IAAA,GAAO,KAAA;EAAA,SACP,OAAA,GAAU,QAAA,CAAS,MAAA;EAAA,SACnB,MAAA,GAAS,QAAA,CAAS,MAAA;EAAA,SAClB,MAAA,GAAS,WAAA;AAAA;;;;UAUH,UAAA;EACf,GAAA,sBACE,IAAA,UACA,OAAA,GAAU,cAAA,GACT,OAAA,CAAQ,aAAA,CAAc,SAAA;EAEzB,IAAA,uCACE,IAAA,UACA,OAAA,GAAU,cAAA,CAAe,KAAA,IACxB,OAAA,CAAQ,aAAA,CAAc,SAAA;EAEzB,GAAA,uCACE,IAAA,UACA,OAAA,GAAU,cAAA,CAAe,KAAA,IACxB,OAAA,CAAQ,aAAA,CAAc,SAAA;EAEzB,KAAA,uCACE,IAAA,UACA,OAAA,GAAU,cAAA,CAAe,KAAA,IACxB,OAAA,CAAQ,aAAA,CAAc,SAAA;EAEzB,MAAA,sBACE,IAAA,UACA,OAAA,GAAU,cAAA,GACT,OAAA,CAAQ,aAAA,CAAc,SAAA;AAAA;;;;UAUV,WAAA;EAAA,SACN,SAAA;EAAA,SACA,OAAA;EAAA,SACA,cAAA,GAAiB,QAAA,CAAS,MAAA;AAAA;;;;;;AApErC;;;;;;;;iBCWgB,IAAA,CAAK,OAAA,EAAS,WAAA,GAAc,UAAA;;;;;;UCVlC,uBAAA;EAAA,SACC,OAAA;EAAA,SACA,UAAA,GAAa,cAAA;EAAA,SACb,cAAA,GAAiB,QAAA,CAAS,MAAA;AAAA;;;;;;;iBASrB,gBAAA,CAAiB,OAAA,EAAS,uBAAA,GAA0B,UAAA"}
1
+ {"version":3,"file":"http.d.ts","names":[],"sources":["../../src/middleware/http/types.ts","../../src/middleware/http/http.ts","../../src/middleware/http/create-http-client.ts"],"mappings":";;;;;;;;UAoBiB,aAAA;EAAA,SACN,IAAA,EAAM,KAAA;EAAA,SACN,MAAA;EAAA,SACA,OAAA,EAAS,OAAA;EAAA,SACT,EAAA;EAAA,SACA,GAAA,EAAK,QAAA;AAAA;;AAYhB;;;;UAAiB,cAAA;EAAA,SACN,IAAA,GAAO,KAAA;EAAA,SACP,OAAA,GAAU,QAAA,CAAS,MAAA;EAAA,SACnB,MAAA,GAAS,QAAA,CAAS,MAAA;EAAA,SAClB,MAAA,GAAS,WAAA;AAAA;;;;UAUH,UAAA;EACf,GAAA,sBACE,IAAA,UACA,OAAA,GAAU,cAAA,GACT,OAAA,CAAQ,aAAA,CAAc,SAAA;EAEzB,IAAA,uCACE,IAAA,UACA,OAAA,GAAU,cAAA,CAAe,KAAA,IACxB,OAAA,CAAQ,aAAA,CAAc,SAAA;EAEzB,GAAA,uCACE,IAAA,UACA,OAAA,GAAU,cAAA,CAAe,KAAA,IACxB,OAAA,CAAQ,aAAA,CAAc,SAAA;EAEzB,KAAA,uCACE,IAAA,UACA,OAAA,GAAU,cAAA,CAAe,KAAA,IACxB,OAAA,CAAQ,aAAA,CAAc,SAAA;EAEzB,MAAA,sBACE,IAAA,UACA,OAAA,GAAU,cAAA,GACT,OAAA,CAAQ,aAAA,CAAc,SAAA;AAAA;;;;;;;;UAcV,WAAA;EAAA,SACN,SAAA;EAAA,SACA,OAAA;EAAA,SACA,OAAA,GACL,QAAA,CAAS,MAAA,sBACP,GAAA,EAAK,OAAA,KAAY,QAAA,CAAS,MAAA;AAAA;;;;AA1ElC;;;;;;;;;iBCUgB,IAAA,CAAK,OAAA,EAAS,WAAA,GAAc,UAAA;;;;;ADV5C;UEJU,uBAAA;EAAA,SACC,OAAA;EAAA,SACA,cAAA,GAAiB,QAAA,CAAS,MAAA;EAAA,SAC1B,cAAA,SAAuB,QAAA,CAAS,MAAA;AAAA;;;;;;;iBAS3B,gBAAA,CAAiB,OAAA,EAAS,uBAAA,GAA0B,UAAA"}
@@ -1,52 +1,28 @@
1
- import { n as decorateContext, t as middleware } from "../middleware-D3psyhYo.js";
1
+ import { n as decorateContext, t as middleware } from "../middleware-BWnPSRWR.js";
2
2
  import { attemptAsync } from "@kidd-cli/utils/fp";
3
- import { match as match$1 } from "ts-pattern";
4
- import { Buffer } from "node:buffer";
5
-
6
- //#region src/middleware/http/build-auth-headers.ts
7
- /**
8
- * Convert auth credentials into HTTP headers.
9
- *
10
- * Uses exhaustive pattern matching to map each credential variant to
11
- * the appropriate header format.
12
- *
13
- * @module
14
- */
15
- /**
16
- * Convert an auth credential into HTTP headers.
17
- *
18
- * @param credential - The credential to convert.
19
- * @returns A record of header name to header value.
20
- */
21
- function buildAuthHeaders(credential) {
22
- return match$1(credential).with({ type: "bearer" }, (c) => ({ Authorization: `Bearer ${c.token}` })).with({ type: "basic" }, (c) => ({ Authorization: `Basic ${Buffer.from(`${c.username}:${c.password}`).toString("base64")}` })).with({ type: "api-key" }, (c) => ({ [c.headerName]: c.key })).with({ type: "custom" }, (c) => ({ ...c.headers })).exhaustive();
23
- }
24
-
25
- //#endregion
26
3
  //#region src/middleware/http/create-http-client.ts
27
4
  /**
28
5
  * Typed HTTP client factory.
29
6
  *
30
- * Creates a closure-based {@link HttpClient} with pre-configured base URL,
31
- * auth credentials, and default headers. All methods delegate to a shared
32
- * request executor.
7
+ * Creates a closure-based {@link HttpClient} with pre-configured base URL
8
+ * and default headers. All methods delegate to a shared request executor.
33
9
  *
34
10
  * @module
35
11
  */
36
12
  /**
37
- * Create a typed HTTP client with pre-configured base URL, auth, and headers.
13
+ * Create a typed HTTP client with pre-configured base URL and headers.
38
14
  *
39
15
  * @param options - Client configuration.
40
16
  * @returns An HttpClient instance.
41
17
  */
42
18
  function createHttpClient(options) {
43
- const { baseUrl, credential, defaultHeaders } = options;
19
+ const { baseUrl, defaultHeaders, resolveHeaders } = options;
44
20
  return {
45
- delete: (path, requestOptions) => executeRequest(baseUrl, "DELETE", path, credential, defaultHeaders, requestOptions),
46
- get: (path, requestOptions) => executeRequest(baseUrl, "GET", path, credential, defaultHeaders, requestOptions),
47
- patch: (path, requestOptions) => executeRequest(baseUrl, "PATCH", path, credential, defaultHeaders, requestOptions),
48
- post: (path, requestOptions) => executeRequest(baseUrl, "POST", path, credential, defaultHeaders, requestOptions),
49
- put: (path, requestOptions) => executeRequest(baseUrl, "PUT", path, credential, defaultHeaders, requestOptions)
21
+ delete: (path, requestOptions) => executeRequest(baseUrl, "DELETE", path, defaultHeaders, resolveHeaders, requestOptions),
22
+ get: (path, requestOptions) => executeRequest(baseUrl, "GET", path, defaultHeaders, resolveHeaders, requestOptions),
23
+ patch: (path, requestOptions) => executeRequest(baseUrl, "PATCH", path, defaultHeaders, resolveHeaders, requestOptions),
24
+ post: (path, requestOptions) => executeRequest(baseUrl, "POST", path, defaultHeaders, resolveHeaders, requestOptions),
25
+ put: (path, requestOptions) => executeRequest(baseUrl, "PUT", path, defaultHeaders, resolveHeaders, requestOptions)
50
26
  };
51
27
  }
52
28
  /**
@@ -64,65 +40,47 @@ function buildUrl(baseUrl, path, params) {
64
40
  return url.toString();
65
41
  }
66
42
  /**
67
- * Resolve auth headers from a credential, returning an empty record
68
- * when no credential is provided.
43
+ * Merge default, dynamic, and per-request headers into a single record.
69
44
  *
70
- * @private
71
- * @param credential - Optional auth credential.
72
- * @returns A record of auth headers.
73
- */
74
- function resolveAuthHeaders(credential) {
75
- if (credential !== void 0) return buildAuthHeaders(credential);
76
- return {};
77
- }
78
- /**
79
- * Merge auth, default, and per-request headers into a single record.
80
- *
81
- * Per-request headers take highest priority, then default headers,
82
- * then auth headers.
45
+ * Per-request headers take highest priority, then dynamic headers,
46
+ * then default headers.
83
47
  *
84
48
  * @private
85
- * @param credential - Optional auth credential.
86
49
  * @param defaultHeaders - Optional default headers.
50
+ * @param dynamicHeaders - Optional dynamically resolved headers.
87
51
  * @param requestHeaders - Optional per-request headers.
88
52
  * @returns The merged headers record.
89
53
  */
90
- function mergeHeaders(credential, defaultHeaders, requestHeaders) {
54
+ function mergeHeaders(defaultHeaders, dynamicHeaders, requestHeaders) {
91
55
  return {
92
- ...resolveAuthHeaders(credential),
93
56
  ...defaultHeaders,
57
+ ...dynamicHeaders,
94
58
  ...requestHeaders
95
59
  };
96
60
  }
97
61
  /**
98
- * Extract the params field from request options if present.
62
+ * Normalize optional request options into a concrete object with safe defaults.
99
63
  *
100
- * @private
101
- * @param options - Optional per-request options.
102
- * @returns The params record or undefined.
103
- */
104
- function extractParams(options) {
105
- if (options !== void 0) return options.params;
106
- }
107
- /**
108
- * Extract the headers field from request options if present.
64
+ * When `options` is `undefined`, returns an empty object so callers can use
65
+ * direct property access without additional nil checks.
109
66
  *
110
67
  * @private
111
68
  * @param options - Optional per-request options.
112
- * @returns The headers record or undefined.
69
+ * @returns The resolved options object.
113
70
  */
114
- function extractHeaders(options) {
115
- if (options !== void 0) return options.headers;
71
+ function resolveRequestOptions(options) {
72
+ if (options !== void 0) return options;
73
+ return {};
116
74
  }
117
75
  /**
118
- * Extract the signal field from request options if present.
76
+ * Invoke the dynamic header resolver if provided.
119
77
  *
120
78
  * @private
121
- * @param options - Optional per-request options.
122
- * @returns The AbortSignal or undefined.
79
+ * @param resolveHeaders - Optional function to resolve dynamic headers.
80
+ * @returns The resolved headers record, or undefined.
123
81
  */
124
- function extractSignal(options) {
125
- if (options !== void 0) return options.signal;
82
+ function resolveDynamicHeaders(resolveHeaders) {
83
+ if (resolveHeaders !== void 0) return resolveHeaders();
126
84
  }
127
85
  /**
128
86
  * Resolve the serialized body string and content-type header mutation.
@@ -167,14 +125,15 @@ function buildFetchInit(method, headers, body, signal) {
167
125
  * @param baseUrl - The base URL.
168
126
  * @param method - The HTTP method.
169
127
  * @param path - The request path.
170
- * @param credential - Optional auth credential.
171
128
  * @param defaultHeaders - Optional default headers.
129
+ * @param resolveHeaders - Optional function to resolve dynamic headers per-request.
172
130
  * @param options - Optional per-request options.
173
131
  * @returns A typed response wrapper.
174
132
  */
175
- async function executeRequest(baseUrl, method, path, credential, defaultHeaders, options) {
176
- const url = buildUrl(baseUrl, path, extractParams(options));
177
- const init = buildFetchInit(method, mergeHeaders(credential, defaultHeaders, extractHeaders(options)), resolveBody(options), extractSignal(options));
133
+ async function executeRequest(baseUrl, method, path, defaultHeaders, resolveHeaders, options) {
134
+ const resolved = resolveRequestOptions(options);
135
+ const url = buildUrl(baseUrl, path, resolved.params);
136
+ const init = buildFetchInit(method, mergeHeaders(defaultHeaders, resolveDynamicHeaders(resolveHeaders), resolved.headers), resolveBody(options), resolved.signal);
178
137
  const response = await fetch(url, init);
179
138
  return {
180
139
  data: await parseResponseBody(response),
@@ -200,14 +159,16 @@ async function parseResponseBody(response) {
200
159
  if (error) return null;
201
160
  return data;
202
161
  }
203
-
204
162
  //#endregion
205
163
  //#region src/middleware/http/http.ts
206
164
  /**
207
165
  * HTTP client middleware factory.
208
166
  *
209
167
  * Creates a middleware that decorates the context with a typed
210
- * {@link HttpClient} bound to a base URL and optional auth credentials.
168
+ * {@link HttpClient} bound to a base URL and optional headers.
169
+ *
170
+ * This middleware is fully decoupled from auth. For automatic credential
171
+ * injection, use `auth({ http: { ... } })` instead.
211
172
  *
212
173
  * @module
213
174
  */
@@ -215,41 +176,39 @@ async function parseResponseBody(response) {
215
176
  * Create an HTTP client middleware that decorates the context
216
177
  * with a typed client.
217
178
  *
218
- * Reads auth credentials from `ctx.auth.credential()` (set by the auth
219
- * middleware), builds a typed {@link HttpClient}, and attaches it to
220
- * `ctx[namespace]`.
179
+ * Resolves headers from the `headers` option (static record or function),
180
+ * builds a typed {@link HttpClient}, and attaches it to `ctx[namespace]`.
221
181
  *
222
182
  * @param options - HTTP middleware configuration.
223
183
  * @returns A Middleware that adds an HttpClient to ctx[namespace].
224
184
  */
225
185
  function http(options) {
226
- const { namespace, baseUrl, defaultHeaders } = options;
227
- return middleware(async (ctx, next) => {
186
+ const { namespace, baseUrl, headers } = options;
187
+ return middleware((ctx, next) => {
228
188
  decorateContext(ctx, namespace, createHttpClient({
229
189
  baseUrl,
230
- credential: resolveCredential(ctx),
231
- defaultHeaders
190
+ defaultHeaders: resolveHeaders(ctx, headers)
232
191
  }));
233
- await next();
192
+ return next();
234
193
  });
235
194
  }
236
195
  /**
237
- * Resolve the auth credential from the context.
196
+ * Resolve headers from the options value.
238
197
  *
239
- * Calls `ctx.auth.credential()` when the auth middleware has run.
240
- * Returns undefined when no auth context is available.
198
+ * Calls the function form with ctx when provided, returns static headers
199
+ * directly, or returns undefined when no headers are configured.
241
200
  *
242
201
  * @private
243
202
  * @param ctx - The context object.
244
- * @returns The credential or undefined.
203
+ * @param headers - The headers option (static, function, or undefined).
204
+ * @returns The resolved headers record or undefined.
245
205
  */
246
- function resolveCredential(ctx) {
247
- if (ctx.auth === void 0) return;
248
- const cred = ctx.auth.credential();
249
- if (cred === null) return;
250
- return cred;
206
+ function resolveHeaders(ctx, headers) {
207
+ if (headers === void 0) return;
208
+ if (typeof headers === "function") return headers(ctx);
209
+ return headers;
251
210
  }
252
-
253
211
  //#endregion
254
212
  export { createHttpClient, http };
213
+
255
214
  //# sourceMappingURL=http.js.map