@steipete/oracle 0.8.6 → 0.10.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 (181) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +130 -45
  3. package/dist/bin/oracle-cli.js +613 -379
  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/assistantResponse.js +149 -101
  18. package/dist/src/browser/actions/attachmentDataTransfer.js +49 -47
  19. package/dist/src/browser/actions/attachments.js +246 -150
  20. package/dist/src/browser/actions/domEvents.js +2 -2
  21. package/dist/src/browser/actions/modelSelection.js +314 -104
  22. package/dist/src/browser/actions/navigation.js +161 -136
  23. package/dist/src/browser/actions/promptComposer.js +100 -64
  24. package/dist/src/browser/actions/remoteFileTransfer.js +10 -10
  25. package/dist/src/browser/actions/thinkingTime.js +207 -110
  26. package/dist/src/browser/chromeLifecycle.js +62 -60
  27. package/dist/src/browser/config.js +34 -15
  28. package/dist/src/browser/constants.js +17 -12
  29. package/dist/src/browser/cookies.js +19 -19
  30. package/dist/src/browser/detect.js +62 -62
  31. package/dist/src/browser/domDebug.js +1 -1
  32. package/dist/src/browser/index.js +452 -303
  33. package/dist/src/browser/modelStrategy.js +1 -1
  34. package/dist/src/browser/pageActions.js +5 -5
  35. package/dist/src/browser/policies.js +16 -13
  36. package/dist/src/browser/profileState.js +44 -39
  37. package/dist/src/browser/prompt.js +72 -42
  38. package/dist/src/browser/promptSummary.js +5 -5
  39. package/dist/src/browser/providerDomFlow.js +17 -0
  40. package/dist/src/browser/providers/chatgptDomProvider.js +49 -0
  41. package/dist/src/browser/providers/geminiDeepThinkDomProvider.js +254 -0
  42. package/dist/src/browser/providers/index.js +2 -0
  43. package/dist/src/browser/reattach.js +67 -34
  44. package/dist/src/browser/reattachHelpers.js +31 -26
  45. package/dist/src/browser/sessionRunner.js +37 -25
  46. package/dist/src/browser/utils.js +9 -9
  47. package/dist/src/browserMode.js +1 -1
  48. package/dist/src/cli/bridge/claudeConfig.js +16 -16
  49. package/dist/src/cli/bridge/client.js +28 -20
  50. package/dist/src/cli/bridge/codexConfig.js +16 -16
  51. package/dist/src/cli/bridge/doctor.js +47 -39
  52. package/dist/src/cli/bridge/host.js +58 -56
  53. package/dist/src/cli/browserConfig.js +65 -45
  54. package/dist/src/cli/browserDefaults.js +27 -26
  55. package/dist/src/cli/bundleWarnings.js +1 -1
  56. package/dist/src/cli/clipboard.js +11 -2
  57. package/dist/src/cli/detach.js +7 -4
  58. package/dist/src/cli/dryRun.js +29 -25
  59. package/dist/src/cli/duplicatePromptGuard.js +3 -3
  60. package/dist/src/cli/engine.js +9 -9
  61. package/dist/src/cli/errorUtils.js +1 -1
  62. package/dist/src/cli/fileSize.js +11 -0
  63. package/dist/src/cli/format.js +2 -2
  64. package/dist/src/cli/help.js +28 -28
  65. package/dist/src/cli/hiddenAliases.js +3 -3
  66. package/dist/src/cli/markdownBundle.js +12 -8
  67. package/dist/src/cli/markdownRenderer.js +15 -15
  68. package/dist/src/cli/notifier.js +77 -67
  69. package/dist/src/cli/options.js +145 -87
  70. package/dist/src/cli/oscUtils.js +1 -1
  71. package/dist/src/cli/promptRequirement.js +2 -2
  72. package/dist/src/cli/renderOutput.js +1 -1
  73. package/dist/src/cli/rootAlias.js +1 -1
  74. package/dist/src/cli/runOptions.js +37 -25
  75. package/dist/src/cli/sessionCommand.js +31 -21
  76. package/dist/src/cli/sessionDisplay.js +182 -79
  77. package/dist/src/cli/sessionLineage.js +60 -0
  78. package/dist/src/cli/sessionRunner.js +118 -90
  79. package/dist/src/cli/sessionTable.js +28 -24
  80. package/dist/src/cli/stdin.js +22 -0
  81. package/dist/src/cli/tagline.js +121 -124
  82. package/dist/src/cli/tui/index.js +140 -127
  83. package/dist/src/cli/writeOutputPath.js +5 -5
  84. package/dist/src/config.js +7 -7
  85. package/dist/src/gemini-web/browserSessionManager.js +80 -0
  86. package/dist/src/gemini-web/client.js +81 -64
  87. package/dist/src/gemini-web/executionMode.js +16 -0
  88. package/dist/src/gemini-web/executor.js +327 -169
  89. package/dist/src/gemini-web/index.js +1 -1
  90. package/dist/src/mcp/server.js +16 -12
  91. package/dist/src/mcp/tools/consult.js +81 -64
  92. package/dist/src/mcp/tools/sessionResources.js +12 -12
  93. package/dist/src/mcp/tools/sessions.js +26 -17
  94. package/dist/src/mcp/types.js +5 -5
  95. package/dist/src/mcp/utils.js +15 -7
  96. package/dist/src/oracle/background.js +15 -15
  97. package/dist/src/oracle/claude.js +53 -25
  98. package/dist/src/oracle/client.js +84 -46
  99. package/dist/src/oracle/config.js +124 -58
  100. package/dist/src/oracle/errors.js +38 -38
  101. package/dist/src/oracle/files.js +69 -45
  102. package/dist/src/oracle/finishLine.js +10 -8
  103. package/dist/src/oracle/format.js +3 -3
  104. package/dist/src/oracle/gemini.js +37 -30
  105. package/dist/src/oracle/logging.js +7 -7
  106. package/dist/src/oracle/markdown.js +28 -28
  107. package/dist/src/oracle/modelResolver.js +16 -16
  108. package/dist/src/oracle/multiModelRunner.js +12 -12
  109. package/dist/src/oracle/oscProgress.js +8 -8
  110. package/dist/src/oracle/promptAssembly.js +6 -3
  111. package/dist/src/oracle/request.js +23 -15
  112. package/dist/src/oracle/run.js +172 -140
  113. package/dist/src/oracle/runUtils.js +8 -5
  114. package/dist/src/oracle/tokenEstimate.js +6 -6
  115. package/dist/src/oracle/tokenStats.js +5 -5
  116. package/dist/src/oracle/tokenStringifier.js +5 -5
  117. package/dist/src/oracle.js +12 -12
  118. package/dist/src/oracleHome.js +3 -3
  119. package/dist/src/remote/client.js +25 -25
  120. package/dist/src/remote/health.js +20 -20
  121. package/dist/src/remote/remoteServiceConfig.js +9 -9
  122. package/dist/src/remote/server.js +129 -118
  123. package/dist/src/sessionManager.js +81 -75
  124. package/dist/src/sessionStore.js +3 -3
  125. package/dist/src/version.js +10 -10
  126. package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
  127. package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
  128. package/dist/vendor/oracle-notifier/README.md +2 -0
  129. package/package.json +69 -65
  130. package/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
  131. package/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
  132. package/vendor/oracle-notifier/README.md +2 -0
  133. package/dist/markdansi/types/index.js +0 -4
  134. package/dist/oracle/bin/oracle-cli.js +0 -472
  135. package/dist/oracle/src/browser/actions/assistantResponse.js +0 -471
  136. package/dist/oracle/src/browser/actions/attachments.js +0 -82
  137. package/dist/oracle/src/browser/actions/modelSelection.js +0 -190
  138. package/dist/oracle/src/browser/actions/navigation.js +0 -75
  139. package/dist/oracle/src/browser/actions/promptComposer.js +0 -167
  140. package/dist/oracle/src/browser/chromeLifecycle.js +0 -104
  141. package/dist/oracle/src/browser/config.js +0 -33
  142. package/dist/oracle/src/browser/constants.js +0 -40
  143. package/dist/oracle/src/browser/cookies.js +0 -210
  144. package/dist/oracle/src/browser/domDebug.js +0 -36
  145. package/dist/oracle/src/browser/index.js +0 -331
  146. package/dist/oracle/src/browser/pageActions.js +0 -5
  147. package/dist/oracle/src/browser/prompt.js +0 -88
  148. package/dist/oracle/src/browser/promptSummary.js +0 -20
  149. package/dist/oracle/src/browser/sessionRunner.js +0 -80
  150. package/dist/oracle/src/browser/utils.js +0 -62
  151. package/dist/oracle/src/browserMode.js +0 -1
  152. package/dist/oracle/src/cli/browserConfig.js +0 -44
  153. package/dist/oracle/src/cli/dryRun.js +0 -59
  154. package/dist/oracle/src/cli/engine.js +0 -17
  155. package/dist/oracle/src/cli/errorUtils.js +0 -9
  156. package/dist/oracle/src/cli/help.js +0 -70
  157. package/dist/oracle/src/cli/markdownRenderer.js +0 -15
  158. package/dist/oracle/src/cli/options.js +0 -103
  159. package/dist/oracle/src/cli/promptRequirement.js +0 -14
  160. package/dist/oracle/src/cli/rootAlias.js +0 -30
  161. package/dist/oracle/src/cli/sessionCommand.js +0 -77
  162. package/dist/oracle/src/cli/sessionDisplay.js +0 -270
  163. package/dist/oracle/src/cli/sessionRunner.js +0 -94
  164. package/dist/oracle/src/heartbeat.js +0 -43
  165. package/dist/oracle/src/oracle/client.js +0 -48
  166. package/dist/oracle/src/oracle/config.js +0 -29
  167. package/dist/oracle/src/oracle/errors.js +0 -101
  168. package/dist/oracle/src/oracle/files.js +0 -220
  169. package/dist/oracle/src/oracle/format.js +0 -33
  170. package/dist/oracle/src/oracle/fsAdapter.js +0 -7
  171. package/dist/oracle/src/oracle/oscProgress.js +0 -60
  172. package/dist/oracle/src/oracle/request.js +0 -48
  173. package/dist/oracle/src/oracle/run.js +0 -444
  174. package/dist/oracle/src/oracle/tokenStats.js +0 -39
  175. package/dist/oracle/src/oracle/types.js +0 -1
  176. package/dist/oracle/src/oracle.js +0 -9
  177. package/dist/oracle/src/sessionManager.js +0 -205
  178. package/dist/oracle/src/version.js +0 -39
  179. package/dist/scripts/chrome/browser-tools.js +0 -295
  180. package/dist/src/browser/profileSync.js +0 -141
  181. /package/dist/{oracle/src/browser/types.js → src/gemini-web/executionClients.js} +0 -0
