@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,19 +1,20 @@
1
- import chalk from 'chalk';
2
- import inquirer from 'inquirer';
3
- import kleur from 'kleur';
4
- import path from 'node:path';
5
- import os from 'node:os';
6
- import fs from 'node:fs/promises';
7
- import { DEFAULT_MODEL, MODEL_CONFIGS } from '../../oracle.js';
8
- import { renderMarkdownAnsi } from '../markdownRenderer.js';
9
- import { sessionStore, pruneOldSessions } from '../../sessionStore.js';
10
- import { performSessionRun } from '../sessionRunner.js';
11
- import { MAX_RENDER_BYTES, trimBeforeFirstAnswer } from '../sessionDisplay.js';
12
- import { formatSessionTableHeader, formatSessionTableRow } from '../sessionTable.js';
13
- import { buildBrowserConfig, resolveBrowserModelLabel } from '../browserConfig.js';
14
- import { resolveNotificationSettings } from '../notifier.js';
15
- import { loadUserConfig } from '../../config.js';
16
- import { formatTokenCount } from '../../oracle/runUtils.js';
1
+ import chalk from "chalk";
2
+ import inquirer from "inquirer";
3
+ import kleur from "kleur";
4
+ import path from "node:path";
5
+ import os from "node:os";
6
+ import fs from "node:fs/promises";
7
+ import { DEFAULT_MODEL, MODEL_CONFIGS, } from "../../oracle.js";
8
+ import { renderMarkdownAnsi } from "../markdownRenderer.js";
9
+ import { sessionStore, pruneOldSessions } from "../../sessionStore.js";
10
+ import { performSessionRun } from "../sessionRunner.js";
11
+ import { MAX_RENDER_BYTES, trimBeforeFirstAnswer } from "../sessionDisplay.js";
12
+ import { formatSessionTableHeader, formatSessionTableRow } from "../sessionTable.js";
13
+ import { buildBrowserConfig, resolveBrowserModelLabel } from "../browserConfig.js";
14
+ import { resolveNotificationSettings } from "../notifier.js";
15
+ import { loadUserConfig } from "../../config.js";
16
+ import { resolveConfiguredMaxFileSizeBytes } from "../fileSize.js";
17
+ import { formatTokenCount } from "../../oracle/runUtils.js";
17
18
  const isTty = () => Boolean(process.stdout.isTTY && chalk.level > 0);
18
19
  const dim = (text) => (isTty() ? kleur.dim(text) : text);
19
20
  const RECENT_WINDOW_HOURS = 24;
