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