@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,28 @@
1
- import { spawn } from 'node:child_process';
2
- import fs from 'node:fs/promises';
3
- import path from 'node:path';
4
- import { randomBytes } from 'node:crypto';
5
- import chalk from 'chalk';
6
- import { getOracleHomeDir } from '../../oracleHome.js';
7
- import { parseHostPort, normalizeHostPort, formatBridgeConnectionString } from '../../bridge/connection.js';
8
- import { serveRemote } from '../../remote/server.js';
1
+ import { spawn } from "node:child_process";
2
+ import fs from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { randomBytes } from "node:crypto";
5
+ import chalk from "chalk";
6
+ import { getOracleHomeDir } from "../../oracleHome.js";
7
+ import { parseHostPort, normalizeHostPort, formatBridgeConnectionString, } from "../../bridge/connection.js";
8
+ import { serveRemote } from "../../remote/server.js";
9
9
  export async function runBridgeHost(options) {
10
- const bindRaw = options.bind?.trim() || '127.0.0.1:9473';
10
+ const bindRaw = options.bind?.trim() || "127.0.0.1:9473";
11
11
  const { hostname: bindHost, port: bindPort } = parseHostPort(bindRaw);
12
- const tokenRaw = options.token?.trim() || 'auto';
13
- const token = tokenRaw === 'auto' ? randomBytes(16).toString('hex') : tokenRaw;
12
+ const tokenRaw = options.token?.trim() || "auto";
13
+ const token = tokenRaw === "auto" ? randomBytes(16).toString("hex") : tokenRaw;
14
14
  if (!token.trim()) {
15
- throw new Error('Token is required (use --token auto to generate one).');
15
+ throw new Error("Token is required (use --token auto to generate one).");
16
16
  }
17
- const writeConnectionPath = options.writeConnection?.trim() || path.join(getOracleHomeDir(), 'bridge-connection.json');
17
+ const writeConnectionPath = options.writeConnection?.trim() || path.join(getOracleHomeDir(), "bridge-connection.json");
18
18
  const sshTarget = options.ssh?.trim();
19
- const sshRemotePort = typeof options.sshRemotePort === 'number' ? options.sshRemotePort : bindPort;
19
+ const sshRemotePort = typeof options.sshRemotePort === "number" ? options.sshRemotePort : bindPort;
20
20
  if (sshRemotePort <= 0 || sshRemotePort > 65_535) {
21
21
  throw new Error(`Invalid --ssh-remote-port: ${sshRemotePort}. Expected 1-65535.`);
22
22
  }
23
- const connectionHostForClient = sshTarget ? normalizeHostPort('127.0.0.1', sshRemotePort) : normalizeHostPort(bindHost === '0.0.0.0' || bindHost === '::' ? '127.0.0.1' : bindHost, bindPort);
23
+ const connectionHostForClient = sshTarget
24
+ ? normalizeHostPort("127.0.0.1", sshRemotePort)
25
+ : normalizeHostPort(bindHost === "0.0.0.0" || bindHost === "::" ? "127.0.0.1" : bindHost, bindPort);
24
26
  const artifact = await upsertConnectionArtifact(writeConnectionPath, {
25
27
  remoteHost: connectionHostForClient,
26
28
  remoteToken: token,
@@ -52,11 +54,11 @@ export async function runBridgeHost(options) {
52
54
  });
53
55
  return;
54
56
  }
55
- console.log(chalk.cyanBright('Bridge host starting...'));
57
+ console.log(chalk.cyanBright("Bridge host starting..."));
56
58
  console.log(chalk.dim(`- Local bind: ${normalizeHostPort(bindHost, bindPort)}`));
57
59
  console.log(chalk.dim(`- Connection artifact: ${writeConnectionPath}`));
58
60
  console.log(chalk.dim(`- Client remoteHost: ${artifact.remoteHost}`));
59
- console.log(chalk.dim('Token stored in connection artifact (not printed). Use --print or --print-token if needed.'));
61
+ console.log(chalk.dim("Token stored in connection artifact (not printed). Use --print or --print-token if needed."));
60
62
  let tunnel = null;
61
63
  if (sshTarget) {
62
64
  tunnel = startReverseTunnel({
@@ -70,7 +72,7 @@ export async function runBridgeHost(options) {
70
72
  console.log(chalk.dim(`Reverse SSH tunnel active (remote 127.0.0.1:${sshRemotePort} -> local 127.0.0.1:${bindPort})`));
71
73
  }
72
74
  const filteredServeLogger = (message) => {
73
- if (message.includes('Access token:')) {
75
+ if (message.includes("Access token:")) {
74
76
  return;
75
77
  }
76
78
  console.log(message);
@@ -91,12 +93,12 @@ async function upsertConnectionArtifact(filePath, input) {
91
93
  const dir = path.dirname(filePath);
92
94
  await fs.mkdir(dir, { recursive: true, mode: 0o700 });
93
95
  const now = new Date().toISOString();
94
- const existing = await fs.readFile(filePath, 'utf8').catch(() => null);
96
+ const existing = await fs.readFile(filePath, "utf8").catch(() => null);
95
97
  let createdAt = now;
96
98
  if (existing) {
97
99
  try {
98
100
  const parsed = JSON.parse(existing);
99
- if (typeof parsed.createdAt === 'string' && parsed.createdAt.trim().length > 0) {
101
+ if (typeof parsed.createdAt === "string" && parsed.createdAt.trim().length > 0) {
100
102
  createdAt = parsed.createdAt;
101
103
  }
102
104
  }
@@ -113,9 +115,9 @@ async function upsertConnectionArtifact(filePath, input) {
113
115
  };
114
116
  const contents = `${JSON.stringify(artifact, null, 2)}\n`;
115
117
  const tempPath = `${filePath}.tmp-${process.pid}-${Date.now()}`;
116
- await fs.writeFile(tempPath, contents, { encoding: 'utf8', mode: 0o600 });
118
+ await fs.writeFile(tempPath, contents, { encoding: "utf8", mode: 0o600 });
117
119
  await fs.rename(tempPath, filePath);
118
- if (process.platform !== 'win32') {
120
+ if (process.platform !== "win32") {
119
121
  await fs.chmod(filePath, 0o600).catch(() => undefined);
120
122
  }
121
123
  return artifact;
@@ -129,27 +131,27 @@ function startReverseTunnel({ sshTarget, remotePort, localPort, identity, extraA
129
131
  if (stopped)
130
132
  return;
131
133
  const args = [
132
- '-N',
133
- '-R',
134
+ "-N",
135
+ "-R",
134
136
  `${remotePort}:127.0.0.1:${localPort}`,
135
- '-o',
136
- 'ExitOnForwardFailure=yes',
137
- '-o',
138
- 'ServerAliveInterval=30',
139
- '-o',
140
- 'ServerAliveCountMax=3',
137
+ "-o",
138
+ "ExitOnForwardFailure=yes",
139
+ "-o",
140
+ "ServerAliveInterval=30",
141
+ "-o",
142
+ "ServerAliveCountMax=3",
141
143
  ];
142
144
  if (identity) {
143
- args.push('-i', identity);
145
+ args.push("-i", identity);
144
146
  }
145
147
  if (extraArgs) {
146
148
  args.push(...splitArgs(extraArgs));
147
149
  }
148
150
  args.push(sshTarget);
149
- child = spawn('ssh', args, { stdio: 'ignore' });
151
+ child = spawn("ssh", args, { stdio: "ignore" });
150
152
  const pid = child.pid;
151
- log(`[bridge host] ssh tunnel started${pid ? ` (pid ${pid})` : ''}: ${sshTarget}`);
152
- child.once('exit', (code, signal) => {
153
+ log(`[bridge host] ssh tunnel started${pid ? ` (pid ${pid})` : ""}: ${sshTarget}`);
154
+ child.once("exit", (code, signal) => {
153
155
  child = null;
154
156
  if (stopped)
155
157
  return;
@@ -179,16 +181,16 @@ function startReverseTunnel({ sshTarget, remotePort, localPort, identity, extraA
179
181
  }
180
182
  function splitArgs(input) {
181
183
  const args = [];
182
- let current = '';
184
+ let current = "";
183
185
  let quote = null;
184
186
  const push = () => {
185
187
  const trimmed = current.trim();
186
188
  if (trimmed.length)
187
189
  args.push(trimmed);
188
- current = '';
190
+ current = "";
189
191
  };
190
192
  for (let i = 0; i < input.length; i += 1) {
191
- const ch = input[i] ?? '';
193
+ const ch = input[i] ?? "";
192
194
  if (quote) {
193
195
  if (ch === quote) {
194
196
  quote = null;
@@ -214,46 +216,46 @@ function splitArgs(input) {
214
216
  async function spawnBridgeHostInBackground({ bind, token, writeConnectionPath, sshTarget, sshRemotePort, sshIdentity, sshExtraArgs, }) {
215
217
  const oracleHome = getOracleHomeDir();
216
218
  await fs.mkdir(oracleHome, { recursive: true, mode: 0o700 });
217
- const logPath = path.join(oracleHome, 'bridge-host.log');
218
- const pidPath = path.join(oracleHome, 'bridge-host.pid');
219
- const logHandle = await fs.open(logPath, 'a');
220
- const stdio = ['ignore', logHandle.fd, logHandle.fd];
219
+ const logPath = path.join(oracleHome, "bridge-host.log");
220
+ const pidPath = path.join(oracleHome, "bridge-host.pid");
221
+ const logHandle = await fs.open(logPath, "a");
222
+ const stdio = ["ignore", logHandle.fd, logHandle.fd];
221
223
  const scriptPath = process.argv[1];
222
224
  if (!scriptPath) {
223
- throw new Error('Unable to determine CLI entrypoint for background mode.');
225
+ throw new Error("Unable to determine CLI entrypoint for background mode.");
224
226
  }
225
227
  const args = [
226
228
  scriptPath,
227
- 'bridge',
228
- 'host',
229
- '--foreground',
230
- '--bind',
229
+ "bridge",
230
+ "host",
231
+ "--foreground",
232
+ "--bind",
231
233
  bind,
232
- '--token',
234
+ "--token",
233
235
  token,
234
- '--write-connection',
236
+ "--write-connection",
235
237
  writeConnectionPath,
236
238
  ];
237
239
  if (sshTarget) {
238
- args.push('--ssh', sshTarget);
240
+ args.push("--ssh", sshTarget);
239
241
  }
240
- if (typeof sshRemotePort === 'number') {
241
- args.push('--ssh-remote-port', String(sshRemotePort));
242
+ if (typeof sshRemotePort === "number") {
243
+ args.push("--ssh-remote-port", String(sshRemotePort));
242
244
  }
243
245
  if (sshIdentity) {
244
- args.push('--ssh-identity', sshIdentity);
246
+ args.push("--ssh-identity", sshIdentity);
245
247
  }
246
248
  if (sshExtraArgs) {
247
- args.push('--ssh-extra-args', sshExtraArgs);
249
+ args.push("--ssh-extra-args", sshExtraArgs);
248
250
  }
249
251
  const child = spawn(process.execPath, args, { detached: true, stdio });
250
252
  child.unref();
251
- await fs.writeFile(pidPath, `${child.pid ?? ''}\n`, { encoding: 'utf8', mode: 0o600 });
252
- if (process.platform !== 'win32') {
253
+ await fs.writeFile(pidPath, `${child.pid ?? ""}\n`, { encoding: "utf8", mode: 0o600 });
254
+ if (process.platform !== "win32") {
253
255
  await fs.chmod(pidPath, 0o600).catch(() => undefined);
254
256
  }
255
257
  await logHandle.close();
256
- console.log(chalk.green(`Bridge host running in background (pid ${child.pid ?? '?'})`));
258
+ console.log(chalk.green(`Bridge host running in background (pid ${child.pid ?? "?"})`));
257
259
  console.log(chalk.dim(`- Log: ${logPath}`));
258
260
  console.log(chalk.dim(`- PID: ${pidPath}`));
259
261
  }
@@ -1,51 +1,62 @@
1
- import fs from 'node:fs/promises';
2
- import path from 'node:path';
3
- import { CHATGPT_URL, DEFAULT_MODEL_STRATEGY, DEFAULT_MODEL_TARGET, isTemporaryChatUrl, normalizeChatgptUrl, parseDuration } from '../browserMode.js';
4
- import { normalizeBrowserModelStrategy } from '../browser/modelStrategy.js';
5
- import { getOracleHomeDir } from '../oracleHome.js';
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { CHATGPT_URL, DEFAULT_MODEL_STRATEGY, DEFAULT_MODEL_TARGET, isTemporaryChatUrl, normalizeChatgptUrl, parseDuration, } from "../browserMode.js";
4
+ import { normalizeBrowserModelStrategy } from "../browser/modelStrategy.js";
5
+ import { getOracleHomeDir } from "../oracleHome.js";
6
6
  const DEFAULT_BROWSER_TIMEOUT_MS = 1_200_000;
7
7
  const DEFAULT_BROWSER_INPUT_TIMEOUT_MS = 60_000;
8
8
  const DEFAULT_BROWSER_RECHECK_TIMEOUT_MS = 120_000;
9
9
  const DEFAULT_BROWSER_AUTO_REATTACH_TIMEOUT_MS = 120_000;
10
- const DEFAULT_CHROME_PROFILE = 'Default';
10
+ const DEFAULT_CHROME_PROFILE = "Default";
11
11
  // Ordered array: most specific models first to ensure correct selection.
12
12
  // The browser label is passed to the model picker which fuzzy-matches against ChatGPT's UI.
13
13
  const BROWSER_MODEL_LABELS = [
14
14
  // Most specific first (e.g., "gpt-5.2-thinking" before "gpt-5.2")
15
- ['gpt-5.2-thinking', 'GPT-5.2 Thinking'],
16
- ['gpt-5.2-instant', 'GPT-5.2 Instant'],
17
- ['gpt-5.2-pro', 'GPT-5.2 Pro'],
18
- ['gpt-5.1-pro', 'GPT-5.2 Pro'],
19
- ['gpt-5-pro', 'GPT-5.2 Pro'],
15
+ ["gpt-5.5-pro", "GPT-5.5 Pro"],
16
+ ["gpt-5.5", "Thinking 5.5"],
17
+ ["gpt-5.4-pro", "GPT-5.4 Pro"],
18
+ ["gpt-5.2-thinking", "GPT-5.2 Thinking"],
19
+ ["gpt-5.2-instant", "GPT-5.2 Instant"],
20
+ ["gpt-5.2-pro", "GPT-5.5 Pro"],
21
+ ["gpt-5.1-pro", "GPT-5.5 Pro"],
22
+ ["gpt-5-pro", "GPT-5.5 Pro"],
20
23
  // Base models last (least specific)
21
- ['gpt-5.2', 'GPT-5.2'], // Selects "Auto" in ChatGPT UI
22
- ['gpt-5.1', 'GPT-5.2'], // Legacy alias Auto
23
- ['gemini-3-pro', 'Gemini 3 Pro'],
24
+ ["gpt-5.4", "Thinking 5.4"],
25
+ ["gpt-5.2", "GPT-5.2"], // Selects "Auto" in ChatGPT UI
26
+ ["gpt-5.1", "GPT-5.2"], // Legacy alias → Auto
27
+ ["gemini-3-pro", "Gemini 3 Pro"],
28
+ ["gemini-3-pro-deep-think", "gemini-3-deep-think"],
24
29
  ];
25
30
  export function normalizeChatGptModelForBrowser(model) {
26
31
  const normalized = model.toLowerCase();
27
- if (!normalized.startsWith('gpt-') || normalized.includes('codex')) {
32
+ if (!normalized.startsWith("gpt-") || normalized.includes("codex")) {
28
33
  return model;
29
34
  }
30
- // Pro variants: always resolve to the latest Pro model in ChatGPT.
31
- if (normalized === 'gpt-5-pro' || normalized === 'gpt-5.1-pro' || normalized.endsWith('-pro')) {
32
- return 'gpt-5.2-pro';
35
+ if (normalized === "gpt-5.5-pro" ||
36
+ normalized === "gpt-5.5" ||
37
+ normalized === "gpt-5.4-pro" ||
38
+ normalized === "gpt-5.4") {
39
+ return normalized;
40
+ }
41
+ // Pro variants: resolve to the latest Pro model in ChatGPT.
42
+ if (normalized === "gpt-5-pro" || normalized === "gpt-5.1-pro" || normalized === "gpt-5.2-pro") {
43
+ return "gpt-5.5-pro";
33
44
  }
34
45
  // Explicit model variants: keep as-is (they have their own browser labels)
35
- if (normalized === 'gpt-5.2-thinking' || normalized === 'gpt-5.2-instant') {
46
+ if (normalized === "gpt-5.2-thinking" || normalized === "gpt-5.2-instant") {
36
47
  return normalized;
37
48
  }
38
49
  // Legacy aliases: map to base GPT-5.2 (Auto)
39
- if (normalized === 'gpt-5.1') {
40
- return 'gpt-5.2';
50
+ if (normalized === "gpt-5.1") {
51
+ return "gpt-5.2";
41
52
  }
42
53
  return model;
43
54
  }
44
55
  export async function buildBrowserConfig(options) {
45
56
  const desiredModelOverride = options.browserModelLabel?.trim();
46
- const normalizedOverride = desiredModelOverride?.toLowerCase() ?? '';
57
+ const normalizedOverride = desiredModelOverride?.toLowerCase() ?? "";
47
58
  const baseModel = options.model.toLowerCase();
48
- const isChatGptModel = baseModel.startsWith('gpt-') && !baseModel.includes('codex');
59
+ const isChatGptModel = baseModel.startsWith("gpt-") && !baseModel.includes("codex");
49
60
  const shouldUseOverride = !isChatGptModel && normalizedOverride.length > 0 && normalizedOverride !== baseModel;
50
61
  const modelStrategy = normalizeBrowserModelStrategy(options.browserModelStrategy) ?? DEFAULT_MODEL_STRATEGY;
51
62
  const cookieNames = parseCookieNames(options.browserCookieNames ?? process.env.ORACLE_BROWSER_COOKIE_NAMES);
@@ -56,7 +67,7 @@ export async function buildBrowserConfig(options) {
56
67
  envFile: process.env.ORACLE_BROWSER_COOKIES_FILE,
57
68
  cwd: process.cwd(),
58
69
  });
59
- if (inline?.source?.startsWith('home:') && options.browserNoCookieSync !== true) {
70
+ if (inline?.source?.startsWith("home:") && options.browserNoCookieSync !== true) {
60
71
  inline = undefined;
61
72
  }
62
73
  let remoteChrome;
@@ -70,8 +81,11 @@ export async function buildBrowserConfig(options) {
70
81
  : shouldUseOverride
71
82
  ? desiredModelOverride
72
83
  : mapModelToBrowserLabel(options.model);
73
- if (modelStrategy === 'select' && url && isTemporaryChatUrl(url) && /\bpro\b/i.test(desiredModel ?? '')) {
74
- throw new Error('Temporary Chat mode does not expose Pro models in the ChatGPT model picker. ' +
84
+ if (modelStrategy === "select" &&
85
+ url &&
86
+ isTemporaryChatUrl(url) &&
87
+ /\bpro\b/i.test(desiredModel ?? "")) {
88
+ throw new Error("Temporary Chat mode does not expose Pro models in the ChatGPT model picker. " +
75
89
  'Remove "temporary-chat=true" from --chatgpt-url (or omit --chatgpt-url), or use a non-Pro model (e.g. --model gpt-5.2).');
76
90
  }
77
91
  return {
@@ -80,7 +94,9 @@ export async function buildBrowserConfig(options) {
80
94
  chromeCookiePath: options.browserCookiePath ?? null,
81
95
  url,
82
96
  debugPort: selectBrowserPort(options),
83
- timeoutMs: options.browserTimeout ? parseDuration(options.browserTimeout, DEFAULT_BROWSER_TIMEOUT_MS) : undefined,
97
+ timeoutMs: options.browserTimeout
98
+ ? parseDuration(options.browserTimeout, DEFAULT_BROWSER_TIMEOUT_MS)
99
+ : undefined,
84
100
  inputTimeoutMs: options.browserInputTimeout
85
101
  ? parseDuration(options.browserInputTimeout, DEFAULT_BROWSER_INPUT_TIMEOUT_MS)
86
102
  : undefined,
@@ -90,7 +106,9 @@ export async function buildBrowserConfig(options) {
90
106
  assistantRecheckTimeoutMs: options.browserRecheckTimeout
91
107
  ? parseDuration(options.browserRecheckTimeout, DEFAULT_BROWSER_RECHECK_TIMEOUT_MS)
92
108
  : undefined,
93
- reuseChromeWaitMs: options.browserReuseWait ? parseDuration(options.browserReuseWait, 0) : undefined,
109
+ reuseChromeWaitMs: options.browserReuseWait
110
+ ? parseDuration(options.browserReuseWait, 0)
111
+ : undefined,
94
112
  profileLockTimeoutMs: options.browserProfileLockTimeout
95
113
  ? parseDuration(options.browserProfileLockTimeout, 0)
96
114
  : undefined,
@@ -103,7 +121,9 @@ export async function buildBrowserConfig(options) {
103
121
  autoReattachTimeoutMs: options.browserAutoReattachTimeout
104
122
  ? parseDuration(options.browserAutoReattachTimeout, DEFAULT_BROWSER_AUTO_REATTACH_TIMEOUT_MS)
105
123
  : undefined,
106
- cookieSyncWaitMs: options.browserCookieWait ? parseDuration(options.browserCookieWait, 0) : undefined,
124
+ cookieSyncWaitMs: options.browserCookieWait
125
+ ? parseDuration(options.browserCookieWait, 0)
126
+ : undefined,
107
127
  cookieSync: options.browserNoCookieSync ? false : undefined,
108
128
  cookieNames,
109
129
  inlineCookies: inline?.cookies,
@@ -142,7 +162,7 @@ export function mapModelToBrowserLabel(model) {
142
162
  return DEFAULT_MODEL_TARGET;
143
163
  }
144
164
  export function resolveBrowserModelLabel(input, model) {
145
- const trimmed = input?.trim?.() ?? '';
165
+ const trimmed = input?.trim?.() ?? "";
146
166
  if (!trimmed) {
147
167
  return mapModelToBrowserLabel(model);
148
168
  }
@@ -155,7 +175,7 @@ export function resolveBrowserModelLabel(input, model) {
155
175
  function parseRemoteChromeTarget(raw) {
156
176
  const target = raw.trim();
157
177
  if (!target) {
158
- throw new Error('Invalid remote-chrome value: expected host:port but received an empty string.');
178
+ throw new Error("Invalid remote-chrome value: expected host:port but received an empty string.");
159
179
  }
160
180
  const ipv6Match = target.match(/^\[(.+)]:(\d+)$/);
161
181
  let host;
@@ -165,22 +185,22 @@ function parseRemoteChromeTarget(raw) {
165
185
  portSegment = ipv6Match[2]?.trim();
166
186
  }
167
187
  else {
168
- const lastColon = target.lastIndexOf(':');
188
+ const lastColon = target.lastIndexOf(":");
169
189
  if (lastColon === -1) {
170
190
  throw new Error(`Invalid remote-chrome format: ${target}. Expected host:port (IPv6 must use [host]:port notation).`);
171
191
  }
172
192
  host = target.slice(0, lastColon).trim();
173
193
  portSegment = target.slice(lastColon + 1).trim();
174
- if (host.includes(':')) {
194
+ if (host.includes(":")) {
175
195
  throw new Error(`Invalid remote-chrome format: ${target}. Wrap IPv6 addresses in brackets, e.g. --remote-chrome "[2001:db8::1]:9222".`);
176
196
  }
177
197
  }
178
198
  if (!host) {
179
199
  throw new Error(`Invalid remote-chrome format: ${target}. Host portion is missing; expected host:port.`);
180
200
  }
181
- const port = Number.parseInt(portSegment ?? '', 10);
201
+ const port = Number.parseInt(portSegment ?? "", 10);
182
202
  if (!Number.isFinite(port) || port <= 0 || port > 65_535) {
183
- throw new Error(`Invalid remote-chrome port: "${portSegment ?? ''}". Expected a number between 1 and 65535.`);
203
+ throw new Error(`Invalid remote-chrome port: "${portSegment ?? ""}". Expected a number between 1 and 65535.`);
184
204
  }
185
205
  return { host, port };
186
206
  }
@@ -188,7 +208,7 @@ function parseCookieNames(raw) {
188
208
  if (!raw)
189
209
  return undefined;
190
210
  const names = raw
191
- .split(',')
211
+ .split(",")
192
212
  .map((entry) => entry.trim())
193
213
  .filter(Boolean);
194
214
  return names.length ? names : undefined;
@@ -205,7 +225,7 @@ async function resolveInlineCookies({ inlineArg, inlineFileArg, envPayload, envF
205
225
  try {
206
226
  const stat = await fs.stat(resolved);
207
227
  if (stat.isFile()) {
208
- const fileContent = await fs.readFile(resolved, 'utf8');
228
+ const fileContent = await fs.readFile(resolved, "utf8");
209
229
  const parsed = parseInlineCookiesPayload(fileContent);
210
230
  if (parsed)
211
231
  return parsed;
@@ -218,10 +238,10 @@ async function resolveInlineCookies({ inlineArg, inlineFileArg, envPayload, envF
218
238
  return parseInlineCookiesPayload(trimmed);
219
239
  };
220
240
  const sources = [
221
- { value: inlineFileArg, allowPath: true, source: 'inline-file' },
222
- { value: inlineArg, allowPath: true, source: 'inline-arg' },
223
- { value: envFile, allowPath: true, source: 'env-file' },
224
- { value: envPayload, allowPath: false, source: 'env-payload' },
241
+ { value: inlineFileArg, allowPath: true, source: "inline-file" },
242
+ { value: inlineArg, allowPath: true, source: "inline-arg" },
243
+ { value: envFile, allowPath: true, source: "env-file" },
244
+ { value: envPayload, allowPath: false, source: "env-payload" },
225
245
  ];
226
246
  for (const { value, allowPath, source } of sources) {
227
247
  const parsed = await tryLoad(value, allowPath);
@@ -230,14 +250,14 @@ async function resolveInlineCookies({ inlineArg, inlineFileArg, envPayload, envF
230
250
  }
231
251
  // fallback: ~/.oracle/cookies.{json,base64}
232
252
  const oracleHome = getOracleHomeDir();
233
- const candidates = ['cookies.json', 'cookies.base64'];
253
+ const candidates = ["cookies.json", "cookies.base64"];
234
254
  for (const file of candidates) {
235
255
  const fullPath = path.join(oracleHome, file);
236
256
  try {
237
257
  const stat = await fs.stat(fullPath);
238
258
  if (!stat.isFile())
239
259
  continue;
240
- const content = await fs.readFile(fullPath, 'utf8');
260
+ const content = await fs.readFile(fullPath, "utf8");
241
261
  const parsed = parseInlineCookiesPayload(content);
242
262
  if (parsed)
243
263
  return { cookies: parsed, source: `home:${file}` };
@@ -257,8 +277,8 @@ function parseInlineCookiesPayload(raw) {
257
277
  let jsonPayload = text;
258
278
  // Attempt base64 decode first; fall back to raw text on failure.
259
279
  try {
260
- const decoded = Buffer.from(text, 'base64').toString('utf8');
261
- if (decoded.trim().startsWith('[')) {
280
+ const decoded = Buffer.from(text, "base64").toString("utf8");
281
+ if (decoded.trim().startsWith("[")) {
262
282
  jsonPayload = decoded;
263
283
  }
264
284
  }
@@ -1,81 +1,82 @@
1
- import { normalizeChatgptUrl, CHATGPT_URL } from '../browserMode.js';
1
+ import { normalizeChatgptUrl, CHATGPT_URL } from "../browserMode.js";
2
2
  export function applyBrowserDefaultsFromConfig(options, config, getSource) {
3
3
  const browser = config.browser;
4
4
  if (!browser)
5
5
  return;
6
6
  const isUnset = (key) => {
7
7
  const source = getSource(key);
8
- return source === undefined || source === 'default';
8
+ return source === undefined || source === "default";
9
9
  };
10
10
  const configuredChatgptUrl = browser.chatgptUrl ?? browser.url;
11
11
  const cliChatgptSet = options.chatgptUrl !== undefined || options.browserUrl !== undefined;
12
- if (isUnset('chatgptUrl') && !cliChatgptSet && configuredChatgptUrl !== undefined) {
13
- options.chatgptUrl = normalizeChatgptUrl(configuredChatgptUrl ?? '', CHATGPT_URL);
12
+ if (isUnset("chatgptUrl") && !cliChatgptSet && configuredChatgptUrl !== undefined) {
13
+ options.chatgptUrl = normalizeChatgptUrl(configuredChatgptUrl ?? "", CHATGPT_URL);
14
14
  }
15
- if (isUnset('browserChromeProfile') && browser.chromeProfile !== undefined) {
15
+ if (isUnset("browserChromeProfile") && browser.chromeProfile !== undefined) {
16
16
  options.browserChromeProfile = browser.chromeProfile ?? undefined;
17
17
  }
18
- if (isUnset('browserChromePath') && browser.chromePath !== undefined) {
18
+ if (isUnset("browserChromePath") && browser.chromePath !== undefined) {
19
19
  options.browserChromePath = browser.chromePath ?? undefined;
20
20
  }
21
- if (isUnset('browserCookiePath') && browser.chromeCookiePath !== undefined) {
21
+ if (isUnset("browserCookiePath") && browser.chromeCookiePath !== undefined) {
22
22
  options.browserCookiePath = browser.chromeCookiePath ?? undefined;
23
23
  }
24
- if (isUnset('browserUrl') && options.browserUrl === undefined && browser.url !== undefined) {
24
+ if (isUnset("browserUrl") && options.browserUrl === undefined && browser.url !== undefined) {
25
25
  options.browserUrl = browser.url;
26
26
  }
27
- if (isUnset('browserTimeout') && typeof browser.timeoutMs === 'number') {
27
+ if (isUnset("browserTimeout") && typeof browser.timeoutMs === "number") {
28
28
  options.browserTimeout = String(browser.timeoutMs);
29
29
  }
30
- if (isUnset('browserPort') && typeof browser.debugPort === 'number') {
30
+ if (isUnset("browserPort") && typeof browser.debugPort === "number") {
31
31
  options.browserPort = browser.debugPort;
32
32
  }
33
- if (isUnset('browserInputTimeout') && typeof browser.inputTimeoutMs === 'number') {
33
+ if (isUnset("browserInputTimeout") && typeof browser.inputTimeoutMs === "number") {
34
34
  options.browserInputTimeout = String(browser.inputTimeoutMs);
35
35
  }
36
- if (isUnset('browserRecheckDelay') && typeof browser.assistantRecheckDelayMs === 'number') {
36
+ if (isUnset("browserRecheckDelay") && typeof browser.assistantRecheckDelayMs === "number") {
37
37
  options.browserRecheckDelay = String(browser.assistantRecheckDelayMs);
38
38
  }
39
- if (isUnset('browserRecheckTimeout') && typeof browser.assistantRecheckTimeoutMs === 'number') {
39
+ if (isUnset("browserRecheckTimeout") && typeof browser.assistantRecheckTimeoutMs === "number") {
40
40
  options.browserRecheckTimeout = String(browser.assistantRecheckTimeoutMs);
41
41
  }
42
- if (isUnset('browserReuseWait') && typeof browser.reuseChromeWaitMs === 'number') {
42
+ if (isUnset("browserReuseWait") && typeof browser.reuseChromeWaitMs === "number") {
43
43
  options.browserReuseWait = String(browser.reuseChromeWaitMs);
44
44
  }
45
- if (isUnset('browserProfileLockTimeout') && typeof browser.profileLockTimeoutMs === 'number') {
45
+ if (isUnset("browserProfileLockTimeout") && typeof browser.profileLockTimeoutMs === "number") {
46
46
  options.browserProfileLockTimeout = String(browser.profileLockTimeoutMs);
47
47
  }
48
- if (isUnset('browserAutoReattachDelay') && typeof browser.autoReattachDelayMs === 'number') {
48
+ if (isUnset("browserAutoReattachDelay") && typeof browser.autoReattachDelayMs === "number") {
49
49
  options.browserAutoReattachDelay = String(browser.autoReattachDelayMs);
50
50
  }
51
- if (isUnset('browserAutoReattachInterval') && typeof browser.autoReattachIntervalMs === 'number') {
51
+ if (isUnset("browserAutoReattachInterval") &&
52
+ typeof browser.autoReattachIntervalMs === "number") {
52
53
  options.browserAutoReattachInterval = String(browser.autoReattachIntervalMs);
53
54
  }
54
- if (isUnset('browserAutoReattachTimeout') && typeof browser.autoReattachTimeoutMs === 'number') {
55
+ if (isUnset("browserAutoReattachTimeout") && typeof browser.autoReattachTimeoutMs === "number") {
55
56
  options.browserAutoReattachTimeout = String(browser.autoReattachTimeoutMs);
56
57
  }
57
- if (isUnset('browserCookieWait') && typeof browser.cookieSyncWaitMs === 'number') {
58
+ if (isUnset("browserCookieWait") && typeof browser.cookieSyncWaitMs === "number") {
58
59
  options.browserCookieWait = String(browser.cookieSyncWaitMs);
59
60
  }
60
- if (isUnset('browserHeadless') && browser.headless !== undefined) {
61
+ if (isUnset("browserHeadless") && browser.headless !== undefined) {
61
62
  options.browserHeadless = browser.headless;
62
63
  }
63
- if (isUnset('browserHideWindow') && browser.hideWindow !== undefined) {
64
+ if (isUnset("browserHideWindow") && browser.hideWindow !== undefined) {
64
65
  options.browserHideWindow = browser.hideWindow;
65
66
  }
66
- if (isUnset('browserKeepBrowser') && browser.keepBrowser !== undefined) {
67
+ if (isUnset("browserKeepBrowser") && browser.keepBrowser !== undefined) {
67
68
  options.browserKeepBrowser = browser.keepBrowser;
68
69
  }
69
- if (isUnset('browserModelStrategy') && browser.modelStrategy !== undefined) {
70
+ if (isUnset("browserModelStrategy") && browser.modelStrategy !== undefined) {
70
71
  options.browserModelStrategy = browser.modelStrategy;
71
72
  }
72
- if (isUnset('browserThinkingTime') && browser.thinkingTime !== undefined) {
73
+ if (isUnset("browserThinkingTime") && browser.thinkingTime !== undefined) {
73
74
  options.browserThinkingTime = browser.thinkingTime;
74
75
  }
75
- if (isUnset('browserManualLogin') && browser.manualLogin !== undefined) {
76
+ if (isUnset("browserManualLogin") && browser.manualLogin !== undefined) {
76
77
  options.browserManualLogin = browser.manualLogin;
77
78
  }
78
- if (isUnset('browserManualLoginProfileDir') && browser.manualLoginProfileDir !== undefined) {
79
+ if (isUnset("browserManualLoginProfileDir") && browser.manualLoginProfileDir !== undefined) {
79
80
  options.browserManualLoginProfileDir = browser.manualLoginProfileDir;
80
81
  }
81
82
  }
@@ -1,4 +1,4 @@
1
- import chalk from 'chalk';
1
+ import chalk from "chalk";
2
2
  export function warnIfOversizeBundle(estimatedTokens, threshold = 196_000, log = console.log) {
3
3
  if (Number.isNaN(estimatedTokens) || estimatedTokens <= threshold) {
4
4
  return false;
@@ -1,8 +1,17 @@
1
- import clipboard from 'clipboardy';
1
+ async function loadClipboard() {
2
+ if (process.platform === "darwin" && process.arch === "x64") {
3
+ const paths = (process.env.PATH ?? "").split(":").filter(Boolean);
4
+ if (!paths.includes("/usr/sbin")) {
5
+ process.env.PATH = ["/usr/sbin", ...paths].join(":");
6
+ }
7
+ }
8
+ return (await import("clipboardy")).default;
9
+ }
2
10
  export async function copyToClipboard(text) {
3
11
  try {
12
+ const clipboard = await loadClipboard();
4
13
  await clipboard.write(text);
5
- return { success: true, command: 'clipboardy' };
14
+ return { success: true, command: "clipboardy" };
6
15
  }
7
16
  catch (error) {
8
17
  return { success: false, error };
@@ -1,11 +1,14 @@
1
- import { isProModel } from '../oracle/modelResolver.js';
1
+ import { isProModel } from "../oracle/modelResolver.js";
2
2
  export function shouldDetachSession({
3
- // Params kept for future policy tweaks; currently only model/disableDetachEnv matter.
4
- engine, model, waitPreference: _waitPreference, disableDetachEnv, }) {
3
+ // Params kept for policy tweaks.
4
+ engine, model, waitPreference, disableDetachEnv, }) {
5
5
  if (disableDetachEnv)
6
6
  return false;
7
+ // Explicit --wait means "stay attached", regardless of model defaults.
8
+ if (waitPreference)
9
+ return false;
7
10
  // Only Pro-tier API runs should start detached by default; browser runs stay inline so failures surface.
8
- if (isProModel(model) && engine === 'api')
11
+ if (isProModel(model) && engine === "api")
9
12
  return true;
10
13
  return false;
11
14
  }