@@ -25,20 +26,20 @@ export async function launchTui({ version, printIntro = true }) {
25
26
  let exitMessageShown = false;
26
27
  if (printIntro) {
27
28
  if (rich) {
28
- console.log(chalk.bold('🧿 oracle'), `${version}`, dim('— Whispering your tokens to the silicon sage'));
29
+ console.log(chalk.bold("🧿 oracle"), `${version}`, dim("— Whispering your tokens to the silicon sage"));
29
30
  }
30
31
  else {
31
32
  console.log(`🧿 oracle ${version} — Whispering your tokens to the silicon sage`);
32
33
  }
33
34
  }
34
- console.log('');
35
+ console.log("");
35
36
  let showingOlder = false;
36
37
  for (;;) {
37
38
  const { recent, older, olderTotal } = await fetchSessionBuckets();
38
39
  const choices = [];
39
40
  const headerLabel = formatSessionTableHeader(isTty());
40
41
  // Start with a selectable row so focus never lands on a separator
41
- choices.push({ name: chalk.bold.green('ask oracle'), value: '__ask__' });
42
+ choices.push({ name: chalk.bold.green("ask oracle"), value: "__ask__" });
42
43
  if (!showingOlder) {
43
44
  if (recent.length > 0) {
44
45
  choices.push(new inquirer.Separator(headerLabel));
@@ -54,22 +55,22 @@ export async function launchTui({ version, printIntro = true }) {
54
55
  choices.push(new inquirer.Separator(headerLabel));
55
56
  choices.push(...older.map(toSessionChoice));
56
57
  }
57
- choices.push(new inquirer.Separator(' '));
58
- choices.push(new inquirer.Separator('Actions'));
59
- choices.push({ name: chalk.bold.green('ask oracle'), value: '__ask__' });
58
+ choices.push(new inquirer.Separator(" "));
59
+ choices.push(new inquirer.Separator("Actions"));
60
+ choices.push({ name: chalk.bold.green("ask oracle"), value: "__ask__" });
60
61
  if (!showingOlder && olderTotal > 0) {
61
- choices.push({ name: 'Older page', value: '__older__' });
62
+ choices.push({ name: "Older page", value: "__older__" });
62
63
  }
63
64
  else {
64
- choices.push({ name: 'Newer (recent)', value: '__reset__' });
65
+ choices.push({ name: "Newer (recent)", value: "__reset__" });
65
66
  }
66
- choices.push({ name: 'Exit', value: '__exit__' });
67
+ choices.push({ name: "Exit", value: "__exit__" });
67
68
  const selection = await new Promise((resolve) => {
68
69
  const prompt = inquirer.prompt([
69
70
  {
70
- name: 'selection',
71
- type: 'select',
72
- message: 'Select a session or action',
71
+ name: "selection",
72
+ type: "select",
73
+ message: "Select a session or action",
73
74
  choices,
74
75
  pageSize: 16,
75
76
  loop: false,
@@ -80,40 +81,40 @@ export async function launchTui({ version, printIntro = true }) {
80
81
  .catch((error) => {
81
82
  pagingFailures += 1;
82
83
  const message = error instanceof Error ? error.message : String(error);
83
- if (message.includes('SIGINT') || message.includes('force closed the prompt')) {
84
- console.log(chalk.green('🧿 Closing the book. See you next prompt.'));
84
+ if (message.includes("SIGINT") || message.includes("force closed the prompt")) {
85
+ console.log(chalk.green("🧿 Closing the book. See you next prompt."));
85
86
  exitMessageShown = true;
86
- resolve('__exit__');
87
+ resolve("__exit__");
87
88
  return;
88
89
  }
89
- console.error(chalk.red('Paging failed; returning to recent list.'), message);
90
- if (message.includes('setRawMode') || message.includes('EIO') || pagingFailures >= 3) {
91
- console.error(chalk.red('Terminal input unavailable; exiting TUI.'), dim('Try `stty sane` then rerun oracle, or use `oracle recent`.'));
92
- resolve('__exit__');
90
+ console.error(chalk.red("Paging failed; returning to recent list."), message);
91
+ if (message.includes("setRawMode") || message.includes("EIO") || pagingFailures >= 3) {
92
+ console.error(chalk.red("Terminal input unavailable; exiting TUI."), dim("Try `stty sane` then rerun oracle, or use `oracle recent`."));
93
+ resolve("__exit__");
93
94
  return;
94
95
  }
95
- resolve('__reset__');
96
+ resolve("__reset__");
96
97
  });
97
98
  });
98
- if (process.env.ORACLE_DEBUG_TUI === '1') {
99
+ if (process.env.ORACLE_DEBUG_TUI === "1") {
99
100
  console.error(`[tui] selection=${JSON.stringify(selection)}`);
100
101
  }
101
102
  pagingFailures = 0;
102
- if (selection === '__exit__') {
103
+ if (selection === "__exit__") {
103
104
  if (!exitMessageShown) {
104
- console.log(chalk.green('🧿 Closing the book. See you next prompt.'));
105
+ console.log(chalk.green("🧿 Closing the book. See you next prompt."));
105
106
  }
106
107
  return;
107
108
  }
108
- if (selection === '__ask__') {
109
+ if (selection === "__ask__") {
109
110
  await askOracleFlow(version, userConfig);
110
111
  continue;
111
112
  }
112
- if (selection === '__older__') {
113
+ if (selection === "__older__") {
113
114
  showingOlder = true;
114
115
  continue;
115
116
  }
116
- if (selection === '__reset__') {
117
+ if (selection === "__reset__") {
117
118
  showingOlder = false;
118
119
  continue;
119
120
  }
@@ -123,13 +124,20 @@ export async function launchTui({ version, printIntro = true }) {
123
124
  async function fetchSessionBuckets() {
124
125
  const all = await sessionStore.listSessions();
125
126
  const cutoff = Date.now() - RECENT_WINDOW_HOURS * 60 * 60 * 1000;
126
- const recent = all.filter((meta) => new Date(meta.createdAt).getTime() >= cutoff).slice(0, PAGE_SIZE);
127
+ const recent = all
128
+ .filter((meta) => new Date(meta.createdAt).getTime() >= cutoff)
129
+ .slice(0, PAGE_SIZE);
127
130
  const olderAll = all.filter((meta) => new Date(meta.createdAt).getTime() < cutoff);
128
131
  const older = olderAll.slice(0, PAGE_SIZE);
129
132
  const hasMoreOlder = olderAll.length > PAGE_SIZE;
130
133
  if (recent.length === 0 && older.length === 0 && olderAll.length > 0) {
131
134
  // No recent entries; fall back to top 10 overall.
132
- return { recent: olderAll.slice(0, PAGE_SIZE), older: [], hasMoreOlder: olderAll.length > PAGE_SIZE, olderTotal: olderAll.length };
135
+ return {
136
+ recent: olderAll.slice(0, PAGE_SIZE),
137
+ older: [],
138
+ hasMoreOlder: olderAll.length > PAGE_SIZE,
139
+ olderTotal: olderAll.length,
140
+ };
133
141
  }
134
142
  return { recent, older, hasMoreOlder, olderTotal: olderAll.length };
135
143
  }
@@ -153,78 +161,80 @@ async function showSessionDetail(sessionId) {
153
161
  }
154
162
  const prompt = await readStoredPrompt(sessionId);
155
163
  if (prompt) {
156
- console.log(chalk.bold('Prompt:'));
164
+ console.log(chalk.bold("Prompt:"));
157
165
  console.log(renderMarkdownAnsi(prompt));
158
- console.log(dim('---'));
166
+ console.log(dim("---"));
159
167
  }
160
168
  const logPath = await getSessionLogPath(sessionId);
161
169
  if (logPath) {
162
170
  console.log(dim(`Log file: ${logPath}`));
163
171
  }
164
- console.log('');
172
+ console.log("");
165
173
  await renderSessionLog(sessionId);
166
- const isRunning = meta.status === 'running';
174
+ const isRunning = meta.status === "running";
167
175
  const modelActions = meta.models?.map((run) => ({
168
176
  name: `View ${run.model} log (${run.status})`,
169
177
  value: `log:${run.model}`,
170
178
  })) ?? [];
171
179
  const actions = [
172
- { name: 'View combined log', value: 'log:__all__' },
180
+ { name: "View combined log", value: "log:__all__" },
173
181
  ...modelActions,
174
- ...(isRunning ? [{ name: 'Refresh', value: 'refresh' }] : []),
175
- { name: 'Back', value: 'back' },
182
+ ...(isRunning ? [{ name: "Refresh", value: "refresh" }] : []),
183
+ { name: "Back", value: "back" },
176
184
  ];
177
185
  let next;
178
186
  try {
179
187
  ({ next } = await inquirer.prompt([
180
188
  {
181
- name: 'next',
182
- type: 'select',
183
- message: 'Actions',
189
+ name: "next",
190
+ type: "select",
191
+ message: "Actions",
184
192
  choices: actions,
185
193
  },
186
194
  ]));
187
195
  }
188
196
  catch (error) {
189
197
  const message = error instanceof Error ? error.message : String(error);
190
- if (message.includes('SIGINT') || message.includes('force closed the prompt')) {
191
- console.log(chalk.green('🧿 Closing the book. See you next prompt.'));
198
+ if (message.includes("SIGINT") || message.includes("force closed the prompt")) {
199
+ console.log(chalk.green("🧿 Closing the book. See you next prompt."));
192
200
  return;
193
201
  }
194
- console.error(chalk.red('Paging failed; returning to session list.'), message);
202
+ console.error(chalk.red("Paging failed; returning to session list."), message);
195
203
  return;
196
204
  }
197
- if (next === 'back') {
205
+ if (next === "back") {
198
206
  return;
199
207
  }
200
- if (next === 'refresh') {
208
+ if (next === "refresh") {
201
209
  continue;
202
210
  }
203
- if (next.startsWith('log:')) {
204
- const [, target] = next.split(':');
205
- await renderSessionLog(sessionId, target === '__all__' ? undefined : target);
211
+ if (next.startsWith("log:")) {
212
+ const [, target] = next.split(":");
213
+ await renderSessionLog(sessionId, target === "__all__" ? undefined : target);
206
214
  }
207
215
  }
208
216
  }
209
217
  async function renderSessionLog(sessionId, model) {
210
- const raw = model ? await sessionStore.readModelLog(sessionId, model) : await sessionStore.readLog(sessionId);
211
- const headerLabel = model ? `Log (${model})` : 'Log';
218
+ const raw = model
219
+ ? await sessionStore.readModelLog(sessionId, model)
220
+ : await sessionStore.readLog(sessionId);
221
+ const headerLabel = model ? `Log (${model})` : "Log";
212
222
  console.log(chalk.bold(headerLabel));
213
223
  const text = trimBeforeFirstAnswer(raw);
214
- const size = Buffer.byteLength(text, 'utf8');
224
+ const size = Buffer.byteLength(text, "utf8");
215
225
  if (size > MAX_RENDER_BYTES) {
216
226
  console.log(chalk.yellow(`Log is large (${size.toLocaleString()} bytes). Rendering raw text; open the log file for full context.`));
217
227
  process.stdout.write(text);
218
- console.log('');
228
+ console.log("");
219
229
  return;
220
230
  }
221
231
  if (!text.trim()) {
222
- console.log(dim('(log is empty)'));
223
- console.log('');
232
+ console.log(dim("(log is empty)"));
233
+ console.log("");
224
234
  return;
225
235
  }
226
236
  process.stdout.write(renderMarkdownAnsi(text));
227
- console.log('');
237
+ console.log("");
228
238
  }
229
239
  async function getSessionLogPath(sessionId) {
230
240
  try {
@@ -237,14 +247,14 @@ async function getSessionLogPath(sessionId) {
237
247
  }
238
248
  function printSessionHeader(meta) {
239
249
  console.log(chalk.bold(`Session ${chalk.cyan(meta.id)}`));
240
- console.log(`${chalk.white('Status:')} ${meta.status}`);
241
- console.log(`${chalk.white('Created:')} ${meta.createdAt}`);
250
+ console.log(`${chalk.white("Status:")} ${meta.status}`);
251
+ console.log(`${chalk.white("Created:")} ${meta.createdAt}`);
242
252
  if (meta.model) {
243
- console.log(`${chalk.white('Model:')} ${meta.model}`);
253
+ console.log(`${chalk.white("Model:")} ${meta.model}`);
244
254
  }
245
255
  const mode = meta.mode ?? meta.options?.mode;
246
256
  if (mode) {
247
- console.log(`${chalk.white('Mode:')} ${mode}`);
257
+ console.log(`${chalk.white("Mode:")} ${mode}`);
248
258
  }
249
259
  if (meta.errorMessage) {
250
260
  console.log(chalk.red(`Error: ${meta.errorMessage}`));
@@ -254,66 +264,66 @@ function printModelSummaries(models) {
254
264
  if (models.length === 0) {
255
265
  return;
256
266
  }
257
- console.log(chalk.bold('Models:'));
267
+ console.log(chalk.bold("Models:"));
258
268
  for (const run of models) {
259
269
  const usage = run.usage
260
270
  ? ` tok=${formatTokenCount(run.usage.outputTokens ?? 0)}/${formatTokenCount(run.usage.totalTokens ?? 0)}`
261
- : '';
271
+ : "";
262
272
  console.log(` - ${chalk.cyan(run.model)} — ${run.status}${usage}`);
263
273
  }
264
- console.log('');
274
+ console.log("");
265
275
  }
266
276
  async function askOracleFlow(version, userConfig) {
267
277
  const modelChoices = Object.keys(MODEL_CONFIGS);
268
278
  const hasApiKey = Boolean(process.env.OPENAI_API_KEY);
269
- const initialMode = hasApiKey ? 'api' : 'browser';
279
+ const initialMode = hasApiKey ? "api" : "browser";
270
280
  const preferredMode = userConfig.engine ?? initialMode;
271
281
  const wizardQuestions = [
272
282
  {
273
- name: 'promptInput',
274
- type: 'input',
275
- message: 'Paste your prompt text or a path to a file (leave blank to cancel):',
283
+ name: "promptInput",
284
+ type: "input",
285
+ message: "Paste your prompt text or a path to a file (leave blank to cancel):",
276
286
  },
277
287
  ...(hasApiKey
278
288
  ? [
279
289
  {
280
- name: 'mode',
281
- type: 'select',
282
- message: 'Engine',
290
+ name: "mode",
291
+ type: "select",
292
+ message: "Engine",
283
293
  default: preferredMode,
284
294
  choices: [
285
- { name: 'API', value: 'api' },
286
- { name: 'Browser', value: 'browser' },
295
+ { name: "API", value: "api" },
296
+ { name: "Browser", value: "browser" },
287
297
  ],
288
298
  },
289
299
  ]
290
300
  : [
291
301
  {
292
- name: 'mode',
293
- type: 'select',
294
- message: 'Engine',
302
+ name: "mode",
303
+ type: "select",
304
+ message: "Engine",
295
305
  default: preferredMode,
296
- choices: [{ name: 'Browser', value: 'browser' }],
306
+ choices: [{ name: "Browser", value: "browser" }],
297
307
  },
298
308
  ]),
299
309
  {
300
- name: 'slug',
301
- type: 'input',
302
- message: 'Optional slug (3–5 words, leave blank for auto):',
310
+ name: "slug",
311
+ type: "input",
312
+ message: "Optional slug (3–5 words, leave blank for auto):",
303
313
  },
304
314
  {
305
- name: 'model',
306
- type: 'select',
307
- message: 'Model',
315
+ name: "model",
316
+ type: "select",
317
+ message: "Model",
308
318
  default: DEFAULT_MODEL,
309
319
  choices: modelChoices,
310
320
  },
311
321
  {
312
- name: 'models',
313
- type: 'checkbox',
314
- message: 'Additional API models to fan out to (optional)',
322
+ name: "models",
323
+ type: "checkbox",
324
+ message: "Additional API models to fan out to (optional)",
315
325
  choices: modelChoices,
316
- when: (ans) => ans.mode === 'api',
326
+ when: (ans) => ans.mode === "api",
317
327
  filter: (values) => Array.isArray(values)
318
328
  ? values
319
329
  .map((entry) => entry.trim())
@@ -321,50 +331,52 @@ async function askOracleFlow(version, userConfig) {
321
331
  : [],
322
332
  },
323
333
  {
324
- name: 'files',
325
- type: 'input',
326
- message: 'Files or globs to attach (comma-separated, optional):',
334
+ name: "files",
335
+ type: "input",
336
+ message: "Files or globs to attach (comma-separated, optional):",
327
337
  filter: (value) => value
328
- .split(',')
338
+ .split(",")
329
339
  .map((entry) => entry.trim())
330
340
  .filter(Boolean),
331
341
  },
332
342
  {
333
- name: 'chromeProfile',
334
- type: 'input',
335
- message: 'Chrome profile to reuse cookies from:',
336
- default: 'Default',
337
- when: (ans) => ans.mode === 'browser',
343
+ name: "chromeProfile",
344
+ type: "input",
345
+ message: "Chrome profile to reuse cookies from:",
346
+ default: "Default",
347
+ when: (ans) => ans.mode === "browser",
338
348
  },
339
349
  {
340
- name: 'chromeCookiePath',
341
- type: 'input',
342
- message: 'Cookie DB path (Chromium/Edge, optional):',
343
- when: (ans) => ans.mode === 'browser',
350
+ name: "chromeCookiePath",
351
+ type: "input",
352
+ message: "Cookie DB path (Chromium/Edge, optional):",
353
+ when: (ans) => ans.mode === "browser",
344
354
  },
345
355
  {
346
- name: 'hideWindow',
347
- type: 'confirm',
348
- message: 'Hide Chrome window (macOS headful only)?',
356
+ name: "hideWindow",
357
+ type: "confirm",
358
+ message: "Hide Chrome window (macOS headful only)?",
349
359
  default: false,
350
- when: (ans) => ans.mode === 'browser',
360
+ when: (ans) => ans.mode === "browser",
351
361
  },
352
362
  {
353
- name: 'keepBrowser',
354
- type: 'confirm',
355
- message: 'Keep browser open after completion?',
363
+ name: "keepBrowser",
364
+ type: "confirm",
365
+ message: "Keep browser open after completion?",
356
366
  default: false,
357
- when: (ans) => ans.mode === 'browser',
367
+ when: (ans) => ans.mode === "browser",
358
368
  },
359
369
  ];
360
370
  const answers = await inquirer.prompt(wizardQuestions);
361
371
  const mode = (answers.mode ?? initialMode);
362
372
  const prompt = await resolvePromptInput(answers.promptInput);
363
373
  if (!prompt.trim()) {
364
- console.log(chalk.yellow('Cancelled.'));
374
+ console.log(chalk.yellow("Cancelled."));
365
375
  return;
366
376
  }
367
- const promptWithSuffix = userConfig.promptSuffix ? `${prompt.trim()}\n${userConfig.promptSuffix}` : prompt;
377
+ const promptWithSuffix = userConfig.promptSuffix
378
+ ? `${prompt.trim()}\n${userConfig.promptSuffix}`
379
+ : prompt;
368
380
  await sessionStore.ensureStorage();
369
381
  await pruneOldSessions(userConfig.sessionRetentionHours, (message) => console.log(chalk.dim(message)));
370
382
  const normalizedMultiModels = Array.isArray(answers.models) && answers.models.length > 0
@@ -374,6 +386,7 @@ async function askOracleFlow(version, userConfig) {
374
386
  prompt: promptWithSuffix,
375
387
  model: answers.model,
376
388
  file: answers.files,
389
+ maxFileSizeBytes: resolveConfiguredMaxFileSizeBytes(userConfig, process.env),
377
390
  models: normalizedMultiModels.length > 1 ? normalizedMultiModels : undefined,
378
391
  slug: answers.slug,
379
392
  filesReport: false,
@@ -388,12 +401,12 @@ async function askOracleFlow(version, userConfig) {
388
401
  sessionId: undefined,
389
402
  verbose: false,
390
403
  heartbeatIntervalMs: undefined,
391
- browserAttachments: 'auto',
404
+ browserAttachments: "auto",
392
405
  browserInlineFiles: false,
393
406
  browserBundleFiles: false,
394
407
  background: undefined,
395
408
  };
396
- const browserConfig = mode === 'browser'
409
+ const browserConfig = mode === "browser"
397
410
  ? await buildBrowserConfig({
398
411
  browserChromeProfile: answers.chromeProfile,
399
412
  browserCookiePath: answers.chromeCookiePath,
@@ -428,7 +441,7 @@ async function askOracleFlow(version, userConfig) {
428
441
  return true;
429
442
  };
430
443
  console.log(chalk.bold(`Session ${sessionMeta.id} starting...`));
431
- console.log(dim(`Log path: ${path.join(os.homedir(), '.oracle', 'sessions', sessionMeta.id, 'output.log')}`));
444
+ console.log(dim(`Log path: ${path.join(os.homedir(), ".oracle", "sessions", sessionMeta.id, "output.log")}`));
432
445
  try {
433
446
  await performSessionRun({
434
447
  sessionMeta,
@@ -461,7 +474,7 @@ async function resolvePromptInput(rawInput) {
461
474
  try {
462
475
  const stats = await fs.stat(asPath);
463
476
  if (stats.isFile()) {
464
- const contents = await fs.readFile(asPath, 'utf8');
477
+ const contents = await fs.readFile(asPath, "utf8");
465
478
  return contents;
466
479
  }
467
480
  }
@@ -483,4 +496,4 @@ async function readStoredPrompt(sessionId) {
483
496
  }
484
497
  // Exported for testing
485
498
  export { askOracleFlow, showSessionDetail };
486
- export { resolveSessionCost as resolveCost } from '../sessionTable.js';
499
+ export { resolveSessionCost as resolveCost } from "../sessionTable.js";
@@ -1,12 +1,12 @@
1
- import os from 'node:os';
2
- import path from 'node:path';
3
- import { sessionStore } from '../sessionStore.js';
1
+ import os from "node:os";
2
+ import path from "node:path";
3
+ import { sessionStore } from "../sessionStore.js";
4
4
  export function resolveOutputPath(input, cwd) {
5
5
  if (!input || input.trim().length === 0) {
6
6
  return undefined;
7
7
  }
8
- const expanded = input.startsWith('~/') ? path.join(os.homedir(), input.slice(2)) : input;
9
- if (expanded === '-' || expanded === '/dev/stdout') {
8
+ const expanded = input.startsWith("~/") ? path.join(os.homedir(), input.slice(2)) : input;
9
+ if (expanded === "-" || expanded === "/dev/stdout") {
10
10
  return expanded;
11
11
  }
12
12
  const absolute = path.isAbsolute(expanded) ? expanded : path.resolve(cwd, expanded);
@@ -1,20 +1,20 @@
1
- import fs from 'node:fs/promises';
2
- import path from 'node:path';
3
- import JSON5 from 'json5';
4
- import { getOracleHomeDir } from './oracleHome.js';
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import JSON5 from "json5";
4
+ import { getOracleHomeDir } from "./oracleHome.js";
5
5
  function resolveConfigPath() {
6
- return path.join(getOracleHomeDir(), 'config.json');
6
+ return path.join(getOracleHomeDir(), "config.json");
7
7
  }
8
8
  export async function loadUserConfig() {
9
9
  const CONFIG_PATH = resolveConfigPath();
10
10
  try {
11
- const raw = await fs.readFile(CONFIG_PATH, 'utf8');
11
+ const raw = await fs.readFile(CONFIG_PATH, "utf8");
12
12
  const parsed = JSON5.parse(raw);
13
13
  return { config: parsed ?? {}, path: CONFIG_PATH, loaded: true };
14
14
  }
15
15
  catch (error) {
16
16
  const code = error.code;
17
- if (code === 'ENOENT') {
17
+ if (code === "ENOENT") {
18
18
  return { config: {}, path: CONFIG_PATH, loaded: false };
19
19
  }
20
20
  console.warn(`Failed to read ${CONFIG_PATH}: ${error instanceof Error ? error.message : String(error)}`);
@@ -0,0 +1,80 @@
1
+ import path from "node:path";
2
+ import os from "node:os";
3
+ import { mkdir } from "node:fs/promises";
4
+ import { launchChrome, connectWithNewTab, closeTab } from "../browser/chromeLifecycle.js";
5
+ import { resolveBrowserConfig } from "../browser/config.js";
6
+ import { readDevToolsPort, writeDevToolsActivePort, writeChromePid, cleanupStaleProfileState, verifyDevToolsReachable, } from "../browser/profileState.js";
7
+ export async function openGeminiBrowserSession(input) {
8
+ const { browserConfig, keepBrowserDefault, purpose, log } = input;
9
+ const resolvedConfig = resolveBrowserConfig({
10
+ ...browserConfig,
11
+ manualLogin: true,
12
+ keepBrowser: browserConfig?.keepBrowser ?? keepBrowserDefault,
13
+ });
14
+ const profileDir = resolvedConfig.manualLoginProfileDir ?? path.join(os.homedir(), ".oracle", "browser-profile");
15
+ await mkdir(profileDir, { recursive: true });
16
+ const keepBrowser = Boolean(resolvedConfig.keepBrowser);
17
+ let port = await readDevToolsPort(profileDir);
18
+ let launchedChrome = null;
19
+ let chromeWasLaunched = false;
20
+ if (port) {
21
+ const probe = await verifyDevToolsReachable({ port });
22
+ if (!probe.ok) {
23
+ log?.(`[gemini-web] Stale DevTools port ${port}; launching fresh Chrome for ${purpose}.`);
24
+ await cleanupStaleProfileState(profileDir, log, { lockRemovalMode: "if_oracle_pid_dead" });
25
+ port = null;
26
+ }
27
+ }
28
+ if (!port) {
29
+ log?.(`[gemini-web] Launching Chrome for ${purpose}.`);
30
+ launchedChrome = await launchChrome(resolvedConfig, profileDir, log ?? (() => { }));
31
+ port = launchedChrome.port;
32
+ chromeWasLaunched = true;
33
+ await writeDevToolsActivePort(profileDir, port);
34
+ if (launchedChrome.pid) {
35
+ await writeChromePid(profileDir, launchedChrome.pid);
36
+ }
37
+ }
38
+ else {
39
+ log?.(`[gemini-web] Reusing Chrome on port ${port} for ${purpose}.`);
40
+ }
41
+ const connection = await connectWithNewTab(port, log ?? (() => { }), undefined);
42
+ const client = connection.client;
43
+ const targetId = connection.targetId;
44
+ const close = async () => {
45
+ if (keepBrowser) {
46
+ try {
47
+ await client.close();
48
+ }
49
+ catch {
50
+ /* ignore */
51
+ }
52
+ return;
53
+ }
54
+ if (targetId && port) {
55
+ await closeTab(port, targetId, log ?? (() => { })).catch(() => undefined);
56
+ }
57
+ try {
58
+ await client.close();
59
+ }
60
+ catch {
61
+ /* ignore */
62
+ }
63
+ if (chromeWasLaunched && launchedChrome) {
64
+ try {
65
+ launchedChrome.kill();
66
+ }
67
+ catch {
68
+ /* ignore */
69
+ }
70
+ await cleanupStaleProfileState(profileDir, log, { lockRemovalMode: "never" }).catch(() => undefined);
71
+ }
72
+ };
73
+ return {
74
+ profileDir,
75
+ port,
76
+ client,
77
+ targetId: targetId ?? undefined,
78
+ close,
79
+ };
80
+ }