@steipete/oracle 0.16.1 → 0.17.1

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 (70) hide show
  1. package/README.md +72 -337
  2. package/dist/bin/oracle-cli.js +185 -56
  3. package/dist/docs-site/.nojekyll +0 -0
  4. package/dist/docs-site/CNAME +1 -0
  5. package/dist/docs-site/RELEASING.html +410 -0
  6. package/dist/docs-site/agents.html +374 -0
  7. package/dist/docs-site/anthropic.html +368 -0
  8. package/dist/docs-site/bridge.html +416 -0
  9. package/dist/docs-site/browser-mode.html +594 -0
  10. package/dist/docs-site/chromium-forks.html +347 -0
  11. package/dist/docs-site/cli-reference.html +346 -0
  12. package/dist/docs-site/configuration.html +462 -0
  13. package/dist/docs-site/favicon.svg +14 -0
  14. package/dist/docs-site/followup.html +375 -0
  15. package/dist/docs-site/gemini.html +383 -0
  16. package/dist/docs-site/grok.html +325 -0
  17. package/dist/docs-site/index.html +360 -0
  18. package/dist/docs-site/install.html +335 -0
  19. package/dist/docs-site/linux.html +321 -0
  20. package/dist/docs-site/llms.txt +43 -0
  21. package/dist/docs-site/manual-tests.html +596 -0
  22. package/dist/docs-site/mcp.html +391 -0
  23. package/dist/docs-site/multimodel.html +364 -0
  24. package/dist/docs-site/mythical-pro-agents.html +360 -0
  25. package/dist/docs-site/notifier.html +338 -0
  26. package/dist/docs-site/openai-endpoints.html +410 -0
  27. package/dist/docs-site/openrouter.html +344 -0
  28. package/dist/docs-site/quickstart.html +369 -0
  29. package/dist/docs-site/refactor/ux.html +532 -0
  30. package/dist/docs-site/sessions.html +389 -0
  31. package/dist/docs-site/social-card.png +0 -0
  32. package/dist/docs-site/social-card.svg +79 -0
  33. package/dist/docs-site/spec.html +363 -0
  34. package/dist/docs-site/testing.html +320 -0
  35. package/dist/docs-site/tui-debug.html +326 -0
  36. package/dist/docs-site/windows-work.html +324 -0
  37. package/dist/docs-site/windows.html +320 -0
  38. package/dist/scripts/test-browser.js +1 -25
  39. package/dist/src/browser/actions/modelSelection.js +36 -34
  40. package/dist/src/browser/actions/navigation.js +8 -3
  41. package/dist/src/browser/actions/thinkingTime.js +96 -45
  42. package/dist/src/browser/chromeLifecycle.js +8 -37
  43. package/dist/src/browser/index.js +23 -8
  44. package/dist/src/browser/modelDisplay.js +67 -0
  45. package/dist/src/browser/reattach.js +14 -1
  46. package/dist/src/browser/recoverConversation.js +2 -1
  47. package/dist/src/browser/sessionRunner.js +9 -10
  48. package/dist/src/browser/wslHost.js +50 -0
  49. package/dist/src/cli/browserConfig.js +31 -11
  50. package/dist/src/cli/detach.js +21 -4
  51. package/dist/src/cli/dryRun.js +13 -2
  52. package/dist/src/cli/engine.js +2 -2
  53. package/dist/src/cli/options.js +5 -1
  54. package/dist/src/cli/sessionDisplay.js +60 -8
  55. package/dist/src/cli/sessionLifecycle.js +2 -1
  56. package/dist/src/cli/sessionRunner.js +110 -60
  57. package/dist/src/cli/sessionTable.js +5 -1
  58. package/dist/src/cli/tui/index.js +12 -4
  59. package/dist/src/duration.js +3 -0
  60. package/dist/src/gemini-web/client.js +19 -1
  61. package/dist/src/oracle/modelResolver.js +8 -1
  62. package/dist/src/oracle/request.js +9 -2
  63. package/dist/src/oracle/run.js +43 -3
  64. package/dist/src/oracle/thinkingTime.js +9 -3
  65. package/dist/src/sessionManager.js +41 -12
  66. package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
  67. package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
  68. package/package.json +17 -17
  69. package/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
  70. package/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
