@almightygpt/core 0.9.1 → 0.9.2

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 (40) hide show
  1. package/dist/adapters/claude.d.ts.map +1 -1
  2. package/dist/adapters/claude.js +3 -1
  3. package/dist/adapters/claude.js.map +1 -1
  4. package/dist/adapters/defaults.d.ts +34 -0
  5. package/dist/adapters/defaults.d.ts.map +1 -0
  6. package/dist/adapters/defaults.js +41 -0
  7. package/dist/adapters/defaults.js.map +1 -0
  8. package/dist/adapters/gemini.d.ts.map +1 -1
  9. package/dist/adapters/gemini.js +3 -1
  10. package/dist/adapters/gemini.js.map +1 -1
  11. package/dist/adapters/openai.d.ts.map +1 -1
  12. package/dist/adapters/openai.js +3 -1
  13. package/dist/adapters/openai.js.map +1 -1
  14. package/dist/auth/keychain.d.ts +11 -1
  15. package/dist/auth/keychain.d.ts.map +1 -1
  16. package/dist/auth/keychain.js +13 -4
  17. package/dist/auth/keychain.js.map +1 -1
  18. package/dist/auth/resolver.d.ts.map +1 -1
  19. package/dist/auth/resolver.js +19 -3
  20. package/dist/auth/resolver.js.map +1 -1
  21. package/dist/auth/types.d.ts +18 -5
  22. package/dist/auth/types.d.ts.map +1 -1
  23. package/dist/auth/types.js +8 -6
  24. package/dist/auth/types.js.map +1 -1
  25. package/dist/auth/validator.d.ts +23 -3
  26. package/dist/auth/validator.d.ts.map +1 -1
  27. package/dist/auth/validator.js +126 -21
  28. package/dist/auth/validator.js.map +1 -1
  29. package/dist/index.d.ts +1 -1
  30. package/dist/index.js +1 -1
  31. package/package.json +1 -1
  32. package/src/adapters/claude.ts +3 -1
  33. package/src/adapters/defaults.ts +44 -0
  34. package/src/adapters/gemini.ts +3 -1
  35. package/src/adapters/openai.ts +3 -1
  36. package/src/auth/keychain.ts +20 -6
  37. package/src/auth/resolver.ts +23 -3
  38. package/src/auth/types.ts +27 -8
  39. package/src/auth/validator.ts +149 -25
  40. package/src/index.ts +1 -1
@@ -13,14 +13,20 @@
13
13
  * - Google: tiny generateContent call against gemini-2.5-flash
14
14
  *
15
15
  * Cost is fractions of a cent per call. Latency is ~1-3 seconds.
