@steipete/oracle 0.12.0 → 0.13.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.
@@ -14,7 +14,7 @@ import { getFileTokenStats, printFileTokenStats } from "./tokenStats.js";
14
14
  import { OracleResponseError, OracleTransportError, PromptValidationError, describeTransportError, toTransportError, } from "./errors.js";
15
15
  import { isCustomBaseUrl } from "./baseUrl.js";
16
16
  import { createDefaultClientFactory } from "./client.js";
17
- import { formatBaseUrlForLog, maskApiKey } from "./logging.js";
17
+ import { maskApiKey } from "./logging.js";
18
18
  import { startHeartbeat } from "../heartbeat.js";
19
19
  import { startOscProgress } from "./oscProgress.js";
20
20
  import { createFsAdapter } from "./fsAdapter.js";
@@ -25,60 +25,49 @@ import { createMarkdownStreamer } from "markdansi";
25
25
  import { executeBackgroundResponse } from "./background.js";
26
26
  import { formatTokenEstimate, formatTokenValue, resolvePreviewMode } from "./runUtils.js";
27
27
  import { estimateUsdCost } from "tokentally";
28
- import { defaultOpenRouterBaseUrl, isOpenRouterBaseUrl, isProModel, resolveModelConfig, normalizeOpenRouterBaseUrl, } from "./modelResolver.js";
28
+ import { isOpenRouterBaseUrl, isProModel, resolveModelConfig } from "./modelResolver.js";
29
29
  import { validateProviderRouting } from "./providerRouting.js";
30
+ import { formatRouteTargetForLog, resolveProviderRoute, } from "./providerRoutePlan.js";
30
31
  const isStdoutTty = process.stdout.isTTY && chalk.level > 0;
31
32
  const dim = (text) => (isStdoutTty ? kleur.dim(text) : text);
32
33
  // Default timeout for non-pro API runs (fast models) — give them up to 120s.
33
34
  const DEFAULT_TIMEOUT_NON_PRO_MS = 120_000;
34
35
  const DEFAULT_TIMEOUT_PRO_MS = 60 * 60 * 1000;
35
- const DEFAULT_PROVIDER_HOSTS = {
36
- anthropic: "api.anthropic.com",
37
- google: "generativelanguage.googleapis.com",
38
- openai: "api.openai.com",
39
- xai: "api.x.ai",
40
- };
41
36
  const defaultWait = (ms) => new Promise((resolve) => {
42
37
  setTimeout(resolve, ms);
43
38
  });
