@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,27 +1,32 @@
1
- import path from 'node:path';
2
- import { getCookies } from '@steipete/sweet-cookie';
3
- import { runGeminiWebWithFallback, saveFirstGeminiImageFromOutput } from './client.js';
1
+ import path from "node:path";
2
+ import { getCookies } from "@steipete/sweet-cookie";
3
+ import { runProviderDomFlow } from "../browser/providerDomFlow.js";
4
+ import { delay } from "../browser/utils.js";
5
+ import { runGeminiWebWithFallback, saveFirstGeminiImageFromOutput } from "./client.js";
6
+ import { geminiDeepThinkDomProvider } from "../browser/providers/index.js";
7
+ import { openGeminiBrowserSession } from "./browserSessionManager.js";
8
+ import { selectGeminiExecutionMode } from "./executionMode.js";
4
9
  const GEMINI_COOKIE_NAMES = [
5
- '__Secure-1PSID',
6
- '__Secure-1PSIDTS',
7
- '__Secure-1PSIDCC',
8
- '__Secure-1PAPISID',
9
- 'NID',
10
- 'AEC',
11
- 'SOCS',
12
- '__Secure-BUCKET',
13
- '__Secure-ENID',
14
- 'SID',
15
- 'HSID',
16
- 'SSID',
17
- 'APISID',
18
- 'SAPISID',
19
- '__Secure-3PSID',
20
- '__Secure-3PSIDTS',
21
- '__Secure-3PAPISID',
22
- 'SIDCC',
10
+ "__Secure-1PSID",
11
+ "__Secure-1PSIDTS",
12
+ "__Secure-1PSIDCC",
13
+ "__Secure-1PAPISID",
14
+ "NID",
15
+ "AEC",
16
+ "SOCS",
17
+ "__Secure-BUCKET",
18
+ "__Secure-ENID",
19
+ "SID",
20
+ "HSID",
21
+ "SSID",
22
+ "APISID",
23
+ "SAPISID",
24
+ "__Secure-3PSID",
25
+ "__Secure-3PSIDTS",
26
+ "__Secure-3PAPISID",
27
+ "SIDCC",
23
28
  ];
24
- const GEMINI_REQUIRED_COOKIES = ['__Secure-1PSID', '__Secure-1PSIDTS'];
29
+ const GEMINI_REQUIRED_COOKIES = ["__Secure-1PSID", "__Secure-1PSIDTS"];
25
30
  function estimateTokenCount(text) {
26
31
  return Math.ceil(text.length / 4);
27
32
  }
@@ -34,28 +39,33 @@ function resolveInvocationPath(value) {
34
39
  return path.isAbsolute(trimmed) ? trimmed : path.resolve(process.cwd(), trimmed);
35
40
  }
