@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,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,7 +95,7 @@ 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,
@@ -116,30 +116,30 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
116
116
  storeResponse: runOptions.background,
117
117
  });
118
118
  const estimatedTokens = estimateRequestTokens(requestBody, modelConfig);
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
+ 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(", ");
122
122
  log(`Calling ${isTty ? kleur.cyan(modelsLabel) : modelsLabel} — ${tokenLabel} tokens, ${filesPhrase}.`);
123
123
  const multiRunTips = [];
124
124
  if (files.length === 0) {
125
- 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.");
126
126
  }
127
127
  const shortPrompt = (runOptions.prompt?.trim().length ?? 0) < 80;
128
128
  if (shortPrompt) {
129
- 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.");
130
130
  }
131
131
  for (const tip of multiRunTips) {
132
132
  log(dim(tip));
133
133
  }
134
134
  // Surface long-running model expectations up front so users know why a response might lag.
135
- 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");
136
136
  if (longRunningModels.length > 0) {
137
137
  for (const model of longRunningModels) {
138
- log('');
138
+ log("");
139
139
  const headingLabel = `[${model}]`;
140
140
  log(isTty ? kleur.bold(headingLabel) : headingLabel);
141
- log(dim('This model can take up to 60 minutes (usually replies much faster).'));
142
- 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."));
143
143
  }
144
144
  }
145
145
  const shouldStreamInline = !muteStdout && process.stdout.isTTY;
@@ -152,7 +152,7 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
152
152
  return;
153
153
  printedModels.add(model);
154
154
  const body = stripOscProgress(await sessionStore.readModelLog(sessionMeta.id, model));
155
- log('');
155
+ log("");
156
156
  const fallback = answerFallbacks.get(model);
157
157
  const hasBody = body.length > 0;
158
158
  if (!hasBody && !fallback) {
@@ -162,11 +162,11 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
162
162
  const headingLabel = `[${model}]`;
163
163
  const heading = shouldStreamInline ? kleur.bold(headingLabel) : headingLabel;
164
164
  log(heading);
165
- const content = hasBody ? body : fallback ?? '';
165
+ const content = hasBody ? body : (fallback ?? "");
166
166
  const printable = shouldRenderMarkdown ? renderMarkdownAnsi(content) : content;
167
167
  writeInline(printable);
168
- if (!printable.endsWith('\n')) {
169
- log('');
168
+ if (!printable.endsWith("\n")) {
169
+ log("");
170
170
  }
171
171
  };
172
172
  const summary = await runMultiModelApiSession({
@@ -192,7 +192,7 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
192
192
  // If we couldn't stream inline (e.g., non-TTY), print all logs after completion.
193
193
  for (const [index, result] of summary.fulfilled.entries()) {
194
194
  if (index > 0) {
195
- log('');
195
+ log("");
196
196
  }
197
197
  await printModelLog(result.model);
198
198
  }
@@ -216,14 +216,18 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
216
216
  reasoning_tokens: aggregateUsage.reasoningTokens,
217
217
  total_tokens: aggregateUsage.totalTokens,
218
218
  }, idx))
219
- .join('/');
219
+ .join("/");
220
220
  const tokensPart = (() => {
221
- const parts = tokensDisplay.split('/');
221
+ const parts = tokensDisplay.split("/");
222
222
  if (parts.length !== 4)
223
223
  return tokensDisplay;
224
224
  return `↑${parts[0]} ↓${parts[1]} ↻${parts[2]} Δ${parts[3]}`;
225
225
  })();
226
- 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;
227
231
  const overallText = `${summary.fulfilled.length}/${multiModels.length} models`;
