@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,28 +1,28 @@
1
- import kleur from 'kleur';
2
- import fs from 'node:fs/promises';
3
- import path from 'node:path';
4
- import { runOracle, OracleResponseError, OracleTransportError, extractResponseMetadata, asOracleUserError, extractTextOutput, } from '../oracle.js';
5
- import { runBrowserSessionExecution } from '../browser/sessionRunner.js';
6
- import { renderMarkdownAnsi } from './markdownRenderer.js';
7
- import { formatResponseMetadata, formatTransportMetadata } from './sessionDisplay.js';
8
- import { markErrorLogged } from './errorUtils.js';
9
- import { sendSessionNotification, deriveNotificationSettingsFromMetadata, } from './notifier.js';
10
- import { sessionStore } from '../sessionStore.js';
11
- import { wait } from '../sessionManager.js';
12
- import { runMultiModelApiSession } from '../oracle/multiModelRunner.js';
13
- import { MODEL_CONFIGS, DEFAULT_SYSTEM_PROMPT } from '../oracle/config.js';
14
- import { isKnownModel } from '../oracle/modelResolver.js';
15
- import { resolveModelConfig } from '../oracle/modelResolver.js';
16
- import { buildPrompt, buildRequestBody } from '../oracle/request.js';
17
- import { estimateRequestTokens } from '../oracle/tokenEstimate.js';
18
- import { formatTokenEstimate, formatTokenValue } from '../oracle/runUtils.js';
19
- import { formatFinishLine } from '../oracle/finishLine.js';
20
- import { sanitizeOscProgress } from './oscUtils.js';
21
- import { readFiles } from '../oracle/files.js';
22
- import { cwd as getCwd } from 'node:process';
23
- import { resumeBrowserSession } from '../browser/reattach.js';
24
- import { estimateTokenCount } from '../browser/utils.js';
25
- import { formatElapsed } from '../oracle/format.js';
1
+ import kleur from "kleur";
2
+ import fs from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { runOracle, OracleResponseError, OracleTransportError, extractResponseMetadata, asOracleUserError, extractTextOutput, } from "../oracle.js";
5
+ import { runBrowserSessionExecution, } from "../browser/sessionRunner.js";
6
+ import { renderMarkdownAnsi } from "./markdownRenderer.js";
7
+ import { formatResponseMetadata, formatTransportMetadata } from "./sessionDisplay.js";
8
+ import { markErrorLogged } from "./errorUtils.js";
9
+ import { sendSessionNotification, deriveNotificationSettingsFromMetadata, } from "./notifier.js";
10
+ import { sessionStore } from "../sessionStore.js";
11
+ import { wait } from "../sessionManager.js";
12
+ import { runMultiModelApiSession } from "../oracle/multiModelRunner.js";
13
+ import { MODEL_CONFIGS, DEFAULT_SYSTEM_PROMPT } from "../oracle/config.js";
14
+ import { isKnownModel } from "../oracle/modelResolver.js";
15
+ import { resolveModelConfig } from "../oracle/modelResolver.js";
16
+ import { buildPrompt, buildRequestBody } from "../oracle/request.js";
17
+ import { estimateRequestTokens } from "../oracle/tokenEstimate.js";
18
+ import { formatTokenEstimate, formatTokenValue } from "../oracle/runUtils.js";
19
+ import { formatFinishLine } from "../oracle/finishLine.js";
20
+ import { sanitizeOscProgress } from "./oscUtils.js";
21
+ import { readFiles } from "../oracle/files.js";
22
+ import { cwd as getCwd } from "node:process";
23
+ import { resumeBrowserSession } from "../browser/reattach.js";
24
+ import { estimateTokenCount } from "../browser/utils.js";
25
+ import { formatElapsed } from "../oracle/format.js";
26
26
  const isTty = process.stdout.isTTY;
27
27
  const dim = (text) => (isTty ? kleur.dim(text) : text);
28
28
  export async function performSessionRun({ sessionMeta, runOptions, mode, browserConfig, cwd, log, write, version, notifications, browserDeps, muteStdout = false, }) {
@@ -32,7 +32,7 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
32
32
  return muteStdout ? true : process.stdout.write(chunk);
33
33
  };
