/** * API Client for CLI Commands * Issue #518: HTTP communication layer with auth, error handling * * [DR1-01] Responsibilities separated: * - resolveAuthToken(): Token resolution (--token > CM_AUTH_TOKEN) * - handleApiError(): Error classification and user-friendly messages * - ApiClient: HTTP get/post with base URL and auth headers */ /** Maximum stop-pattern length [SEC4-06] */ export declare const MAX_STOP_PATTERN_LENGTH = 500; /** * Validate worktree ID format [SEC4-04] * @param id - Worktree ID to validate * @returns True if valid */ export declare function isValidWorktreeId(id: string): boolean; /** * Validate agent instance ID format (Issue #868). * @param id - Instance ID to validate * @returns True if valid */ export declare function isValidInstanceId(id: string): boolean; /** * Resolve authentication token from options or environment. * [SEC4-01] Warns on stderr when --token flag is used. * * @param options - Options object with optional token * @returns Resolved token or undefined */ export declare function resolveAuthToken(options?: { token?: string; }): string | undefined; /** * Error result from API operations */ export interface ApiErrorResult { message: string; exitCode: number; } /** * What this function knows about the request, beyond its status (Issue #2404). * * `handleApiError` is a pure classifier over `(status, payload)` and has no way * to know which server answered — which is exactly the fact a 404 needs. The * caller's `Resource not found. Check the worktree ID.` sent an agent looking * for a typo in an id that was correct: the CLI was dialling a *different* * CommandMate server than the one that had started its session, and the id it * was asking about lived on the other one. Optional so the two callers that * classify without a client ({@link ApiClient} aside, `server-capabilities.ts`) * keep compiling and keep their wording. */ export interface ApiErrorContext { /** The base URL the failing request was actually sent to. */ serverUrl?: string; } /** * Classify API errors into user-friendly messages and exit codes. * [IA3-09] Covers: ECONNREFUSED, 400, 401/403, 404, 429, 500, timeout * * @param error - Error object or unknown * @param status - HTTP status code if available * @param payload - Parsed error body, when the response carried one (Issue #1637). * Used for 5xx only: the 4xx messages below are already specific, and are * pinned by tests as the CLI's own wording. * @param context - Which server answered, when the caller knows (Issue #2404). * Read by the 404 branch only; the other messages do not send anyone looking * in the wrong place. * @returns User-friendly error message and exit code */ export declare function handleApiError(error: unknown, status?: number, payload?: ApiErrorPayload, context?: ApiErrorContext): ApiErrorResult; /** * API Client for CLI HTTP communication. * Uses Node.js built-in fetch. Handles auth headers and error mapping. */ export declare class ApiClient { private baseUrl; private token?; constructor(options?: { baseUrl?: string; token?: string; }); /** * The base URL this client actually dials (Issue #2404). * * Resolved once in the constructor through the precedence in * `loadClientEnv()` (`--base-url` > exported `CM_PORT` > `~/.commandmate/.env` * > 3000), which is why it is read from here rather than re-derived by each * caller: a second derivation is a second answer, and "which server did this * command talk to" only helps if it is the one the request went to. */ get serverUrl(): string; private getHeaders; /** * GET without any status handling, for callers that must read the raw response. * * Issue #1925: the server-capability probe has to tell a real 404 (an older * daemon that predates the endpoint) from a 302 to /login (this request never * authenticated) and from an HTML body served by something in the middle. * {@link get} collapses all three into an exception with a message, which is * exactly the distinction the probe exists to make — so it issues its own * request, with `redirect: 'manual'` so a redirect stays visible instead of * being followed to a 200 that parses as nothing. * * @param path - Path appended to the client's base URL * @param init - Extra request headers merged over the client's own * @returns The raw Response, whatever its status */ rawGet(path: string, init?: { headers?: Record; }): Promise; /** * HTTP GET request * [DR1-05] Generic type parameter specified at call site */ get(path: string): Promise; /** * HTTP POST request * [DR1-05] Generic type parameter specified at call site */ post(path: string, body?: unknown): Promise; /** * HTTP PATCH request (Issue #1000: agent-instance roster mutations via * PATCH /api/worktrees/[id]). * [DR1-05] Generic type parameter specified at call site */ patch(path: string, body?: unknown): Promise; } /** * Machine-readable part of a failed API response (Issue #1237). * Every Skill route answers `{ error, code }`; the uninstall routes add `blockers`. */ export interface ApiErrorPayload { code?: string; error?: string; blockers?: Array<{ code: string; path: string | null; }>; /** Issue #1544: run already in flight, sent with the verify route's 409. */ runningRunId?: number; /** * Issue #1545: every task-contract violation, sent with the tasks route's 400. * Printed in full so a broken contract is fixed in one pass, not one round * trip per mistake. */ issues?: string[]; } /** * API Error with exit code for CLI process.exit() */ export declare class ApiError extends Error { readonly exitCode: number; readonly statusCode?: number | undefined; /** Typed error body, when the server sent one (Issue #1237). */ readonly payload?: ApiErrorPayload | undefined; constructor(message: string, exitCode: number, statusCode?: number | undefined, /** Typed error body, when the server sent one (Issue #1237). */ payload?: ApiErrorPayload | undefined); /** Stable machine code the server assigned to this failure, if any. */ get apiCode(): string | undefined; } /** * Assert that a parsed API response contains the given required top-level fields * (Issue #1357). * * get/post/patch cast the parsed body with `as T` and perform no runtime * validation, so a response from a stale daemon that omits a field silently * degrades to `undefined` downstream (e.g. a missing roster read as an empty * roster). Route responses whose fields you depend on through this guard so a * missing field surfaces as an actionable version-skew error instead of a silent * wrong value. * * @param value - Parsed response body * @param fields - Required top-level field names * @param context - Short endpoint/response label included in the error message * @returns value, typed as T, when it is an object and every required field is present * @throws ApiError when value is not an object or a required field is absent */ export declare function assertResponseShape(value: unknown, fields: ReadonlyArray, context: string): T; /** * Read the version the running daemon reports via GET /api/app/update-check. * Issue #1359 made that field a runtime package.json read, so it reflects the * daemon's actual running code rather than a build-time constant. * * Returns undefined when the endpoint is unreachable or omits currentVersion (an * older daemon predating the field) — callers treat that as "unknown", never as * an error. Never throws. */ export declare function fetchDaemonVersion(client: ApiClient): Promise; /** * Compare this CLI's version against the running daemon's and print a stderr * warning when they differ (Issue #1357). Advisory only: never throws, and stays * silent when either version is unknown. Intended to run once per CLI invocation * that connects to the server, so a stale daemon serving an older API is called * out before its responses are misread. */ export declare function warnIfVersionSkew(client: ApiClient): Promise; //# sourceMappingURL=api-client.d.ts.map