36
41
  function resolveGeminiWebModel(desiredModel, log) {
37
- const desired = typeof desiredModel === 'string' ? desiredModel.trim() : '';
42
+ const desired = typeof desiredModel === "string" ? desiredModel.trim() : "";
38
43
  if (!desired)
39
- return 'gemini-3-pro';
40
- switch (desired) {
41
- case 'gemini-3-pro':
42
- case 'gemini-3.0-pro':
43
- return 'gemini-3-pro';
44
- case 'gemini-2.5-pro':
45
- return 'gemini-2.5-pro';
46
- case 'gemini-2.5-flash':
47
- return 'gemini-2.5-flash';
44
+ return "gemini-3-pro";
45
+ const normalized = desired.toLowerCase().replace(/[_\s]+/g, "-");
46
+ switch (normalized) {
47
+ case "gemini-3-pro":
48
+ case "gemini-3.0-pro":
49
+ return "gemini-3-pro";
50
+ case "gemini-3-deep-think":
51
+ case "gemini-3-pro-deep-think":
52
+ case "gemini-3-pro-deepthink":
53
+ return "gemini-3-pro-deep-think";
54
+ case "gemini-2.5-pro":
55
+ return "gemini-2.5-pro";
56
+ case "gemini-2.5-flash":
57
+ return "gemini-2.5-flash";
48
58
  default:
49
- if (desired.startsWith('gemini-')) {
59
+ if (normalized.startsWith("gemini-") || normalized.includes("gemini")) {
50
60
  log?.(`[gemini-web] Unsupported Gemini web model "${desired}". Falling back to gemini-3-pro.`);
51
61
  }
52
- return 'gemini-3-pro';
62
+ return "gemini-3-pro";
53
63
  }
54
64
  }
55
65
  function resolveCookieDomain(cookie) {
56
66
  const rawDomain = cookie.domain?.trim();
57
67
  if (rawDomain) {
58
- return rawDomain.startsWith('.') ? rawDomain.slice(1) : rawDomain;
68
+ return rawDomain.startsWith(".") ? rawDomain.slice(1) : rawDomain;
59
69
  }
60
70
  const rawUrl = cookie.url?.trim();
61
71
  if (rawUrl) {
@@ -69,14 +79,14 @@ function resolveCookieDomain(cookie) {
69
79
  return null;
70
80
  }
71
81
  function pickCookieValue(cookies, name) {
72
- const matches = cookies.filter((cookie) => cookie.name === name && typeof cookie.value === 'string');
82
+ const matches = cookies.filter((cookie) => cookie.name === name && typeof cookie.value === "string");
73
83
  if (matches.length === 0)
74
84
  return undefined;
75
85
  const preferredDomain = matches.find((cookie) => {
76
86
  const domain = resolveCookieDomain(cookie);
77
- return domain === 'google.com' && (cookie.path ?? '/') === '/';
87
+ return domain === "google.com" && (cookie.path ?? "/") === "/";
78
88
  });
79
- const googleDomain = matches.find((cookie) => (resolveCookieDomain(cookie) ?? '').endsWith('google.com'));
89
+ const googleDomain = matches.find((cookie) => (resolveCookieDomain(cookie) ?? "").endsWith("google.com"));
80
90
  return (preferredDomain ?? googleDomain ?? matches[0])?.value;
81
91
  }
82
92
  function buildGeminiCookieMap(cookies) {
@@ -91,87 +101,181 @@ function buildGeminiCookieMap(cookies) {
91
101
  function hasRequiredGeminiCookies(cookieMap) {
92
102
  return GEMINI_REQUIRED_COOKIES.every((name) => Boolean(cookieMap[name]));
93
103
  }
104
+ const GEMINI_CDP_COOKIE_URLS = [
105
+ "https://gemini.google.com",
106
+ "https://accounts.google.com",
107
+ "https://www.google.com",
108
+ ];
109
+ async function loadGeminiCookiesFromCDP(browserConfig, log) {
110
+ const session = await openGeminiBrowserSession({
111
+ browserConfig,
112
+ keepBrowserDefault: false,
113
+ purpose: "Gemini manual-login cookie extraction (no keychain)",
114
+ log,
115
+ });
116
+ try {
117
+ const client = session.client;
118
+ const { Network, Page } = client;
119
+ await Network.enable({});
120
+ await Page.enable();
121
+ log?.("[gemini-web] Navigating to gemini.google.com for sign-in/cookie capture...");
122
+ await Page.navigate({ url: "https://gemini.google.com" });
123
+ await delay(2_000);
124
+ const pollTimeoutMs = 5 * 60_000;
125
+ const pollIntervalMs = 2_000;
126
+ const deadline = Date.now() + pollTimeoutMs;
127
+ let lastNotice = 0;
128
+ let cookieMap = {};
129
+ while (Date.now() < deadline) {
130
+ const { cookies } = await Network.getCookies({ urls: GEMINI_CDP_COOKIE_URLS });
131
+ cookieMap = buildGeminiCookieMap(cookies);
132
+ if (hasRequiredGeminiCookies(cookieMap)) {
133
+ log?.(`[gemini-web] Extracted ${Object.keys(cookieMap).length} Gemini cookie(s) via CDP.`);
134
+ return { cookieMap, warnings: [] };
135
+ }
136
+ const now = Date.now();
137
+ if (now - lastNotice > 10_000) {
138
+ log?.("[gemini-web] Waiting for Google sign-in... please sign in in the opened Chrome window.");
139
+ lastNotice = now;
140
+ }
141
+ await delay(pollIntervalMs);
142
+ }
143
+ throw new Error("Timed out waiting for Google sign-in (5 minutes). Please sign in and retry.");
144
+ }
145
+ finally {
146
+ await session.close();
147
+ }
148
+ }
149
+ async function runGeminiDeepThinkViaBrowser(prompt, browserConfig, log) {
150
+ const session = await openGeminiBrowserSession({
151
+ browserConfig,
152
+ keepBrowserDefault: true,
153
+ purpose: "Gemini Deep Think",
154
+ log,
155
+ });
156
+ try {
157
+ const client = session.client;
158
+ const { Runtime, Page } = client;
159
+ if (!Runtime ||
160
+ typeof Runtime.enable !== "function" ||
161
+ typeof Runtime.evaluate !== "function") {
162
+ throw new Error("Chrome Runtime domain unavailable for Gemini Deep Think DOM automation.");
163
+ }
164
+ if (!Page || typeof Page.enable !== "function" || typeof Page.navigate !== "function") {
165
+ throw new Error("Chrome Page domain unavailable for Gemini Deep Think DOM automation.");
166
+ }
167
+ await Runtime.enable();
168
+ await Page.enable();
169
+ const evaluate = async (expression) => {
170
+ const { result } = await Runtime.evaluate({ expression, returnByValue: true });
171
+ return result?.value;
172
+ };
173
+ log?.("[gemini-web] Navigating to gemini.google.com...");
174
+ await Page.navigate({ url: "https://gemini.google.com/app" });
175
+ await delay(3_000);
176
+ const domResult = await runProviderDomFlow(geminiDeepThinkDomProvider, {
177
+ prompt,
178
+ evaluate,
179
+ delay,
180
+ log,
181
+ state: {
182
+ inputTimeoutMs: browserConfig?.inputTimeoutMs,
183
+ timeoutMs: browserConfig?.timeoutMs,
184
+ },
185
+ });
186
+ log?.(`[gemini-web] Deep Think response received (${domResult.text.length} chars).`);
187
+ return domResult;
188
+ }
189
+ finally {
190
+ await session.close();
191
+ }
192
+ }
94
193
  async function loadGeminiCookiesFromInline(browserConfig, log) {
95
194
  const inline = browserConfig?.inlineCookies;
96
195
  if (!inline || inline.length === 0)
97
- return {};
98
- const cookieMap = buildGeminiCookieMap(inline.filter((cookie) => Boolean(cookie?.name && typeof cookie.value === 'string')));
196
+ return { cookieMap: {}, warnings: [] };
197
+ const cookieMap = buildGeminiCookieMap(inline.filter((cookie) => Boolean(cookie?.name && typeof cookie.value === "string")));
99
198
  if (Object.keys(cookieMap).length > 0) {
100
- const source = browserConfig?.inlineCookiesSource ?? 'inline';
199
+ const source = browserConfig?.inlineCookiesSource ?? "inline";
101
200
  log?.(`[gemini-web] Loaded Gemini cookies from inline payload (${source}): ${Object.keys(cookieMap).length} cookie(s).`);
102
201
  }
103
202
  else {
104
- log?.('[gemini-web] Inline cookie payload provided but no Gemini cookies matched.');
203
+ log?.("[gemini-web] Inline cookie payload provided but no Gemini cookies matched.");
105
204
  }
106
- return cookieMap;
205
+ return { cookieMap, warnings: [] };
107
206
  }
108
207
  async function loadGeminiCookiesFromChrome(browserConfig, log) {
109
208
  try {
110
209
  // Learned: Gemini web relies on Google auth cookies in the *browser* profile, not API keys.
111
210
  const profileCandidate = browserConfig?.chromeCookiePath ?? browserConfig?.chromeProfile ?? undefined;
112
- const profile = typeof profileCandidate === 'string' && profileCandidate.trim().length > 0
211
+ const profile = typeof profileCandidate === "string" && profileCandidate.trim().length > 0
113
212
  ? profileCandidate.trim()
114
213
  : undefined;
115
214
  const sources = [
116
- 'https://gemini.google.com',
117
- 'https://accounts.google.com',
118
- 'https://www.google.com',
215
+ "https://gemini.google.com",
216
+ "https://accounts.google.com",
217
+ "https://www.google.com",
119
218
  ];
120
219
  const { cookies, warnings } = await getCookies({
121
220
  url: sources[0],
122
221
  origins: sources,
123
222
  names: [...GEMINI_COOKIE_NAMES],
124
- browsers: ['chrome'],
125
- mode: 'merge',
223
+ browsers: ["chrome"],
224
+ mode: "merge",
126
225
  chromeProfile: profile,
127
226
  timeoutMs: 5_000,
128
227
  });
129
228
  if (warnings.length && log?.verbose) {
130
- log(`[gemini-web] Cookie warnings:\n- ${warnings.join('\n- ')}`);
229
+ log(`[gemini-web] Cookie warnings:\n- ${warnings.join("\n- ")}`);
131
230
  }
132
231
  const cookieMap = buildGeminiCookieMap(cookies);
133
232
  log?.(`[gemini-web] Loaded Gemini cookies from Chrome (node): ${Object.keys(cookieMap).length} cookie(s).`);
134
- return cookieMap;
233
+ return { cookieMap, warnings };
135
234
  }
136
235
  catch (error) {
137
- log?.(`[gemini-web] Failed to load Chrome cookies via node: ${error instanceof Error ? error.message : String(error ?? '')}`);
138
- return {};
236
+ log?.(`[gemini-web] Failed to load Chrome cookies via node: ${error instanceof Error ? error.message : String(error ?? "")}`);
237
+ return { cookieMap: {}, warnings: [] };
139
238
  }
140
239
  }
141
- async function loadGeminiCookies(browserConfig, log) {
142
- const inlineMap = await loadGeminiCookiesFromInline(browserConfig, log);
143
- const hasInlineRequired = hasRequiredGeminiCookies(inlineMap);
144
- if (hasInlineRequired && browserConfig?.cookieSync === false) {
145
- return inlineMap;
240
+ function formatGeminiCookieError(warnings) {
241
+ const base = "Gemini browser mode requires Chrome cookies for google.com (missing __Secure-1PSID/__Secure-1PSIDTS).";
242
+ const guidance = "Try --browser-manual-login or --browser-inline-cookies-file if local cookie extraction is unavailable.";
243
+ if (warnings.length === 0) {
244
+ return `${base} ${guidance}`;
245
+ }
246
+ return `${base}\nCookie read warnings:\n- ${warnings.join("\n- ")}\n${guidance}`;
247
+ }
248
+ async function loadGeminiCookies(browserConfig, log, options) {
249
+ const inlineResult = await loadGeminiCookiesFromInline(browserConfig, log);
250
+ const hasInlineRequired = hasRequiredGeminiCookies(inlineResult.cookieMap);
251
+ if (hasInlineRequired) {
252
+ return inlineResult;
253
+ }
254
+ const manualNoKeychain = Boolean(browserConfig?.manualLogin) || Boolean(options?.preferManualNoKeychain);
255
+ if (manualNoKeychain) {
256
+ log?.("[gemini-web] Using manual-login cookie extraction path (no keychain cookie read).");
257
+ const cdpResult = await loadGeminiCookiesFromCDP(browserConfig, log);
258
+ return {
259
+ cookieMap: { ...cdpResult.cookieMap, ...inlineResult.cookieMap },
260
+ warnings: [...inlineResult.warnings, ...cdpResult.warnings],
261
+ };
146
262
  }
147
263
  if (browserConfig?.cookieSync === false && !hasInlineRequired) {
148
- log?.('[gemini-web] Cookie sync disabled and inline cookies missing Gemini auth tokens.');
149
- return inlineMap;
264
+ log?.("[gemini-web] Cookie sync disabled and inline cookies missing Gemini auth tokens.");
265
+ return inlineResult;
150
266
  }
151
- const chromeMap = await loadGeminiCookiesFromChrome(browserConfig, log);
152
- const merged = { ...chromeMap, ...inlineMap };
153
- return merged;
267
+ const chromeResult = await loadGeminiCookiesFromChrome(browserConfig, log);
268
+ return {
269
+ cookieMap: { ...chromeResult.cookieMap, ...inlineResult.cookieMap },
270
+ warnings: [...inlineResult.warnings, ...chromeResult.warnings],
271
+ };
154
272
  }
155
273
  export function createGeminiWebExecutor(geminiOptions) {
156
274
  return async (runOptions) => {
157
275
  const startTime = Date.now();
158
276
  const log = runOptions.log;
159
- log?.('[gemini-web] Starting Gemini web executor (TypeScript)');
160
- const cookieMap = await loadGeminiCookies(runOptions.config, log);
161
- if (!hasRequiredGeminiCookies(cookieMap)) {
162
- throw new Error('Gemini browser mode requires Chrome cookies for google.com (missing __Secure-1PSID/__Secure-1PSIDTS).');
163
- }
164
- const configTimeout = typeof runOptions.config?.timeoutMs === 'number' && Number.isFinite(runOptions.config.timeoutMs)
165
- ? Math.max(1_000, runOptions.config.timeoutMs)
166
- : null;
167
- const defaultTimeoutMs = geminiOptions.youtube
168
- ? 240_000
169
- : geminiOptions.generateImage || geminiOptions.editImage
170
- ? 300_000
171
- : 120_000;
172
- const timeoutMs = Math.min(configTimeout ?? defaultTimeoutMs, 600_000);
173
- const controller = new AbortController();
174
- const timeout = setTimeout(() => controller.abort(), timeoutMs);
277
+ log?.("[gemini-web] Starting Gemini web executor (TypeScript)");
278
+ const model = resolveGeminiWebModel(runOptions.config?.desiredModel, log);
175
279
  const generateImagePath = resolveInvocationPath(geminiOptions.generateImage);
176
280
  const editImagePath = resolveInvocationPath(geminiOptions.editImage);
177
281
  const outputPath = resolveInvocationPath(geminiOptions.outputPath);
@@ -186,100 +290,154 @@ export function createGeminiWebExecutor(geminiOptions) {
186
290
  if (generateImagePath && !editImagePath) {
187
291
  prompt = `Generate an image: ${prompt}`;
188
292
  }
189
- const model = resolveGeminiWebModel(runOptions.config?.desiredModel, log);
190
- let response;
191
- try {
192
- if (editImagePath) {
193
- const intro = await runGeminiWebWithFallback({
194
- prompt: 'Here is an image to edit',
195
- files: [editImagePath],
196
- model,
197
- cookieMap,
198
- chatMetadata: null,
199
- signal: controller.signal,
200
- });
201
- const editPrompt = `Use image generation tool to ${prompt}`;
202
- const out = await runGeminiWebWithFallback({
203
- prompt: editPrompt,
204
- files: attachmentPaths,
205
- model,
206
- cookieMap,
207
- chatMetadata: intro.metadata,
208
- signal: controller.signal,
209
- });
210
- response = {
211
- text: out.text ?? null,
212
- thoughts: geminiOptions.showThoughts ? out.thoughts : null,
213
- has_images: false,
214
- image_count: 0,
215
- };
216
- const resolvedOutputPath = outputPath ?? generateImagePath ?? 'generated.png';
217
- const imageSave = await saveFirstGeminiImageFromOutput(out, cookieMap, resolvedOutputPath, controller.signal);
218
- response.has_images = imageSave.saved;
219
- response.image_count = imageSave.imageCount;
220
- if (!imageSave.saved) {
221
- throw new Error(`No images generated. Response text:\n${out.text || '(empty response)'}`);
293
+ const modeSelection = selectGeminiExecutionMode({
294
+ model,
295
+ attachmentPaths,
296
+ generateImagePath,
297
+ editImagePath,
298
+ });
299
+ const domClient = {
300
+ mode: "dom",
301
+ execute: async () => {
302
+ log?.("[gemini-web] Using browser DOM automation for Deep Think.");
303
+ const browserResult = await runGeminiDeepThinkViaBrowser(prompt, runOptions.config, log);
304
+ const tookMs = Date.now() - startTime;
305
+ let answerMarkdown = browserResult.text;
306
+ if (geminiOptions.showThoughts && browserResult.thoughts) {
307
+ answerMarkdown = `## Thinking\n\n${browserResult.thoughts}\n\n## Response\n\n${browserResult.text}`;
222
308
  }
223
- }
224
- else if (generateImagePath) {
225
- const out = await runGeminiWebWithFallback({
226
- prompt,
227
- files: attachmentPaths,
228
- model,
229
- cookieMap,
230
- chatMetadata: null,
231
- signal: controller.signal,
232
- });
233
- response = {
234
- text: out.text ?? null,
235
- thoughts: geminiOptions.showThoughts ? out.thoughts : null,
236
- has_images: false,
237
- image_count: 0,
309
+ log?.(`[gemini-web] Completed in ${tookMs}ms`);
310
+ return {
311
+ answerText: browserResult.text,
312
+ answerMarkdown,
313
+ tookMs,
314
+ answerTokens: estimateTokenCount(browserResult.text),
315
+ answerChars: browserResult.text.length,
238
316
  };
239
- const imageSave = await saveFirstGeminiImageFromOutput(out, cookieMap, generateImagePath, controller.signal);
240
- response.has_images = imageSave.saved;
241
- response.image_count = imageSave.imageCount;
242
- if (!imageSave.saved) {
243
- throw new Error(`No images generated. Response text:\n${out.text || '(empty response)'}`);
244
- }
245
- }
246
- else {
247
- const out = await runGeminiWebWithFallback({
248
- prompt,
249
- files: attachmentPaths,
250
- model,
251
- cookieMap,
252
- chatMetadata: null,
253
- signal: controller.signal,
317
+ },
318
+ };
319
+ const httpClient = {
320
+ mode: "http",
321
+ execute: async () => {
322
+ const useNoKeychainPath = Boolean(runOptions.config?.manualLogin);
323
+ const cookieResult = await loadGeminiCookies(runOptions.config, log, {
324
+ preferManualNoKeychain: useNoKeychainPath,
254
325
  });
255
- response = {
256
- text: out.text ?? null,
257
- thoughts: geminiOptions.showThoughts ? out.thoughts : null,
258
- has_images: out.images.length > 0,
259
- image_count: out.images.length,
326
+ if (!hasRequiredGeminiCookies(cookieResult.cookieMap)) {
327
+ throw new Error(formatGeminiCookieError(cookieResult.warnings));
328
+ }
329
+ const configTimeout = typeof runOptions.config?.timeoutMs === "number" &&
330
+ Number.isFinite(runOptions.config.timeoutMs)
331
+ ? Math.max(1_000, runOptions.config.timeoutMs)
332
+ : null;
333
+ const defaultTimeoutMs = geminiOptions.youtube
334
+ ? 240_000
335
+ : geminiOptions.generateImage || geminiOptions.editImage
336
+ ? 300_000
337
+ : 120_000;
338
+ const timeoutMs = Math.min(configTimeout ?? defaultTimeoutMs, 600_000);
339
+ const controller = new AbortController();
340
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
341
+ let response;
342
+ try {
343
+ if (editImagePath) {
344
+ const intro = await runGeminiWebWithFallback({
345
+ prompt: "Here is an image to edit",
346
+ files: [editImagePath],
347
+ model,
348
+ cookieMap: cookieResult.cookieMap,
349
+ chatMetadata: null,
350
+ signal: controller.signal,
351
+ });
352
+ const editPrompt = `Use image generation tool to ${prompt}`;
353
+ const out = await runGeminiWebWithFallback({
354
+ prompt: editPrompt,
355
+ files: attachmentPaths,
356
+ model,
357
+ cookieMap: cookieResult.cookieMap,
358
+ chatMetadata: intro.metadata,
359
+ signal: controller.signal,
360
+ });
361
+ response = {
362
+ text: out.text ?? null,
363
+ thoughts: geminiOptions.showThoughts ? out.thoughts : null,
364
+ has_images: false,
365
+ image_count: 0,
366
+ };
367
+ const resolvedOutputPath = outputPath ?? generateImagePath ?? "generated.png";
368
+ const imageSave = await saveFirstGeminiImageFromOutput(out, cookieResult.cookieMap, resolvedOutputPath, controller.signal);
369
+ response.has_images = imageSave.saved;
370
+ response.image_count = imageSave.imageCount;
371
+ if (!imageSave.saved) {
372
+ throw new Error(`No images generated. Response text:\n${out.text || "(empty response)"}`);
373
+ }
374
+ }
375
+ else if (generateImagePath) {
376
+ const out = await runGeminiWebWithFallback({
377
+ prompt,
378
+ files: attachmentPaths,
379
+ model,
380
+ cookieMap: cookieResult.cookieMap,
381
+ chatMetadata: null,
382
+ signal: controller.signal,
383
+ });
384
+ response = {
385
+ text: out.text ?? null,
386
+ thoughts: geminiOptions.showThoughts ? out.thoughts : null,
387
+ has_images: false,
388
+ image_count: 0,
389
+ };
390
+ const imageSave = await saveFirstGeminiImageFromOutput(out, cookieResult.cookieMap, generateImagePath, controller.signal);
391
+ response.has_images = imageSave.saved;
392
+ response.image_count = imageSave.imageCount;
393
+ if (!imageSave.saved) {
394
+ throw new Error(`No images generated. Response text:\n${out.text || "(empty response)"}`);
395
+ }
396
+ }
397
+ else {
398
+ const out = await runGeminiWebWithFallback({
399
+ prompt,
400
+ files: attachmentPaths,
401
+ model,
402
+ cookieMap: cookieResult.cookieMap,
403
+ chatMetadata: null,
404
+ signal: controller.signal,
405
+ });
406
+ response = {
407
+ text: out.text ?? null,
408
+ thoughts: geminiOptions.showThoughts ? out.thoughts : null,
409
+ has_images: out.images.length > 0,
410
+ image_count: out.images.length,
411
+ };
412
+ }
413
+ }
414
+ finally {
415
+ clearTimeout(timeout);
416
+ }
417
+ const answerText = response.text ?? "";
418
+ let answerMarkdown = answerText;
419
+ if (geminiOptions.showThoughts && response.thoughts) {
420
+ answerMarkdown = `## Thinking\n\n${response.thoughts}\n\n## Response\n\n${answerText}`;
421
+ }
422
+ if (response.has_images && response.image_count > 0) {
423
+ const imagePath = generateImagePath || outputPath || "generated.png";
424
+ answerMarkdown += `\n\n*Generated ${response.image_count} image(s). Saved to: ${imagePath}*`;
425
+ }
426
+ const tookMs = Date.now() - startTime;
427
+ log?.(`[gemini-web] Completed in ${tookMs}ms`);
428
+ return {
429
+ answerText,
430
+ answerMarkdown,
431
+ tookMs,
432
+ answerTokens: estimateTokenCount(answerText),
433
+ answerChars: answerText.length,
260
434
  };
261
- }
262
- }
263
- finally {
264
- clearTimeout(timeout);
265
- }
266
- const answerText = response.text ?? '';
267
- let answerMarkdown = answerText;
268
- if (geminiOptions.showThoughts && response.thoughts) {
269
- answerMarkdown = `## Thinking\n\n${response.thoughts}\n\n## Response\n\n${answerText}`;
270
- }
271
- if (response.has_images && response.image_count > 0) {
272
- const imagePath = generateImagePath || outputPath || 'generated.png';
273
- answerMarkdown += `\n\n*Generated ${response.image_count} image(s). Saved to: ${imagePath}*`;
274
- }
275
- const tookMs = Date.now() - startTime;
276
- log?.(`[gemini-web] Completed in ${tookMs}ms`);
277
- return {
278
- answerText,
279
- answerMarkdown,
280
- tookMs,
281
- answerTokens: estimateTokenCount(answerText),
282
- answerChars: answerText.length,
435
+ },
283
436
  };
437
+ if (model === "gemini-3-pro-deep-think" && modeSelection.mode === "http") {
438
+ log?.(`[gemini-web] Deep Think DOM path skipped (${modeSelection.reasons.join(", ")} requested); using HTTP/header fallback path.`);
439
+ }
440
+ const executionClient = modeSelection.mode === "dom" ? domClient : httpClient;
441
+ return executionClient.execute();
284
442
  };
285
443
  }
@@ -1 +1 @@
1
- export { createGeminiWebExecutor } from './executor.js';
1
+ export { createGeminiWebExecutor } from "./executor.js";
@@ -1,15 +1,16 @@
1
1
  #!/usr/bin/env node
2
- import 'dotenv/config';
3
- import process from 'node:process';
4
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
5
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
6
- import { getCliVersion } from '../version.js';
7
- import { registerConsultTool } from './tools/consult.js';
8
- import { registerSessionsTool } from './tools/sessions.js';
9
- import { registerSessionResources } from './tools/sessionResources.js';
2
+ import "dotenv/config";
3
+ import process from "node:process";
4
+ import { pathToFileURL } from "node:url";
5
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
6
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
7
+ import { getCliVersion } from "../version.js";
8
+ import { registerConsultTool } from "./tools/consult.js";
9
+ import { registerSessionsTool } from "./tools/sessions.js";
10
+ import { registerSessionResources } from "./tools/sessionResources.js";
10
11
  export async function startMcpServer() {
11
12
  const server = new McpServer({
12
- name: 'oracle-mcp',
13
+ name: "oracle-mcp",
13
14
  version: getCliVersion(),
14
15
  }, {
15
16
  capabilities: {
@@ -21,7 +22,7 @@ export async function startMcpServer() {
21
22
  registerSessionResources(server);
22
23
  const transport = new StdioServerTransport();
23
24
  transport.onerror = (error) => {
24
- console.error('MCP transport error:', error);
25
+ console.error("MCP transport error:", error);
25
26
  };
26
27
  const closed = new Promise((resolve) => {
27
28
  transport.onclose = () => {
@@ -32,9 +33,12 @@ export async function startMcpServer() {
32
33
  await server.connect(transport);
33
34
  await closed;
34
35
  }
35
- if (import.meta.url === `file://${process.argv[1]}` || process.argv[1]?.endsWith('oracle-mcp')) {
36
+ export function shouldStartMcpServerFromModule(moduleUrl = import.meta.url, argv1 = process.argv[1]) {
37
+ return argv1 ? moduleUrl === pathToFileURL(argv1).href : false;
38
+ }
39
+ if (shouldStartMcpServerFromModule()) {
36
40
  startMcpServer().catch((error) => {
37
- console.error('Failed to start oracle-mcp:', error);
41
+ console.error("Failed to start oracle-mcp:", error);
38
42
  process.exitCode = 1;
39
43
  });
40
44
  }