16
- * Failure surfaces the provider's exact error message so the user
17
- * can fix it.
16
+ *
17
+ * **Error handling (per Codex v0.8 P2 #6):** failure responses are
18
+ * parsed into a short user-safe message + status code. We never
19
+ * return the raw provider body to the user — those bodies can include
20
+ * request IDs, billing/account state, org/project details, quota
21
+ * metadata, or other account information that's too noisy for CLI
22
+ * logs. The raw body is available via the `rawBody` field for callers
23
+ * that explicitly want it (e.g. debug mode).
24
+ *
25
+ * **Default models (per Codex v0.8 P2 #5):** sourced from
26
+ * `adapters/defaults.ts` — the single source of truth — so the
27
+ * validator can't drift from the adapters.
18
28
  */
19
- const DEFAULT_VALIDATION_MODELS = {
20
- openai: "gpt-4o",
21
- anthropic: "claude-sonnet-4-6",
22
- google: "gemini-2.5-flash",
23
- };
29
+ import { DEFAULT_MODELS } from "../adapters/defaults.js";
24
30
  const VALIDATION_TIMEOUT_MS = 15_000;
25
31
  /**
26
32
  * Validate that a key can actually invoke the model real reviews use.
@@ -28,25 +34,33 @@ const VALIDATION_TIMEOUT_MS = 15_000;
28
34
  * on programmer errors like an unsupported provider).
29
35
  */
30
36
  export async function validateKey(provider, key) {
37
+ const start = Date.now();
31
38
  try {
39
+ let result;
32
40
  switch (provider) {
33
41
  case "openai":
34
- return await validateOpenAI(key);
42
+ result = await validateOpenAI(key);
43
+ break;
35
44
  case "anthropic":
36
- return await validateAnthropic(key);
45
+ result = await validateAnthropic(key);
46
+ break;
37
47
  case "google":
38
- return await validateGoogle(key);
48
+ result = await validateGoogle(key);
49
+ break;
39
50
  }
51
+ result.latencyMs = Date.now() - start;
52
+ return result;
40
53
  }
41
54
  catch (err) {
42
55
  return {
43
56
  ok: false,
44
- error: err instanceof Error ? err.message : String(err),
57
+ error: friendlyNetworkError(err),
58
+ latencyMs: Date.now() - start,
45
59
  };
46
60
  }
47
61
  }
48
62
  async function validateOpenAI(key) {
49
- const model = DEFAULT_VALIDATION_MODELS.openai;
63
+ const model = DEFAULT_MODELS.openai;
50
64
  const controller = new AbortController();
51
65
  const timer = setTimeout(() => controller.abort(), VALIDATION_TIMEOUT_MS);
52
66
  try {
@@ -64,7 +78,13 @@ async function validateOpenAI(key) {
64
78
  signal: controller.signal,
65
79
  });
66
80
  if (!res.ok) {
67
- return { ok: false, error: await res.text().catch(() => res.statusText) };
81
+ const rawBody = await res.text().catch(() => "");
82
+ return {
83
+ ok: false,
84
+ statusCode: res.status,
85
+ error: normalizeOpenAIError(res.status, rawBody),
86
+ rawBody,
87
+ };
68
88
  }
69
89
  const data = (await res.json());
70
90
  return { ok: true, model: data.model ?? model };
@@ -74,7 +94,7 @@ async function validateOpenAI(key) {
74
94
  }
75
95
  }
76
96
  async function validateAnthropic(key) {
77
- const model = DEFAULT_VALIDATION_MODELS.anthropic;
97
+ const model = DEFAULT_MODELS.anthropic;
78
98
  const controller = new AbortController();
79
99
  const timer = setTimeout(() => controller.abort(), VALIDATION_TIMEOUT_MS);
80
100
  try {
@@ -93,7 +113,13 @@ async function validateAnthropic(key) {
93
113
  signal: controller.signal,
94
114
  });
95
115
  if (!res.ok) {
96
- return { ok: false, error: await res.text().catch(() => res.statusText) };
116
+ const rawBody = await res.text().catch(() => "");
117
+ return {
118
+ ok: false,
119
+ statusCode: res.status,
120
+ error: normalizeAnthropicError(res.status, rawBody),
121
+ rawBody,
122
+ };
97
123
  }
98
124
  const data = (await res.json());
99
125
  return { ok: true, model: data.model ?? model };
@@ -103,14 +129,11 @@ async function validateAnthropic(key) {
103
129
  }
104
130
  }
105
131
  async function validateGoogle(key) {
106
- const model = DEFAULT_VALIDATION_MODELS.google;
132
+ const model = DEFAULT_MODELS.google;
107
133
  const controller = new AbortController();
108
134
  const timer = setTimeout(() => controller.abort(), VALIDATION_TIMEOUT_MS);
109
135
  try {
110
- // Use x-goog-api-key header rather than ?key= URL parameter. URLs
111
- // leak into proxy logs, HTTP tooling, crash diagnostics. The header
112
- // path is supported by all v1beta endpoints. Codex's v0.8 security
113
- // review flagged the URL-key approach as a P1.
136
+ // x-goog-api-key header (per v0.9.1 fix) — never the URL query.
114
137
  const url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent`;
115
138
  const res = await fetch(url, {
116
139
  method: "POST",
@@ -125,7 +148,13 @@ async function validateGoogle(key) {
125
148
  signal: controller.signal,
126
149
  });
127
150
  if (!res.ok) {
128
- return { ok: false, error: await res.text().catch(() => res.statusText) };
151
+ const rawBody = await res.text().catch(() => "");
152
+ return {
153
+ ok: false,
154
+ statusCode: res.status,
155
+ error: normalizeGoogleError(res.status, rawBody, key),
156
+ rawBody,
157
+ };
129
158
  }
130
159
  return { ok: true, model };
131
160
  }
@@ -133,4 +162,80 @@ async function validateGoogle(key) {
133
162
  clearTimeout(timer);
134
163
  }
135
164
  }
165
+ // ─── Error normalization (Codex v0.8 P2 #6) ──────────────────────────
166
+ //
167
+ // Parse known provider JSON error shapes into short, user-safe messages.
168
+ // Never echo the raw key back even by accident (defense in depth: we
169
+ // also redact anything that looks like the submitted key).
170
+ function normalizeOpenAIError(status, rawBody) {
171
+ // OpenAI shape: { "error": { "message": "...", "type": "...", "code": "..." } }
172
+ try {
173
+ const parsed = JSON.parse(rawBody);
174
+ const msg = parsed.error?.message;
175
+ if (msg)
176
+ return `[${status}] OpenAI: ${truncate(msg, 200)}`;
177
+ }
178
+ catch {
179
+ /* fall through */
180
+ }
181
+ return statusOnlyMessage("OpenAI", status);
182
+ }
183
+ function normalizeAnthropicError(status, rawBody) {
184
+ // Anthropic shape: { "type": "error", "error": { "type": "...", "message": "..." } }
185
+ try {
186
+ const parsed = JSON.parse(rawBody);
187
+ const msg = parsed.error?.message;
188
+ if (msg)
189
+ return `[${status}] Anthropic: ${truncate(msg, 200)}`;
190
+ }
191
+ catch {
192
+ /* fall through */
193
+ }
194
+ return statusOnlyMessage("Anthropic", status);
195
+ }
196
+ function normalizeGoogleError(status, rawBody, submittedKey) {
197
+ // Google shape: { "error": { "code": N, "message": "...", "status": "..." } }
198
+ try {
199
+ const parsed = JSON.parse(rawBody);
200
+ let msg = parsed.error?.message ?? "";
201
+ // Belt-and-braces redaction: Google sometimes echoes the key in
202
+ // error messages (e.g. "API key not valid. Pass a valid API key.")
203
+ // — we don't ship the actual key value if it ever ends up here.
204
+ if (submittedKey && msg.includes(submittedKey)) {
205
+ msg = msg.replace(submittedKey, "<redacted-key>");
206
+ }
207
+ if (msg)
208
+ return `[${status}] Google: ${truncate(msg, 200)}`;
209
+ }
210
+ catch {
211
+ /* fall through */
212
+ }
213
+ return statusOnlyMessage("Google", status);
214
+ }
215
+ function statusOnlyMessage(provider, status) {
216
+ if (status === 401 || status === 403) {
217
+ return `[${status}] ${provider} rejected the key (unauthorized).`;
218
+ }
219
+ if (status === 429) {
220
+ return `[${status}] ${provider} rate-limited or quota exceeded.`;
221
+ }
222
+ if (status >= 500) {
223
+ return `[${status}] ${provider} returned a server error; try again.`;
224
+ }
225
+ return `[${status}] ${provider} returned an unrecognized error.`;
226
+ }
227
+ function truncate(s, n) {
228
+ return s.length <= n ? s : s.slice(0, n - 1) + "…";
229
+ }
230
+ function friendlyNetworkError(err) {
231
+ const msg = err instanceof Error ? err.message : String(err);
232
+ if (msg.includes("abort")) {
233
+ return "Validation timed out (network or provider too slow).";
234
+ }
235
+ if (msg.includes("ENOTFOUND") || msg.includes("ECONNREFUSED")) {
236
+ return "Could not reach the provider (network issue).";
237
+ }
238
+ // Generic — but never include random stack data; just the message.
239
+ return `Network error: ${truncate(msg, 200)}`;
240
+ }
136
241
  //# sourceMappingURL=validator.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"validator.js","sourceRoot":"","sources":["../../src/auth/validator.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"validator.js","sourceRoot":"","sources":["../../src/auth/validator.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -12,7 +12,7 @@
12
12
  * - review/ ✅ task #11 diff review pipeline (with #12/#13/#14 wiring)
13
13
  * - budget/ ✅ task #14 BudgetTracker + BudgetExceededError
14
14
  */
15
- export declare const VERSION = "0.9.1";
15
+ export declare const VERSION = "0.9.2";
16
16
  export { startMcpServer } from "./mcp/server.js";
17
17
  export { checkGitStatus, assertSafeToWrite, GitStatusDirtyError, type GitStatusCheck, } from "./git/status.js";
18
18
  export { installTemplate, hasExistingConfig, type InstallOptions, type InstallResult, } from "./templates/install.js";
package/dist/index.js CHANGED
@@ -12,7 +12,7 @@
12
12
  * - review/ ✅ task #11 diff review pipeline (with #12/#13/#14 wiring)
13
13
  * - budget/ ✅ task #14 BudgetTracker + BudgetExceededError
14
14
  */
15
- export const VERSION = "0.9.1";
15
+ export const VERSION = "0.9.2";
16
16
  // MCP server (v0.9.0+) — exposes AlmightyGPT's review surface as MCP tools.
17
17
  export { startMcpServer } from "./mcp/server.js";
18
18
  // Git safety primitives
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@almightygpt/core",
3
- "version": "0.9.1",
3
+ "version": "0.9.2",
4
4
  "description": "Core orchestrator, adapters, config, runs, and review logic for AlmightyGPT",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -22,6 +22,7 @@ import {
22
22
  type AdapterOutput,
23
23
  } from "./types.js";
24
24
  import { resolveApiKey } from "../auth/resolver.js";
25
+ import { DEFAULT_MODELS } from "./defaults.js";
25
26
 
26
27
  const PRICING_USD_PER_1M: Record<string, { input: number; output: number }> = {
27
28
  // Claude 4.x family (current)
@@ -35,7 +36,8 @@ const PRICING_USD_PER_1M: Record<string, { input: number; output: number }> = {
35
36
  "claude-3-opus": { input: 15.0, output: 75.0 },
36
37
  };
37
38
 
38
- const DEFAULT_MODEL = "claude-sonnet-4-6";
39
+ // Default lives in adapters/defaults.ts so validator + adapter can't drift.
40
+ const DEFAULT_MODEL = DEFAULT_MODELS.anthropic;
39
41
 
40
42
  export interface ClaudeAdapterOptions {
41
43
  apiKey?: string;
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Single source of truth for each adapter's default model.
3
+ *
4
+ * Per Codex's v0.8 security review (P2): the validator previously
5
+ * hardcoded a separate `DEFAULT_VALIDATION_MODELS` constant that
6
+ * could drift from the adapters' defaults. We already paid the
7
+ * cost of one drift incident (v0.7.1, the `claude-3-5-sonnet-latest`
8
+ * 404 after Anthropic retired the alias) — having two drift surfaces
9
+ * for the same fact would have made the next one worse.
10
+ *
11
+ * Now both the adapters AND the validator import from here. Updating
12
+ * a default in one place updates everywhere.
13
+ *
14
+ * When you change a default model:
15
+ * 1. Update the value here.
16
+ * 2. Confirm the pricing entry in the adapter still covers the new
17
+ * model (`startsWith` matching means `claude-sonnet-5-X` still
18
+ * hits the `claude-sonnet-5` pricing entry, but a new family
19
+ * needs a new entry).
20
+ * 3. Update the comment in the relevant adapter's header block.
21
+ */
22
+
23
+ import type { ProviderId } from "../auth/types.js";
24
+
25
+ /**
26
+ * Default model per provider used by both the adapter (when no
27
+ * per-call override is passed) and the auth validator (the
28
+ * model-level validation call).
29
+ */
30
+ export const DEFAULT_MODELS: Record<ProviderId, string> = {
31
+ openai: "gpt-4o",
32
+ anthropic: "claude-sonnet-4-6",
33
+ google: "gemini-2.5-flash",
34
+ };
35
+
36
+ /**
37
+ * Pretty name for each provider — used in CLI prompts, error
38
+ * messages, and the wizard UI.
39
+ */
40
+ export const PROVIDER_PRETTY_NAME: Record<ProviderId, string> = {
41
+ openai: "OpenAI",
42
+ anthropic: "Anthropic",
43
+ google: "Google",
44
+ };
@@ -30,6 +30,7 @@ import {
30
30
  type AdapterOutput,
31
31
  } from "./types.js";
32
32
  import { resolveApiKey } from "../auth/resolver.js";
33
+ import { DEFAULT_MODELS } from "./defaults.js";
33
34
 
34
35
  const PRICING_USD_PER_1M: Record<string, { input: number; output: number }> = {
35
36
  "gemini-2.5-pro": { input: 1.25, output: 10.0 },
@@ -46,7 +47,8 @@ const PRICING_USD_PER_1M: Record<string, { input: number; output: number }> = {
46
47
  // field lands in v0.3) or by constructing the adapter directly. For code
47
48
  // review of typical-sized diffs, flash quality is sufficient — pro pays for
48
49
  // itself only on very large diffs or complex reasoning chains.
49
- const DEFAULT_MODEL = "gemini-2.5-flash";
50
+ // Default lives in adapters/defaults.ts so validator + adapter can't drift.
51
+ const DEFAULT_MODEL = DEFAULT_MODELS.google;
50
52
 
51
53
  export interface GeminiAdapterOptions {
52
54
  apiKey?: string;
@@ -15,6 +15,7 @@
15
15
  import OpenAI from "openai";
16
16
  import { AdapterError, type Adapter, type AdapterInput, type AdapterOutput } from "./types.js";
17
17
  import { resolveApiKey } from "../auth/resolver.js";
18
+ import { DEFAULT_MODELS } from "./defaults.js";
18
19
 
19
20
  /** USD per 1M tokens, by model. Lowercased keys. */
20
21
  const PRICING_USD_PER_1M: Record<string, { input: number; output: number }> = {
@@ -24,7 +25,8 @@ const PRICING_USD_PER_1M: Record<string, { input: number; output: number }> = {
24
25
  "gpt-3.5-turbo": { input: 0.5, output: 1.5 },
25
26
  };
26
27
 
27
- const DEFAULT_MODEL = "gpt-4o";
28
+ // Default lives in adapters/defaults.ts so validator + adapter can't drift.
29
+ const DEFAULT_MODEL = DEFAULT_MODELS.openai;
28
30
 
29
31
  export interface OpenAIAdapterOptions {
30
32
  /** Override the API key source. Defaults to process.env.OPENAI_API_KEY. */
@@ -20,9 +20,15 @@ import type { ProviderId } from "./types.js";
20
20
 
21
21
  const KEYCHAIN_SERVICE = "almightygpt";
22
22
 
23
+ /** Result of a keychain `get` — distinguishes "no entry" from "read failed". */
24
+ export type KeychainGetResult =
25
+ | { kind: "found"; key: string }
26
+ | { kind: "absent" }
27
+ | { kind: "error"; message: string };
28
+
23
29
  export interface KeychainAdapter {
24
30
  available: boolean;
25
- get(provider: ProviderId): Promise<string | undefined>;
31
+ get(provider: ProviderId): Promise<KeychainGetResult>;
26
32
  set(provider: ProviderId, key: string): Promise<void>;
27
33
  remove(provider: ProviderId): Promise<boolean>;
28
34
  /** Optional diagnostic — backend name, or "unavailable". */
@@ -68,12 +74,20 @@ async function loadKeychain(): Promise<KeychainAdapter> {
68
74
  return {
69
75
  available: true,
70
76
  describeBackend: () => detectBackend(),
71
- async get(provider) {
77
+ async get(provider): Promise<KeychainGetResult> {
72
78
  try {
73
79
  const v = entryFor(provider).getPassword();
74
- return v === null ? undefined : v;
75
- } catch {
76
- return undefined;
80
+ if (v === null || v === undefined) return { kind: "absent" };
81
+ return { kind: "found", key: v };
82
+ } catch (err) {
83
+ // Real read failure — do NOT collapse into "absent". Codex's
84
+ // v0.8 review (P2 #4): silent fallthrough hid macOS Keychain
85
+ // denied access, Secret Service unavailability, corrupted
86
+ // entries, native-binding faults. Bubble up instead.
87
+ return {
88
+ kind: "error",
89
+ message: err instanceof Error ? err.message : String(err),
90
+ };
77
91
  }
78
92
  },
79
93
  async set(provider, key) {
@@ -99,7 +113,7 @@ function makeUnavailable(reason: string): KeychainAdapter {
99
113
  available: false,
100
114
  describeBackend: () => `unavailable (${reason})`,
101
115
  async get() {
102
- return undefined;
116
+ return { kind: "absent" };
103
117
  },
104
118
  async set() {
105
119
  throw new Error(
@@ -53,10 +53,20 @@ export async function resolveApiKey(
53
53
  if (!options.skipKeychain) {
54
54
  const keychain = await getKeychain();
55
55
  if (keychain.available) {
56
- const v = await keychain.get(provider);
57
- if (v && v.length > 0) {
58
- return { provider, source: "keychain", key: v };
56
+ const result = await keychain.get(provider);
57
+ if (result.kind === "found" && result.key.length > 0) {
58
+ return { provider, source: "keychain", key: result.key };
59
59
  }
60
+ if (result.kind === "error") {
61
+ // Per Codex's v0.8 review (P2 #4) — surface as a distinct state
62
+ // so the user sees "stored but couldn't be read" not "missing".
63
+ return {
64
+ provider,
65
+ source: "keychain_error",
66
+ keychainError: result.message,
67
+ };
68
+ }
69
+ // kind === "absent" → fall through to "missing".
60
70
  }
61
71
  }
62
72
 
@@ -77,5 +87,15 @@ export async function requireApiKey(
77
87
  const primaryEnv = PROVIDER_ENV_VARS[provider][0]!;
78
88
  throw new AuthMissingError(provider, primaryEnv);
79
89
  }
90
+ if (resolved.source === "keychain_error") {
91
+ const primaryEnv = PROVIDER_ENV_VARS[provider][0]!;
92
+ throw new AuthMissingError(
93
+ provider,
94
+ primaryEnv,
95
+ `Keychain read failed: ${resolved.keychainError ?? "unknown error"}. ` +
96
+ `Set ${primaryEnv} as a per-process override, or fix the keychain ` +
97
+ `state (the entry exists but the OS denied or failed the read).`,
98
+ );
99
+ }
80
100
  return resolved.key!;
81
101
  }
package/src/auth/types.ts CHANGED
@@ -19,29 +19,48 @@
19
19
 
20
20
  export type ProviderId = "openai" | "anthropic" | "google";
21
21
 
22
- export type KeySource = "explicit" | "env" | "keychain" | "missing";
22
+ /**
23
+ * Where a resolved API key came from.
24
+ *
25
+ * `keychain_error` is a distinct state from `missing` — per Codex's
26
+ * v0.8 security review, silently treating a keychain read failure as
27
+ * "no key" hides important states (denied access, daemon unavailable,
28
+ * corrupted entry, native binding fault). Callers can choose to fall
29
+ * back or surface the error, but the resolver no longer pretends the
30
+ * key simply doesn't exist.
31
+ */
32
+ export type KeySource =
33
+ | "explicit"
34
+ | "env"
35
+ | "keychain"
36
+ | "keychain_error"
37
+ | "missing";
23
38
 
24
39
  export interface KeyResolution {
25
40
  provider: ProviderId;
26
41
  source: KeySource;
27
- /** Present iff source !== "missing". */
42
+ /** Present iff source resolves to a usable key. */
28
43
  key?: string;
29
44
  /** Which env var was used (only set when source === "env"). */
30
45
  envVar?: string;
46
+ /** Human-readable diagnostic when source === "keychain_error". */
47
+ keychainError?: string;
31
48
  }
32
49
 
33
50
  /**
34
- * Thrown when no key is found in any source. Caller should surface
35
- * the message verbatim — it tells the user exactly how to fix it.
51
+ * Thrown when no key is found in any source (or when keychain read failed).
52
+ * Caller should surface the message verbatim — it tells the user exactly
53
+ * how to fix it.
36
54
  */
37
55
  export class AuthMissingError extends Error {
38
56
  readonly provider: ProviderId;
39
57
  readonly envVar: string;
40
- constructor(provider: ProviderId, envVar: string) {
58
+ constructor(provider: ProviderId, envVar: string, customMessage?: string) {
41
59
  super(
42
- `No API key found for ${provider}. ` +
43
- `Run "almightygpt auth ${provider}" to set one up interactively, ` +
44
- `or export ${envVar} in your shell.`,
60
+ customMessage ??
61
+ `No API key found for ${provider}. ` +
62
+ `Run "almightygpt auth ${provider}" to set one up interactively, ` +
63
+ `or export ${envVar} in your shell.`,
45
64
  );
46
65
  this.name = "AuthMissingError";
47
66
  this.provider = provider;