UNPKG

aiwg

Version:

Deployment tool and support utility for AI context. Copies agents, skills, commands, rules, and behaviors into the paths each AI platform reads (Claude Code, Codex, Copilot, Cursor, Warp, OpenClaw, and 6 more) so one source of truth works across 10 platfo

150 lines 5.64 kB
/** * AIWG Structured Errors * * Every intentional error thrown by CLI code should be an `AiwgError` with: * - `code` — stable UPPER_SNAKE_CASE identifier (ERR_CATEGORY_REASON) * - `exitCode` — process exit code (see EXIT_CODES table below) * - `hint` — optional one-line user-facing hint with a suggested next action * - `cause` — optional underlying error for chain preservation (ES2022) * * The top-level formatter in `bin/aiwg.mjs` prints a single-line message and * only reveals stack traces when `AIWG_DEBUG=1` or `--verbose` is set. * * Code naming: category-prefixed uppercase snake_case. * ERR_CONFIG_* — problems reading/writing .aiwg/aiwg.config * ERR_NETWORK_* — timeouts, unreachable services, HTTP errors * ERR_USAGE_* — bad flags, unknown commands, missing required args * ERR_FS_* — filesystem errors (permission, ENOENT outside config) * ERR_SANDBOX_* — sandbox-specific failures * ERR_INTERNAL — unexpected bugs (last resort; prefer a specific code) * * Phase 4 of the CLI Stabilization Epic (#921). */ /** * Process exit codes used by the CLI. Pragmatic subset of sysexits.h — * enough categorization for scripts to branch on without full sysexits * ceremony. */ export const EXIT_CODES = { /** Command completed successfully. */ OK: 0, /** General / uncategorized failure. */ GENERAL: 1, /** User error: unknown command, bad flag, missing required argument. */ USAGE: 2, /** Filesystem write failure: permission denied, disk full, read-only. */ CANT_CREATE: 73, /** Config file missing, malformed, or otherwise unusable. */ CONFIG: 78, /** Interrupted by SIGINT (Ctrl-C). 128 + 2. */ INTERRUPTED: 130, /** Terminated by SIGTERM. 128 + 15. */ TERMINATED: 143, }; /** * Structured CLI error with a stable code and a prescribed exit code. * * Always throw these from handler code instead of bare `Error` so the * top-level formatter can render consistent output and so tests can assert * on the error code rather than brittle message strings. */ export class AiwgError extends Error { code; exitCode; hint; constructor(init) { super(init.message, init.cause !== undefined ? { cause: init.cause } : undefined); this.name = 'AiwgError'; this.code = init.code; this.exitCode = init.exitCode ?? EXIT_CODES.GENERAL; if (init.hint !== undefined) this.hint = init.hint; // Stabilize stack trace capture across engines. if (typeof Error.captureStackTrace === 'function') { Error.captureStackTrace(this, AiwgError); } } } /** * Convenience checker — ts-narrows an unknown to AiwgError. Avoids importing * the class just for a typeof-like check at catch sites. */ export function isAiwgError(err) { return err instanceof AiwgError; } /** * Format an error for terminal output. Always returns a single-line primary * message; `hint` (if present) is returned as a second line; `stack` and the * `cause` chain are included only when verbose is true. * * Does NOT color the output — caller chooses whether to apply ui.red() etc. * so non-TTY stderr paths stay ANSI-clean. */ export function formatError(err, opts = {}) { const lines = []; if (isAiwgError(err)) { lines.push(`aiwg: error: ${err.message} (code: ${err.code})`); if (err.hint) lines.push(` hint: ${err.hint}`); if (opts.verbose) { if (err.stack) lines.push(err.stack); if (err.cause) { lines.push(' cause:'); lines.push(formatError(err.cause, { verbose: true })); } } } else if (err instanceof Error) { lines.push(`aiwg: error: ${err.message}`); if (opts.verbose && err.stack) lines.push(err.stack); } else { lines.push(`aiwg: error: ${String(err)}`); } return lines.join('\n'); } /** * Resolve the exit code to use for an error. Non-AiwgError throws and plain * strings map to EXIT_CODES.GENERAL. */ export function exitCodeFor(err) { if (isAiwgError(err)) return err.exitCode; return EXIT_CODES.GENERAL; } /** * Convert any thrown value into a uniform HandlerResult shape, preserving * AiwgError's semantic fields (code, exitCode, hint) where present. * * This is the bridge between the in-handler `try { ... } catch (err) { ... }` * pattern and the HandlerResult contract the router expects. Use it in every * handler's top-level catch: * * try { * await doWork(ctx); * return { exitCode: 0 }; * } catch (err) { * return handlerResultFromError(err); * } * * Non-AiwgError throws map to `{ exitCode: 1, message, error }`. AiwgError * throws carry their `exitCode` (USAGE=2, CONFIG=78, CANT_CREATE=73, etc.) * plus the `hint` merged into the message for single-line output. * * Phase 5 of the CLI Stabilization Epic (#922) — unblocks the exit-code * propagation from Phase 4 (#921) that was previously flattened to 1. */ export function handlerResultFromError(err) { if (isAiwgError(err)) { const message = err.hint ? `${err.message} — hint: ${err.hint}` : err.message; return { exitCode: err.exitCode, message, error: err }; } if (err instanceof Error) { return { exitCode: EXIT_CODES.GENERAL, message: err.message, error: err }; } const message = String(err); return { exitCode: EXIT_CODES.GENERAL, message, error: new Error(message) }; } //# sourceMappingURL=errors.js.map