228
232
  const { line1 } = formatFinishLine({
229
233
  elapsedMs: summary.elapsedMs,
@@ -234,7 +238,7 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
234
238
  log(statusColor(line1));
235
239
  const hasFailure = summary.rejected.length > 0;
236
240
  await sessionStore.updateSession(sessionMeta.id, {
237
- status: hasFailure ? 'error' : 'completed',
241
+ status: hasFailure ? "error" : "completed",
238
242
  completedAt: new Date().toISOString(),
239
243
  usage: aggregateUsage,
240
244
  elapsedMs: summary.elapsedMs,
@@ -261,7 +265,7 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
261
265
  }
262
266
  }
263
267
  if (savedOutputs.length > 0) {
264
- log(dim('Saved outputs:'));
268
+ log(dim("Saved outputs:"));
265
269
  for (const item of savedOutputs) {
266
270
  log(dim(`- ${item.model} -> ${item.path}`));
267
271
  }
@@ -278,7 +282,7 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
278
282
  : runOptions;
279
283
  if (modelForStatus && singleModelOverride == null) {
280
284
  await sessionStore.updateModelRun(sessionMeta.id, modelForStatus, {
281
- status: 'running',
285
+ status: "running",
282
286
  startedAt: new Date().toISOString(),
283
287
  });
284
288
  }
@@ -288,11 +292,11 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
288
292
  write,
289
293
  allowStdout: !muteStdout,
290
294
  });
291
- if (result.mode !== 'live') {
292
- 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.");
293
297
  }
294
298
  await sessionStore.updateSession(sessionMeta.id, {
295
- status: 'completed',
299
+ status: "completed",
296
300
  completedAt: new Date().toISOString(),
297
301
  usage: result.usage,
298
302
  elapsedMs: result.elapsedMs,
@@ -302,7 +306,7 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
302
306
  });
303
307
  if (modelForStatus && singleModelOverride == null) {
304
308
  await sessionStore.updateModelRun(sessionMeta.id, modelForStatus, {
305
- status: 'completed',
309
+ status: "completed",
306
310
  completedAt: new Date().toISOString(),
307
311
  usage: result.usage,
308
312
  });
@@ -323,49 +327,53 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
323
327
  log(`ERROR: ${message}`);
324
328
  markErrorLogged(error);
325
329
  const userError = asOracleUserError(error);
326
- const connectionLost = userError?.category === 'browser-automation' && userError.details?.stage === 'connection-lost';
327
- const assistantTimeout = userError?.category === 'browser-automation' && userError.details?.stage === 'assistant-timeout';
328
- const cloudflareChallenge = userError?.category === 'browser-automation' &&
329
- userError.details?.stage === 'cloudflare-challenge';
330
- if (connectionLost && mode === 'browser') {
331
- const runtime = userError.details?.runtime;
332
- 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."));
333
340
  if (modelForStatus) {
334
341
  await sessionStore.updateModelRun(sessionMeta.id, modelForStatus, {
335
- status: 'running',
342
+ status: "running",
336
343
  completedAt: undefined,
337
344
  });
338
345
  }
339
346
  await sessionStore.updateSession(sessionMeta.id, {
340
- status: 'running',
347
+ status: "running",
341
348
  errorMessage: message,
342
349
  mode,
343
350
  browser: {
344
351
  config: browserConfig,
345
352
  runtime: runtime ?? sessionMeta.browser?.runtime,
346
353
  },
347
- response: { status: 'running', incompleteReason: 'chrome-disconnected' },
354
+ response: { status: "running", incompleteReason: "chrome-disconnected" },
348
355
  });
349
356
  return;
350
357
  }
351
- if (assistantTimeout && mode === 'browser') {
352
- const runtime = userError.details?.runtime;
353
- 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."));
354
362
  if (modelForStatus) {
355
363
  await sessionStore.updateModelRun(sessionMeta.id, modelForStatus, {
356
- status: 'running',
364
+ status: "running",
357
365
  completedAt: undefined,
358
366
  });
359
367
  }
360
368
  await sessionStore.updateSession(sessionMeta.id, {
361
- status: 'running',
369
+ status: "running",
362
370
  errorMessage: message,
363
371
  mode,
364
372
  browser: {
365
373
  config: browserConfig,
366
374
  runtime: runtime ?? sessionMeta.browser?.runtime,
367
375
  },
368
- response: { status: 'running', incompleteReason: 'assistant-timeout' },
376
+ response: { status: "running", incompleteReason: "assistant-timeout" },
369
377
  });
370
378
  const autoReattachIntervalMs = browserConfig?.autoReattachIntervalMs ?? 0;
371
379
  if (autoReattachIntervalMs > 0) {
@@ -386,9 +394,9 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
386
394
  log(dim(`Reattach later with: oracle session ${sessionMeta.id}`));
387
395
  return;
388
396
  }
389
- if (cloudflareChallenge && mode === 'browser') {
397
+ if (cloudflareChallenge && mode === "browser") {
390
398
  const details = userError.details;
391
- log(dim('Cloudflare challenge detected; browser left running so you can complete the check.'));
399
+ log(dim("Cloudflare challenge detected; browser left running so you can complete the check."));
392
400
  if (details?.reuseProfileHint) {
393
401
  log(dim(`Reuse this browser profile with: ${details.reuseProfileHint}`));
394
402
  }
@@ -406,9 +414,11 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
406
414
  if (transportLine) {
407
415
  log(dim(`Transport: ${transportLine}`));
408
416
  }
409
- const browserRuntime = mode === 'browser' ? userError?.details?.runtime : undefined;
417
+ const browserRuntime = mode === "browser"
418
+ ? userError?.details?.runtime
419
+ : undefined;
410
420
  await sessionStore.updateSession(sessionMeta.id, {
411
- status: 'error',
421
+ status: "error",
412
422
  completedAt: new Date().toISOString(),
413
423
  errorMessage: message,
414
424
  mode,
@@ -430,7 +440,7 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
430
440
  });
431
441
  if (modelForStatus) {
432
442
  await sessionStore.updateModelRun(sessionMeta.id, modelForStatus, {
433
- status: 'error',
443
+ status: "error",
434
444
  completedAt: new Date().toISOString(),
435
445
  });
436
446
  }
@@ -444,7 +454,7 @@ async function writeAssistantOutput(targetPath, content, log) {
444
454
  if (!targetPath)
445
455
  return;
446
456
  if (!content || content.trim().length === 0) {
447
- log(dim('write-output skipped: no assistant content to save.'));
457
+ log(dim("write-output skipped: no assistant content to save."));
448
458
  return;
449
459
  }
450
460
  const normalizedTarget = path.resolve(targetPath);
@@ -456,8 +466,8 @@ async function writeAssistantOutput(targetPath, content, log) {
456
466
  }
457
467
  try {
458
468
  await fs.mkdir(path.dirname(normalizedTarget), { recursive: true });
459
- const payload = content.endsWith('\n') ? content : `${content}\n`;
460
- await fs.writeFile(normalizedTarget, payload, 'utf8');
469
+ const payload = content.endsWith("\n") ? content : `${content}\n`;
470
+ await fs.writeFile(normalizedTarget, payload, "utf8");
461
471
  log(dim(`Saved assistant output to ${normalizedTarget}`));
462
472
  return normalizedTarget;
463
473
  }
@@ -468,8 +478,8 @@ async function writeAssistantOutput(targetPath, content, log) {
468
478
  if (fallbackPath) {
469
479
  try {
470
480
  await fs.mkdir(path.dirname(fallbackPath), { recursive: true });
471
- const payload = content.endsWith('\n') ? content : `${content}\n`;
472
- await fs.writeFile(fallbackPath, payload, 'utf8');
481
+ const payload = content.endsWith("\n") ? content : `${content}\n`;
482
+ await fs.writeFile(fallbackPath, payload, "utf8");
473
483
  log(dim(`write-output fallback to ${fallbackPath} (original failed: ${reason})`));
474
484
  return fallbackPath;
475
485
  }
@@ -485,7 +495,7 @@ async function writeAssistantOutput(targetPath, content, log) {
485
495
  }
486
496
  async function autoReattachUntilComplete({ sessionMeta, runtime, browserConfig, runOptions, modelForStatus, notificationSettings, log, }) {
487
497
  if (!runtime || !browserConfig) {
488
- log(dim('Auto-reattach disabled: missing runtime or browser config.'));
498
+ log(dim("Auto-reattach disabled: missing runtime or browser config."));
489
499
  return false;
490
500
  }
491
501
  const delayMs = Math.max(0, browserConfig.autoReattachDelayMs ?? 0);
@@ -526,16 +536,16 @@ async function autoReattachUntilComplete({ sessionMeta, runtime, browserConfig,
526
536
  const result = await resumeBrowserSession(runtime, reattachConfig, logger, {
527
537
  promptPreview: sessionMeta.promptPreview,
528
538
  });
529
- const answerText = result.answerMarkdown || result.answerText || '';
539
+ const answerText = result.answerMarkdown || result.answerText || "";
530
540
  const outputTokens = estimateTokenCount(answerText);
531
541
  const logWriter = sessionStore.createLogWriter(sessionMeta.id);
532
542
  logWriter.logLine(`[auto-reattach] captured assistant response on attempt ${attempt}`);
533
- logWriter.logLine('Answer:');
543
+ logWriter.logLine("Answer:");
534
544
  logWriter.logLine(answerText);
535
545
  logWriter.stream.end();
536
546
  if (modelForStatus) {
537
547
  await sessionStore.updateModelRun(sessionMeta.id, modelForStatus, {
538
- status: 'completed',
548
+ status: "completed",
539
549
  completedAt: new Date().toISOString(),
540
550
  usage: {
541
551
  inputTokens: 0,
@@ -546,7 +556,7 @@ async function autoReattachUntilComplete({ sessionMeta, runtime, browserConfig,
546
556
  });
547
557
  }
548
558
  await sessionStore.updateSession(sessionMeta.id, {
549
- status: 'completed',
559
+ status: "completed",
550
560
  completedAt: new Date().toISOString(),
551
561
  usage: {
552
562
  inputTokens: 0,
@@ -558,7 +568,7 @@ async function autoReattachUntilComplete({ sessionMeta, runtime, browserConfig,
558
568
  config: browserConfig,
559
569
  runtime,
560
570
  },
561
- response: { status: 'completed' },
571
+ response: { status: "completed" },
562
572
  error: undefined,
563
573
  transport: undefined,
564
574
  });
@@ -566,7 +576,7 @@ async function autoReattachUntilComplete({ sessionMeta, runtime, browserConfig,
566
576
  await sendSessionNotification({
567
577
  sessionId: sessionMeta.id,
568
578
  sessionName: sessionMeta.options?.slug ?? sessionMeta.id,
569
- mode: sessionMeta.mode ?? 'browser',
579
+ mode: sessionMeta.mode ?? "browser",
570
580
  model: sessionMeta.model ?? runOptions.model,
571
581
  usage: {
572
582
  inputTokens: 0,
@@ -574,7 +584,7 @@ async function autoReattachUntilComplete({ sessionMeta, runtime, browserConfig,
574
584
  },
575
585
  characters: answerText.length,
576
586
  }, notificationSettings, log, answerText.slice(0, 140));
577
- log(kleur.green('Auto-reattach succeeded; session marked completed.'));
587
+ log(kleur.green("Auto-reattach succeeded; session marked completed."));
578
588
  return true;
579
589
  }
580
590
  catch (error) {
@@ -602,7 +612,7 @@ function isPermissionError(error) {
602
612
  if (!(error instanceof Error))
603
613
  return false;
604
614
  const code = error.code;
605
- return code === 'EACCES' || code === 'EPERM';
615
+ return code === "EACCES" || code === "EPERM";
606
616
  }
607
617
  function buildFallbackPath(original) {
608
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,23 +11,23 @@ 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
32
  const slugValue = options?.displaySlug ?? meta.id;
33
33
  const slug = rich ? chalk.cyan(slugValue) : slugValue;
@@ -35,7 +35,7 @@ export function formatSessionTableRow(meta, options) {
35
35
  }
36
36
  export function resolveSessionCost(meta) {
37
37
  const mode = meta.mode ?? meta.options?.mode;
38
- if (mode === 'browser') {
38
+ if (mode === "browser") {
39
39
  return null;
40
40
  }
41
41
  if (meta.usage?.cost != null) {
@@ -52,25 +52,28 @@ export function resolveSessionCost(meta) {
52
52
  const output = meta.usage.outputTokens ?? 0;
53
53
  const cost = estimateUsdCost({
54
54
  usage: { inputTokens: input, outputTokens: output },
55
- pricing: { inputUsdPerToken: pricing.inputPerToken, outputUsdPerToken: pricing.outputPerToken },
55
+ pricing: {
56
+ inputUsdPerToken: pricing.inputPerToken,
57
+ outputUsdPerToken: pricing.outputPerToken,
58
+ },
56
59
  })?.totalUsd ?? 0;
57
60
  return cost > 0 ? cost : null;
58
61
  }
59
62
  export function formatTimestampAligned(iso) {
60
63
  const date = new Date(iso);
61
- const locale = 'en-US';
64
+ const locale = "en-US";
62
65
  const opts = {
63
- year: 'numeric',
64
- month: '2-digit',
65
- day: '2-digit',
66
- hour: 'numeric',
67
- minute: '2-digit',
66
+ year: "numeric",
67
+ month: "2-digit",
68
+ day: "2-digit",
69
+ hour: "numeric",
70
+ minute: "2-digit",
68
71
  second: undefined,
69
72
  hour12: true,
70
73
  };
71
74
  let formatted = date.toLocaleString(locale, opts);
72
- formatted = formatted.replace(', ', ' ');
73
- return formatted.replace(/(\s)(\d:)/, '$1 $2');
75
+ formatted = formatted.replace(", ", " ");
76
+ return formatted.replace(/(\s)(\d:)/, "$1 $2");
74
77
  }
75
78
  function formatCostTable(cost) {
76
79
  return `$${cost.toFixed(3)}`.padStart(COST_PAD);
@@ -81,11 +84,11 @@ function colorStatus(status, rich) {
81
84
  return padded;
82
85
  }
83
86
  switch (status) {
84
- case 'completed':
87
+ case "completed":
85
88
  return chalk.green(padded);
86
- case 'error':
89
+ case "error":
87
90
  return chalk.red(padded);
88
- case 'running':
91
+ case "running":
89
92
  return chalk.yellow(padded);
90
93
  default:
91
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
+ }