@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.
- package/LICENSE +1 -1
- package/README.md +107 -49
- package/dist/bin/oracle-cli.js +551 -410
- package/dist/bin/oracle-mcp.js +2 -2
- package/dist/bin/oracle.js +165 -279
- package/dist/scripts/agent-send.js +31 -31
- package/dist/scripts/check.js +6 -6
- package/dist/scripts/debug/extract-chatgpt-response.js +10 -10
- package/dist/scripts/docs-list.js +30 -30
- package/dist/scripts/git-policy.js +25 -23
- package/dist/scripts/run-cli.js +8 -8
- package/dist/scripts/runner.js +203 -195
- package/dist/scripts/test-browser.js +21 -18
- package/dist/scripts/test-remote-chrome.js +20 -20
- package/dist/src/bridge/connection.js +18 -18
- package/dist/src/bridge/userConfigFile.js +7 -7
- package/dist/src/browser/actions/archiveConversation.js +224 -0
- package/dist/src/browser/actions/assistantResponse.js +175 -101
- package/dist/src/browser/actions/attachmentDataTransfer.js +49 -47
- package/dist/src/browser/actions/attachments.js +246 -150
- package/dist/src/browser/actions/deepResearch.js +662 -0
- package/dist/src/browser/actions/domEvents.js +2 -2
- package/dist/src/browser/actions/modelSelection.js +342 -119
- package/dist/src/browser/actions/navigation.js +183 -137
- package/dist/src/browser/actions/projectSources.js +491 -0
- package/dist/src/browser/actions/promptComposer.js +152 -91
- package/dist/src/browser/actions/remoteFileTransfer.js +10 -10
- package/dist/src/browser/actions/thinkingStatus.js +391 -0
- package/dist/src/browser/actions/thinkingTime.js +207 -110
- package/dist/src/browser/artifacts.js +150 -0
- package/dist/src/browser/attachRunning.js +31 -0
- package/dist/src/browser/chatgptImages.js +315 -0
- package/dist/src/browser/chromeLifecycle.js +276 -63
- package/dist/src/browser/config.js +59 -16
- package/dist/src/browser/constants.js +25 -12
- package/dist/src/browser/controlPlan.js +81 -0
- package/dist/src/browser/cookies.js +19 -19
- package/dist/src/browser/detect.js +250 -77
- package/dist/src/browser/domDebug.js +50 -1
- package/dist/src/browser/index.js +1559 -692
- package/dist/src/browser/liveTabs.js +434 -0
- package/dist/src/browser/modelStrategy.js +1 -1
- package/dist/src/browser/pageActions.js +5 -5
- package/dist/src/browser/policies.js +16 -13
- package/dist/src/browser/profileState.js +127 -42
- package/dist/src/browser/projectSourcesRunner.js +366 -0
- package/dist/src/browser/prompt.js +72 -42
- package/dist/src/browser/promptSummary.js +5 -5
- package/dist/src/browser/providerDomFlow.js +1 -1
- package/dist/src/browser/providers/chatgptDomProvider.js +9 -9
- package/dist/src/browser/providers/geminiDeepThinkDomProvider.js +51 -42
- package/dist/src/browser/providers/index.js +2 -2
- package/dist/src/browser/reattach.js +178 -73
- package/dist/src/browser/reattachHelpers.js +32 -27
- package/dist/src/browser/sessionRunner.js +89 -25
- package/dist/src/browser/tabLeaseRegistry.js +182 -0
- package/dist/src/browser/utils.js +9 -9
- package/dist/src/browserMode.js +1 -1
- package/dist/src/cli/bridge/claudeConfig.js +24 -20
- package/dist/src/cli/bridge/client.js +28 -20
- package/dist/src/cli/bridge/codexConfig.js +16 -16
- package/dist/src/cli/bridge/doctor.js +47 -39
- package/dist/src/cli/bridge/host.js +58 -56
- package/dist/src/cli/browserConfig.js +102 -48
- package/dist/src/cli/browserDefaults.js +51 -26
- package/dist/src/cli/browserTabs.js +228 -0
- package/dist/src/cli/bundleWarnings.js +1 -1
- package/dist/src/cli/clipboard.js +11 -2
- package/dist/src/cli/detach.js +2 -2
- package/dist/src/cli/dryRun.js +62 -26
- package/dist/src/cli/duplicatePromptGuard.js +12 -4
- package/dist/src/cli/engine.js +9 -9
- package/dist/src/cli/errorUtils.js +1 -1
- package/dist/src/cli/fileSize.js +3 -3
- package/dist/src/cli/format.js +2 -2
- package/dist/src/cli/help.js +28 -28
- package/dist/src/cli/hiddenAliases.js +3 -3
- package/dist/src/cli/markdownBundle.js +7 -7
- package/dist/src/cli/markdownRenderer.js +15 -15
- package/dist/src/cli/notifier.js +77 -67
- package/dist/src/cli/options.js +131 -106
- package/dist/src/cli/oscUtils.js +1 -1
- package/dist/src/cli/projectSources.js +116 -0
- package/dist/src/cli/promptRequirement.js +2 -2
- package/dist/src/cli/renderOutput.js +1 -1
- package/dist/src/cli/rootAlias.js +1 -1
- package/dist/src/cli/runOptions.js +32 -28
- package/dist/src/cli/sessionCommand.js +82 -21
- package/dist/src/cli/sessionDisplay.js +213 -87
- package/dist/src/cli/sessionLineage.js +6 -2
- package/dist/src/cli/sessionRunner.js +149 -95
- package/dist/src/cli/sessionTable.js +26 -23
- package/dist/src/cli/stdin.js +22 -0
- package/dist/src/cli/tagline.js +121 -124
- package/dist/src/cli/tui/index.js +139 -128
- package/dist/src/cli/writeOutputPath.js +5 -5
- package/dist/src/config.js +7 -7
- package/dist/src/gemini-web/browserSessionManager.js +19 -15
- package/dist/src/gemini-web/client.js +76 -70
- package/dist/src/gemini-web/executionMode.js +6 -8
- package/dist/src/gemini-web/executor.js +98 -93
- package/dist/src/gemini-web/index.js +1 -1
- package/dist/src/mcp/consultPresets.js +19 -0
- package/dist/src/mcp/server.js +18 -12
- package/dist/src/mcp/tools/consult.js +246 -67
- package/dist/src/mcp/tools/projectSources.js +123 -0
- package/dist/src/mcp/tools/sessionResources.js +12 -12
- package/dist/src/mcp/tools/sessions.js +26 -17
- package/dist/src/mcp/types.js +12 -5
- package/dist/src/mcp/utils.js +21 -8
- package/dist/src/oracle/background.js +15 -15
- package/dist/src/oracle/claude.js +53 -25
- package/dist/src/oracle/client.js +50 -41
- package/dist/src/oracle/config.js +96 -66
- package/dist/src/oracle/errors.js +38 -38
- package/dist/src/oracle/files.js +55 -46
- package/dist/src/oracle/finishLine.js +10 -8
- package/dist/src/oracle/format.js +3 -3
- package/dist/src/oracle/gemini.js +37 -33
- package/dist/src/oracle/logging.js +7 -7
- package/dist/src/oracle/markdown.js +28 -28
- package/dist/src/oracle/modelResolver.js +16 -16
- package/dist/src/oracle/multiModelRunner.js +12 -12
- package/dist/src/oracle/oscProgress.js +8 -8
- package/dist/src/oracle/promptAssembly.js +6 -3
- package/dist/src/oracle/request.js +16 -13
- package/dist/src/oracle/run.js +160 -135
- package/dist/src/oracle/runUtils.js +8 -5
- package/dist/src/oracle/tokenEstimate.js +6 -6
- package/dist/src/oracle/tokenStats.js +5 -5
- package/dist/src/oracle/tokenStringifier.js +5 -5
- package/dist/src/oracle.js +12 -12
- package/dist/src/oracleHome.js +3 -3
- package/dist/src/projectSources/plan.js +27 -0
- package/dist/src/projectSources/url.js +23 -0
- package/dist/src/remote/client.js +25 -25
- package/dist/src/remote/health.js +20 -20
- package/dist/src/remote/remoteServiceConfig.js +9 -9
- package/dist/src/remote/server.js +129 -118
- package/dist/src/sessionManager.js +78 -75
- package/dist/src/sessionStore.js +3 -3
- package/dist/src/version.js +10 -10
- package/dist/vendor/oracle-notifier/README.md +2 -0
- package/package.json +67 -62
- package/vendor/oracle-notifier/README.md +2 -0
- package/dist/markdansi/types/index.js +0 -4
- package/dist/oracle/bin/oracle-cli.js +0 -472
- package/dist/oracle/src/browser/actions/assistantResponse.js +0 -471
- package/dist/oracle/src/browser/actions/attachments.js +0 -82
- package/dist/oracle/src/browser/actions/modelSelection.js +0 -190
- package/dist/oracle/src/browser/actions/navigation.js +0 -75
- package/dist/oracle/src/browser/actions/promptComposer.js +0 -167
- package/dist/oracle/src/browser/chromeLifecycle.js +0 -104
- package/dist/oracle/src/browser/config.js +0 -33
- package/dist/oracle/src/browser/constants.js +0 -40
- package/dist/oracle/src/browser/cookies.js +0 -210
- package/dist/oracle/src/browser/domDebug.js +0 -36
- package/dist/oracle/src/browser/index.js +0 -331
- package/dist/oracle/src/browser/pageActions.js +0 -5
- package/dist/oracle/src/browser/prompt.js +0 -88
- package/dist/oracle/src/browser/promptSummary.js +0 -20
- package/dist/oracle/src/browser/sessionRunner.js +0 -80
- package/dist/oracle/src/browser/utils.js +0 -62
- package/dist/oracle/src/browserMode.js +0 -1
- package/dist/oracle/src/cli/browserConfig.js +0 -44
- package/dist/oracle/src/cli/dryRun.js +0 -59
- package/dist/oracle/src/cli/engine.js +0 -17
- package/dist/oracle/src/cli/errorUtils.js +0 -9
- package/dist/oracle/src/cli/help.js +0 -70
- package/dist/oracle/src/cli/markdownRenderer.js +0 -15
- package/dist/oracle/src/cli/options.js +0 -103
- package/dist/oracle/src/cli/promptRequirement.js +0 -14
- package/dist/oracle/src/cli/rootAlias.js +0 -30
- package/dist/oracle/src/cli/sessionCommand.js +0 -77
- package/dist/oracle/src/cli/sessionDisplay.js +0 -270
- package/dist/oracle/src/cli/sessionRunner.js +0 -94
- package/dist/oracle/src/heartbeat.js +0 -43
- package/dist/oracle/src/oracle/client.js +0 -48
- package/dist/oracle/src/oracle/config.js +0 -29
- package/dist/oracle/src/oracle/errors.js +0 -101
- package/dist/oracle/src/oracle/files.js +0 -220
- package/dist/oracle/src/oracle/format.js +0 -33
- package/dist/oracle/src/oracle/fsAdapter.js +0 -7
- package/dist/oracle/src/oracle/oscProgress.js +0 -60
- package/dist/oracle/src/oracle/request.js +0 -48
- package/dist/oracle/src/oracle/run.js +0 -444
- package/dist/oracle/src/oracle/tokenStats.js +0 -39
- package/dist/oracle/src/oracle/types.js +0 -1
- package/dist/oracle/src/oracle.js +0 -9
- package/dist/oracle/src/sessionManager.js +0 -205
- package/dist/oracle/src/version.js +0 -39
- package/dist/scripts/chrome/browser-tools.js +0 -295
- package/dist/src/browser/profileSync.js +0 -141
- /package/dist/{oracle/src/browser → src/projectSources}/types.js +0 -0
package/dist/src/oracle/files.js
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
|
-
import fs from
|
|
2
|
-
import path from
|
|
3
|
-
import fg from
|
|
4
|
-
import { FileValidationError } from
|
|
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 = [
|
|
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(
|
|
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.
|
|
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(
|
|
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 ===
|
|
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(
|
|
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,
|
|
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
|
|
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(
|
|
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,
|
|
182
|
+
const raw = await fs.readFile(filePath, "utf8");
|
|
172
183
|
const patterns = raw
|
|
173
|
-
.split(
|
|
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.
|
|
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,
|
|
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.
|
|
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,
|
|
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(
|
|
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 ===
|
|
306
|
-
return
|
|
314
|
+
if (relative === "." || relative === "") {
|
|
315
|
+
return "**/*";
|
|
307
316
|
}
|
|
308
317
|
return `${stripTrailingSlashes(relative)}/**/*`;
|
|
309
318
|
}
|
|
310
319
|
function isNativeFsModule(fsModule) {
|
|
311
|
-
return (
|
|
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 =
|
|
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 ===
|
|
373
|
+
const parsed = typeof value === "number"
|
|
365
374
|
? value
|
|
366
|
-
: Number.parseInt(typeof value ===
|
|
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(
|
|
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
|
|
1
|
+
import { formatUSD } from "./format.js";
|
|
2
2
|
export function formatElapsedCompact(ms) {
|
|
3
3
|
if (!Number.isFinite(ms) || ms < 0) {
|
|
4
|
-
return
|
|
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,
|
|
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,
|
|
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 ===
|
|
21
|
+
typeof costUsd === "number" ? formatUSD(costUsd) : null,
|
|
22
22
|
model,
|
|
23
23
|
tokensPart,
|
|
24
24
|
...(summaryExtraParts ?? []),
|
|
25
25
|
];
|
|
26
|
-
const line1 = line1Parts
|
|
27
|
-
|
|
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
|
|
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
|
|
10
|
+
return "n/a";
|
|
11
11
|
}
|
|
12
|
-
const suffix = estimated ?
|
|
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
|
|
1
|
+
import { GoogleGenAI, HarmCategory, HarmBlockThreshold, } from "@google/genai";
|
|
2
2
|
const MODEL_ID_MAP = {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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 =
|
|
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 ===
|
|
29
|
+
role: inputItem.role === "user" ? "user" : "model",
|
|
28
30
|
parts: inputItem.content
|
|
29
31
|
.map((contentPart) => {
|
|
30
|
-
if (contentPart.type ===
|
|
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 ===
|
|
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:
|
|
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:
|
|
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) +
|
|
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:
|
|
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:
|
|
114
|
+
status: "completed",
|
|
112
115
|
output_text: [text],
|
|
113
|
-
output: [{ type:
|
|
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(
|
|
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:
|
|
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(
|
|
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:
|
|
187
|
-
error: { message:
|
|
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] ??
|
|
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(
|
|
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 = [
|
|
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
|
|
1
|
+
import path from "node:path";
|
|
2
2
|
const EXT_TO_LANG = {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
|
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,
|
|
45
|
+
return [header, fenceOpen, normalized, fence, ""].join("\n");
|
|
46
46
|
}
|