@steipete/oracle 0.9.0 → 0.11.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.
Files changed (194) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +107 -49
  3. package/dist/bin/oracle-cli.js +551 -410
  4. package/dist/bin/oracle-mcp.js +2 -2
  5. package/dist/bin/oracle.js +165 -279
  6. package/dist/scripts/agent-send.js +31 -31
  7. package/dist/scripts/check.js +6 -6
  8. package/dist/scripts/debug/extract-chatgpt-response.js +10 -10
  9. package/dist/scripts/docs-list.js +30 -30
  10. package/dist/scripts/git-policy.js +25 -23
  11. package/dist/scripts/run-cli.js +8 -8
  12. package/dist/scripts/runner.js +203 -195
  13. package/dist/scripts/test-browser.js +21 -18
  14. package/dist/scripts/test-remote-chrome.js +20 -20
  15. package/dist/src/bridge/connection.js +18 -18
  16. package/dist/src/bridge/userConfigFile.js +7 -7
  17. package/dist/src/browser/actions/archiveConversation.js +224 -0
  18. package/dist/src/browser/actions/assistantResponse.js +175 -101
  19. package/dist/src/browser/actions/attachmentDataTransfer.js +49 -47
  20. package/dist/src/browser/actions/attachments.js +246 -150
  21. package/dist/src/browser/actions/deepResearch.js +662 -0
  22. package/dist/src/browser/actions/domEvents.js +2 -2
  23. package/dist/src/browser/actions/modelSelection.js +342 -119
  24. package/dist/src/browser/actions/navigation.js +183 -137
  25. package/dist/src/browser/actions/projectSources.js +491 -0
  26. package/dist/src/browser/actions/promptComposer.js +152 -91
  27. package/dist/src/browser/actions/remoteFileTransfer.js +10 -10
  28. package/dist/src/browser/actions/thinkingStatus.js +391 -0
  29. package/dist/src/browser/actions/thinkingTime.js +207 -110
  30. package/dist/src/browser/artifacts.js +150 -0
  31. package/dist/src/browser/attachRunning.js +31 -0
  32. package/dist/src/browser/chatgptImages.js +315 -0
  33. package/dist/src/browser/chromeLifecycle.js +276 -63
  34. package/dist/src/browser/config.js +59 -16
  35. package/dist/src/browser/constants.js +25 -12
  36. package/dist/src/browser/controlPlan.js +81 -0
  37. package/dist/src/browser/cookies.js +19 -19
  38. package/dist/src/browser/detect.js +250 -77
  39. package/dist/src/browser/domDebug.js +50 -1
  40. package/dist/src/browser/index.js +1559 -692
  41. package/dist/src/browser/liveTabs.js +434 -0
  42. package/dist/src/browser/modelStrategy.js +1 -1
  43. package/dist/src/browser/pageActions.js +5 -5
  44. package/dist/src/browser/policies.js +16 -13
  45. package/dist/src/browser/profileState.js +127 -42
  46. package/dist/src/browser/projectSourcesRunner.js +366 -0
  47. package/dist/src/browser/prompt.js +72 -42
  48. package/dist/src/browser/promptSummary.js +5 -5
  49. package/dist/src/browser/providerDomFlow.js +1 -1
  50. package/dist/src/browser/providers/chatgptDomProvider.js +9 -9
  51. package/dist/src/browser/providers/geminiDeepThinkDomProvider.js +51 -42
  52. package/dist/src/browser/providers/index.js +2 -2
  53. package/dist/src/browser/reattach.js +178 -73
  54. package/dist/src/browser/reattachHelpers.js +32 -27
  55. package/dist/src/browser/sessionRunner.js +89 -25
  56. package/dist/src/browser/tabLeaseRegistry.js +182 -0
  57. package/dist/src/browser/utils.js +9 -9
  58. package/dist/src/browserMode.js +1 -1
  59. package/dist/src/cli/bridge/claudeConfig.js +24 -20
  60. package/dist/src/cli/bridge/client.js +28 -20
  61. package/dist/src/cli/bridge/codexConfig.js +16 -16
  62. package/dist/src/cli/bridge/doctor.js +47 -39
  63. package/dist/src/cli/bridge/host.js +58 -56
  64. package/dist/src/cli/browserConfig.js +102 -48
  65. package/dist/src/cli/browserDefaults.js +51 -26
  66. package/dist/src/cli/browserTabs.js +228 -0
  67. package/dist/src/cli/bundleWarnings.js +1 -1
  68. package/dist/src/cli/clipboard.js +11 -2
  69. package/dist/src/cli/detach.js +2 -2
  70. package/dist/src/cli/dryRun.js +62 -26
  71. package/dist/src/cli/duplicatePromptGuard.js +12 -4
  72. package/dist/src/cli/engine.js +9 -9
  73. package/dist/src/cli/errorUtils.js +1 -1
  74. package/dist/src/cli/fileSize.js +3 -3
  75. package/dist/src/cli/format.js +2 -2
  76. package/dist/src/cli/help.js +28 -28
  77. package/dist/src/cli/hiddenAliases.js +3 -3
  78. package/dist/src/cli/markdownBundle.js +7 -7
  79. package/dist/src/cli/markdownRenderer.js +15 -15
  80. package/dist/src/cli/notifier.js +77 -67
  81. package/dist/src/cli/options.js +131 -106
  82. package/dist/src/cli/oscUtils.js +1 -1
  83. package/dist/src/cli/projectSources.js +116 -0
  84. package/dist/src/cli/promptRequirement.js +2 -2
  85. package/dist/src/cli/renderOutput.js +1 -1
  86. package/dist/src/cli/rootAlias.js +1 -1
  87. package/dist/src/cli/runOptions.js +32 -28
  88. package/dist/src/cli/sessionCommand.js +82 -21
  89. package/dist/src/cli/sessionDisplay.js +213 -87
  90. package/dist/src/cli/sessionLineage.js +6 -2
  91. package/dist/src/cli/sessionRunner.js +149 -95
  92. package/dist/src/cli/sessionTable.js +26 -23
  93. package/dist/src/cli/stdin.js +22 -0
  94. package/dist/src/cli/tagline.js +121 -124
  95. package/dist/src/cli/tui/index.js +139 -128
  96. package/dist/src/cli/writeOutputPath.js +5 -5
  97. package/dist/src/config.js +7 -7
  98. package/dist/src/gemini-web/browserSessionManager.js +19 -15
  99. package/dist/src/gemini-web/client.js +76 -70
  100. package/dist/src/gemini-web/executionMode.js +6 -8
  101. package/dist/src/gemini-web/executor.js +98 -93
  102. package/dist/src/gemini-web/index.js +1 -1
  103. package/dist/src/mcp/consultPresets.js +19 -0
  104. package/dist/src/mcp/server.js +18 -12
  105. package/dist/src/mcp/tools/consult.js +246 -67
  106. package/dist/src/mcp/tools/projectSources.js +123 -0
  107. package/dist/src/mcp/tools/sessionResources.js +12 -12
  108. package/dist/src/mcp/tools/sessions.js +26 -17
  109. package/dist/src/mcp/types.js +12 -5
  110. package/dist/src/mcp/utils.js +21 -8
  111. package/dist/src/oracle/background.js +15 -15
  112. package/dist/src/oracle/claude.js +53 -25
  113. package/dist/src/oracle/client.js +50 -41
  114. package/dist/src/oracle/config.js +96 -66
  115. package/dist/src/oracle/errors.js +38 -38
  116. package/dist/src/oracle/files.js +55 -46
  117. package/dist/src/oracle/finishLine.js +10 -8
  118. package/dist/src/oracle/format.js +3 -3
  119. package/dist/src/oracle/gemini.js +37 -33
  120. package/dist/src/oracle/logging.js +7 -7
  121. package/dist/src/oracle/markdown.js +28 -28
  122. package/dist/src/oracle/modelResolver.js +16 -16
  123. package/dist/src/oracle/multiModelRunner.js +12 -12
  124. package/dist/src/oracle/oscProgress.js +8 -8
  125. package/dist/src/oracle/promptAssembly.js +6 -3
  126. package/dist/src/oracle/request.js +16 -13
  127. package/dist/src/oracle/run.js +160 -135
  128. package/dist/src/oracle/runUtils.js +8 -5
  129. package/dist/src/oracle/tokenEstimate.js +6 -6
  130. package/dist/src/oracle/tokenStats.js +5 -5
  131. package/dist/src/oracle/tokenStringifier.js +5 -5
  132. package/dist/src/oracle.js +12 -12
  133. package/dist/src/oracleHome.js +3 -3
  134. package/dist/src/projectSources/plan.js +27 -0
  135. package/dist/src/projectSources/url.js +23 -0
  136. package/dist/src/remote/client.js +25 -25
  137. package/dist/src/remote/health.js +20 -20
  138. package/dist/src/remote/remoteServiceConfig.js +9 -9
  139. package/dist/src/remote/server.js +129 -118
  140. package/dist/src/sessionManager.js +78 -75
  141. package/dist/src/sessionStore.js +3 -3
  142. package/dist/src/version.js +10 -10
  143. package/dist/vendor/oracle-notifier/README.md +2 -0
  144. package/package.json +67 -62
  145. package/vendor/oracle-notifier/README.md +2 -0
  146. package/dist/markdansi/types/index.js +0 -4
  147. package/dist/oracle/bin/oracle-cli.js +0 -472
  148. package/dist/oracle/src/browser/actions/assistantResponse.js +0 -471
  149. package/dist/oracle/src/browser/actions/attachments.js +0 -82
  150. package/dist/oracle/src/browser/actions/modelSelection.js +0 -190
  151. package/dist/oracle/src/browser/actions/navigation.js +0 -75
  152. package/dist/oracle/src/browser/actions/promptComposer.js +0 -167
  153. package/dist/oracle/src/browser/chromeLifecycle.js +0 -104
  154. package/dist/oracle/src/browser/config.js +0 -33
  155. package/dist/oracle/src/browser/constants.js +0 -40
  156. package/dist/oracle/src/browser/cookies.js +0 -210
  157. package/dist/oracle/src/browser/domDebug.js +0 -36
  158. package/dist/oracle/src/browser/index.js +0 -331
  159. package/dist/oracle/src/browser/pageActions.js +0 -5
  160. package/dist/oracle/src/browser/prompt.js +0 -88
  161. package/dist/oracle/src/browser/promptSummary.js +0 -20
  162. package/dist/oracle/src/browser/sessionRunner.js +0 -80
  163. package/dist/oracle/src/browser/utils.js +0 -62
  164. package/dist/oracle/src/browserMode.js +0 -1
  165. package/dist/oracle/src/cli/browserConfig.js +0 -44
  166. package/dist/oracle/src/cli/dryRun.js +0 -59
  167. package/dist/oracle/src/cli/engine.js +0 -17
  168. package/dist/oracle/src/cli/errorUtils.js +0 -9
  169. package/dist/oracle/src/cli/help.js +0 -70
  170. package/dist/oracle/src/cli/markdownRenderer.js +0 -15
  171. package/dist/oracle/src/cli/options.js +0 -103
  172. package/dist/oracle/src/cli/promptRequirement.js +0 -14
  173. package/dist/oracle/src/cli/rootAlias.js +0 -30
  174. package/dist/oracle/src/cli/sessionCommand.js +0 -77
  175. package/dist/oracle/src/cli/sessionDisplay.js +0 -270
  176. package/dist/oracle/src/cli/sessionRunner.js +0 -94
  177. package/dist/oracle/src/heartbeat.js +0 -43
  178. package/dist/oracle/src/oracle/client.js +0 -48
  179. package/dist/oracle/src/oracle/config.js +0 -29
  180. package/dist/oracle/src/oracle/errors.js +0 -101
  181. package/dist/oracle/src/oracle/files.js +0 -220
  182. package/dist/oracle/src/oracle/format.js +0 -33
  183. package/dist/oracle/src/oracle/fsAdapter.js +0 -7
  184. package/dist/oracle/src/oracle/oscProgress.js +0 -60
  185. package/dist/oracle/src/oracle/request.js +0 -48
  186. package/dist/oracle/src/oracle/run.js +0 -444
  187. package/dist/oracle/src/oracle/tokenStats.js +0 -39
  188. package/dist/oracle/src/oracle/types.js +0 -1
  189. package/dist/oracle/src/oracle.js +0 -9
  190. package/dist/oracle/src/sessionManager.js +0 -205
  191. package/dist/oracle/src/version.js +0 -39
  192. package/dist/scripts/chrome/browser-tools.js +0 -295
  193. package/dist/src/browser/profileSync.js +0 -141
  194. /package/dist/{oracle/src/browser → src/projectSources}/types.js +0 -0