@@ -1,26 +1,26 @@
1
- import { rm } from 'node:fs/promises';
2
- import { readFileSync } from 'node:fs';
3
- import os from 'node:os';
4
- import net from 'node:net';
5
- import { execFile } from 'node:child_process';
6
- import { promisify } from 'node:util';
7
- import CDP from 'chrome-remote-interface';
8
- import { launch, Launcher } from 'chrome-launcher';
9
- import { cleanupStaleProfileState } from './profileState.js';
10
- import { delay } from './utils.js';
1
+ import { rm } from "node:fs/promises";
2
+ import { readFileSync } from "node:fs";
3
+ import os from "node:os";
4
+ import net from "node:net";
5
+ import { execFile } from "node:child_process";
6
+ import { promisify } from "node:util";
7
+ import CDP from "chrome-remote-interface";
8
+ import { launch, Launcher } from "chrome-launcher";
9
+ import { cleanupStaleProfileState } from "./profileState.js";
10
+ import { delay } from "./utils.js";
11
11
  const execFileAsync = promisify(execFile);
12
12
  export async function launchChrome(config, userDataDir, logger) {
13
13
  const connectHost = resolveRemoteDebugHost();
14
- const debugBindAddress = connectHost && connectHost !== '127.0.0.1' ? '0.0.0.0' : connectHost;
14
+ const debugBindAddress = connectHost && connectHost !== "127.0.0.1" ? "0.0.0.0" : connectHost;
15
15
  const debugPort = config.debugPort ?? parseDebugPortEnv();
16
16
  const chromeFlags = buildChromeFlags(config.headless ?? false, debugBindAddress);
17
- const usePatchedLauncher = Boolean(connectHost && connectHost !== '127.0.0.1');
17
+ const usePatchedLauncher = Boolean(connectHost && connectHost !== "127.0.0.1");
18
18
  const launcher = usePatchedLauncher
19
19
  ? await launchWithCustomHost({
20
20
  chromeFlags,
21
21
  chromePath: config.chromePath ?? undefined,
22
22
  userDataDir,
23
- host: connectHost ?? '127.0.0.1',
23
+ host: connectHost ?? "127.0.0.1",
24
24
  requestedPort: debugPort ?? undefined,
25
25
  })
26
26
  : await launch({
@@ -30,13 +30,13 @@ export async function launchChrome(config, userDataDir, logger) {
30
30
  handleSIGINT: false,
31
31
  port: debugPort ?? undefined,
32
32
  });
33
- const pidLabel = typeof launcher.pid === 'number' ? ` (pid ${launcher.pid})` : '';
34
- const hostLabel = connectHost ? ` on ${connectHost}` : '';
33
+ const pidLabel = typeof launcher.pid === "number" ? ` (pid ${launcher.pid})` : "";
34
+ const hostLabel = connectHost ? ` on ${connectHost}` : "";
35
35
  logger(`Launched Chrome${pidLabel} on port ${launcher.port}${hostLabel}`);
36
- return Object.assign(launcher, { host: connectHost ?? '127.0.0.1' });
36
+ return Object.assign(launcher, { host: connectHost ?? "127.0.0.1" });
37
37
  }
38
38
  export function registerTerminationHooks(chrome, userDataDir, keepBrowser, logger, opts) {
39
- const signals = ['SIGINT', 'SIGTERM', 'SIGQUIT'];
39
+ const signals = ["SIGINT", "SIGTERM", "SIGQUIT"];
40
40
  let handling;
41
41
  const handleSignal = (signal) => {
42
42
  if (handling) {
@@ -46,7 +46,7 @@ export function registerTerminationHooks(chrome, userDataDir, keepBrowser, logge
46
46
  const inFlight = opts?.isInFlight?.() ?? false;
47
47
  const leaveRunning = keepBrowser || inFlight;
48
48
  if (leaveRunning) {
49
- logger(`Received ${signal}; leaving Chrome running${inFlight ? ' (assistant response pending)' : ''}`);
49
+ logger(`Received ${signal}; leaving Chrome running${inFlight ? " (assistant response pending)" : ""}`);
50
50
  }
51
51
  else {
52
52
  logger(`Received ${signal}; terminating Chrome process`);
@@ -69,18 +69,18 @@ export function registerTerminationHooks(chrome, userDataDir, keepBrowser, logge
69
69
  if (opts?.preserveUserDataDir) {
70
70
  // Preserve the profile directory (manual login), but clear reattach hints so we don't
71
71
  // try to reuse a dead DevTools port on the next run.
72
- await cleanupStaleProfileState(userDataDir, logger, { lockRemovalMode: 'never' }).catch(() => undefined);
72
+ await cleanupStaleProfileState(userDataDir, logger, { lockRemovalMode: "never" }).catch(() => undefined);
73
73
  }
74
74
  else {
75
75
  await rm(userDataDir, { recursive: true, force: true }).catch(() => undefined);
76
76
  }
77
77
  }
78
78
  })().finally(() => {
79
- const exitCode = signal === 'SIGINT' ? 130 : 1;
79
+ const exitCode = signal === "SIGINT" ? 130 : 1;
80
80
  // Vitest treats any `process.exit()` call as an unhandled failure, even if mocked.
81
81
  // Keep production behavior (hard-exit on signals) while letting tests observe state changes.
82
82
  process.exitCode = exitCode;
83
- const isTestRun = process.env.VITEST === '1' || process.env.NODE_ENV === 'test';
83
+ const isTestRun = process.env.VITEST === "1" || process.env.NODE_ENV === "test";
84
84
  if (!isTestRun) {
85
85
  process.exit(exitCode);
86
86
  }
@@ -96,12 +96,12 @@ export function registerTerminationHooks(chrome, userDataDir, keepBrowser, logge
96
96
  };
97
97
  }
98
98
  export async function hideChromeWindow(chrome, logger) {
99
- if (process.platform !== 'darwin') {
100
- logger('Window hiding is only supported on macOS');
99
+ if (process.platform !== "darwin") {
100
+ logger("Window hiding is only supported on macOS");
101
101
  return;
102
102
  }
103
103
  if (!chrome.pid) {
104
- logger('Unable to hide window: missing Chrome PID');
104
+ logger("Unable to hide window: missing Chrome PID");
105
105
  return;
106
106
  }
107
107
  const script = `tell application "System Events"
@@ -110,8 +110,8 @@ export async function hideChromeWindow(chrome, logger) {
110
110
  end try
111
111
  end tell`;
112
112
  try {
113
- await execFileAsync('osascript', ['-e', script]);
114
- logger('Chrome window hidden (Cmd-H)');
113
+ await execFileAsync("osascript", ["-e", script]);
114
+ logger("Chrome window hidden (Cmd-H)");
115
115
  }
116
116
  catch (error) {
117
117
  const message = error instanceof Error ? error.message : String(error);
@@ -120,7 +120,7 @@ export async function hideChromeWindow(chrome, logger) {
120
120
  }
121
121
  export async function connectToChrome(port, logger, host) {
122
122
  const client = await CDP({ port, host });
123
- logger('Connected to Chrome DevTools protocol');
123
+ logger("Connected to Chrome DevTools protocol");
124
124
  return client;
125
125
  }
126
126
  export async function connectToRemoteChrome(host, port, logger, targetUrl) {
@@ -183,12 +183,14 @@ async function connectToNewTarget(host, port, url, logger, messages) {
183
183
  return null;
184
184
  }
185
185
  export async function connectWithNewTab(port, logger, initialUrl, host, options) {
186
- const effectiveHost = host ?? '127.0.0.1';
187
- const url = initialUrl ?? 'about:blank';
186
+ const effectiveHost = host ?? "127.0.0.1";
187
+ const url = initialUrl ?? "about:blank";
188
188
  const fallbackToDefault = options?.fallbackToDefault ?? true;
189
189
  const retries = Math.max(0, options?.retries ?? 0);
190
190
  const retryDelayMs = Math.max(0, options?.retryDelayMs ?? 250);
191
- const fallbackLabel = fallbackToDefault ? 'falling back to default target.' : 'strict mode: not falling back.';
191
+ const fallbackLabel = fallbackToDefault
192
+ ? "falling back to default target."
193
+ : "strict mode: not falling back.";
192
194
  let attempt = 0;
193
195
  while (attempt <= retries) {
194
196
  const targetConnection = await connectToNewTarget(effectiveHost, port, url, logger, {
@@ -207,13 +209,13 @@ export async function connectWithNewTab(port, logger, initialUrl, host, options)
207
209
  await delay(retryDelayMs * attempt);
208
210
  }
209
211
  if (!fallbackToDefault) {
210
- throw new Error('Failed to open isolated browser tab; refusing to attach to default target.');
212
+ throw new Error("Failed to open isolated browser tab; refusing to attach to default target.");
211
213
  }
212
214
  const client = await connectToChrome(port, logger, effectiveHost);
213
215
  return { client };
214
216
  }
215
217
  export async function closeTab(port, targetId, logger, host) {
216
- const effectiveHost = host ?? '127.0.0.1';
218
+ const effectiveHost = host ?? "127.0.0.1";
217
219
  try {
218
220
  await CDP.Close({ host: effectiveHost, port, id: targetId });
219
221
  logger(`Closed isolated browser tab (target=${targetId})`);
@@ -225,33 +227,33 @@ export async function closeTab(port, targetId, logger, host) {
225
227
  }
226
228
  function buildChromeFlags(headless, debugBindAddress) {
227
229
  const flags = [
228
- '--disable-background-networking',
229
- '--disable-background-timer-throttling',
230
- '--disable-breakpad',
231
- '--disable-client-side-phishing-detection',
232
- '--disable-default-apps',
233
- '--disable-hang-monitor',
234
- '--disable-popup-blocking',
235
- '--disable-prompt-on-repost',
236
- '--disable-sync',
237
- '--disable-translate',
238
- '--metrics-recording-only',
239
- '--no-first-run',
240
- '--safebrowsing-disable-auto-update',
241
- '--disable-features=TranslateUI,AutomationControlled',
242
- '--mute-audio',
243
- '--window-size=1280,720',
244
- '--lang=en-US',
245
- '--accept-lang=en-US,en',
230
+ "--disable-background-networking",
231
+ "--disable-background-timer-throttling",
232
+ "--disable-breakpad",
233
+ "--disable-client-side-phishing-detection",
234
+ "--disable-default-apps",
235
+ "--disable-hang-monitor",
236
+ "--disable-popup-blocking",
237
+ "--disable-prompt-on-repost",
238
+ "--disable-sync",
239
+ "--disable-translate",
240
+ "--metrics-recording-only",
241
+ "--no-first-run",
242
+ "--safebrowsing-disable-auto-update",
243
+ "--disable-features=TranslateUI,AutomationControlled",
244
+ "--mute-audio",
245
+ "--window-size=1280,720",
246
+ "--lang=en-US",
247
+ "--accept-lang=en-US,en",
246
248
  ];
247
- if (process.platform !== 'win32' && !isWsl()) {
248
- flags.push('--password-store=basic', '--use-mock-keychain');
249
+ if (process.platform !== "win32" && !isWsl()) {
250
+ flags.push("--password-store=basic", "--use-mock-keychain");
249
251
  }
250
252
  if (debugBindAddress) {
251
253
  flags.push(`--remote-debugging-address=${debugBindAddress}`);
252
254
  }
253
255
  if (headless) {
254
- flags.push('--headless=new');
256
+ flags.push("--headless=new");
255
257
  }
256
258
  return flags;
257
259
  }
@@ -274,8 +276,8 @@ function resolveRemoteDebugHost() {
274
276
  return null;
275
277
  }
276
278
  try {
277
- const resolv = readFileSync('/etc/resolv.conf', 'utf8');
278
- for (const line of resolv.split('\n')) {
279
+ const resolv = readFileSync("/etc/resolv.conf", "utf8");
280
+ for (const line of resolv.split("\n")) {
279
281
  const match = line.match(/^nameserver\s+([0-9.]+)/);
280
282
  if (match?.[1]) {
281
283
  return match[1];
@@ -288,14 +290,14 @@ function resolveRemoteDebugHost() {
288
290
  return null;
289
291
  }
290
292
  function isWsl() {
291
- if (process.platform !== 'linux') {
293
+ if (process.platform !== "linux") {
292
294
  return false;
293
295
  }
294
296
  if (process.env.WSL_DISTRO_NAME) {
295
297
  return true;
296
298
  }
297
299
  const release = os.release();
298
- return release.toLowerCase().includes('microsoft');
300
+ return release.toLowerCase().includes("microsoft");
299
301
  }
300
302
  async function launchWithCustomHost({ chromeFlags, chromePath, userDataDir, host, requestedPort, }) {
301
303
  const launcher = new Launcher({
@@ -310,7 +312,7 @@ async function launchWithCustomHost({ chromeFlags, chromePath, userDataDir, host
310
312
  patched.isDebuggerReady = function patchedIsDebuggerReady() {
311
313
  const debugPort = this.port ?? 0;
312
314
  if (!debugPort) {
313
- return Promise.reject(new Error('Missing Chrome debug port'));
315
+ return Promise.reject(new Error("Missing Chrome debug port"));
314
316
  }
315
317
  return new Promise((resolve, reject) => {
316
318
  const client = net.createConnection({ port: debugPort, host });
@@ -320,11 +322,11 @@ async function launchWithCustomHost({ chromeFlags, chromePath, userDataDir, host
320
322
  client.destroy();
321
323
  client.unref();
322
324
  };
323
- client.once('error', (err) => {
325
+ client.once("error", (err) => {
324
326
  cleanup();
325
327
  reject(err);
326
328
  });
327
- client.once('connect', () => {
329
+ client.once("connect", () => {
328
330
  cleanup();
329
331
  resolve();
330
332
  });
@@ -1,8 +1,19 @@
1
- import { CHATGPT_URL, DEFAULT_MODEL_STRATEGY, DEFAULT_MODEL_TARGET } from './constants.js';
2
- import { normalizeBrowserModelStrategy } from './modelStrategy.js';
3
- import { isTemporaryChatUrl, normalizeChatgptUrl } from './utils.js';
4
- import os from 'node:os';
5
- import path from 'node:path';
1
+ import { CHATGPT_URL, DEFAULT_MODEL_STRATEGY, DEFAULT_MODEL_TARGET } from "./constants.js";
2
+ import { normalizeBrowserModelStrategy } from "./modelStrategy.js";
3
+ import { isTemporaryChatUrl, normalizeChatgptUrl } from "./utils.js";
4
+ import os from "node:os";
5
+ import path from "node:path";
6
+ export const DEFAULT_CHATGPT_COOKIE_NAMES = [
7
+ "__Secure-next-auth.session-token",
8
+ "__Secure-next-auth.session-token.0",
9
+ "__Secure-next-auth.session-token.1",
10
+ "_account",
11
+ "cf_clearance",
12
+ "__cf_bm",
13
+ "_cfuvid",
14
+ "CF_Authorization",
15
+ "__cflb",
16
+ ];
6
17
  export const DEFAULT_BROWSER_CONFIG = {
7
18
  chromeProfile: null,
8
19
  chromePath: null,
@@ -20,7 +31,7 @@ export const DEFAULT_BROWSER_CONFIG = {
20
31
  autoReattachIntervalMs: 0,
21
32
  autoReattachTimeoutMs: 120_000,
22
33
  cookieSync: true,
23
- cookieNames: null,
34
+ cookieNames: DEFAULT_CHATGPT_COOKIE_NAMES,
24
35
  cookieSyncWaitMs: 0,
25
36
  inlineCookies: null,
26
37
  inlineCookiesSource: null,
@@ -38,27 +49,27 @@ export const DEFAULT_BROWSER_CONFIG = {
38
49
  };
39
50
  export function resolveBrowserConfig(config) {
40
51
  const debugPortEnv = parseDebugPort(process.env.ORACLE_BROWSER_PORT ?? process.env.ORACLE_BROWSER_DEBUG_PORT);
41
- const envAllowCookieErrors = (process.env.ORACLE_BROWSER_ALLOW_COOKIE_ERRORS ?? '').trim().toLowerCase() === 'true' ||
42
- (process.env.ORACLE_BROWSER_ALLOW_COOKIE_ERRORS ?? '').trim() === '1';
52
+ const envAllowCookieErrors = (process.env.ORACLE_BROWSER_ALLOW_COOKIE_ERRORS ?? "").trim().toLowerCase() === "true" ||
53
+ (process.env.ORACLE_BROWSER_ALLOW_COOKIE_ERRORS ?? "").trim() === "1";
43
54
  const rawUrl = config?.chatgptUrl ?? config?.url ?? DEFAULT_BROWSER_CONFIG.url;
44
55
  const normalizedUrl = normalizeChatgptUrl(rawUrl ?? DEFAULT_BROWSER_CONFIG.url, DEFAULT_BROWSER_CONFIG.url);
45
56
  const desiredModel = config?.desiredModel ?? DEFAULT_BROWSER_CONFIG.desiredModel ?? DEFAULT_MODEL_TARGET;
46
57
  const modelStrategy = normalizeBrowserModelStrategy(config?.modelStrategy) ??
47
58
  DEFAULT_BROWSER_CONFIG.modelStrategy ??
48
59
  DEFAULT_MODEL_STRATEGY;
49
- if (modelStrategy === 'select' && isTemporaryChatUrl(normalizedUrl) && /\bpro\b/i.test(desiredModel)) {
50
- throw new Error('Temporary Chat mode does not expose Pro models in the ChatGPT model picker. ' +
60
+ if (modelStrategy === "select" &&
61
+ isTemporaryChatUrl(normalizedUrl) &&
62
+ /\bpro\b/i.test(desiredModel)) {
63
+ throw new Error("Temporary Chat mode does not expose Pro models in the ChatGPT model picker. " +
51
64
  'Remove "temporary-chat=true" from your browser URL, or use a non-Pro model label (e.g. "GPT-5.2").');
52
65
  }
53
- const isWindows = process.platform === 'win32';
66
+ const isWindows = process.platform === "win32";
54
67
  const manualLogin = config?.manualLogin ?? (isWindows ? true : DEFAULT_BROWSER_CONFIG.manualLogin);
55
68
  const cookieSyncDefault = isWindows ? false : DEFAULT_BROWSER_CONFIG.cookieSync;
56
- const resolvedProfileDir = config?.manualLoginProfileDir ??
57
- process.env.ORACLE_BROWSER_PROFILE_DIR ??
58
- path.join(os.homedir(), '.oracle', 'browser-profile');
69
+ const resolvedProfileDir = resolveManualLoginProfileDir(config?.manualLoginProfileDir, process.env.ORACLE_BROWSER_PROFILE_DIR);
59
70
  return {
60
71
  ...DEFAULT_BROWSER_CONFIG,
61
- ...(config ?? {}),
72
+ ...config,
62
73
  url: normalizedUrl,
63
74
  chatgptUrl: normalizedUrl,
64
75
  timeoutMs: config?.timeoutMs ?? DEFAULT_BROWSER_CONFIG.timeoutMs,
@@ -101,3 +112,11 @@ function parseDebugPort(raw) {
101
112
  }
102
113
  return value;
103
114
  }
115
+ function resolveManualLoginProfileDir(...candidates) {
116
+ for (const candidate of candidates) {
117
+ const profileDir = candidate?.trim();
118
+ if (profileDir)
119
+ return profileDir;
120
+ }
121
+ return path.join(os.homedir(), ".oracle", "browser-profile");
122
+ }
@@ -1,15 +1,19 @@
1
- export const CHATGPT_URL = 'https://chatgpt.com/';
2
- export const DEFAULT_MODEL_TARGET = 'GPT-5.2 Pro';
3
- export const DEFAULT_MODEL_STRATEGY = 'select';
4
- export const COOKIE_URLS = ['https://chatgpt.com', 'https://chat.openai.com', 'https://atlas.openai.com'];
1
+ export const CHATGPT_URL = "https://chatgpt.com/";
2
+ export const DEFAULT_MODEL_TARGET = "GPT-5.5 Pro";
3
+ export const DEFAULT_MODEL_STRATEGY = "select";
4
+ export const COOKIE_URLS = [
5
+ "https://chatgpt.com",
6
+ "https://chat.openai.com",
7
+ "https://atlas.openai.com",
8
+ ];
5
9
  export const INPUT_SELECTORS = [
6
10
  'textarea[data-id="prompt-textarea"]',
7
11
  'textarea[placeholder*="Send a message"]',
8
12
  'textarea[aria-label="Message ChatGPT"]',
9
- 'textarea:not([disabled])',
13
+ "textarea:not([disabled])",
10
14
  'textarea[name="prompt-textarea"]',
11
- '#prompt-textarea',
12
- '.ProseMirror',
15
+ "#prompt-textarea",
16
+ ".ProseMirror",
13
17
  '[contenteditable="true"][data-virtualkeyboard="true"]',
14
18
  ];
15
19
  export const ANSWER_SELECTORS = [
@@ -24,12 +28,12 @@ export const ANSWER_SELECTORS = [
24
28
  '[data-turn="assistant"]',
25
29
  ];
26
30
  export const CONVERSATION_TURN_SELECTOR = 'article[data-testid^="conversation-turn"], div[data-testid^="conversation-turn"], section[data-testid^="conversation-turn"], ' +
27
- 'article[data-message-author-role], div[data-message-author-role], section[data-message-author-role], ' +
28
- 'article[data-turn], div[data-turn], section[data-turn]';
31
+ "article[data-message-author-role], div[data-message-author-role], section[data-message-author-role], " +
32
+ "article[data-turn], div[data-turn], section[data-turn]";
29
33
  export const ASSISTANT_ROLE_SELECTOR = '[data-message-author-role="assistant"], [data-turn="assistant"]';
30
34
  export const CLOUDFLARE_SCRIPT_SELECTOR = 'script[src*="/challenge-platform/"]';
31
- export const CLOUDFLARE_TITLE = 'just a moment';
32
- export const PROMPT_PRIMARY_SELECTOR = '#prompt-textarea';
35
+ export const CLOUDFLARE_TITLE = "just a moment";
36
+ export const PROMPT_PRIMARY_SELECTOR = "#prompt-textarea";
33
37
  export const PROMPT_FALLBACK_SELECTOR = 'textarea[name="prompt-textarea"]';
34
38
  export const FILE_INPUT_SELECTORS = [
35
39
  'form input[type="file"]:not([accept])',
@@ -65,7 +69,8 @@ export const SEND_BUTTON_SELECTORS = [
65
69
  'button[aria-label*="Send"]',
66
70
  ];
67
71
  export const SEND_BUTTON_SELECTOR = SEND_BUTTON_SELECTORS[0];
68
- export const MODEL_BUTTON_SELECTOR = '[data-testid="model-switcher-dropdown-button"]';
72
+ export const MODEL_BUTTON_SELECTOR = '[data-testid="model-switcher-dropdown-button"], button.__composer-pill[aria-haspopup="menu"]';
73
+ export const COMPOSER_MODEL_SIGNAL_SELECTOR = '[data-testid="composer-footer-actions"]';
69
74
  export const COPY_BUTTON_SELECTOR = 'button[data-testid="copy-turn-action-button"]';
70
75
  // Action buttons that only appear once a turn has finished rendering.
71
76
  export const FINISHED_ACTIONS_SELECTOR = 'button[data-testid="copy-turn-action-button"], button[data-testid="good-response-turn-action-button"], button[data-testid="bad-response-turn-action-button"], button[aria-label="Share"]';
@@ -1,6 +1,6 @@
1
- import { COOKIE_URLS } from './constants.js';
2
- import { delay } from './utils.js';
3
- import { getCookies } from '@steipete/sweet-cookie';
1
+ import { COOKIE_URLS } from "./constants.js";
2
+ import { delay } from "./utils.js";
3
+ import { getCookies } from "@steipete/sweet-cookie";
4
4
  export class ChromeCookieSyncError extends Error {
5
5
  }
6
6
  export async function syncCookies(Network, url, profile, logger, options = {}) {
@@ -55,7 +55,7 @@ async function readChromeCookiesWithWait(url, profile, filterNames, cookiePath,
55
55
  return cookies;
56
56
  }
57
57
  const waitLabel = waitMs >= 1000 ? `${Math.round(waitMs / 1000)}s` : `${waitMs}ms`;
58
- const message = firstError instanceof Error ? firstError.message : String(firstError ?? '');
58
+ const message = firstError instanceof Error ? firstError.message : String(firstError ?? "");
59
59
  if (firstError) {
60
60
  logger(`[cookies] Cookie read failed (${message}); waiting ${waitLabel} then retrying once.`);
61
61
  }
@@ -68,27 +68,27 @@ async function readChromeCookiesWithWait(url, profile, filterNames, cookiePath,
68
68
  async function readChromeCookies(url, profile, filterNames, cookiePath) {
69
69
  const origins = Array.from(new Set([stripQuery(url), ...COOKIE_URLS]));
70
70
  const chromeProfile = cookiePath ?? profile ?? undefined;
71
- const timeoutMs = readDuration('ORACLE_COOKIE_LOAD_TIMEOUT_MS', 5_000);
71
+ const timeoutMs = readDuration("ORACLE_COOKIE_LOAD_TIMEOUT_MS", 5_000);
72
72
  // Learned: read from multiple origins to capture auth cookies that land on chat.openai.com + atlas.
73
73
  const { cookies, warnings } = await getCookies({
74
74
  url,
75
75
  origins,
76
76
  names: filterNames?.length ? filterNames : undefined,
77
- browsers: ['chrome'],
78
- mode: 'merge',
77
+ browsers: ["chrome"],
78
+ mode: "merge",
79
79
  chromeProfile,
80
80
  timeoutMs,
81
81
  });
82
- if (process.env.ORACLE_DEBUG_COOKIES === '1' && warnings.length) {
82
+ if (process.env.ORACLE_DEBUG_COOKIES === "1" && warnings.length) {
83
83
  // eslint-disable-next-line no-console
84
- console.log(`[cookies] sweet-cookie warnings:\n- ${warnings.join('\n- ')}`);
84
+ console.log(`[cookies] sweet-cookie warnings:\n- ${warnings.join("\n- ")}`);
85
85
  }
86
86
  const merged = new Map();
87
87
  for (const cookie of cookies) {
88
88
  const normalized = toCdpCookie(cookie);
89
89
  if (!normalized)
90
90
  continue;
91
- const key = `${normalized.domain ?? ''}:${normalized.name}`;
91
+ const key = `${normalized.domain ?? ""}:${normalized.name}`;
92
92
  if (!merged.has(key))
93
93
  merged.set(key, normalized);
94
94
  }
@@ -102,10 +102,10 @@ function normalizeInlineCookies(rawCookies, fallbackHost) {
102
102
  // Learned: inline cookies may omit url/domain; default to current host with a safe path.
103
103
  const normalized = {
104
104
  name: cookie.name,
105
- value: cookie.value ?? '',
105
+ value: cookie.value ?? "",
106
106
  url: cookie.url,
107
107
  domain: cookie.domain ?? fallbackHost,
108
- path: cookie.path ?? '/',
108
+ path: cookie.path ?? "/",
109
109
  expires: normalizeExpiration(cookie.expires),
110
110
  secure: cookie.secure ?? true,
111
111
  httpOnly: cookie.httpOnly ?? false,
@@ -125,13 +125,13 @@ function toCdpCookie(cookie) {
125
125
  name: cookie.name,
126
126
  value: cookie.value,
127
127
  domain: cookie.domain,
128
- path: cookie.path ?? '/',
128
+ path: cookie.path ?? "/",
129
129
  secure: cookie.secure ?? true,
130
130
  httpOnly: cookie.httpOnly ?? false,
131
131
  };
132
- if (typeof cookie.expires === 'number')
132
+ if (typeof cookie.expires === "number")
133
133
  out.expires = cookie.expires;
134
- if (cookie.sameSite === 'Lax' || cookie.sameSite === 'Strict' || cookie.sameSite === 'None') {
134
+ if (cookie.sameSite === "Lax" || cookie.sameSite === "Strict" || cookie.sameSite === "None") {
135
135
  out.sameSite = cookie.sameSite;
136
136
  }
137
137
  return out;
@@ -139,10 +139,10 @@ function toCdpCookie(cookie) {
139
139
  function attachUrl(cookie, fallbackUrl) {
140
140
  const cookieWithUrl = { ...cookie };
141
141
  if (!cookieWithUrl.url) {
142
- if (!cookieWithUrl.domain || cookieWithUrl.domain === 'localhost') {
142
+ if (!cookieWithUrl.domain || cookieWithUrl.domain === "localhost") {
143
143
  cookieWithUrl.url = fallbackUrl;
144
144
  }
145
- else if (!cookieWithUrl.domain.startsWith('.')) {
145
+ else if (!cookieWithUrl.domain.startsWith(".")) {
146
146
  cookieWithUrl.url = `https://${cookieWithUrl.domain}`;
147
147
  }
148
148
  }
@@ -156,8 +156,8 @@ function attachUrl(cookie, fallbackUrl) {
156
156
  function stripQuery(url) {
157
157
  try {
158
158
  const parsed = new URL(url);
159
- parsed.hash = '';
160
- parsed.search = '';
159
+ parsed.hash = "";
160
+ parsed.search = "";
161
161
  return parsed.toString();
162
162
  }
163
163
  catch {