@@ -21,6 +21,9 @@ export function parseDuration(input, fallback) {
21
21
  let lastIndex = 0;
22
22
  let match = multiDuration.exec(normalized);
23
23
  while (match !== null) {
24
+ if (match.index !== lastIndex) {
25
+ return fallback;
26
+ }
24
27
  total += convertUnit(Number(match[1]), match[2]);
25
28
  lastIndex = multiDuration.lastIndex;
26
29
  match = multiDuration.exec(normalized);
@@ -12,11 +12,29 @@ const GEMINI_UPLOAD_MIME_TYPES = {
12
12
  ".gif": "image/gif",
13
13
  ".jpeg": "image/jpeg",
14
14
  ".jpg": "image/jpeg",
15
+ ".mov": "video/quicktime",
16
+ ".mp4": "video/mp4",
15
17
  ".pdf": "application/pdf",
16
18
  ".png": "image/png",
17
19
  ".svg": "image/svg+xml",
20
+ ".webm": "video/webm",
18
21
  ".webp": "image/webp",
19
22
  };
23
+ /**
24
+ * Resolve the MIME type Gemini should be told an upload carries.
25
+ *
26
+ * Gemini silently discards uploads it cannot type: the run still reports the file as
27
+ * attached, but the model never receives it and answers as though nothing was sent.
28
+ * Anything falling back to `application/octet-stream` is therefore invisible to the model.
29
+ *
30
+ * Only formats confirmed to work against the Gemini web upload endpoint are listed. The
31
+ * endpoint also gates on the file extension, not just the declared type — an `.m4v` byte
32
+ * for byte identical to a working `.mp4`, and declared `video/mp4`, is still dropped — so
33
+ * entries here cannot be extrapolated from what the Gemini API documents.
34
+ */
35
+ export function resolveGeminiUploadMimeType(filePath) {
36
+ return (GEMINI_UPLOAD_MIME_TYPES[path.extname(filePath).toLowerCase()] ?? "application/octet-stream");
37
+ }
20
38
  function getNestedValue(value, pathParts, fallback) {
21
39
  let current = value;
22
40
  for (const part of pathParts) {
@@ -125,7 +143,7 @@ async function uploadGeminiFile(filePath, signal) {
125
143
  const absPath = path.resolve(process.cwd(), filePath);
126
144
  const data = await readFile(absPath);
127
145
  const fileName = path.basename(absPath);
128
- const mimeType = GEMINI_UPLOAD_MIME_TYPES[path.extname(absPath).toLowerCase()] ?? "application/octet-stream";
146
+ const mimeType = resolveGeminiUploadMimeType(absPath);
129
147
  const form = new FormData();
130
148
  form.append("file", new Blob([data], { type: mimeType }), fileName);
131
149
  const res = await fetch(GEMINI_UPLOAD_URL, {
@@ -192,7 +192,14 @@ async function resolveBaseModelConfig(model, options = {}) {
192
192
  export function isProModel(model) {
193
193
  return isKnownModel(model) && PRO_MODELS.has(model);
194
194
  }
195
- const VALID_REASONING_EFFORTS = ["low", "medium", "high", "xhigh"];
195
+ const VALID_REASONING_EFFORTS = [
196
+ "none",
197
+ "low",
198
+ "medium",
199
+ "high",
200
+ "xhigh",
201
+ "max",
202
+ ];
196
203
  function isRecord(value) {
197
204
  return typeof value === "object" && value !== null && !Array.isArray(value);
198
205
  }
@@ -11,8 +11,15 @@ export function buildPrompt(basePrompt, files, cwd = process.cwd()) {
11
11
  const sectionText = formatFileSections(sections, { includeFileIndex: true });
12
12
  return `${basePrompt.trim()}\n\n${sectionText}`;
13
13
  }
14
- export function buildRequestBody({ modelConfig, systemPrompt, userPrompt, searchEnabled, maxOutputTokens, background, storeResponse, previousResponseId, }) {
14
+ export function buildRequestBody({ modelConfig, reasoningEffort, reasoningMode, systemPrompt, userPrompt, searchEnabled, maxOutputTokens, background, storeResponse, previousResponseId, }) {
15
15
  const searchToolType = modelConfig.searchToolType ?? "web_search_preview";
16
+ const reasoning = modelConfig.reasoning || reasoningEffort || reasoningMode
17
+ ? {
18
+ ...(modelConfig.reasoning ?? {}),
19
+ ...(reasoningEffort ? { effort: reasoningEffort } : {}),
20
+ ...(reasoningMode ? { mode: reasoningMode } : {}),
21
+ }
22
+ : undefined;
16
23
  return {
17
24
  model: modelConfig.apiModel ?? modelConfig.model,
18
25
  previous_response_id: previousResponseId ? previousResponseId : undefined,
@@ -29,7 +36,7 @@ export function buildRequestBody({ modelConfig, systemPrompt, userPrompt, search
29
36
  },
30
37
  ],
31
38
  tools: searchEnabled ? [{ type: searchToolType }] : undefined,
32
- reasoning: modelConfig.reasoning || undefined,
39
+ reasoning,
33
40
  max_output_tokens: maxOutputTokens,
34
41
  background: background ? true : undefined,
35
42
  store: storeResponse ? true : undefined,
@@ -33,6 +33,9 @@ const dim = (text) => (isStdoutTty ? kleur.dim(text) : text);
33
33
  // Default timeout for non-pro API runs (fast models) — give them up to 120s.
34
34
  const DEFAULT_TIMEOUT_NON_PRO_MS = 120_000;
35
35
  const DEFAULT_TIMEOUT_PRO_MS = 60 * 60 * 1000;
36
+ const GPT_5_6_API_MODELS = new Set(["gpt-5.6", "gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna"]);
37
+ const REASONING_EFFORTS = new Set(["none", "low", "medium", "high", "xhigh", "max"]);
38
+ const REASONING_MODES = new Set(["standard", "pro"]);
36
39
  const defaultWait = (ms) => new Promise((resolve) => {
37
40
  setTimeout(resolve, ms);
38
41
  });
@@ -69,6 +72,33 @@ function runtimeKeySource({ route, providerMode, optionsApiKey, }) {
69
72
  return "XAI_API_KEY";
70
73
  return optionsApiKey ? "apiKey option" : route.keySource;
71
74
  }
75
+ function validateReasoningOptions(options, route) {
76
+ const { reasoningEffort, reasoningMode } = options;
77
+ if (!reasoningEffort && !reasoningMode)
78
+ return;
79
+ if (reasoningEffort && !REASONING_EFFORTS.has(reasoningEffort)) {
80
+ throw new PromptValidationError(`Invalid reasoning effort "${reasoningEffort}". Expected none, low, medium, high, xhigh, or max.`, { model: options.model, reasoningEffort });
81
+ }
82
+ if (reasoningMode && !REASONING_MODES.has(reasoningMode)) {
83
+ throw new PromptValidationError(`Invalid reasoning mode "${reasoningMode}". Expected standard or pro.`, { model: options.model, reasoningMode });
84
+ }
85
+ if (!GPT_5_6_API_MODELS.has(options.model)) {
86
+ const option = reasoningMode
87
+ ? `Reasoning mode "${reasoningMode}"`
88
+ : `Reasoning effort "${reasoningEffort}"`;
89
+ const guidance = reasoningMode
90
+ ? `Use --model gpt-5.6-sol --reasoning-mode ${reasoningMode}.`
91
+ : `Use --model gpt-5.6-sol --reasoning-effort ${reasoningEffort}.`;
92
+ throw new PromptValidationError(`${option} is available only for GPT-5.6 API models. ${guidance}`, { model: options.model, reasoningEffort, reasoningMode });
93
+ }
94
+ if (reasoningMode &&
95
+ !route.isAzureOpenAI &&
96
+ (route.openRouterFallback ||
97
+ isOpenRouterBaseUrl(route.baseUrl) ||
98
+ isCustomBaseUrl(route.baseUrl))) {
99
+ throw new PromptValidationError("--reasoning-mode requires the OpenAI or Azure OpenAI Responses API; OpenRouter and custom --base-url routes use the Chat Completions adapter.", { model: options.model, reasoningMode });
100
+ }
101
+ }
72
102
  export async function runOracle(options, deps = {}) {
73
103
  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;
74
104
  const stdoutWrite = allowStdout
@@ -100,6 +130,7 @@ export async function runOracle(options, deps = {}) {
100
130
  const { isAzureOpenAI, azureDeploymentName } = route;
101
131
  const baseUrl = route.baseUrl;
102
132
  const openRouterFallback = route.openRouterFallback;
133
+ validateReasoningOptions(options, route);
103
134
  const logVerbose = (message) => {
104
135
  if (options.verbose) {
105
136
  log(dim(`[verbose] ${message}`));
@@ -125,7 +156,7 @@ export async function runOracle(options, deps = {}) {
125
156
  const minPromptLength = Number.parseInt(process.env.ORACLE_MIN_PROMPT_CHARS ?? "10", 10);
126
157
  const promptLength = options.prompt?.trim().length ?? 0;
127
158
  // Enforce the short-prompt guardrail on pro-tier models because they're costly; cheaper models can run short prompts without blocking.
128
- const isProTierModel = isProModel(options.model);
159
+ const isProTierModel = isProModel(options.model) || options.reasoningMode === "pro";
129
160
  if (isProTierModel && !Number.isNaN(minPromptLength) && promptLength < minPromptLength) {
130
161
  throw new PromptValidationError(`Prompt is too short (<${minPromptLength} chars). This was likely accidental; please provide more detail.`, { minPromptLength, promptLength });
131
162
  }
@@ -205,6 +236,8 @@ export async function runOracle(options, deps = {}) {
205
236
  }
206
237
  const requestBody = buildRequestBody({
207
238
  modelConfig,
239
+ reasoningEffort: options.reasoningEffort,
240
+ reasoningMode: options.reasoningMode,
208
241
  systemPrompt,
209
242
  userPrompt: promptWithFiles,
210
243
  searchEnabled,
@@ -249,6 +282,12 @@ export async function runOracle(options, deps = {}) {
249
282
  if (options.background && !supportsBackground) {
250
283
  log(dim("Background runs are not supported for this model; streaming in foreground instead."));
251
284
  }
285
+ if (options.reasoningMode) {
286
+ log(dim(`Reasoning mode: ${options.reasoningMode}`));
287
+ }
288
+ if (options.reasoningEffort) {
289
+ log(dim(`Reasoning effort: ${options.reasoningEffort}`));
290
+ }
252
291
  if (!options.suppressTips) {
253
292
  if (pendingNoFilesTip) {
254
293
  log(dim(pendingNoFilesTip));
@@ -547,8 +586,9 @@ export async function runOracle(options, deps = {}) {
547
586
  },
548
587
  })?.totalUsd
549
588
  : undefined;
550
- const effortLabel = modelConfig.reasoning?.effort;
551
- const modelLabel = effortLabel ? `${modelConfig.model}[${effortLabel}]` : modelConfig.model;
589
+ const effortLabel = options.reasoningEffort ?? modelConfig.reasoning?.effort;
590
+ const reasoningLabel = [options.reasoningMode, effortLabel].filter(Boolean).join("/");
591
+ const modelLabel = reasoningLabel ? `${modelConfig.model}[${reasoningLabel}]` : modelConfig.model;
552
592
  const sessionIdContainsModel = typeof options.sessionId === "string" &&
553
593
  options.sessionId.toLowerCase().includes(modelConfig.model.toLowerCase());
554
594
  const tokensDisplay = [inputTokens, outputTokens, reasoningTokens, totalTokens]
@@ -1,10 +1,15 @@
1
- export const THINKING_TIME_LEVELS = ["light", "standard", "extended", "heavy"];
1
+ export const THINKING_TIME_LEVELS = [
2
+ "light",
3
+ "standard",
4
+ "extended",
5
+ "extra-high",
6
+ "heavy",
7
+ ];
2
8
  export const THINKING_TIME_ALIASES = [
3
9
  "instant",
4
10
  "low",
5
11
  "medium",
6
12
  "high",
7
- "extra-high",
8
13
  "extra high",
9
14
  "extrahigh",
10
15
  "xhigh",
@@ -29,10 +34,11 @@ export function normalizeThinkingTimeLevel(value) {
29
34
  case "extended":
30
35
  case "high":
31
36
  return "extended";
32
- case "heavy":
33
37
  case "extra-high":
34
38
  case "extrahigh":
35
39
  case "xhigh":
40
+ return "extra-high";
41
+ case "heavy":
36
42
  return "heavy";
37
43
  default:
38
44
  return null;
@@ -1,6 +1,7 @@
1
1
  import path from "node:path";
2
2
  import fs from "node:fs/promises";
3
3
  import { createWriteStream, mkdirSync } from "node:fs";
4
+ import { randomUUID } from "node:crypto";
4
5
  import net from "node:net";
5
6
  import { DEFAULT_MODEL } from "./oracle/config.js";
6
7
  import { formatElapsed } from "./oracle/format.js";
@@ -57,6 +58,20 @@ function sessionDir(id) {
57
58
  function metaPath(id) {
58
59
  return path.join(sessionDir(id), METADATA_FILENAME);
59
60
  }
61
+ async function writeSessionMetadataFile(sessionId, metadata) {
62
+ const targetPath = metaPath(sessionId);
63
+ const temporaryPath = `${targetPath}.${process.pid}.${randomUUID()}.tmp`;
64
+ try {
65
+ await fs.writeFile(temporaryPath, JSON.stringify(metadata, null, 2), {
66
+ encoding: "utf8",
67
+ mode: 0o600,
68
+ });
69
+ await fs.rename(temporaryPath, targetPath);
70
+ }
71
+ finally {
72
+ await fs.rm(temporaryPath, { force: true }).catch(() => undefined);
73
+ }
74
+ }
60
75
  function requestPath(id) {
61
76
  return path.join(sessionDir(id), LEGACY_REQUEST_FILENAME);
62
77
  }
@@ -193,6 +208,8 @@ export async function initializeSession(options, cwd, notifications, baseSlugOve
193
208
  maxFileSizeBytes: options.maxFileSizeBytes,
194
209
  model: options.model,
195
210
  models: modelList,
211
+ reasoningEffort: options.reasoningEffort,
212
+ reasoningMode: options.reasoningMode,
196
213
  previousResponseId: options.previousResponseId,
197
214
  followupSessionId: options.followupSessionId,
198
215
  followupModel: options.followupModel,
@@ -235,7 +252,7 @@ export async function initializeSession(options, cwd, notifications, baseSlugOve
235
252
  },
236
253
  };
237
254
  await ensureDir(modelsDir(sessionId));
238
- await fs.writeFile(metaPath(sessionId), JSON.stringify(metadata, null, 2), "utf8");
255
+ await writeSessionMetadataFile(sessionId, metadata);
239
256
  await Promise.all((modelList.length > 0 ? modelList : [metadata.model ?? DEFAULT_MODEL]).map(async (modelName) => {
240
257
  const jsonPath = modelJsonPath(sessionId, modelName);
241
258
  const logFilePath = modelLogPath(sessionId, modelName);
@@ -266,7 +283,7 @@ export async function updateSessionMetadata(sessionId, updates) {
266
283
  (await readLegacySessionMetadata(sessionId, { reconcile: false, persist: false })) ??
267
284
  { id: sessionId };
268
285
  const next = { ...existing, ...updates };
269
- await fs.writeFile(metaPath(sessionId), JSON.stringify(next, null, 2), "utf8");
286
+ await writeSessionMetadataFile(sessionId, next);
270
287
  return next;
271
288
  }
272
289
  async function readModernSessionMetadata(sessionId, options) {
@@ -299,8 +316,26 @@ async function readRawSessionMetadata(sessionId) {
299
316
  (await readLegacySessionMetadata(sessionId, { reconcile: false, persist: false })));
300
317
  }
301
318
  async function reconcileSessionMetadata(meta, { persist }) {
302
- const runtimeChecked = await markDeadBrowser(meta, { persist });
303
- return await markZombie(runtimeChecked, { persist });
319
+ let current = meta;
320
+ const workerPid = current.lifecycle?.workerPid;
321
+ if (workerPid) {
322
+ if (isProcessAlive(workerPid)) {
323
+ return current;
324
+ }
325
+ current = (await readRawSessionMetadata(current.id)) ?? current;
326
+ if (current.status !== "running") {
327
+ return current;
328
+ }
329
+ if (current.lifecycle?.workerPid && isProcessAlive(current.lifecycle.workerPid)) {
330
+ return current;
331
+ }
332
+ }
333
+ const runtimeChecked = await markDeadBrowser(current);
334
+ const reconciled = await markZombie(runtimeChecked);
335
+ if (persist && reconciled !== current) {
336
+ await writeSessionMetadataFile(current.id, reconciled);
337
+ }
338
+ return reconciled;
304
339
  }
305
340
  function isSessionMetadataRecord(value) {
306
341
  return Boolean(value && typeof value.id === "string" && value.status);
@@ -479,7 +514,7 @@ export async function getSessionPaths(sessionId) {
479
514
  }
480
515
  return { dir, metadata, log, request };
481
516
  }
482
- async function markZombie(meta, { persist }) {
517
+ async function markZombie(meta) {
483
518
  if (!(await isZombie(meta))) {
484
519
  return meta;
485
520
  }
@@ -506,12 +541,9 @@ async function markZombie(meta, { persist }) {
506
541
  errorMessage: `Session marked as zombie (> ${formatElapsed(maxAgeMs)} stale)`,
507
542
  completedAt: new Date().toISOString(),
508
543
  };
509
- if (persist) {
510
- await fs.writeFile(metaPath(meta.id), JSON.stringify(updated, null, 2), "utf8");
511
- }
512
544
  return updated;
513
545
  }
514
- async function markDeadBrowser(meta, { persist }) {
546
+ async function markDeadBrowser(meta) {
515
547
  if (meta.status !== "running" || meta.mode !== "browser") {
516
548
  return meta;
517
549
  }
@@ -544,9 +576,6 @@ async function markDeadBrowser(meta, { persist }) {
544
576
  completedAt: new Date().toISOString(),
545
577
  response,
546
578
  };
547
- if (persist) {
548
- await fs.writeFile(metaPath(meta.id), JSON.stringify(updated, null, 2), "utf8");
549
- }
550
579
  return updated;
551
580
  }
552
581
  async function isZombie(meta) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@steipete/oracle",
3
- "version": "0.16.1",
3
+ "version": "0.17.1",
4
4
  "description": "CLI wrapper around OpenAI Responses API with GPT-5.6 Sol, GPT-5.6, 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
6
  "homepage": "https://askoracle.sh",
@@ -58,11 +58,11 @@
58
58
  },
59
59
  "dependencies": {
60
60
  "@anthropic-ai/tokenizer": "^0.0.4",
61
- "@google/genai": "^2.13.0",
61
+ "@google/genai": "^2.15.0",
62
62
  "@google/generative-ai": "^0.24.1",
63
- "@modelcontextprotocol/sdk": "^1.29.0",
64
- "@steipete/sweet-cookie": "^0.4.0",
65
- "chalk": "^5.6.2",
63
+ "@modelcontextprotocol/sdk": "^1.30.0",
64
+ "@steipete/sweet-cookie": "^0.4.1",
65
+ "chalk": "^6.0.0",
66
66
  "chrome-launcher": "^1.2.1",
67
67
  "chrome-remote-interface": "^0.34.0",
68
68
  "clipboardy": "^5.3.2",
@@ -73,28 +73,28 @@
73
73
  "inquirer": "14.0.2",
74
74
  "json5": "^2.2.3",
75
75
  "kleur": "^4.1.5",
76
- "markdansi": "0.3.2",
77
- "openai": "^6.48.0",
76
+ "markdansi": "0.3.3",
77
+ "openai": "^7.4.0",
78
78
  "osc-progress": "^0.3.2",
79
79
  "qs": "^6.15.3",
80
- "shiki": "^4.3.1",
80
+ "shiki": "^4.4.2",
81
81
  "toasted-notifier": "^10.1.0",
82
- "tokentally": "^0.1.2",
82
+ "tokentally": "^0.1.3",
83
83
  "zod": "^4.4.3"
84
84
  },
85
85
  "devDependencies": {
86
86
  "@anthropic-ai/tokenizer": "^0.0.4",
87
87
  "@types/chrome-remote-interface": "^0.34.0",
88
88
  "@types/inquirer": "^9.0.10",
89
- "@types/node": "^26.1.1",
89
+ "@types/node": "^26.1.2",
90
90
  "@vitest/coverage-v8": "4.1.10",
91
- "devtools-protocol": "0.0.1666840",
92
- "es-toolkit": "^1.49.0",
91
+ "devtools-protocol": "0.0.1673900",
92
+ "es-toolkit": "^1.50.0",
93
93
  "esbuild": "^0.28.1",
94
- "oxfmt": "0.60.0",
95
- "oxlint": "^1.75.0",
96
- "puppeteer-core": "^25.3.0",
97
- "tsx": "^4.23.1",
94
+ "oxfmt": "0.62.0",
95
+ "oxlint": "^1.77.0",
96
+ "puppeteer-core": "^25.5.0",
97
+ "tsx": "^4.23.7",
98
98
  "typescript": "^7.0.2",
99
99
  "vitest": "^4.1.10"
100
100
  },
@@ -109,5 +109,5 @@
109
109
  "engines": {
110
110
  "node": ">=24"
111
111
  },
112
- "packageManager": "pnpm@10.33.2"
112
+ "packageManager": "pnpm@10.34.5"
113
113
  }