import type { CommandSpec } from './command.ts'; // What can go wrong, and the exit code each kind of failure leaves. // // Agents branch on exit codes, so they are few and stable: // // 0 done // 1 anything else that went wrong (a server error, a file that is not there) // 2 the command line was wrong: an unknown option, a missing argument // 3 not signed in, or the session cannot be used and needs a new login // 4 the thing named does not exist: an unknown handle, a deleted post // 5 the server said to slow down, for longer than is worth waiting // 6 a question needed answering and there was no terminal to ask on // // With --json, every failure is also printed on stdout as // {"error": {"code", "message", "status"?, "exit"}}, so a caller that reads only // stdout still learns what happened. export const EXIT = { ok: 0, error: 1, usage: 2, auth: 3, notFound: 4, rateLimited: 5, needsInput: 6, } as const; export class CliError extends Error { exit: number; code: string; constructor(message: string, code = 'Error', exit: number = EXIT.error) { super(message); this.code = code; this.exit = exit; } } export class UsageError extends CliError { /** The command the mistake was made in, so its help can be pointed at. */ path: CommandSpec[]; constructor(message: string, path: CommandSpec[] = []) { super(message, 'UsageError', EXIT.usage); this.path = path; } } export class AuthError extends CliError { constructor(message: string, code = 'AuthRequired') { super(message, code, EXIT.auth); } } export class NotFoundError extends CliError { constructor(message: string, code = 'NotFound') { super(message, code, EXIT.notFound); } } export class NeedsInputError extends CliError { constructor(message: string) { super(message, 'NeedsInput', EXIT.needsInput); } } /** An error an XRPC service answered with. */ export class XrpcError extends CliError { status: number; nsid: string; constructor(nsid: string, status: number, code: string, message: string) { super(message, code, exitFor(status, code)); this.status = status; this.nsid = nsid; } } function exitFor(status: number, code: string): number { if (status === 401 || /^(AuthRequired|AuthMissing|ExpiredToken|InvalidToken|invalid_token|AccountTakedown|AccountDeactivated)$/.test(code)) return EXIT.auth; if (status === 404 || /NotFound$|^(UnknownFeed|UnknownList|InvalidUri)$/.test(code)) return EXIT.notFound; if (status === 429) return EXIT.rateLimited; return EXIT.error; }