34
34
  await sessionStore.updateSession(sessionMeta.id, {
35
- status: 'running',
35
+ status: "running",
36
36
  startedAt: new Date().toISOString(),
37
37
  mode,
38
38
  ...(browserConfig ? { browser: { config: browserConfig } } : {}),
@@ -40,13 +40,13 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
40
40
  const notificationSettings = notifications ?? deriveNotificationSettingsFromMetadata(sessionMeta, process.env);
41
41
  const modelForStatus = runOptions.model ?? sessionMeta.model;
42
42
  try {
43
- if (mode === 'browser') {
43
+ if (mode === "browser") {
44
44
  if (!browserConfig) {
45
- throw new Error('Missing browser configuration for session.');
45
+ throw new Error("Missing browser configuration for session.");
46
46
  }
47
47
  if (modelForStatus) {
48
48
  await sessionStore.updateModelRun(sessionMeta.id, modelForStatus, {
49
- status: 'running',
49
+ status: "running",
50
50
  startedAt: new Date().toISOString(),
51
51
  });
52
52
  }
@@ -54,7 +54,7 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
54
54
  ...browserDeps,
55
55
  persistRuntimeHint: async (runtime) => {
56
56
  await sessionStore.updateSession(sessionMeta.id, {
57
- status: 'running',
57
+ status: "running",
58
58
  browser: { config: browserConfig, runtime },
59
59
  });
60
60
  },
@@ -62,13 +62,13 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
62
62
  const result = await runBrowserSessionExecution({ runOptions, browserConfig, cwd, log }, runnerDeps);
63
63
  if (modelForStatus) {
64
64
  await sessionStore.updateModelRun(sessionMeta.id, modelForStatus, {
65
- status: 'completed',
65
+ status: "completed",
66
66
  completedAt: new Date().toISOString(),
67
67
  usage: result.usage,
68
68
  });
69
69
  }
70
70
  await sessionStore.updateSession(sessionMeta.id, {
71
- status: 'completed',
71
+ status: "completed",
72
72
  completedAt: new Date().toISOString(),
73
73
  usage: result.usage,
74
74
  elapsedMs: result.elapsedMs,
@@ -80,7 +80,7 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
80
80
  transport: undefined,
81
81
  error: undefined,
82
82
  });
83
- await writeAssistantOutput(runOptions.writeOutputPath, result.answerText ?? '', log);
83
+ await writeAssistantOutput(runOptions.writeOutputPath, result.answerText ?? "", log);
84
84
  await sendSessionNotification({
85
85
  sessionId: sessionMeta.id,
86
86
  sessionName: sessionMeta.options?.slug ?? sessionMeta.id,
@@ -95,13 +95,16 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
95
95
  if (multiModels.length > 1) {
96
96
  const [primaryModel] = multiModels;
97
97
  if (!primaryModel) {
98
- throw new Error('Missing model name for multi-model run.');
98
+ throw new Error("Missing model name for multi-model run.");
99
99
  }
100
100
  const modelConfig = await resolveModelConfig(primaryModel, {
101
101
  baseUrl: runOptions.baseUrl,
102
102
  openRouterApiKey: process.env.OPENROUTER_API_KEY,
103
103
  });
104
- const files = await readFiles(runOptions.file ?? [], { cwd });
104
+ const files = await readFiles(runOptions.file ?? [], {
105
+ cwd,
106
+ maxFileSizeBytes: runOptions.maxFileSizeBytes,
107
+ });
105
108
  const promptWithFiles = buildPrompt(runOptions.prompt, files, cwd);
106
109
  const requestBody = buildRequestBody({
107
110
  modelConfig,
@@ -113,30 +116,30 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
113
116
  storeResponse: runOptions.background,
114
117
  });
115
118
  const estimatedTokens = estimateRequestTokens(requestBody, modelConfig);
116
- const tokenLabel = formatTokenEstimate(estimatedTokens, (text) => (isTty ? kleur.green(text) : text));
117
- const filesPhrase = files.length === 0 ? 'no files' : `${files.length} files`;
118
- const modelsLabel = multiModels.join(', ');
119
+ const tokenLabel = formatTokenEstimate(estimatedTokens, (text) => isTty ? kleur.green(text) : text);
120
+ const filesPhrase = files.length === 0 ? "no files" : `${files.length} files`;
121
+ const modelsLabel = multiModels.join(", ");
119
122
  log(`Calling ${isTty ? kleur.cyan(modelsLabel) : modelsLabel} — ${tokenLabel} tokens, ${filesPhrase}.`);
120
123
  const multiRunTips = [];
121
124
  if (files.length === 0) {
122
- multiRunTips.push('Tip: no files attached — Oracle works best with project context. Add files via --file path/to/code or docs.');
125
+ multiRunTips.push("Tip: no files attached — Oracle works best with project context. Add files via --file path/to/code or docs.");
123
126
  }
124
127
  const shortPrompt = (runOptions.prompt?.trim().length ?? 0) < 80;
125
128
  if (shortPrompt) {
126
- multiRunTips.push('Tip: brief prompts often yield generic answers — aim for 6–30 sentences and attach key files.');
129
+ multiRunTips.push("Tip: brief prompts often yield generic answers — aim for 6–30 sentences and attach key files.");
127
130
  }
128
131
  for (const tip of multiRunTips) {
129
132
  log(dim(tip));
130
133
  }
131
134
  // Surface long-running model expectations up front so users know why a response might lag.
132
- const longRunningModels = multiModels.filter((model) => isKnownModel(model) && MODEL_CONFIGS[model]?.reasoning?.effort === 'high');
135
+ const longRunningModels = multiModels.filter((model) => isKnownModel(model) && MODEL_CONFIGS[model]?.reasoning?.effort === "high");
133
136
  if (longRunningModels.length > 0) {
134
137
  for (const model of longRunningModels) {
135
- log('');
138
+ log("");
136
139
  const headingLabel = `[${model}]`;
137
140
  log(isTty ? kleur.bold(headingLabel) : headingLabel);
138
- log(dim('This model can take up to 60 minutes (usually replies much faster).'));
139
- log(dim('Press Ctrl+C to cancel.'));
141
+ log(dim("This model can take up to 60 minutes (usually replies much faster)."));
142
+ log(dim("Press Ctrl+C to cancel."));
140
143
  }
141
144
  }
142
145
  const shouldStreamInline = !muteStdout && process.stdout.isTTY;
@@ -149,7 +152,7 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
149
152
  return;
150
153
  printedModels.add(model);
151
154
  const body = stripOscProgress(await sessionStore.readModelLog(sessionMeta.id, model));
152
- log('');
155
+ log("");
153
156
  const fallback = answerFallbacks.get(model);
154
157
  const hasBody = body.length > 0;
155
158
  if (!hasBody && !fallback) {
@@ -159,11 +162,11 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
159
162
  const headingLabel = `[${model}]`;
160
163
  const heading = shouldStreamInline ? kleur.bold(headingLabel) : headingLabel;
161
164
  log(heading);
162
- const content = hasBody ? body : fallback ?? '';
165
+ const content = hasBody ? body : (fallback ?? "");
163
166
  const printable = shouldRenderMarkdown ? renderMarkdownAnsi(content) : content;
164
167
  writeInline(printable);
165
- if (!printable.endsWith('\n')) {
166
- log('');
168
+ if (!printable.endsWith("\n")) {
169
+ log("");
167
170
  }
168
171
  };
169
172
  const summary = await runMultiModelApiSession({
@@ -189,7 +192,7 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
189
192
  // If we couldn't stream inline (e.g., non-TTY), print all logs after completion.
190
193
  for (const [index, result] of summary.fulfilled.entries()) {
191
194
  if (index > 0) {
192
- log('');
195
+ log("");
193
196
  }
194
197
  await printModelLog(result.model);
195
198
  }
@@ -213,14 +216,18 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
213
216
  reasoning_tokens: aggregateUsage.reasoningTokens,
214
217
  total_tokens: aggregateUsage.totalTokens,
215
218
  }, idx))
216
- .join('/');
219
+ .join("/");
217
220
  const tokensPart = (() => {
218
- const parts = tokensDisplay.split('/');
221
+ const parts = tokensDisplay.split("/");
219
222
  if (parts.length !== 4)
220
223
  return tokensDisplay;
221
224
  return `↑${parts[0]} ↓${parts[1]} ↻${parts[2]} Δ${parts[3]}`;
222
225
  })();
223
- const statusColor = summary.rejected.length === 0 ? kleur.green : summary.fulfilled.length > 0 ? kleur.yellow : kleur.red;
226
+ const statusColor = summary.rejected.length === 0
227
+ ? kleur.green
228
+ : summary.fulfilled.length > 0
229
+ ? kleur.yellow
230
+ : kleur.red;
224
231
  const overallText = `${summary.fulfilled.length}/${multiModels.length} models`;
225
232
  const { line1 } = formatFinishLine({
226
233
  elapsedMs: summary.elapsedMs,
@@ -231,7 +238,7 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
231
238
  log(statusColor(line1));
232
239
  const hasFailure = summary.rejected.length > 0;
233
240
  await sessionStore.updateSession(sessionMeta.id, {
234
- status: hasFailure ? 'error' : 'completed',
241
+ status: hasFailure ? "error" : "completed",
235
242
  completedAt: new Date().toISOString(),
236
243
  usage: aggregateUsage,
237
244
  elapsedMs: summary.elapsedMs,
@@ -258,7 +265,7 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
258
265
  }
259
266
  }
260
267
  if (savedOutputs.length > 0) {
261
- log(dim('Saved outputs:'));
268
+ log(dim("Saved outputs:"));
262
269
  for (const item of savedOutputs) {
263
270
  log(dim(`- ${item.model} -> ${item.path}`));
264
271
  }
@@ -275,7 +282,7 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
275
282
  : runOptions;
276
283
  if (modelForStatus && singleModelOverride == null) {
277
284
  await sessionStore.updateModelRun(sessionMeta.id, modelForStatus, {
278
- status: 'running',
285
+ status: "running",
279
286
  startedAt: new Date().toISOString(),
280
287
  });
281
288
  }
@@ -285,11 +292,11 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
285
292
  write,
286
293
  allowStdout: !muteStdout,
287
294
  });
288
- if (result.mode !== 'live') {
289
- throw new Error('Unexpected preview result while running a session.');
295
+ if (result.mode !== "live") {
296
+ throw new Error("Unexpected preview result while running a session.");
290
297
  }
291
298
  await sessionStore.updateSession(sessionMeta.id, {
292
- status: 'completed',
299
+ status: "completed",
293
300
  completedAt: new Date().toISOString(),
294
301
  usage: result.usage,
295
302
  elapsedMs: result.elapsedMs,
@@ -299,7 +306,7 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
299
306
  });
300
307
  if (modelForStatus && singleModelOverride == null) {
301
308
  await sessionStore.updateModelRun(sessionMeta.id, modelForStatus, {
302
- status: 'completed',
309
+ status: "completed",
303
310
  completedAt: new Date().toISOString(),
304
311
  usage: result.usage,
305
312
  });
@@ -320,47 +327,53 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
320
327
  log(`ERROR: ${message}`);
321
328
  markErrorLogged(error);
322
329
  const userError = asOracleUserError(error);
323
- const connectionLost = userError?.category === 'browser-automation' && userError.details?.stage === 'connection-lost';
324
- const assistantTimeout = userError?.category === 'browser-automation' && userError.details?.stage === 'assistant-timeout';
325
- if (connectionLost && mode === 'browser') {
326
- const runtime = userError.details?.runtime;
327
- log(dim('Chrome disconnected before completion; keeping session running for reattach.'));
330
+ const connectionLost = userError?.category === "browser-automation" &&
331
+ userError.details?.stage === "connection-lost";
332
+ const assistantTimeout = userError?.category === "browser-automation" &&
333
+ userError.details?.stage === "assistant-timeout";
334
+ const cloudflareChallenge = userError?.category === "browser-automation" &&
335
+ userError.details?.stage === "cloudflare-challenge";
336
+ if (connectionLost && mode === "browser") {
337
+ const runtime = userError.details
338
+ ?.runtime;
339
+ log(dim("Chrome disconnected before completion; keeping session running for reattach."));
328
340
  if (modelForStatus) {
329
341
  await sessionStore.updateModelRun(sessionMeta.id, modelForStatus, {
330
- status: 'running',
342
+ status: "running",
331
343
  completedAt: undefined,
332
344
  });
333
345
  }
334
346
  await sessionStore.updateSession(sessionMeta.id, {
335
- status: 'running',
347
+ status: "running",
336
348
  errorMessage: message,
337
349
  mode,
338
350
  browser: {
339
351
  config: browserConfig,
340
352
  runtime: runtime ?? sessionMeta.browser?.runtime,
341
353
  },
342
- response: { status: 'running', incompleteReason: 'chrome-disconnected' },
354
+ response: { status: "running", incompleteReason: "chrome-disconnected" },
343
355
  });
344
356
  return;
345
357
  }
346
- if (assistantTimeout && mode === 'browser') {
347
- const runtime = userError.details?.runtime;
348
- log(dim('Assistant response timed out; keeping session running for reattach.'));
358
+ if (assistantTimeout && mode === "browser") {
359
+ const runtime = userError.details
360
+ ?.runtime;
361
+ log(dim("Assistant response timed out; keeping session running for reattach."));
349
362
  if (modelForStatus) {
350
363
  await sessionStore.updateModelRun(sessionMeta.id, modelForStatus, {
351
- status: 'running',
364
+ status: "running",
352
365
  completedAt: undefined,
353
366
  });
354
367
  }
355
368
  await sessionStore.updateSession(sessionMeta.id, {
356
- status: 'running',
369
+ status: "running",
357
370
  errorMessage: message,
358
371
  mode,
359
372
  browser: {
360
373
  config: browserConfig,
361
374
  runtime: runtime ?? sessionMeta.browser?.runtime,
362
375
  },
363
- response: { status: 'running', incompleteReason: 'assistant-timeout' },
376
+ response: { status: "running", incompleteReason: "assistant-timeout" },
364
377
  });
365
378
  const autoReattachIntervalMs = browserConfig?.autoReattachIntervalMs ?? 0;
366
379
  if (autoReattachIntervalMs > 0) {
@@ -381,6 +394,13 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
381
394
  log(dim(`Reattach later with: oracle session ${sessionMeta.id}`));
382
395
  return;
383
396
  }
397
+ if (cloudflareChallenge && mode === "browser") {
398
+ const details = userError.details;
399
+ log(dim("Cloudflare challenge detected; browser left running so you can complete the check."));
400
+ if (details?.reuseProfileHint) {
401
+ log(dim(`Reuse this browser profile with: ${details.reuseProfileHint}`));
402
+ }
403
+ }
384
404
  if (userError) {
385
405
  log(dim(`User error (${userError.category}): ${userError.message}`));
386
406
  }
@@ -394,12 +414,20 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
394
414
  if (transportLine) {
395
415
  log(dim(`Transport: ${transportLine}`));
396
416
  }
417
+ const browserRuntime = mode === "browser"
418
+ ? userError?.details?.runtime
419
+ : undefined;
397
420
  await sessionStore.updateSession(sessionMeta.id, {
398
- status: 'error',
421
+ status: "error",
399
422
  completedAt: new Date().toISOString(),
400
423
  errorMessage: message,
401
424
  mode,
402
- browser: browserConfig ? { config: browserConfig } : undefined,
425
+ browser: browserConfig
426
+ ? {
427
+ config: browserConfig,
428
+ runtime: browserRuntime ?? undefined,
429
+ }
430
+ : undefined,
403
431
  response: responseMetadata,
404
432
  transport: transportMetadata,
405
433
  error: userError
@@ -412,7 +440,7 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
412
440
  });
413
441
  if (modelForStatus) {
414
442
  await sessionStore.updateModelRun(sessionMeta.id, modelForStatus, {
415
- status: 'error',
443
+ status: "error",
416
444
  completedAt: new Date().toISOString(),
417
445
  });
418
446
  }
@@ -426,7 +454,7 @@ async function writeAssistantOutput(targetPath, content, log) {
426
454
  if (!targetPath)
427
455
  return;
428
456
  if (!content || content.trim().length === 0) {
429
- log(dim('write-output skipped: no assistant content to save.'));
457
+ log(dim("write-output skipped: no assistant content to save."));
430
458
  return;
431
459
  }
432
460
  const normalizedTarget = path.resolve(targetPath);
@@ -438,8 +466,8 @@ async function writeAssistantOutput(targetPath, content, log) {
438
466
  }
439
467
  try {
440
468
  await fs.mkdir(path.dirname(normalizedTarget), { recursive: true });
441
- const payload = content.endsWith('\n') ? content : `${content}\n`;
442
- await fs.writeFile(normalizedTarget, payload, 'utf8');
469
+ const payload = content.endsWith("\n") ? content : `${content}\n`;
470
+ await fs.writeFile(normalizedTarget, payload, "utf8");
443
471
  log(dim(`Saved assistant output to ${normalizedTarget}`));
444
472
  return normalizedTarget;
445
473
  }
@@ -450,8 +478,8 @@ async function writeAssistantOutput(targetPath, content, log) {
450
478
  if (fallbackPath) {
451
479
  try {
452
480
  await fs.mkdir(path.dirname(fallbackPath), { recursive: true });
453
- const payload = content.endsWith('\n') ? content : `${content}\n`;
454
- await fs.writeFile(fallbackPath, payload, 'utf8');
481
+ const payload = content.endsWith("\n") ? content : `${content}\n`;
482
+ await fs.writeFile(fallbackPath, payload, "utf8");
455
483
  log(dim(`write-output fallback to ${fallbackPath} (original failed: ${reason})`));
456
484
  return fallbackPath;
457
485
  }
@@ -467,7 +495,7 @@ async function writeAssistantOutput(targetPath, content, log) {
467
495
  }
468
496
  async function autoReattachUntilComplete({ sessionMeta, runtime, browserConfig, runOptions, modelForStatus, notificationSettings, log, }) {
469
497
  if (!runtime || !browserConfig) {
470
- log(dim('Auto-reattach disabled: missing runtime or browser config.'));
498
+ log(dim("Auto-reattach disabled: missing runtime or browser config."));
471
499
  return false;
472
500
  }
473
501
  const delayMs = Math.max(0, browserConfig.autoReattachDelayMs ?? 0);
@@ -508,16 +536,16 @@ async function autoReattachUntilComplete({ sessionMeta, runtime, browserConfig,
508
536
  const result = await resumeBrowserSession(runtime, reattachConfig, logger, {
509
537
  promptPreview: sessionMeta.promptPreview,
510
538
  });
511
- const answerText = result.answerMarkdown || result.answerText || '';
539
+ const answerText = result.answerMarkdown || result.answerText || "";
512
540
  const outputTokens = estimateTokenCount(answerText);
513
541
  const logWriter = sessionStore.createLogWriter(sessionMeta.id);
514
542
  logWriter.logLine(`[auto-reattach] captured assistant response on attempt ${attempt}`);
515
- logWriter.logLine('Answer:');
543
+ logWriter.logLine("Answer:");
516
544
  logWriter.logLine(answerText);
517
545
  logWriter.stream.end();
518
546
  if (modelForStatus) {
519
547
  await sessionStore.updateModelRun(sessionMeta.id, modelForStatus, {
520
- status: 'completed',
548
+ status: "completed",
521
549
  completedAt: new Date().toISOString(),
522
550
  usage: {
523
551
  inputTokens: 0,
@@ -528,7 +556,7 @@ async function autoReattachUntilComplete({ sessionMeta, runtime, browserConfig,
528
556
  });
529
557
  }
530
558
  await sessionStore.updateSession(sessionMeta.id, {
531
- status: 'completed',
559
+ status: "completed",
532
560
  completedAt: new Date().toISOString(),
533
561
  usage: {
534
562
  inputTokens: 0,
@@ -540,7 +568,7 @@ async function autoReattachUntilComplete({ sessionMeta, runtime, browserConfig,
540
568
  config: browserConfig,
541
569
  runtime,
542
570
  },
543
- response: { status: 'completed' },
571
+ response: { status: "completed" },
544
572
  error: undefined,
545
573
  transport: undefined,
546
574
  });
@@ -548,7 +576,7 @@ async function autoReattachUntilComplete({ sessionMeta, runtime, browserConfig,
548
576
  await sendSessionNotification({
549
577
  sessionId: sessionMeta.id,
550
578
  sessionName: sessionMeta.options?.slug ?? sessionMeta.id,
551
- mode: sessionMeta.mode ?? 'browser',
579
+ mode: sessionMeta.mode ?? "browser",
552
580
  model: sessionMeta.model ?? runOptions.model,
553
581
  usage: {
554
582
  inputTokens: 0,
@@ -556,7 +584,7 @@ async function autoReattachUntilComplete({ sessionMeta, runtime, browserConfig,
556
584
  },
557
585
  characters: answerText.length,
558
586
  }, notificationSettings, log, answerText.slice(0, 140));
559
- log(kleur.green('Auto-reattach succeeded; session marked completed.'));
587
+ log(kleur.green("Auto-reattach succeeded; session marked completed."));
560
588
  return true;
561
589
  }
562
590
  catch (error) {
@@ -584,7 +612,7 @@ function isPermissionError(error) {
584
612
  if (!(error instanceof Error))
585
613
  return false;
586
614
  const code = error.code;
587
- return code === 'EACCES' || code === 'EPERM';
615
+ return code === "EACCES" || code === "EPERM";
588
616
  }
589
617
  function buildFallbackPath(original) {
590
618
  const ext = path.extname(original);
@@ -1,7 +1,7 @@
1
- import chalk from 'chalk';
2
- import kleur from 'kleur';
3
- import { MODEL_CONFIGS } from '../oracle.js';
4
- import { estimateUsdCost } from 'tokentally';
1
+ import chalk from "chalk";
2
+ import kleur from "kleur";
3
+ import { MODEL_CONFIGS } from "../oracle.js";
4
+ import { estimateUsdCost } from "tokentally";
5
5
  const isRich = (rich) => rich ?? Boolean(process.stdout.isTTY && chalk.level > 0);
6
6
  const dim = (text, rich) => (rich ? kleur.dim(text) : text);
7
7
  export const STATUS_PAD = 9;
@@ -11,30 +11,31 @@ export const TIMESTAMP_PAD = 19;
11
11
  export const CHARS_PAD = 5;
12
12
  export const COST_PAD = 7;
13
13
  export function formatSessionTableHeader(rich) {
14
- const header = `${'Status'.padEnd(STATUS_PAD)} ${'Model'.padEnd(MODEL_PAD)} ${'Mode'.padEnd(MODE_PAD)} ${'Timestamp'.padEnd(TIMESTAMP_PAD)} ${'Chars'.padStart(CHARS_PAD)} ${'Cost'.padStart(COST_PAD)} Slug`;
14
+ const header = `${"Status".padEnd(STATUS_PAD)} ${"Model".padEnd(MODEL_PAD)} ${"Mode".padEnd(MODE_PAD)} ${"Timestamp".padEnd(TIMESTAMP_PAD)} ${"Chars".padStart(CHARS_PAD)} ${"Cost".padStart(COST_PAD)} Slug`;
15
15
  return dim(header, isRich(rich));
16
16
  }
17
17
  export function formatSessionTableRow(meta, options) {
18
18
  const rich = isRich(options?.rich);
19
- const status = colorStatus(meta.status ?? 'unknown', rich);
20
- const modelLabel = (meta.model ?? 'n/a').padEnd(MODEL_PAD);
19
+ const status = colorStatus(meta.status ?? "unknown", rich);
20
+ const modelLabel = (meta.model ?? "n/a").padEnd(MODEL_PAD);
21
21
  const model = rich ? chalk.white(modelLabel) : modelLabel;
22
- const modeLabel = (meta.mode ?? meta.options?.mode ?? 'api').padEnd(MODE_PAD);
22
+ const modeLabel = (meta.mode ?? meta.options?.mode ?? "api").padEnd(MODE_PAD);
23
23
  const mode = rich ? chalk.gray(modeLabel) : modeLabel;
24
24
  const timestampLabel = formatTimestampAligned(meta.createdAt).padEnd(TIMESTAMP_PAD);
25
25
  const timestamp = rich ? chalk.gray(timestampLabel) : timestampLabel;
26
26
  const charsValue = meta.options?.prompt?.length ?? meta.promptPreview?.length ?? 0;
27
- const charsRaw = charsValue > 0 ? String(charsValue).padStart(CHARS_PAD) : `${''.padStart(CHARS_PAD - 1)}-`;
27
+ const charsRaw = charsValue > 0 ? String(charsValue).padStart(CHARS_PAD) : `${"".padStart(CHARS_PAD - 1)}-`;
28
28
  const chars = rich ? chalk.gray(charsRaw) : charsRaw;
29
29
  const costValue = resolveSessionCost(meta);
30
- const costRaw = costValue != null ? formatCostTable(costValue) : `${''.padStart(COST_PAD - 1)}-`;
30
+ const costRaw = costValue != null ? formatCostTable(costValue) : `${"".padStart(COST_PAD - 1)}-`;
31
31
  const cost = rich ? chalk.gray(costRaw) : costRaw;
32
- const slug = rich ? chalk.cyan(meta.id) : meta.id;
32
+ const slugValue = options?.displaySlug ?? meta.id;
33
+ const slug = rich ? chalk.cyan(slugValue) : slugValue;
33
34
  return `${status} ${model} ${mode} ${timestamp} ${chars} ${cost} ${slug}`;
34
35
  }
35
36
  export function resolveSessionCost(meta) {
36
37
  const mode = meta.mode ?? meta.options?.mode;
37
- if (mode === 'browser') {
38
+ if (mode === "browser") {
38
39
  return null;
39
40
  }
40
41
  if (meta.usage?.cost != null) {
@@ -51,25 +52,28 @@ export function resolveSessionCost(meta) {
51
52
  const output = meta.usage.outputTokens ?? 0;
52
53
  const cost = estimateUsdCost({
53
54
  usage: { inputTokens: input, outputTokens: output },
54
- pricing: { inputUsdPerToken: pricing.inputPerToken, outputUsdPerToken: pricing.outputPerToken },
55
+ pricing: {
56
+ inputUsdPerToken: pricing.inputPerToken,
57
+ outputUsdPerToken: pricing.outputPerToken,
58
+ },
55
59
  })?.totalUsd ?? 0;
56
60
  return cost > 0 ? cost : null;
57
61
  }
58
62
  export function formatTimestampAligned(iso) {
59
63
  const date = new Date(iso);
60
- const locale = 'en-US';
64
+ const locale = "en-US";
61
65
  const opts = {
62
- year: 'numeric',
63
- month: '2-digit',
64
- day: '2-digit',
65
- hour: 'numeric',
66
- minute: '2-digit',
66
+ year: "numeric",
67
+ month: "2-digit",
68
+ day: "2-digit",
69
+ hour: "numeric",
70
+ minute: "2-digit",
67
71
  second: undefined,
68
72
  hour12: true,
69
73
  };
70
74
  let formatted = date.toLocaleString(locale, opts);
71
- formatted = formatted.replace(', ', ' ');
72
- return formatted.replace(/(\s)(\d:)/, '$1 $2');
75
+ formatted = formatted.replace(", ", " ");
76
+ return formatted.replace(/(\s)(\d:)/, "$1 $2");
73
77
  }
74
78
  function formatCostTable(cost) {
75
79
  return `$${cost.toFixed(3)}`.padStart(COST_PAD);
@@ -80,11 +84,11 @@ function colorStatus(status, rich) {
80
84
  return padded;
81
85
  }
82
86
  switch (status) {
83
- case 'completed':
87
+ case "completed":
84
88
  return chalk.green(padded);
85
- case 'error':
89
+ case "error":
86
90
  return chalk.red(padded);
87
- case 'running':
91
+ case "running":
88
92
  return chalk.yellow(padded);
89
93
  default:
90
94
  return padded;
@@ -0,0 +1,22 @@
1
+ export async function readStdin(stream = process.stdin) {
2
+ const chunks = [];
3
+ const maybeTextStream = stream;
4
+ maybeTextStream.setEncoding?.("utf8");
5
+ for await (const chunk of stream) {
6
+ chunks.push(typeof chunk === "string" ? chunk : String(chunk));
7
+ }
8
+ return chunks.join("");
9
+ }
10
+ export async function resolveDashPrompt(prompt, stream = process.stdin) {
11
+ if (prompt !== "-") {
12
+ return prompt;
13
+ }
14
+ if (stream.isTTY) {
15
+ throw new Error(`"-p -" requires piped input, for example: echo "prompt" | oracle -p -.`);
16
+ }
17
+ const stdinPrompt = (await readStdin(stream)).trim();
18
+ if (!stdinPrompt) {
19
+ throw new Error(`"-p -" received empty stdin.`);
20
+ }
21
+ return stdinPrompt;
22
+ }