@steipete/oracle 0.17.1 → 0.17.3
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/dist/bin/oracle-cli.js +1 -1
- package/dist/bin/oracle-mcp.js +0 -0
- package/dist/bin/oracle.js +683 -0
- package/dist/docs-site/bridge.html +1 -17
- package/dist/docs-site/browser-mode.html +14 -20
- package/dist/docs-site/cli-reference.html +6 -6
- package/dist/docs-site/configuration.html +7 -33
- package/dist/docs-site/followup.html +9 -16
- package/dist/docs-site/gemini.html +6 -10
- package/dist/docs-site/index.html +7 -7
- package/dist/docs-site/install.html +1 -1
- package/dist/docs-site/manual-tests.html +1 -2
- package/dist/docs-site/mcp.html +5 -28
- package/dist/docs-site/mythical-pro-agents.html +10 -10
- package/dist/docs-site/openai-endpoints.html +1 -24
- package/dist/docs-site/quickstart.html +1 -1
- package/dist/docs-site/sessions.html +5 -6
- package/dist/docs-site/spec.html +1 -1
- package/dist/docs-site/windows-work.html +0 -1
- package/dist/markdansi/types/index.js +4 -0
- package/dist/oracle/bin/oracle-cli.js +472 -0
- package/dist/oracle/src/browser/actions/assistantResponse.js +471 -0
- package/dist/oracle/src/browser/actions/attachments.js +82 -0
- package/dist/oracle/src/browser/actions/modelSelection.js +190 -0
- package/dist/oracle/src/browser/actions/navigation.js +75 -0
- package/dist/oracle/src/browser/actions/promptComposer.js +167 -0
- package/dist/oracle/src/browser/chromeLifecycle.js +104 -0
- package/dist/oracle/src/browser/config.js +33 -0
- package/dist/oracle/src/browser/constants.js +40 -0
- package/dist/oracle/src/browser/cookies.js +210 -0
- package/dist/oracle/src/browser/domDebug.js +36 -0
- package/dist/oracle/src/browser/index.js +331 -0
- package/dist/oracle/src/browser/pageActions.js +5 -0
- package/dist/oracle/src/browser/prompt.js +88 -0
- package/dist/oracle/src/browser/promptSummary.js +20 -0
- package/dist/oracle/src/browser/sessionRunner.js +80 -0
- package/dist/oracle/src/browser/types.js +1 -0
- package/dist/oracle/src/browser/utils.js +62 -0
- package/dist/oracle/src/browserMode.js +1 -0
- package/dist/oracle/src/cli/browserConfig.js +44 -0
- package/dist/oracle/src/cli/dryRun.js +59 -0
- package/dist/oracle/src/cli/engine.js +17 -0
- package/dist/oracle/src/cli/errorUtils.js +9 -0
- package/dist/oracle/src/cli/help.js +70 -0
- package/dist/oracle/src/cli/markdownRenderer.js +15 -0
- package/dist/oracle/src/cli/options.js +103 -0
- package/dist/oracle/src/cli/promptRequirement.js +14 -0
- package/dist/oracle/src/cli/rootAlias.js +30 -0
- package/dist/oracle/src/cli/sessionCommand.js +77 -0
- package/dist/oracle/src/cli/sessionDisplay.js +270 -0
- package/dist/oracle/src/cli/sessionRunner.js +94 -0
- package/dist/oracle/src/heartbeat.js +43 -0
- package/dist/oracle/src/oracle/client.js +48 -0
- package/dist/oracle/src/oracle/config.js +29 -0
- package/dist/oracle/src/oracle/errors.js +101 -0
- package/dist/oracle/src/oracle/files.js +220 -0
- package/dist/oracle/src/oracle/format.js +33 -0
- package/dist/oracle/src/oracle/fsAdapter.js +7 -0
- package/dist/oracle/src/oracle/oscProgress.js +60 -0
- package/dist/oracle/src/oracle/request.js +48 -0
- package/dist/oracle/src/oracle/run.js +444 -0
- package/dist/oracle/src/oracle/tokenStats.js +39 -0
- package/dist/oracle/src/oracle/types.js +1 -0
- package/dist/oracle/src/oracle.js +9 -0
- package/dist/oracle/src/sessionManager.js +205 -0
- package/dist/oracle/src/version.js +39 -0
- package/dist/scripts/chrome/browser-tools.js +295 -0
- package/dist/scripts/git-policy.js +0 -8
- package/dist/scripts/runner.js +1 -5
- package/dist/src/browser/actions/assistantResponse.js +71 -23
- package/dist/src/browser/actions/modelSelection.js +164 -5
- package/dist/src/browser/actions/thinkingTime.js +195 -18
- package/dist/src/browser/chromeCookies.js +312 -0
- package/dist/src/browser/index.js +5 -1
- package/dist/src/browser/keytarShim.js +56 -0
- package/dist/src/browser/policies.js +4 -0
- package/dist/src/browser/profileSync.js +141 -0
- package/dist/src/browser/projectSourcesRunner.js +2 -2
- package/dist/src/browser/reattach.js +47 -35
- package/dist/src/browser/windowsCookies.js +219 -0
- package/dist/src/cli/browserConfig.js +13 -4
- package/dist/src/cli/browserDefaults.js +6 -1
- package/dist/src/cli/options.js +1 -1
- package/dist/src/cli/projectSources.js +8 -1
- package/dist/src/mcp/tools/consult.js +1 -1
- package/dist/src/oracle/thinkingTime.js +6 -0
- package/dist/src/sessionManager.js +90 -9
- package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
- package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
- package/package.json +6 -6
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import { existsSync, promises as fsp } from 'node:fs';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import sqlite3 from 'sqlite3';
|
|
7
|
+
import { createRequire } from 'node:module';
|
|
8
|
+
import { spawnSync } from 'node:child_process';
|
|
9
|
+
let cachedUnprotect = null;
|
|
10
|
+
function getUnprotectData() {
|
|
11
|
+
if (cachedUnprotect) {
|
|
12
|
+
return cachedUnprotect;
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
// win-dpapi is CommonJS; require it explicitly and support both named/default shapes.
|
|
16
|
+
const dpapiModule = createRequire(import.meta.url)('win-dpapi');
|
|
17
|
+
const unprotect = dpapiModule?.Dpapi?.unprotectData ??
|
|
18
|
+
dpapiModule?.default?.unprotectData ??
|
|
19
|
+
((_) => {
|
|
20
|
+
throw new Error('win-dpapi unprotectData not available');
|
|
21
|
+
});
|
|
22
|
+
cachedUnprotect = unprotect;
|
|
23
|
+
return unprotect;
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
if (process.platform !== 'win32') {
|
|
27
|
+
// On macOS/Linux we don't need DPAPI; return a function that makes misuse obvious.
|
|
28
|
+
cachedUnprotect = () => {
|
|
29
|
+
throw new Error('win-dpapi is unavailable on non-Windows platforms');
|
|
30
|
+
};
|
|
31
|
+
return cachedUnprotect;
|
|
32
|
+
}
|
|
33
|
+
throw error;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export async function loadWindowsCookies(dbPath, filterNames) {
|
|
37
|
+
if (process.platform !== 'win32') {
|
|
38
|
+
throw new Error('loadWindowsCookies is only supported on Windows');
|
|
39
|
+
}
|
|
40
|
+
const localStatePath = await locateLocalState(dbPath);
|
|
41
|
+
const cookiesCopy = await copyLockedFile(dbPath, 'Cookies'); // name the copy exactly "Cookies"
|
|
42
|
+
const cookiesDirForFallback = await ensureCookiesDirForFallback(cookiesCopy);
|
|
43
|
+
const localStateCopy = await copyLockedFile(localStatePath, 'Local State');
|
|
44
|
+
const aesKey = await extractWindowsAesKey(localStateCopy);
|
|
45
|
+
const rows = await readChromeCookiesDb(cookiesCopy, filterNames);
|
|
46
|
+
const cookies = [];
|
|
47
|
+
for (const row of rows) {
|
|
48
|
+
const enc = row.encrypted_value && row.encrypted_value.length > 0 ? row.encrypted_value : Buffer.from(row.value ?? '', 'utf8');
|
|
49
|
+
const decrypted = enc.length > 0 ? decryptCookie(enc, aesKey) : '';
|
|
50
|
+
cookies.push({
|
|
51
|
+
name: row.name,
|
|
52
|
+
value: decrypted,
|
|
53
|
+
domain: row.host_key?.startsWith('.') ? row.host_key.slice(1) : row.host_key,
|
|
54
|
+
path: row.path ?? '/',
|
|
55
|
+
expires: normalizeExpiration(row.expires_utc),
|
|
56
|
+
secure: Boolean(row.is_secure),
|
|
57
|
+
httpOnly: Boolean(row.is_httponly),
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
if (process.env.ORACLE_DEBUG_COOKIES === '1') {
|
|
61
|
+
// eslint-disable-next-line no-console
|
|
62
|
+
console.log(`[cookies] windows decrypt decoded ${cookies.length} cookies from ${cookiesCopy} (fallback dir ${cookiesDirForFallback})`);
|
|
63
|
+
}
|
|
64
|
+
return cookies.filter((c) => c.value);
|
|
65
|
+
}
|
|
66
|
+
function decryptCookie(value, aesKey) {
|
|
67
|
+
const prefix = value.slice(0, 3).toString();
|
|
68
|
+
// Chrome prefixes AES-GCM encrypted cookies with version markers like v10/v11/v20; treat all v** the same.
|
|
69
|
+
if (/^v\d{2}$/u.test(prefix)) {
|
|
70
|
+
const iv = value.slice(3, 15);
|
|
71
|
+
const tag = value.slice(value.length - 16);
|
|
72
|
+
const data = value.slice(15, value.length - 16);
|
|
73
|
+
const decipher = crypto.createDecipheriv('aes-256-gcm', aesKey, iv);
|
|
74
|
+
decipher.setAuthTag(tag);
|
|
75
|
+
const decrypted = Buffer.concat([decipher.update(data), decipher.final()]);
|
|
76
|
+
return decrypted.toString('utf8');
|
|
77
|
+
}
|
|
78
|
+
const unprotectData = getUnprotectData();
|
|
79
|
+
const unprotected = unprotectData(value, undefined, 'CurrentUser');
|
|
80
|
+
return Buffer.from(unprotected).toString('utf8');
|
|
81
|
+
}
|
|
82
|
+
async function locateLocalState(dbPath) {
|
|
83
|
+
// Prefer sibling Local State near the profile path; fall back to default location.
|
|
84
|
+
const guess = path.resolve(path.join(path.dirname(dbPath), '..', 'Local State'));
|
|
85
|
+
if (existsSync(guess))
|
|
86
|
+
return guess;
|
|
87
|
+
const localState = path.join(os.homedir(), 'AppData', 'Local', 'Google', 'Chrome', 'User Data', 'Local State');
|
|
88
|
+
if (existsSync(localState))
|
|
89
|
+
return localState;
|
|
90
|
+
throw new Error('Chrome Local State not found for AES key');
|
|
91
|
+
}
|
|
92
|
+
async function extractWindowsAesKey(localStatePath) {
|
|
93
|
+
const raw = await fs.readFile(localStatePath, 'utf8');
|
|
94
|
+
const state = JSON.parse(raw);
|
|
95
|
+
const encKeyB64 = state?.os_crypt?.encrypted_key;
|
|
96
|
+
if (!encKeyB64)
|
|
97
|
+
throw new Error('encrypted_key missing in Local State');
|
|
98
|
+
const encKey = Buffer.from(encKeyB64, 'base64');
|
|
99
|
+
const dpapiBlob = encKey.slice(5); // strip "DPAPI"
|
|
100
|
+
const unprotectData = getUnprotectData();
|
|
101
|
+
const unprotected = unprotectData(dpapiBlob, undefined, 'CurrentUser');
|
|
102
|
+
return Buffer.from(unprotected);
|
|
103
|
+
}
|
|
104
|
+
async function readChromeCookiesDb(dbPath, filterNames) {
|
|
105
|
+
const db = await openSqlite(dbPath);
|
|
106
|
+
const placeholders = filterNames && filterNames.size > 0 ? `AND name IN (${Array.from(filterNames).map(() => '?').join(',')})` : '';
|
|
107
|
+
const params = filterNames ? Array.from(filterNames) : [];
|
|
108
|
+
const sql = `SELECT name,value,encrypted_value,host_key,path,expires_utc,is_secure,is_httponly FROM cookies WHERE host_key LIKE '%chatgpt.com%' ${placeholders}`;
|
|
109
|
+
try {
|
|
110
|
+
return await allSqlite(db, sql, params);
|
|
111
|
+
}
|
|
112
|
+
finally {
|
|
113
|
+
db.close();
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function openSqlite(dbPath) {
|
|
117
|
+
return new Promise((resolve, reject) => {
|
|
118
|
+
const db = new sqlite3.Database(dbPath, sqlite3.OPEN_READONLY, (err) => {
|
|
119
|
+
if (err)
|
|
120
|
+
reject(err);
|
|
121
|
+
else
|
|
122
|
+
resolve(db);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
async function copyLockedFile(sourcePath, targetName) {
|
|
127
|
+
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'oracle-cookie-'));
|
|
128
|
+
const tempPath = targetName ? path.join(tempDir, targetName) : path.join(tempDir, path.basename(sourcePath));
|
|
129
|
+
const psScript = `
|
|
130
|
+
$src = '${sourcePath.replace(/'/g, "''")}'
|
|
131
|
+
$dst = '${tempPath.replace(/'/g, "''")}'
|
|
132
|
+
try {
|
|
133
|
+
$fs = [System.IO.File]::Open($src, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::ReadWrite)
|
|
134
|
+
$bytes = New-Object byte[] $fs.Length
|
|
135
|
+
$null = $fs.Read($bytes, 0, $fs.Length)
|
|
136
|
+
$fs.Close()
|
|
137
|
+
[System.IO.File]::WriteAllBytes($dst, $bytes)
|
|
138
|
+
exit 0
|
|
139
|
+
} catch {
|
|
140
|
+
exit 1
|
|
141
|
+
}
|
|
142
|
+
`;
|
|
143
|
+
const encoded = Buffer.from(psScript, 'utf16le').toString('base64');
|
|
144
|
+
const result = spawnSync('powershell.exe', ['-NoProfile', '-NonInteractive', '-EncodedCommand', encoded], {
|
|
145
|
+
timeout: 15000,
|
|
146
|
+
encoding: 'utf8',
|
|
147
|
+
});
|
|
148
|
+
if (result.status !== 0) {
|
|
149
|
+
// Fallback: best-effort normal copy
|
|
150
|
+
await fs.copyFile(sourcePath, tempPath);
|
|
151
|
+
}
|
|
152
|
+
if (process.env.ORACLE_DEBUG_COOKIES === '1') {
|
|
153
|
+
// eslint-disable-next-line no-console
|
|
154
|
+
console.log(`[cookies] copied locked file ${sourcePath} -> ${tempPath}`);
|
|
155
|
+
}
|
|
156
|
+
return tempPath;
|
|
157
|
+
}
|
|
158
|
+
// Create a directory containing a file named "Cookies" for chrome-cookies-secure fallback
|
|
159
|
+
export async function ensureCookiesDirForFallback(cookieFile) {
|
|
160
|
+
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'oracle-cookies-secure-'));
|
|
161
|
+
const target = path.join(dir, 'Cookies');
|
|
162
|
+
await fs.copyFile(cookieFile, target);
|
|
163
|
+
return dir;
|
|
164
|
+
}
|
|
165
|
+
function allSqlite(db, sql, params) {
|
|
166
|
+
return new Promise((resolve, reject) => {
|
|
167
|
+
db.all(sql, params, (err, rows) => {
|
|
168
|
+
if (err)
|
|
169
|
+
reject(err);
|
|
170
|
+
else
|
|
171
|
+
resolve((rows ?? []));
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
function normalizeExpiration(expires) {
|
|
176
|
+
if (!expires || Number.isNaN(expires)) {
|
|
177
|
+
return undefined;
|
|
178
|
+
}
|
|
179
|
+
const value = Number(expires);
|
|
180
|
+
if (value <= 0)
|
|
181
|
+
return undefined;
|
|
182
|
+
if (value > 1_000_000_000_000) {
|
|
183
|
+
return Math.round(value / 1_000_000 - 11644473600);
|
|
184
|
+
}
|
|
185
|
+
if (value > 1_000_000_000) {
|
|
186
|
+
return Math.round(value / 1000);
|
|
187
|
+
}
|
|
188
|
+
return Math.round(value);
|
|
189
|
+
}
|
|
190
|
+
export async function materializeCookieFile(sourcePath) {
|
|
191
|
+
if (process.platform !== 'win32')
|
|
192
|
+
return sourcePath;
|
|
193
|
+
const resolved = await resolveDirectoryCandidate(sourcePath);
|
|
194
|
+
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'oracle-cookies-'));
|
|
195
|
+
const tempPath = path.join(tempDir, 'Cookies');
|
|
196
|
+
await fs.copyFile(resolved, tempPath);
|
|
197
|
+
return tempPath;
|
|
198
|
+
}
|
|
199
|
+
async function resolveDirectoryCandidate(inputPath) {
|
|
200
|
+
const stat = await fs.stat(inputPath).catch(() => null);
|
|
201
|
+
if (!stat?.isDirectory())
|
|
202
|
+
return inputPath;
|
|
203
|
+
const network = path.join(inputPath, 'Network', 'Cookies');
|
|
204
|
+
const direct = path.join(inputPath, 'Cookies');
|
|
205
|
+
if (await fileExists(network))
|
|
206
|
+
return network;
|
|
207
|
+
if (await fileExists(direct))
|
|
208
|
+
return direct;
|
|
209
|
+
return inputPath;
|
|
210
|
+
}
|
|
211
|
+
async function fileExists(candidate) {
|
|
212
|
+
try {
|
|
213
|
+
const stat = await fsp.stat(candidate);
|
|
214
|
+
return stat.isFile();
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
@@ -19,7 +19,7 @@ const BROWSER_MODEL_LABELS = [
|
|
|
19
19
|
// Most specific first (e.g., "gpt-5.2-thinking" before "gpt-5.2")
|
|
20
20
|
["gpt-5.6-sol", "GPT-5.6 Sol"],
|
|
21
21
|
["gpt-5.6", "GPT-5.6 Sol"],
|
|
22
|
-
["gpt-5.5-pro", "
|
|
22
|
+
["gpt-5.5-pro", "GPT-5.5"],
|
|
23
23
|
["gpt-5.5-instant", "GPT-5.5 Instant"],
|
|
24
24
|
["gpt-5.5", "Thinking 5.5"],
|
|
25
25
|
["gpt-5.4-pro", "Pro"],
|
|
@@ -85,8 +85,11 @@ export async function buildBrowserConfig(options) {
|
|
|
85
85
|
const normalizedOverride = desiredModelOverride?.toLowerCase() ?? "";
|
|
86
86
|
const baseModel = options.model.toLowerCase();
|
|
87
87
|
const isChatGptModel = baseModel.startsWith("gpt-") && !baseModel.includes("codex");
|
|
88
|
+
const normalizedBrowserModel = normalizeChatGptModelForBrowser(options.model);
|
|
88
89
|
const shouldUseOverride = !isChatGptModel && normalizedOverride.length > 0 && normalizedOverride !== baseModel;
|
|
89
90
|
const modelStrategy = normalizeBrowserModelStrategy(options.browserModelStrategy) ?? DEFAULT_MODEL_STRATEGY;
|
|
91
|
+
const thinkingTime = normalizeThinkingTimeLevel(options.browserThinkingTime) ??
|
|
92
|
+
(modelStrategy === "select" && normalizedBrowserModel === "gpt-5.5-pro" ? "pro" : undefined);
|
|
90
93
|
assertBrowserModelAvailable(options.model, modelStrategy);
|
|
91
94
|
const cookieNames = parseCookieNames(options.browserCookieNames ?? process.env.ORACLE_BROWSER_COOKIE_NAMES);
|
|
92
95
|
let inline = await resolveInlineCookies({
|
|
@@ -158,14 +161,19 @@ export async function buildBrowserConfig(options) {
|
|
|
158
161
|
cookieSyncWaitMs: options.browserCookieWait
|
|
159
162
|
? parseBrowserDuration(options.browserCookieWait, "--browser-cookie-wait", 0)
|
|
160
163
|
: undefined,
|
|
161
|
-
cookieSync: options.browserNoCookieSync
|
|
164
|
+
cookieSync: options.browserNoCookieSync
|
|
165
|
+
? false
|
|
166
|
+
: options.browserManualLoginCookieSync === true
|
|
167
|
+
? true
|
|
168
|
+
: undefined,
|
|
162
169
|
cookieNames,
|
|
163
170
|
inlineCookies: inline?.cookies,
|
|
164
171
|
inlineCookiesSource: inline?.source ?? null,
|
|
165
|
-
headless:
|
|
172
|
+
headless: options.browserHeadless === true ? true : undefined,
|
|
166
173
|
keepBrowser: options.browserKeepBrowser ? true : undefined,
|
|
167
174
|
manualLogin: options.browserManualLogin === undefined ? undefined : options.browserManualLogin,
|
|
168
175
|
manualLoginProfileDir: options.browserManualLoginProfileDir ?? undefined,
|
|
176
|
+
manualLoginCookieSync: options.browserManualLoginCookieSync,
|
|
169
177
|
copyProfileSource: options.copyProfile ?? undefined,
|
|
170
178
|
hideWindow: options.browserHideWindow ? true : undefined,
|
|
171
179
|
desiredModel,
|
|
@@ -175,7 +183,7 @@ export async function buildBrowserConfig(options) {
|
|
|
175
183
|
allowCookieErrors: options.browserAllowCookieErrors ?? true,
|
|
176
184
|
remoteChrome,
|
|
177
185
|
browserTabRef: options.browserTab ?? undefined,
|
|
178
|
-
thinkingTime
|
|
186
|
+
thinkingTime,
|
|
179
187
|
researchMode: options.browserResearch === "deep" ? "deep" : "off",
|
|
180
188
|
archiveConversations: options.browserArchive,
|
|
181
189
|
};
|
|
@@ -199,6 +207,7 @@ function validateAttachRunningOptions(options, { attachRunning, hasInlineCookies
|
|
|
199
207
|
options.browserChromeProfile ? "--browser-chrome-profile" : null,
|
|
200
208
|
options.browserCookiePath ? "--browser-cookie-path" : null,
|
|
201
209
|
options.browserNoCookieSync ? "--browser-no-cookie-sync" : null,
|
|
210
|
+
options.browserHeadless ? "--browser-headless" : null,
|
|
202
211
|
options.browserHideWindow ? "--browser-hide-window" : null,
|
|
203
212
|
options.browserKeepBrowser ? "--browser-keep-browser" : null,
|
|
204
213
|
options.browserManualLogin ? "--browser-manual-login" : null,
|
|
@@ -76,7 +76,7 @@ export function applyBrowserDefaultsFromConfig(options, config, getSource) {
|
|
|
76
76
|
if (isUnset("browserCookieWait") && typeof browser.cookieSyncWaitMs === "number") {
|
|
77
77
|
options.browserCookieWait = String(browser.cookieSyncWaitMs);
|
|
78
78
|
}
|
|
79
|
-
if (isUnset("browserHeadless") && browser.headless !== undefined) {
|
|
79
|
+
if (!attachRunningRequested && isUnset("browserHeadless") && browser.headless !== undefined) {
|
|
80
80
|
options.browserHeadless = browser.headless;
|
|
81
81
|
}
|
|
82
82
|
if (!attachRunningRequested && isUnset("browserHideWindow") && browser.hideWindow !== undefined) {
|
|
@@ -111,4 +111,9 @@ export function applyBrowserDefaultsFromConfig(options, config, getSource) {
|
|
|
111
111
|
browser.manualLoginProfileDir !== undefined) {
|
|
112
112
|
options.browserManualLoginProfileDir = browser.manualLoginProfileDir;
|
|
113
113
|
}
|
|
114
|
+
if (!attachRunningRequested &&
|
|
115
|
+
isUnset("browserManualLoginCookieSync") &&
|
|
116
|
+
browser.manualLoginCookieSync !== undefined) {
|
|
117
|
+
options.browserManualLoginCookieSync = browser.manualLoginCookieSync;
|
|
118
|
+
}
|
|
114
119
|
}
|
package/dist/src/cli/options.js
CHANGED
|
@@ -133,7 +133,7 @@ export function parseThinkingTimeOption(value) {
|
|
|
133
133
|
if (normalized) {
|
|
134
134
|
return normalized;
|
|
135
135
|
}
|
|
136
|
-
throw new InvalidArgumentError('Thinking time must be one of "light", "standard", "extended", "extra-high", "heavy", or a ChatGPT UI alias like "instant", "medium", "high", or "xhigh".');
|
|
136
|
+
throw new InvalidArgumentError('Thinking time must be one of "light", "standard", "extended", "extra-high", "pro", "heavy", or a ChatGPT UI alias like "instant", "medium", "high", or "xhigh".');
|
|
137
137
|
}
|
|
138
138
|
export function normalizeModelOption(value) {
|
|
139
139
|
return (value ?? "").trim();
|
|
@@ -71,14 +71,21 @@ export async function buildProjectSourcesBrowserConfig({ options, projectUrl, co
|
|
|
71
71
|
envProfileDir ??
|
|
72
72
|
null)
|
|
73
73
|
: null;
|
|
74
|
+
const manualLoginCookieSync = flagConfig.manualLoginCookieSync ?? configuredBrowser.manualLoginCookieSync;
|
|
75
|
+
const cookieSync = flagConfig.cookieSync === false
|
|
76
|
+
? false
|
|
77
|
+
: manualLogin
|
|
78
|
+
? manualLoginCookieSync === true
|
|
79
|
+
: configuredBrowser.cookieSync;
|
|
74
80
|
return {
|
|
75
81
|
...configuredBrowser,
|
|
76
82
|
...flagConfig,
|
|
77
83
|
url: projectUrl,
|
|
78
84
|
chatgptUrl: projectUrl,
|
|
79
|
-
cookieSync
|
|
85
|
+
cookieSync,
|
|
80
86
|
manualLogin,
|
|
81
87
|
manualLoginProfileDir,
|
|
88
|
+
manualLoginCookieSync,
|
|
82
89
|
desiredModel: null,
|
|
83
90
|
modelStrategy: "ignore",
|
|
84
91
|
researchMode: "off",
|
|
@@ -271,7 +271,7 @@ export function buildConsultBrowserConfig({ userConfig, env, runModel, inputMode
|
|
|
271
271
|
...configuredBrowser,
|
|
272
272
|
url: configuredUrl,
|
|
273
273
|
chatgptUrl: configuredUrl,
|
|
274
|
-
cookieSync: !manualLogin,
|
|
274
|
+
cookieSync: !manualLogin || configuredBrowser.manualLoginCookieSync === true,
|
|
275
275
|
headless: configuredBrowser.headless ?? false,
|
|
276
276
|
hideWindow: configuredBrowser.hideWindow ?? false,
|
|
277
277
|
keepBrowser: browserKeepBrowser ?? configuredBrowser.keepBrowser ?? false,
|
|
@@ -3,6 +3,10 @@ export const THINKING_TIME_LEVELS = [
|
|
|
3
3
|
"standard",
|
|
4
4
|
"extended",
|
|
5
5
|
"extra-high",
|
|
6
|
+
// ChatGPT's unified Intelligence picker exposes Pro as the top effort tier of
|
|
7
|
+
// the active model rather than a separate model row, so it is a level here.
|
|
8
|
+
// Kept distinct from "heavy": requesting Pro must be deliberate.
|
|
9
|
+
"pro",
|
|
6
10
|
"heavy",
|
|
7
11
|
];
|
|
8
12
|
export const THINKING_TIME_ALIASES = [
|
|
@@ -38,6 +42,8 @@ export function normalizeThinkingTimeLevel(value) {
|
|
|
38
42
|
case "extrahigh":
|
|
39
43
|
case "xhigh":
|
|
40
44
|
return "extra-high";
|
|
45
|
+
case "pro":
|
|
46
|
+
return "pro";
|
|
41
47
|
case "heavy":
|
|
42
48
|
return "heavy";
|
|
43
49
|
default:
|
|
@@ -23,11 +23,86 @@ const DEFAULT_SLUG = "session";
|
|
|
23
23
|
const MAX_SLUG_WORDS = 5;
|
|
24
24
|
const MIN_CUSTOM_SLUG_WORDS = 3;
|
|
25
25
|
const MAX_SLUG_WORD_LENGTH = 10;
|
|
26
|
+
// Session artifacts (prompt, attached file contents, model responses) are sensitive.
|
|
27
|
+
// Keep them owner-only, matching the meta.json / bridge-config posture (0o600/0o700).
|
|
28
|
+
const SESSION_DIR_MODE = 0o700;
|
|
29
|
+
const SESSION_FILE_MODE = 0o600;
|
|
30
|
+
const sessionStorageHardening = new Map();
|
|
26
31
|
async function ensureDir(dirPath) {
|
|
27
|
-
await fs.mkdir(dirPath, { recursive: true });
|
|
32
|
+
await fs.mkdir(dirPath, { recursive: true, mode: SESSION_DIR_MODE });
|
|
33
|
+
}
|
|
34
|
+
function isMissingPathError(error) {
|
|
35
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
36
|
+
}
|
|
37
|
+
async function chmodIfPresent(targetPath, mode) {
|
|
38
|
+
try {
|
|
39
|
+
await fs.chmod(targetPath, mode);
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
if (isMissingPathError(error)) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
throw error;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
async function hardenSessionStorageEntry(targetPath) {
|
|
50
|
+
let stats;
|
|
51
|
+
try {
|
|
52
|
+
stats = await fs.lstat(targetPath);
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
if (isMissingPathError(error)) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
throw error;
|
|
59
|
+
}
|
|
60
|
+
if (stats.isSymbolicLink()) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (stats.isDirectory()) {
|
|
64
|
+
if (!(await chmodIfPresent(targetPath, SESSION_DIR_MODE))) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
let entries;
|
|
68
|
+
try {
|
|
69
|
+
entries = await fs.readdir(targetPath);
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
if (isMissingPathError(error)) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
throw error;
|
|
76
|
+
}
|
|
77
|
+
for (const entry of entries) {
|
|
78
|
+
await hardenSessionStorageEntry(path.join(targetPath, entry));
|
|
79
|
+
}
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
if (stats.isFile()) {
|
|
83
|
+
await chmodIfPresent(targetPath, SESSION_FILE_MODE);
|
|
84
|
+
}
|
|
28
85
|
}
|
|
29
86
|
export async function ensureSessionStorage() {
|
|
30
|
-
|
|
87
|
+
const sessionsDir = getSessionsDir();
|
|
88
|
+
await ensureDir(sessionsDir);
|
|
89
|
+
if (process.platform === "win32") {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const stats = await fs.lstat(sessionsDir);
|
|
93
|
+
if (stats.isSymbolicLink() || !stats.isDirectory()) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
const identity = `${sessionsDir}:${stats.dev}:${stats.ino}`;
|
|
97
|
+
let hardening = sessionStorageHardening.get(identity);
|
|
98
|
+
if (!hardening) {
|
|
99
|
+
hardening = hardenSessionStorageEntry(sessionsDir).catch((error) => {
|
|
100
|
+
sessionStorageHardening.delete(identity);
|
|
101
|
+
throw error;
|
|
102
|
+
});
|
|
103
|
+
sessionStorageHardening.set(identity, hardening);
|
|
104
|
+
}
|
|
105
|
+
await hardening;
|
|
31
106
|
}
|
|
32
107
|
function slugify(text, maxWords = MAX_SLUG_WORDS) {
|
|
33
108
|
const normalized = text?.toLowerCase() ?? "";
|
|
@@ -110,7 +185,7 @@ async function reserveUniqueSessionDir(baseSlug) {
|
|
|
110
185
|
for (;;) {
|
|
111
186
|
const dir = sessionDir(candidate);
|
|
112
187
|
try {
|
|
113
|
-
await fs.mkdir(dir, { recursive: false });
|
|
188
|
+
await fs.mkdir(dir, { recursive: false, mode: SESSION_DIR_MODE });
|
|
114
189
|
return candidate;
|
|
115
190
|
}
|
|
116
191
|
catch (error) {
|
|
@@ -171,7 +246,10 @@ export async function updateModelRunMetadata(sessionId, model, updates) {
|
|
|
171
246
|
...updates,
|
|
172
247
|
model,
|
|
173
248
|
});
|
|
174
|
-
await fs.writeFile(modelJsonPath(sessionId, model), JSON.stringify(next, null, 2),
|
|
249
|
+
await fs.writeFile(modelJsonPath(sessionId, model), JSON.stringify(next, null, 2), {
|
|
250
|
+
encoding: "utf8",
|
|
251
|
+
mode: SESSION_FILE_MODE,
|
|
252
|
+
});
|
|
175
253
|
return next;
|
|
176
254
|
}
|
|
177
255
|
export async function readModelRunMetadata(sessionId, model) {
|
|
@@ -261,10 +339,13 @@ export async function initializeSession(options, cwd, notifications, baseSlugOve
|
|
|
261
339
|
status: "pending",
|
|
262
340
|
log: { path: path.relative(sessionDir(sessionId), logFilePath) },
|
|
263
341
|
};
|
|
264
|
-
await fs.writeFile(jsonPath, JSON.stringify(modelRecord, null, 2),
|
|
265
|
-
|
|
342
|
+
await fs.writeFile(jsonPath, JSON.stringify(modelRecord, null, 2), {
|
|
343
|
+
encoding: "utf8",
|
|
344
|
+
mode: SESSION_FILE_MODE,
|
|
345
|
+
});
|
|
346
|
+
await fs.writeFile(logFilePath, "", { encoding: "utf8", mode: SESSION_FILE_MODE });
|
|
266
347
|
}));
|
|
267
|
-
await fs.writeFile(logPath(sessionId), "", "utf8");
|
|
348
|
+
await fs.writeFile(logPath(sessionId), "", { encoding: "utf8", mode: SESSION_FILE_MODE });
|
|
268
349
|
return metadata;
|
|
269
350
|
}
|
|
270
351
|
export async function readSessionMetadata(sessionId) {
|
|
@@ -350,9 +431,9 @@ async function attachModelRuns(meta, sessionId) {
|
|
|
350
431
|
export function createSessionLogWriter(sessionId, model) {
|
|
351
432
|
const targetPath = model ? modelLogPath(sessionId, model) : logPath(sessionId);
|
|
352
433
|
if (model) {
|
|
353
|
-
mkdirSync(modelsDir(sessionId), { recursive: true });
|
|
434
|
+
mkdirSync(modelsDir(sessionId), { recursive: true, mode: SESSION_DIR_MODE });
|
|
354
435
|
}
|
|
355
|
-
const stream = createWriteStream(targetPath, { flags: "a" });
|
|
436
|
+
const stream = createWriteStream(targetPath, { flags: "a", mode: SESSION_FILE_MODE });
|
|
356
437
|
const logLine = (line = "") => {
|
|
357
438
|
stream.write(`${line}\n`);
|
|
358
439
|
};
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@steipete/oracle",
|
|
3
|
-
"version": "0.17.
|
|
3
|
+
"version": "0.17.3",
|
|
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,7 +58,7 @@
|
|
|
58
58
|
},
|
|
59
59
|
"dependencies": {
|
|
60
60
|
"@anthropic-ai/tokenizer": "^0.0.4",
|
|
61
|
-
"@google/genai": "^2.
|
|
61
|
+
"@google/genai": "^2.16.0",
|
|
62
62
|
"@google/generative-ai": "^0.24.1",
|
|
63
63
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
64
64
|
"@steipete/sweet-cookie": "^0.4.1",
|
|
@@ -86,15 +86,15 @@
|
|
|
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.
|
|
89
|
+
"@types/node": "^26.2.0",
|
|
90
90
|
"@vitest/coverage-v8": "4.1.10",
|
|
91
|
-
"devtools-protocol": "0.0.
|
|
91
|
+
"devtools-protocol": "0.0.1676105",
|
|
92
92
|
"es-toolkit": "^1.50.0",
|
|
93
93
|
"esbuild": "^0.28.1",
|
|
94
94
|
"oxfmt": "0.62.0",
|
|
95
95
|
"oxlint": "^1.77.0",
|
|
96
96
|
"puppeteer-core": "^25.5.0",
|
|
97
|
-
"tsx": "^4.23.
|
|
97
|
+
"tsx": "^4.23.11",
|
|
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@
|
|
112
|
+
"packageManager": "pnpm@11.20.0"
|
|
113
113
|
}
|
|
Binary file
|
|
Binary file
|