44
- function formatRouteTargetForLog(raw, fallbackHost = "") {
45
- if (!raw)
46
- return fallbackHost;
47
- try {
48
- const parsed = new URL(raw);
49
- const segments = parsed.pathname.split("/").filter(Boolean);
50
- let path = "";
51
- if (segments.length > 0) {
52
- path = `/${segments[0]}`;
53
- if (segments.length > 1) {
54
- path += "/...";
55
- }
56
- }
57
- return `${parsed.host}${path}`;
58
- }
59
- catch {
60
- const formatted = formatBaseUrlForLog(raw).replace(/^https?:\/\//u, "");
61
- return formatted || fallbackHost;
39
+ function formatProviderRouteLogLine(route, keySource) {
40
+ if (route.isAzureOpenAI) {
41
+ return `Provider: Azure OpenAI | endpoint: ${formatRouteTargetForLog(route.azureEndpoint)} | deployment: ${route.azureDeploymentName || "none"} | key: ${keySource}`;
62
42
  }
43
+ return `Provider: ${route.providerLabel} | base: ${route.base} | key: ${keySource}`;
63
44
  }
64
- function formatProviderRouteLogLine({ provider, baseUrl, openRouterFallback, isAzureOpenAI, azureEndpoint, azureDeploymentName, envVar, }) {
65
- if (isAzureOpenAI) {
66
- return `Provider: Azure OpenAI | endpoint: ${formatRouteTargetForLog(azureEndpoint)} | deployment: ${azureDeploymentName || "none"} | key: ${envVar}`;
45
+ function runtimeKeySource({ route, providerMode, optionsApiKey, }) {
46
+ if (optionsApiKey &&
47
+ (route.isAzureOpenAI ||
48
+ providerMode === "openai" ||
49
+ route.provider === "openai" ||
50
+ route.providerLabel === "OpenAI-compatible")) {
51
+ return "apiKey option";
67
52
  }
68
- const isOpenRouter = isOpenRouterBaseUrl(baseUrl) || openRouterFallback;
69
- const routeProvider = isOpenRouter
70
- ? "OpenRouter"
71
- : baseUrl && isCustomBaseUrl(baseUrl)
72
- ? "OpenAI-compatible"
73
- : provider === "anthropic"
74
- ? "Anthropic"
75
- : provider === "google"
76
- ? "Google Gemini"
77
- : provider === "xai"
78
- ? "xAI"
79
- : "OpenAI";
80
- const fallbackHost = DEFAULT_PROVIDER_HOSTS[provider] ?? DEFAULT_PROVIDER_HOSTS.openai;
81
- return `Provider: ${routeProvider} | base: ${formatRouteTargetForLog(baseUrl, fallbackHost)} | key: ${envVar}`;
53
+ if (route.isAzureOpenAI) {
54
+ return "AZURE_OPENAI_API_KEY|OPENAI_API_KEY";
55
+ }
56
+ if (providerMode === "openai") {
57
+ return "OPENAI_API_KEY";
58
+ }
59
+ if (isOpenRouterBaseUrl(route.baseUrl) || route.openRouterFallback || route.model.includes("/")) {
60
+ return "OPENROUTER_API_KEY";
61
+ }
62
+ if (route.model.startsWith("gpt"))
63
+ return "OPENAI_API_KEY";
64
+ if (route.model.startsWith("gemini"))
65
+ return "GEMINI_API_KEY";
66
+ if (route.model.startsWith("claude"))
67
+ return "ANTHROPIC_API_KEY";
68
+ if (route.model.startsWith("grok"))
69
+ return "XAI_API_KEY";
70
+ return optionsApiKey ? "apiKey option" : route.keySource;
82
71
  }
83
72
  export async function runOracle(options, deps = {}) {
84
73
  const { apiKey: optionsApiKey = options.apiKey, cwd = process.cwd(), fs: fsModule = createFsAdapter(fs), log = console.log, write: sinkWrite = (_text) => true, allowStdout = true, stdoutWrite: stdoutWriteDep, now = () => performance.now(), clientFactory = createDefaultClientFactory(), client, wait = defaultWait, } = deps;
@@ -86,13 +75,10 @@ export async function runOracle(options, deps = {}) {
86
75
  ? (stdoutWriteDep ?? process.stdout.write.bind(process.stdout))
87
76
  : () => true;
88
77
  const isTty = allowStdout && isStdoutTty;
89
- const resolvedXaiBaseUrl = process.env.XAI_BASE_URL?.trim() || "https://api.x.ai/v1";
90
- const openRouterApiKey = process.env.OPENROUTER_API_KEY?.trim();
91
- const defaultOpenRouterBase = defaultOpenRouterBaseUrl();
92
78
  const previewMode = resolvePreviewMode(options.previewMode ?? options.preview);
93
79
  const isPreview = Boolean(previewMode);
94
80
  const providerMode = options.provider ?? "auto";
95
- const routing = validateProviderRouting({
81
+ validateProviderRouting({
96
82
  model: options.model,
97
83
  providerMode,
98
84
  azure: options.azure,
@@ -103,99 +89,23 @@ export async function runOracle(options, deps = {}) {
103
89
  }
104
90
  },
105
91
  });
106
- const { provider, isAzureOpenAI, azureEndpoint, azureDeploymentName } = routing;
107
- const hasOpenAIKey = Boolean(optionsApiKey) ||
108
- Boolean(process.env.OPENAI_API_KEY) ||
109
- Boolean(providerMode !== "openai" && process.env.AZURE_OPENAI_API_KEY && options.azure?.endpoint);
110
- const hasAnthropicKey = Boolean(optionsApiKey) || Boolean(process.env.ANTHROPIC_API_KEY);
111
- const hasGeminiKey = Boolean(optionsApiKey) || Boolean(process.env.GEMINI_API_KEY);
112
- const hasXaiKey = Boolean(optionsApiKey) || Boolean(process.env.XAI_API_KEY);
113
- let baseUrl = options.baseUrl?.trim();
114
- const providerQualifiedOpenRouterCandidate = !isAzureOpenAI && providerMode !== "openai" && options.model.includes("/");
115
- if (baseUrl &&
116
- providerQualifiedOpenRouterCandidate &&
117
- !isOpenRouterBaseUrl(baseUrl) &&
118
- !isCustomBaseUrl(baseUrl)) {
119
- baseUrl = undefined;
120
- }
121
- if (!baseUrl) {
122
- let envBaseUrl;
123
- if (options.model.startsWith("grok")) {
124
- envBaseUrl = resolvedXaiBaseUrl;
125
- }
126
- else if (provider === "anthropic") {
127
- envBaseUrl = process.env.ANTHROPIC_BASE_URL?.trim();
128
- }
129
- else {
130
- envBaseUrl = process.env.OPENAI_BASE_URL?.trim();
131
- }
132
- if (!providerQualifiedOpenRouterCandidate || (envBaseUrl && isCustomBaseUrl(envBaseUrl))) {
133
- baseUrl = envBaseUrl;
134
- }
135
- }
136
- const providerKeyMissing = !isAzureOpenAI &&
137
- (providerMode === "openai"
138
- ? !hasOpenAIKey
139
- : (provider === "openai" && !hasOpenAIKey) ||
140
- (provider === "anthropic" && !hasAnthropicKey) ||
141
- (provider === "google" && !hasGeminiKey) ||
142
- (provider === "xai" && !hasXaiKey) ||
143
- provider === "other");
144
- const providerQualifiedOpenRouterRoute = providerQualifiedOpenRouterCandidate && !baseUrl;
145
- const openRouterFallback = !baseUrl &&
146
- (providerQualifiedOpenRouterRoute ||
147
- (providerMode !== "openai" &&
148
- providerKeyMissing &&
149
- (provider === "other" || Boolean(openRouterApiKey))));
150
- if (!baseUrl || openRouterFallback) {
151
- if (openRouterFallback) {
152
- baseUrl = defaultOpenRouterBase;
153
- }
154
- }
155
- if (baseUrl && isOpenRouterBaseUrl(baseUrl)) {
156
- baseUrl = normalizeOpenRouterBaseUrl(baseUrl);
157
- }
92
+ const route = resolveProviderRoute({
93
+ model: options.model,
94
+ providerMode,
95
+ azure: options.azure,
96
+ baseUrl: options.baseUrl,
97
+ apiKey: optionsApiKey,
98
+ env: process.env,
99
+ });
100
+ const { isAzureOpenAI, azureDeploymentName } = route;
101
+ const baseUrl = route.baseUrl;
102
+ const openRouterFallback = route.openRouterFallback;
158
103
  const logVerbose = (message) => {
159
104
  if (options.verbose) {
160
105
  log(dim(`[verbose] ${message}`));
161
106
  }
162
107
  };
163
- const getApiKeyForModel = (model) => {
164
- if (isAzureOpenAI) {
165
- if (optionsApiKey)
166
- return { key: optionsApiKey, source: "apiKey option" };
167
- const key = process.env.AZURE_OPENAI_API_KEY ?? process.env.OPENAI_API_KEY;
168
- return { key, source: "AZURE_OPENAI_API_KEY|OPENAI_API_KEY" };
169
- }
170
- if (providerMode === "openai") {
171
- if (optionsApiKey)
172
- return { key: optionsApiKey, source: "apiKey option" };
173
- return { key: process.env.OPENAI_API_KEY, source: "OPENAI_API_KEY" };
174
- }
175
- if (isOpenRouterBaseUrl(baseUrl) || openRouterFallback) {
176
- return { key: optionsApiKey ?? openRouterApiKey, source: "OPENROUTER_API_KEY" };
177
- }
178
- if (typeof model === "string" && model.startsWith("gpt")) {
179
- if (optionsApiKey)
180
- return { key: optionsApiKey, source: "apiKey option" };
181
- return { key: process.env.OPENAI_API_KEY, source: "OPENAI_API_KEY" };
182
- }
183
- if (typeof model === "string" && model.startsWith("gemini")) {
184
- return { key: optionsApiKey ?? process.env.GEMINI_API_KEY, source: "GEMINI_API_KEY" };
185
- }
186
- if (typeof model === "string" && model.startsWith("claude")) {
187
- return { key: optionsApiKey ?? process.env.ANTHROPIC_API_KEY, source: "ANTHROPIC_API_KEY" };
188
- }
189
- if (typeof model === "string" && model.startsWith("grok")) {
190
- return { key: optionsApiKey ?? process.env.XAI_API_KEY, source: "XAI_API_KEY" };
191
- }
192
- return {
193
- key: optionsApiKey ?? openRouterApiKey,
194
- source: optionsApiKey ? "apiKey option" : "OPENROUTER_API_KEY",
195
- };
196
- };
197
- const apiKeyResult = getApiKeyForModel(options.model);
198
- const apiKey = apiKeyResult.key;
108
+ const apiKey = route.apiKey;
199
109
  if (!apiKey) {
200
110
  const envVar = isAzureOpenAI
201
111
  ? "AZURE_OPENAI_API_KEY (or OPENAI_API_KEY)"
@@ -203,15 +113,7 @@ export async function runOracle(options, deps = {}) {
203
113
  ? "OPENAI_API_KEY"
204
114
  : isOpenRouterBaseUrl(baseUrl) || openRouterFallback
205
115
  ? "OPENROUTER_API_KEY"
206
- : options.model.startsWith("gpt")
207
- ? "OPENAI_API_KEY"
208
- : options.model.startsWith("gemini")
209
- ? "GEMINI_API_KEY"
210
- : options.model.startsWith("claude")
211
- ? "ANTHROPIC_API_KEY"
212
- : options.model.startsWith("grok")
213
- ? "XAI_API_KEY"
214
- : "OPENROUTER_API_KEY";
116
+ : route.keySource;
215
117
  const browserModeHint = options.model.startsWith("gpt")
216
118
  ? ' If you have a ChatGPT Pro subscription, retry with --engine browser (or MCP engine:"browser" / preset:"chatgpt-pro-heavy"); browser mode uses your signed-in ChatGPT session instead of an API key.'
217
119
  : "";
@@ -219,7 +121,7 @@ export async function runOracle(options, deps = {}) {
219
121
  env: envVar,
220
122
  });
221
123
  }
222
- const envVar = apiKeyResult.source;
124
+ const envVar = runtimeKeySource({ route, providerMode, optionsApiKey });
223
125
  const minPromptLength = Number.parseInt(process.env.ORACLE_MIN_PROMPT_CHARS ?? "10", 10);
224
126
  const promptLength = options.prompt?.trim().length ?? 0;
225
127
  // Enforce the short-prompt guardrail on pro-tier models because they're costly; cheaper models can run short prompts without blocking.
@@ -227,7 +129,7 @@ export async function runOracle(options, deps = {}) {
227
129
  if (isProTierModel && !Number.isNaN(minPromptLength) && promptLength < minPromptLength) {
228
130
  throw new PromptValidationError(`Prompt is too short (<${minPromptLength} chars). This was likely accidental; please provide more detail.`, { minPromptLength, promptLength });
229
131
  }
230
- const resolverOpenRouterApiKey = openRouterFallback || isOpenRouterBaseUrl(baseUrl) ? (openRouterApiKey ?? apiKey) : undefined;
132
+ const resolverOpenRouterApiKey = openRouterFallback || isOpenRouterBaseUrl(baseUrl) ? apiKey : undefined;
231
133
  const modelConfig = await resolveModelConfig(options.model, {
232
134
  baseUrl,
233
135
  openRouterApiKey: resolverOpenRouterApiKey,
@@ -326,15 +228,7 @@ export async function runOracle(options, deps = {}) {
326
228
  if (!isPreview) {
327
229
  if (!options.suppressHeader) {
328
230
  log(headerLine);
329
- log(dim(formatProviderRouteLogLine({
330
- provider,
331
- baseUrl,
332
- openRouterFallback,
333
- isAzureOpenAI,
334
- azureEndpoint,
335
- azureDeploymentName,
336
- envVar,
337
- })));
231
+ log(dim(formatProviderRouteLogLine(route, envVar)));
338
232
  }
339
233
  const maskedKey = maskApiKey(apiKey);
340
234
  if (maskedKey && options.verbose) {
@@ -409,7 +303,7 @@ export async function runOracle(options, deps = {}) {
409
303
  : proxyCompatibleBaseUrl
410
304
  ? proxyCompatibleBaseUrl
411
305
  : modelConfig.model.startsWith("claude")
412
- ? (process.env.ANTHROPIC_BASE_URL ?? baseUrl)
306
+ ? baseUrl
413
307
  : baseUrl;
414
308
  const clientInstance = client ??
415
309
  clientFactory(apiKey, {
@@ -1,6 +1,6 @@
1
1
  import path from "node:path";
2
2
  import fs from "node:fs/promises";
3
- import { createWriteStream } from "node:fs";
3
+ import { createWriteStream, mkdirSync } from "node:fs";
4
4
  import net from "node:net";
5
5
  import { DEFAULT_MODEL } from "./oracle/config.js";
6
6
  import { formatElapsed } from "./oracle/format.js";
@@ -86,14 +86,26 @@ async function fileExists(targetPath) {
86
86
  return false;
87
87
  }
88
88
  }
89
- async function ensureUniqueSessionId(baseSlug) {
89
+ function isFileExistsError(error) {
90
+ return typeof error === "object" && error !== null && "code" in error && error.code === "EEXIST";
91
+ }
92
+ async function reserveUniqueSessionDir(baseSlug) {
90
93
  let candidate = baseSlug;
91
94
  let suffix = 2;
92
- while (await fileExists(sessionDir(candidate))) {
95
+ for (;;) {
96
+ const dir = sessionDir(candidate);
97
+ try {
98
+ await fs.mkdir(dir, { recursive: false });
99
+ return candidate;
100
+ }
101
+ catch (error) {
102
+ if (!isFileExistsError(error)) {
103
+ throw error;
104
+ }
105
+ }
93
106
  candidate = `${baseSlug}-${suffix}`;
94
107
  suffix += 1;
95
108
  }
96
- return candidate;
97
109
  }
98
110
  async function listModelRunFiles(sessionId) {
99
111
  const dir = modelsDir(sessionId);
@@ -153,9 +165,7 @@ export async function readModelRunMetadata(sessionId, model) {
153
165
  export async function initializeSession(options, cwd, notifications, baseSlugOverride) {
154
166
  await ensureSessionStorage();
155
167
  const baseSlug = baseSlugOverride || createSessionId(options.prompt || DEFAULT_SLUG, options.slug);
156
- const sessionId = await ensureUniqueSessionId(baseSlug);
157
- const dir = sessionDir(sessionId);
158
- await ensureDir(dir);
168
+ const sessionId = await reserveUniqueSessionDir(baseSlug);
159
169
  const mode = options.mode ?? "api";
160
170
  const browserConfig = options.browserConfig;
161
171
  const modelList = Array.isArray(options.models) && options.models.length > 0
@@ -239,25 +249,25 @@ export async function initializeSession(options, cwd, notifications, baseSlugOve
239
249
  return metadata;
240
250
  }
241
251
  export async function readSessionMetadata(sessionId) {
242
- const modern = await readModernSessionMetadata(sessionId);
252
+ const modern = await readModernSessionMetadata(sessionId, { reconcile: true, persist: false });
243
253
  if (modern) {
244
254
  return modern;
245
255
  }
246
- const legacy = await readLegacySessionMetadata(sessionId);
256
+ const legacy = await readLegacySessionMetadata(sessionId, { reconcile: true, persist: false });
247
257
  if (legacy) {
248
258
  return legacy;
249
259
  }
250
260
  return null;
251
261
  }
252
262
  export async function updateSessionMetadata(sessionId, updates) {
253
- const existing = (await readModernSessionMetadata(sessionId)) ??
254
- (await readLegacySessionMetadata(sessionId)) ??
263
+ const existing = (await readModernSessionMetadata(sessionId, { reconcile: false, persist: false })) ??
264
+ (await readLegacySessionMetadata(sessionId, { reconcile: false, persist: false })) ??
255
265
  { id: sessionId };
256
266
  const next = { ...existing, ...updates };
257
267
  await fs.writeFile(metaPath(sessionId), JSON.stringify(next, null, 2), "utf8");
258
268
  return next;
259
269
  }
260
- async function readModernSessionMetadata(sessionId) {
270
+ async function readModernSessionMetadata(sessionId, options) {
261
271
  try {
262
272
  const raw = await fs.readFile(metaPath(sessionId), "utf8");
263
273
  const parsed = JSON.parse(raw);
@@ -265,25 +275,31 @@ async function readModernSessionMetadata(sessionId) {
265
275
  return null;
266
276
  }
267
277
  const enriched = await attachModelRuns(parsed, sessionId);
268
- const runtimeChecked = await markDeadBrowser(enriched, { persist: false });
269
- return await markZombie(runtimeChecked, { persist: false });
278
+ return options.reconcile ? reconcileSessionMetadata(enriched, options) : enriched;
270
279
  }
271
280
  catch {
272
281
  return null;
273
282
  }
274
283
  }
275
- async function readLegacySessionMetadata(sessionId) {
284
+ async function readLegacySessionMetadata(sessionId, options) {
276
285
  try {
277
286
  const raw = await fs.readFile(legacySessionPath(sessionId), "utf8");
278
287
  const parsed = JSON.parse(raw);
279
288
  const enriched = await attachModelRuns(parsed, sessionId);
280
- const runtimeChecked = await markDeadBrowser(enriched, { persist: false });
281
- return await markZombie(runtimeChecked, { persist: false });
289
+ return options.reconcile ? reconcileSessionMetadata(enriched, options) : enriched;
282
290
  }
283
291
  catch {
284
292
  return null;
285
293
  }
286
294
  }
295
+ async function readRawSessionMetadata(sessionId) {
296
+ return ((await readModernSessionMetadata(sessionId, { reconcile: false, persist: false })) ??
297
+ (await readLegacySessionMetadata(sessionId, { reconcile: false, persist: false })));
298
+ }
299
+ async function reconcileSessionMetadata(meta, { persist }) {
300
+ const runtimeChecked = await markDeadBrowser(meta, { persist });
301
+ return await markZombie(runtimeChecked, { persist });
302
+ }
287
303
  function isSessionMetadataRecord(value) {
288
304
  return Boolean(value && typeof value.id === "string" && value.status);
289
305
  }
@@ -297,7 +313,7 @@ async function attachModelRuns(meta, sessionId) {
297
313
  export function createSessionLogWriter(sessionId, model) {
298
314
  const targetPath = model ? modelLogPath(sessionId, model) : logPath(sessionId);
299
315
  if (model) {
300
- void ensureDir(modelsDir(sessionId));
316
+ mkdirSync(modelsDir(sessionId), { recursive: true });
301
317
  }
302
318
  const stream = createWriteStream(targetPath, { flags: "a" });
303
319
  const logLine = (line = "") => {
@@ -314,10 +330,10 @@ export async function listSessionsMetadata() {
314
330
  const entries = await fs.readdir(getSessionsDir()).catch(() => []);
315
331
  const metas = [];
316
332
  for (const entry of entries) {
317
- let meta = await readSessionMetadata(entry);
333
+ let meta = await readRawSessionMetadata(entry);
318
334
  if (meta) {
319
- meta = await markDeadBrowser(meta, { persist: true });
320
- meta = await markZombie(meta, { persist: true }); // keep stored metadata consistent with zombie detection
335
+ // Keep stored metadata consistent with status reconciliation done by `oracle status`.
336
+ meta = await reconcileSessionMetadata(meta, { persist: true });
321
337
  metas.push(meta);
322
338
  }
323
339
  }
@@ -388,7 +404,7 @@ export async function readModelLog(sessionId, model) {
388
404
  }
389
405
  }
390
406
  export async function readSessionRequest(sessionId) {
391
- const modern = await readModernSessionMetadata(sessionId);
407
+ const modern = await readModernSessionMetadata(sessionId, { reconcile: false, persist: false });
392
408
  if (modern?.options) {
393
409
  return modern.options;
394
410
  }
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@steipete/oracle",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "description": "CLI wrapper around OpenAI Responses API with GPT-5.5 Pro, GPT-5.5, GPT-5.4, GPT-5.2, GPT-5.1, and GPT-5.1 Codex high reasoning modes.",
5
5
  "keywords": [],
6
- "homepage": "https://github.com/steipete/oracle#readme",
6
+ "homepage": "https://askoracle.sh",
7
7
  "bugs": {
8
8
  "url": "https://github.com/steipete/oracle/issues"
9
9
  },
@@ -46,7 +46,7 @@
46
46
  "test": "vitest run",
47
47
  "test:mcp": "pnpm run build && pnpm run test:mcp:unit && pnpm run test:mcp:mcporter",
48
48
  "test:mcp:unit": "vitest run tests/mcp*.test.ts tests/mcp/**/*.test.ts",
49
- "test:mcp:mcporter": "pnpm dlx mcporter list oracle-local --schema --config config/mcporter.json && pnpm dlx mcporter call oracle-local.sessions limit:1 --config config/mcporter.json",
49
+ "test:mcp:mcporter": "CHROME_DEVTOOLS_URL=http://127.0.0.1:0 pnpm dlx mcporter list oracle-local --schema --config config/mcporter.json && CHROME_DEVTOOLS_URL=http://127.0.0.1:0 pnpm dlx mcporter call oracle-local.sessions limit:1 --config config/mcporter.json",
50
50
  "test:browser": "pnpm run build && tsx scripts/test-browser.ts && ./scripts/browser-smoke.sh",
51
51
  "test:packed-cli": "node scripts/packed-cli-smoke.mjs",
52
52
  "test:live": "ORACLE_LIVE_TEST=1 vitest run tests/live --exclude tests/live/openai-live.test.ts",
@@ -61,7 +61,7 @@
61
61
  "@google/genai": "^2.0.1",
62
62
  "@google/generative-ai": "^0.24.1",
63
63
  "@modelcontextprotocol/sdk": "^1.29.0",
64
- "@steipete/sweet-cookie": "^0.2.0",
64
+ "@steipete/sweet-cookie": "^0.3.0",
65
65
  "chalk": "^5.6.2",
66
66
  "chrome-launcher": "^1.2.1",
67
67
  "chrome-remote-interface": "^0.34.0",
@@ -70,11 +70,11 @@
70
70
  "dotenv": "^17.4.2",
71
71
  "fast-glob": "^3.3.3",
72
72
  "gpt-tokenizer": "^3.4.0",
73
- "inquirer": "13.4.2",
73
+ "inquirer": "13.4.3",
74
74
  "json5": "^2.2.3",
75
75
  "kleur": "^4.1.5",
76
76
  "markdansi": "0.2.1",
77
- "openai": "^6.35.0",
77
+ "openai": "^6.38.0",
78
78
  "osc-progress": "^0.3.0",
79
79
  "qs": "^6.15.1",
80
80
  "shiki": "^4.0.2",
@@ -84,17 +84,17 @@
84
84
  },
85
85
  "devDependencies": {
86
86
  "@anthropic-ai/tokenizer": "^0.0.4",
87
- "@types/chrome-remote-interface": "^0.33.0",
87
+ "@types/chrome-remote-interface": "^0.34.0",
88
88
  "@types/inquirer": "^9.0.9",
89
89
  "@types/node": "^25.6.0",
90
- "@typescript/native-preview": "7.0.0-dev.20260508.1",
91
- "@vitest/coverage-v8": "4.1.5",
92
- "devtools-protocol": "0.0.1627472",
90
+ "@typescript/native-preview": "7.0.0-dev.20260516.1",
91
+ "@vitest/coverage-v8": "4.1.6",
92
+ "devtools-protocol": "0.0.1629771",
93
93
  "es-toolkit": "^1.46.1",
94
94
  "esbuild": "^0.28.0",
95
- "oxfmt": "0.48.0",
95
+ "oxfmt": "0.50.0",
96
96
  "oxlint": "^1.62.0",
97
- "puppeteer-core": "^24.42.0",
97
+ "puppeteer-core": "^25.0.2",
98
98
  "tsx": "^4.21.0",
99
99
  "typescript": "^6.0.3",
100
100
  "vitest": "^4.1.5"
@@ -113,7 +113,8 @@
113
113
  "packageManager": "pnpm@10.33.2",
114
114
  "pnpm": {
115
115
  "overrides": {
116
- "devtools-protocol": "0.0.1627472"
116
+ "devtools-protocol": "0.0.1629771",
117
+ "vite": "7.3.2"
117
118
  },
118
119
  "onlyBuiltDependencies": [
119
120
  "esbuild"