@@ -1,10 +1,19 @@
1
- import fs from 'node:fs/promises';
2
- import path from 'node:path';
3
- import fg from 'fast-glob';
4
- import { FileValidationError } from './errors.js';
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import fg from "fast-glob";
4
+ import { FileValidationError } from "./errors.js";
5
5
  export const DEFAULT_MAX_FILE_SIZE_BYTES = 1 * 1024 * 1024; // 1 MB
6
6
  const DEFAULT_FS = fs;
7
- const DEFAULT_IGNORED_DIRS = ['node_modules', 'dist', 'coverage', '.git', '.turbo', '.next', 'build', 'tmp'];
7
+ const DEFAULT_IGNORED_DIRS = new Set([
8
+ "node_modules",
9
+ "dist",
10
+ "coverage",
11
+ ".git",
12
+ ".turbo",
13
+ ".next",
14
+ "build",
15
+ "tmp",
16
+ ]);
8
17
  export async function readFiles(filePaths, { cwd = process.cwd(), fsModule = DEFAULT_FS, maxFileSizeBytes = DEFAULT_MAX_FILE_SIZE_BYTES, readContents = true, } = {}) {
9
18
  if (!filePaths || filePaths.length === 0) {
10
19
  return [];
@@ -24,13 +33,13 @@ export async function readFiles(filePaths, { cwd = process.cwd(), fsModule = DEF
24
33
  }
25
34
  else {
26
35
  if (partitioned.globPatterns.length > 0 || partitioned.excludePatterns.length > 0) {
27
- throw new Error('Glob patterns and exclusions are only supported for on-disk files.');
36
+ throw new Error("Glob patterns and exclusions are only supported for on-disk files.");
28
37
  }
29
38
  candidatePaths = await expandWithCustomFs(partitioned, fsModule);
30
39
  }
31
40
  const allowedLiteralDirs = partitioned.literalDirectories
32
41
  .map((dir) => path.resolve(dir))
33
- .filter((dir) => DEFAULT_IGNORED_DIRS.includes(path.basename(dir)));
42
+ .filter((dir) => DEFAULT_IGNORED_DIRS.has(path.basename(dir)));
34
43
  const allowedLiteralFiles = partitioned.literalFiles.map((file) => path.resolve(file));
35
44
  const resolvedLiteralDirs = new Set(allowedLiteralDirs);
36
45
  const allowedPaths = new Set([...allowedLiteralDirs, ...allowedLiteralFiles]);
@@ -50,7 +59,7 @@ export async function readFiles(filePaths, { cwd = process.cwd(), fsModule = DEF
50
59
  return false;
51
60
  });
52
61
  if (filteredCandidates.length === 0) {
53
- throw new FileValidationError('No files matched the provided --file patterns.', {
62
+ throw new FileValidationError("No files matched the provided --file patterns.", {
54
63
  patterns: partitioned.globPatterns,
55
64
  excludes: partitioned.excludePatterns,
56
65
  });
@@ -68,7 +77,7 @@ export async function readFiles(filePaths, { cwd = process.cwd(), fsModule = DEF
68
77
  if (!stats.isFile()) {
69
78
  continue;
70
79
  }
71
- if (maxFileSizeBytes && typeof stats.size === 'number' && stats.size > maxFileSizeBytes) {
80
+ if (maxFileSizeBytes && typeof stats.size === "number" && stats.size > maxFileSizeBytes) {
72
81
  const relative = path.relative(cwd, filePath) || filePath;
73
82
  oversized.push(`${relative} (${formatBytes(stats.size)})`);
74
83
  continue;
@@ -76,14 +85,14 @@ export async function readFiles(filePaths, { cwd = process.cwd(), fsModule = DEF
76
85
  accepted.push(filePath);
77
86
  }
78
87
  if (oversized.length > 0) {
79
- throw new FileValidationError(`The following files exceed the ${formatBytes(maxFileSizeBytes)} limit:\n- ${oversized.join('\n- ')}`, {
88
+ throw new FileValidationError(`The following files exceed the ${formatBytes(maxFileSizeBytes)} limit:\n- ${oversized.join("\n- ")}`, {
80
89
  files: oversized,
81
90
  limitBytes: maxFileSizeBytes,
82
91
  });
83
92
  }
84
93
  const files = [];
85
94
  for (const filePath of accepted) {
86
- const content = readContents ? await fsModule.readFile(filePath, 'utf8') : '';
95
+ const content = readContents ? await fsModule.readFile(filePath, "utf8") : "";
87
96
  files.push({ path: filePath, content });
88
97
  }
89
98
  return files;
@@ -100,7 +109,7 @@ async function partitionFileInputs(rawPaths, cwd, fsModule) {
100
109
  if (!raw) {
101
110
  continue;
102
111
  }
103
- if (raw.startsWith('!')) {
112
+ if (raw.startsWith("!")) {
104
113
  const normalized = normalizeGlob(raw.slice(1), cwd);
105
114
  if (normalized) {
106
115
  result.excludePatterns.push(normalized);
@@ -153,11 +162,13 @@ async function expandWithNativeGlob(partitioned, cwd) {
153
162
  }));
154
163
  const resolved = matches.map((match) => path.resolve(cwd, match));
155
164
  const filtered = resolved.filter((filePath) => !isGitignored(filePath, gitignoreSets));
156
- const finalFiles = dotfileOptIn ? filtered : filtered.filter((filePath) => !path.basename(filePath).startsWith('.'));
165
+ const finalFiles = dotfileOptIn
166
+ ? filtered
167
+ : filtered.filter((filePath) => !path.basename(filePath).startsWith("."));
157
168
  return Array.from(new Set(finalFiles));
158
169
  }
159
170
  async function loadGitignoreSets(cwd) {
160
- const gitignorePaths = await fg('**/.gitignore', {
171
+ const gitignorePaths = await fg("**/.gitignore", {
161
172
  cwd,
162
173
  dot: true,
163
174
  absolute: true,
@@ -168,11 +179,11 @@ async function loadGitignoreSets(cwd) {
168
179
  const sets = [];
169
180
  for (const filePath of gitignorePaths) {
170
181
  try {
171
- const raw = await fs.readFile(filePath, 'utf8');
182
+ const raw = await fs.readFile(filePath, "utf8");
172
183
  const patterns = raw
173
- .split('\n')
184
+ .split("\n")
174
185
  .map((line) => line.trim())
175
- .filter((line) => line.length > 0 && !line.startsWith('#'));
186
+ .filter((line) => line.length > 0 && !line.startsWith("#"));
176
187
  if (patterns.length > 0) {
177
188
  sets.push({ dir: path.dirname(filePath), patterns });
178
189
  }
@@ -204,7 +215,7 @@ async function buildIgnoredWhitelist(filePaths, cwd, fsModule) {
204
215
  const parts = rel.split(path.sep).filter(Boolean);
205
216
  for (let i = 0; i < parts.length - 1; i += 1) {
206
217
  const part = parts[i];
207
- if (!DEFAULT_IGNORED_DIRS.includes(part)) {
218
+ if (!DEFAULT_IGNORED_DIRS.has(part)) {
208
219
  continue;
209
220
  }
210
221
  const dirPath = path.resolve(cwd, ...parts.slice(0, i + 1));
@@ -212,7 +223,7 @@ async function buildIgnoredWhitelist(filePaths, cwd, fsModule) {
212
223
  continue;
213
224
  }
214
225
  try {
215
- const stats = await fsModule.stat(path.join(dirPath, '.gitignore'));
226
+ const stats = await fsModule.stat(path.join(dirPath, ".gitignore"));
216
227
  if (stats.isFile()) {
217
228
  whitelist.add(dirPath);
218
229
  }
@@ -233,7 +244,7 @@ function findIgnoredAncestor(filePath, cwd, _literalDirs, allowedPaths, ignoredW
233
244
  const parts = rel.split(path.sep);
234
245
  for (let idx = 0; idx < parts.length; idx += 1) {
235
246
  const part = parts[idx];
236
- if (!DEFAULT_IGNORED_DIRS.includes(part)) {
247
+ if (!DEFAULT_IGNORED_DIRS.has(part)) {
237
248
  continue;
238
249
  }
239
250
  const ignoredDir = path.resolve(cwd, parts.slice(0, idx + 1).join(path.sep));
@@ -251,9 +262,9 @@ function matchesPattern(relativePath, pattern) {
251
262
  if (!pattern) {
252
263
  return false;
253
264
  }
254
- const normalized = pattern.replace(/\\+/g, '/');
265
+ const normalized = pattern.replace(/\\+/g, "/");
255
266
  // Directory rule
256
- if (normalized.endsWith('/')) {
267
+ if (normalized.endsWith("/")) {
257
268
  const dir = normalized.slice(0, -1);
258
269
  return relativePath === dir || relativePath.startsWith(`${dir}/`);
259
270
  }
@@ -262,16 +273,14 @@ function matchesPattern(relativePath, pattern) {
262
273
  return regex.test(relativePath);
263
274
  }
264
275
  function globToRegex(pattern) {
265
- const withMarkers = pattern.replace(/\*\*/g, '§§DOUBLESTAR§§').replace(/\*/g, '§§SINGLESTAR§§');
266
- const escaped = withMarkers.replace(/[.+?^${}()|[\]\\]/g, '\\$&');
267
- const restored = escaped
268
- .replace(/§§DOUBLESTAR§§/g, '.*')
269
- .replace(/§§SINGLESTAR§§/g, '[^/]*');
276
+ const withMarkers = pattern.replace(/\*\*/g, "§§DOUBLESTAR§§").replace(/\*/g, "§§SINGLESTAR§§");
277
+ const escaped = withMarkers.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
278
+ const restored = escaped.replace(/§§DOUBLESTAR§§/g, ".*").replace(/§§SINGLESTAR§§/g, "[^/]*");
270
279
  return new RegExp(`^${restored}$`);
271
280
  }
272
281
  function includesDotfileSegment(pattern) {
273
- const segments = pattern.split('/');
274
- return segments.some((segment) => segment.startsWith('.') && segment.length > 1);
282
+ const segments = pattern.split("/");
283
+ return segments.some((segment) => segment.startsWith(".") && segment.length > 1);
275
284
  }
276
285
  async function expandWithCustomFs(partitioned, fsModule) {
277
286
  const paths = new Set();
@@ -302,38 +311,38 @@ async function expandDirectoryRecursive(directory, fsModule) {
302
311
  return results;
303
312
  }
304
313
  function makeDirectoryPattern(relative) {
305
- if (relative === '.' || relative === '') {
306
- return '**/*';
314
+ if (relative === "." || relative === "") {
315
+ return "**/*";
307
316
  }
308
317
  return `${stripTrailingSlashes(relative)}/**/*`;
309
318
  }
310
319
  function isNativeFsModule(fsModule) {
311
- return ((fsModule.__nativeFs === true ||
320
+ return (fsModule.__nativeFs === true ||
312
321
  (fsModule.readFile === DEFAULT_FS.readFile &&
313
322
  fsModule.stat === DEFAULT_FS.stat &&
314
- fsModule.readdir === DEFAULT_FS.readdir)));
323
+ fsModule.readdir === DEFAULT_FS.readdir));
315
324
  }
316
325
  function normalizeGlob(pattern, cwd) {
317
326
  if (!pattern) {
318
- return '';
327
+ return "";
319
328
  }
320
329
  let normalized = pattern;
321
330
  if (path.isAbsolute(normalized)) {
322
331
  normalized = path.relative(cwd, normalized);
323
332
  }
324
333
  normalized = toPosix(normalized);
325
- if (normalized.startsWith('./')) {
334
+ if (normalized.startsWith("./")) {
326
335
  normalized = normalized.slice(2);
327
336
  }
328
337
  return normalized;
329
338
  }
330
339
  function toPosix(value) {
331
- return value.replace(/\\/g, '/');
340
+ return value.replace(/\\/g, "/");
332
341
  }
333
342
  function toPosixRelative(absPath, cwd) {
334
343
  const relative = path.relative(cwd, absPath);
335
344
  if (!relative) {
336
- return '.';
345
+ return ".";
337
346
  }
338
347
  return toPosix(relative);
339
348
  }
@@ -343,7 +352,7 @@ function toPosixRelativeOrBasename(absPath, cwd) {
343
352
  }
344
353
  function stripTrailingSlashes(value) {
345
354
  const normalized = toPosix(value);
346
- return normalized.replace(/\/+$/g, '');
355
+ return normalized.replace(/\/+$/g, "");
347
356
  }
348
357
  function formatBytes(size) {
349
358
  if (size >= 1024 * 1024) {
@@ -355,15 +364,15 @@ function formatBytes(size) {
355
364
  return `${size} B`;
356
365
  }
357
366
  function formatScaled(value) {
358
- return value.toFixed(1).replace(/\.0$/, '');
367
+ return value.toFixed(1).replace(/\.0$/, "");
359
368
  }
360
- export function normalizeMaxFileSizeBytes(value, source = 'max file size') {
361
- if (value == null || value === '') {
369
+ export function normalizeMaxFileSizeBytes(value, source = "max file size") {
370
+ if (value == null || value === "") {
362
371
  return undefined;
363
372
  }
364
- const parsed = typeof value === 'number'
373
+ const parsed = typeof value === "number"
365
374
  ? value
366
- : Number.parseInt(typeof value === 'string' ? value.trim() : String(value), 10);
375
+ : Number.parseInt(typeof value === "string" ? value.trim() : String(value), 10);
367
376
  if (!Number.isSafeInteger(parsed) || parsed <= 0) {
368
377
  throw new Error(`${source} must be a positive integer number of bytes.`);
369
378
  }
@@ -378,10 +387,10 @@ export function createFileSections(files, cwd = process.cwd()) {
378
387
  const relative = toPosix(path.relative(cwd, file.path) || file.path);
379
388
  const sectionText = [
380
389
  `### File ${index + 1}: ${relative}`,
381
- '```',
390
+ "```",
382
391
  file.content.trimEnd(),
383
- '```',
384
- ].join('\n');
392
+ "```",
393
+ ].join("\n");
385
394
  return {
386
395
  index: index + 1,
387
396
  absolutePath: file.path,
@@ -1,7 +1,7 @@
1
- import { formatUSD } from './format.js';
1
+ import { formatUSD } from "./format.js";
2
2
  export function formatElapsedCompact(ms) {
3
3
  if (!Number.isFinite(ms) || ms < 0) {
4
- return 'unknown';
4
+ return "unknown";
5
5
  }
6
6
  if (ms < 60_000) {
7
7
  return `${(ms / 1000).toFixed(1)}s`;
@@ -9,24 +9,26 @@ export function formatElapsedCompact(ms) {
9
9
  if (ms < 60 * 60_000) {
10
10
  const minutes = Math.floor(ms / 60_000);
11
11
  const seconds = Math.floor((ms % 60_000) / 1000);
12
- return `${minutes}m${seconds.toString().padStart(2, '0')}s`;
12
+ return `${minutes}m${seconds.toString().padStart(2, "0")}s`;
13
13
  }
14
14
  const hours = Math.floor(ms / (60 * 60_000));
15
15
  const minutes = Math.floor((ms % (60 * 60_000)) / 60_000);
16
- return `${hours}h${minutes.toString().padStart(2, '0')}m`;
16
+ return `${hours}h${minutes.toString().padStart(2, "0")}m`;
17
17
  }
18
18
  export function formatFinishLine({ elapsedMs, model, costUsd, tokensPart, summaryExtraParts, detailParts, }) {
19
19
  const line1Parts = [
20
20
  formatElapsedCompact(elapsedMs),
21
- typeof costUsd === 'number' ? formatUSD(costUsd) : null,
21
+ typeof costUsd === "number" ? formatUSD(costUsd) : null,
22
22
  model,
23
23
  tokensPart,
24
24
  ...(summaryExtraParts ?? []),
25
25
  ];
26
- const line1 = line1Parts.filter((part) => typeof part === 'string' && part.length > 0).join(' · ');
27
- const line2Parts = (detailParts ?? []).filter((part) => typeof part === 'string' && part.length > 0);
26
+ const line1 = line1Parts
27
+ .filter((part) => typeof part === "string" && part.length > 0)
28
+ .join(" · ");
29
+ const line2Parts = (detailParts ?? []).filter((part) => typeof part === "string" && part.length > 0);
28
30
  if (line2Parts.length === 0) {
29
31
  return { line1 };
30
32
  }
31
- return { line1, line2: line2Parts.join(' | ') };
33
+ return { line1, line2: line2Parts.join(" | ") };
32
34
  }
@@ -1,15 +1,15 @@
1
1
  export function formatUSD(value) {
2
2
  if (!Number.isFinite(value)) {
3
- return 'n/a';
3
+ return "n/a";
4
4
  }
5
5
  // Display with 4 decimal places, rounding to $0.0001 minimum granularity.
6
6
  return `$${value.toFixed(4)}`;
7
7
  }
8
8
  export function formatNumber(value, { estimated = false } = {}) {
9
9
  if (value == null) {
10
- return 'n/a';
10
+ return "n/a";
11
11
  }
12
- const suffix = estimated ? ' (est.)' : '';
12
+ const suffix = estimated ? " (est.)" : "";
13
13
  return `${value.toLocaleString()}${suffix}`;
14
14
  }
15
15
  export function formatElapsed(ms) {
@@ -1,33 +1,35 @@
1
- import { GoogleGenAI, HarmCategory, HarmBlockThreshold } from '@google/genai';
1
+ import { GoogleGenAI, HarmCategory, HarmBlockThreshold, } from "@google/genai";
2
2
  const MODEL_ID_MAP = {
3
- 'gemini-3.1-pro': 'gemini-3.1-pro-preview',
4
- 'gemini-3-pro': 'gemini-3-pro-preview',
5
- 'gpt-5.4': 'gpt-5.4',
6
- 'gpt-5.4-pro': 'gpt-5.4-pro',
7
- 'gpt-5.1-pro': 'gpt-5.1-pro',
8
- 'gpt-5-pro': 'gpt-5-pro',
9
- 'gpt-5.1': 'gpt-5.1',
10
- 'gpt-5.1-codex': 'gpt-5.1-codex',
11
- 'gpt-5.2': 'gpt-5.2',
12
- 'gpt-5.2-instant': 'gpt-5.2-instant',
13
- 'gpt-5.2-pro': 'gpt-5.2-pro',
14
- 'claude-4.5-sonnet': 'claude-4.5-sonnet',
15
- 'claude-4.1-opus': 'claude-4.1-opus',
16
- 'grok-4.1': 'grok-4.1',
3
+ "gemini-3.1-pro": "gemini-3.1-pro-preview",
4
+ "gemini-3-pro": "gemini-3-pro-preview",
5
+ "gpt-5.5": "gpt-5.5",
6
+ "gpt-5.5-pro": "gpt-5.5-pro",
7
+ "gpt-5.4": "gpt-5.4",
8
+ "gpt-5.4-pro": "gpt-5.4-pro",
9
+ "gpt-5.1-pro": "gpt-5.1-pro",
10
+ "gpt-5-pro": "gpt-5-pro",
11
+ "gpt-5.1": "gpt-5.1",
12
+ "gpt-5.1-codex": "gpt-5.1-codex",
13
+ "gpt-5.2": "gpt-5.2",
14
+ "gpt-5.2-instant": "gpt-5.2-instant",
15
+ "gpt-5.2-pro": "gpt-5.2-pro",
16
+ "claude-4.6-sonnet": "claude-4.6-sonnet",
17
+ "claude-4.1-opus": "claude-4.1-opus",
18
+ "grok-4.1": "grok-4.1",
17
19
  };
18
20
  export function resolveGeminiModelId(modelName) {
19
21
  // Map our logical Gemini names to the exact model ids expected by the SDK.
20
22
  return MODEL_ID_MAP[modelName] ?? modelName;
21
23
  }
22
- export function createGeminiClient(apiKey, modelName = 'gemini-3-pro', resolvedModelId) {
24
+ export function createGeminiClient(apiKey, modelName = "gemini-3-pro", resolvedModelId) {
23
25
  const modelId = resolvedModelId ?? resolveGeminiModelId(modelName);
24
26
  const genAI = new GoogleGenAI({ apiKey });
25
27
  const adaptBodyToGemini = (body) => {
26
28
  const contents = body.input.map((inputItem) => ({
27
- role: inputItem.role === 'user' ? 'user' : 'model',
29
+ role: inputItem.role === "user" ? "user" : "model",
28
30
  parts: inputItem.content
29
31
  .map((contentPart) => {
30
- if (contentPart.type === 'input_text') {
32
+ if (contentPart.type === "input_text") {
31
33
  return { text: contentPart.text };
32
34
  }
33
35
  return null;
@@ -36,7 +38,7 @@ export function createGeminiClient(apiKey, modelName = 'gemini-3-pro', resolvedM
36
38
  }));
37
39
  const tools = body.tools
38
40
  ?.map((tool) => {
39
- if (tool.type === 'web_search_preview') {
41
+ if (tool.type === "web_search_preview") {
40
42
  return {
41
43
  googleSearch: {},
42
44
  };
@@ -63,7 +65,7 @@ export function createGeminiClient(apiKey, modelName = 'gemini-3-pro', resolvedM
63
65
  },
64
66
  ];
65
67
  const systemInstruction = body.instructions
66
- ? { role: 'system', parts: [{ text: body.instructions }] }
68
+ ? { role: "system", parts: [{ text: body.instructions }] }
67
69
  : undefined;
68
70
  return {
69
71
  model: modelId,
@@ -83,18 +85,19 @@ export function createGeminiClient(apiKey, modelName = 'gemini-3-pro', resolvedM
83
85
  candidate.content?.parts?.forEach((part) => {
84
86
  if (part.text) {
85
87
  outputText.push(part.text);
86
- output.push({ type: 'text', text: part.text });
88
+ output.push({ type: "text", text: part.text });
87
89
  }
88
90
  });
89
91
  });
90
92
  const usage = {
91
93
  input_tokens: geminiResponse.usageMetadata?.promptTokenCount || 0,
92
94
  output_tokens: geminiResponse.usageMetadata?.candidatesTokenCount || 0,
93
- total_tokens: (geminiResponse.usageMetadata?.promptTokenCount || 0) + (geminiResponse.usageMetadata?.candidatesTokenCount || 0),
95
+ total_tokens: (geminiResponse.usageMetadata?.promptTokenCount || 0) +
96
+ (geminiResponse.usageMetadata?.candidatesTokenCount || 0),
94
97
  };
95
98
  return {
96
99
  id: geminiResponse.responseId ?? `gemini-${Date.now()}`,
97
- status: 'completed',
100
+ status: "completed",
98
101
  output_text: outputText,
99
102
  output,
100
103
  usage,
@@ -108,15 +111,15 @@ export function createGeminiClient(apiKey, modelName = 'gemini-3-pro', resolvedM
108
111
  };
109
112
  return {
110
113
  id: responseId ?? `gemini-${Date.now()}`,
111
- status: 'completed',
114
+ status: "completed",
112
115
  output_text: [text],
113
- output: [{ type: 'text', text }],
116
+ output: [{ type: "text", text }],
114
117
  usage,
115
118
  };
116
119
  };
117
120
  const enrichGeminiError = (error) => {
118
121
  const message = error instanceof Error ? error.message : String(error);
119
- if (message.includes('404')) {
122
+ if (message.includes("404")) {
120
123
  return new Error(`Gemini model not available to this API key/region. Confirm preview access and model ID (${modelId}). Original: ${message}`);
121
124
  }
122
125
  return error instanceof Error ? error : new Error(message);
@@ -126,7 +129,7 @@ export function createGeminiClient(apiKey, modelName = 'gemini-3-pro', resolvedM
126
129
  stream: (body) => {
127
130
  const geminiBody = adaptBodyToGemini(body);
128
131
  let finalResponsePromise = null;
129
- let aggregatedText = '';
132
+ let aggregatedText = "";
130
133
  let lastUsage;
131
134
  let responseId;
132
135
  async function* iterator() {
@@ -141,7 +144,7 @@ export function createGeminiClient(apiKey, modelName = 'gemini-3-pro', resolvedM
141
144
  const text = chunk.text;
142
145
  if (text) {
143
146
  aggregatedText += text;
144
- yield { type: 'chunk', delta: text };
147
+ yield { type: "chunk", delta: text };
145
148
  }
146
149
  if (chunk.usageMetadata) {
147
150
  lastUsage = chunk.usageMetadata;
@@ -160,13 +163,14 @@ export function createGeminiClient(apiKey, modelName = 'gemini-3-pro', resolvedM
160
163
  if (!finalResponsePromise) {
161
164
  // In case the user calls finalResponse before iterating, we need to consume the stream
162
165
  // This is a bit edge-casey but safe.
163
- for await (const _ of generator) { }
166
+ for await (const _ of generator) {
167
+ }
164
168
  }
165
169
  if (!finalResponsePromise) {
166
- throw new Error('Response promise not initialized');
170
+ throw new Error("Response promise not initialized");
167
171
  }
168
172
  return finalResponsePromise;
169
- }
173
+ },
170
174
  };
171
175
  },
172
176
  create: async (body) => {
@@ -183,8 +187,8 @@ export function createGeminiClient(apiKey, modelName = 'gemini-3-pro', resolvedM
183
187
  retrieve: async (id) => {
184
188
  return {
185
189
  id,
186
- status: 'error',
187
- error: { message: 'Retrieve by ID not supported for Gemini API yet.' },
190
+ status: "error",
191
+ error: { message: "Retrieve by ID not supported for Gemini API yet." },
188
192
  };
189
193
  },
190
194
  },
@@ -2,29 +2,29 @@ export function maskApiKey(key) {
2
2
  if (!key)
3
3
  return null;
4
4
  if (key.length <= 8)
5
- return `${key[0] ?? ''}***${key[key.length - 1] ?? ''}`;
5
+ return `${key[0] ?? ""}***${key[key.length - 1] ?? ""}`;
6
6
  const prefix = key.slice(0, 4);
7
7
  const suffix = key.slice(-4);
8
8
  return `${prefix}****${suffix}`;
9
9
  }
10
10
  export function formatBaseUrlForLog(raw) {
11
11
  if (!raw)
12
- return '';
12
+ return "";
13
13
  try {
14
14
  const parsed = new URL(raw);
15
- const segments = parsed.pathname.split('/').filter(Boolean);
16
- let path = '';
15
+ const segments = parsed.pathname.split("/").filter(Boolean);
16
+ let path = "";
17
17
  if (segments.length > 0) {
18
18
  path = `/${segments[0]}`;
19
19
  if (segments.length > 1) {
20
- path += '/...';
20
+ path += "/...";
21
21
  }
22
22
  }
23
- const allowedQueryKeys = ['api-version'];
23
+ const allowedQueryKeys = ["api-version"];
24
24
  const maskedQuery = allowedQueryKeys
25
25
  .filter((key) => parsed.searchParams.has(key))
26
26
  .map((key) => `${key}=***`);
27
- const query = maskedQuery.length > 0 ? `?${maskedQuery.join('&')}` : '';
27
+ const query = maskedQuery.length > 0 ? `?${maskedQuery.join("&")}` : "";
28
28
  return `${parsed.protocol}//${parsed.host}${path}${query}`;
29
29
  }
30
30
  catch {
@@ -1,29 +1,29 @@
1
- import path from 'node:path';
1
+ import path from "node:path";
2
2
  const EXT_TO_LANG = {
3
- '.ts': 'ts',
4
- '.tsx': 'tsx',
5
- '.js': 'js',
6
- '.jsx': 'jsx',
7
- '.json': 'json',
8
- '.swift': 'swift',
9
- '.md': 'md',
10
- '.sh': 'bash',
11
- '.bash': 'bash',
12
- '.zsh': 'bash',
13
- '.py': 'python',
14
- '.rb': 'ruby',
15
- '.rs': 'rust',
16
- '.go': 'go',
17
- '.java': 'java',
18
- '.c': 'c',
19
- '.h': 'c',
20
- '.cpp': 'cpp',
21
- '.hpp': 'cpp',
22
- '.css': 'css',
23
- '.scss': 'scss',
24
- '.sql': 'sql',
25
- '.yaml': 'yaml',
26
- '.yml': 'yaml',
3
+ ".ts": "ts",
4
+ ".tsx": "tsx",
5
+ ".js": "js",
6
+ ".jsx": "jsx",
7
+ ".json": "json",
8
+ ".swift": "swift",
9
+ ".md": "md",
10
+ ".sh": "bash",
11
+ ".bash": "bash",
12
+ ".zsh": "bash",
13
+ ".py": "python",
14
+ ".rb": "ruby",
15
+ ".rs": "rust",
16
+ ".go": "go",
17
+ ".java": "java",
18
+ ".c": "c",
19
+ ".h": "c",
20
+ ".cpp": "cpp",
21
+ ".hpp": "cpp",
22
+ ".css": "css",
23
+ ".scss": "scss",
24
+ ".sql": "sql",
25
+ ".yaml": "yaml",
26
+ ".yml": "yaml",
27
27
  };
28
28
  function detectFenceLanguage(displayPath) {
29
29
  const ext = path.extname(displayPath).toLowerCase();
@@ -34,13 +34,13 @@ function pickFence(content) {
34
34
  const matches = [...content.matchAll(/`+/g)];
35
35
  const maxTicks = matches.reduce((max, m) => Math.max(max, m[0].length), 0);
36
36
  const fenceLength = Math.max(3, maxTicks + 1);
37
- return '`'.repeat(fenceLength);
37
+ return "`".repeat(fenceLength);
38
38
  }
39
39
  export function formatFileSection(displayPath, content) {
40
40
  const fence = pickFence(content);
41
41
  const lang = detectFenceLanguage(displayPath);
42
- const normalized = content.replace(/\s+$/u, '');
42
+ const normalized = content.replace(/\s+$/u, "");
43
43
  const header = `### File: ${displayPath}`;
44
44
  const fenceOpen = lang ? `${fence}${lang}` : fence;
45
- return [header, fenceOpen, normalized, fence, ''].join('\n');
45
+ return [header, fenceOpen, normalized, fence, ""].join("\n");
46
46
  }