@steipete/oracle 0.9.0 → 0.11.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 (194) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +107 -49
  3. package/dist/bin/oracle-cli.js +551 -410
  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/archiveConversation.js +224 -0
  18. package/dist/src/browser/actions/assistantResponse.js +175 -101
  19. package/dist/src/browser/actions/attachmentDataTransfer.js +49 -47
  20. package/dist/src/browser/actions/attachments.js +246 -150
  21. package/dist/src/browser/actions/deepResearch.js +662 -0
  22. package/dist/src/browser/actions/domEvents.js +2 -2
  23. package/dist/src/browser/actions/modelSelection.js +342 -119
  24. package/dist/src/browser/actions/navigation.js +183 -137
  25. package/dist/src/browser/actions/projectSources.js +491 -0
  26. package/dist/src/browser/actions/promptComposer.js +152 -91
  27. package/dist/src/browser/actions/remoteFileTransfer.js +10 -10
  28. package/dist/src/browser/actions/thinkingStatus.js +391 -0
  29. package/dist/src/browser/actions/thinkingTime.js +207 -110
  30. package/dist/src/browser/artifacts.js +150 -0
  31. package/dist/src/browser/attachRunning.js +31 -0
  32. package/dist/src/browser/chatgptImages.js +315 -0
  33. package/dist/src/browser/chromeLifecycle.js +276 -63
  34. package/dist/src/browser/config.js +59 -16
  35. package/dist/src/browser/constants.js +25 -12
  36. package/dist/src/browser/controlPlan.js +81 -0
  37. package/dist/src/browser/cookies.js +19 -19
  38. package/dist/src/browser/detect.js +250 -77
  39. package/dist/src/browser/domDebug.js +50 -1
  40. package/dist/src/browser/index.js +1559 -692
  41. package/dist/src/browser/liveTabs.js +434 -0
  42. package/dist/src/browser/modelStrategy.js +1 -1
  43. package/dist/src/browser/pageActions.js +5 -5
  44. package/dist/src/browser/policies.js +16 -13
  45. package/dist/src/browser/profileState.js +127 -42
  46. package/dist/src/browser/projectSourcesRunner.js +366 -0
  47. package/dist/src/browser/prompt.js +72 -42
  48. package/dist/src/browser/promptSummary.js +5 -5
  49. package/dist/src/browser/providerDomFlow.js +1 -1
  50. package/dist/src/browser/providers/chatgptDomProvider.js +9 -9
  51. package/dist/src/browser/providers/geminiDeepThinkDomProvider.js +51 -42
  52. package/dist/src/browser/providers/index.js +2 -2
  53. package/dist/src/browser/reattach.js +178 -73
  54. package/dist/src/browser/reattachHelpers.js +32 -27
  55. package/dist/src/browser/sessionRunner.js +89 -25
  56. package/dist/src/browser/tabLeaseRegistry.js +182 -0
  57. package/dist/src/browser/utils.js +9 -9
  58. package/dist/src/browserMode.js +1 -1
  59. package/dist/src/cli/bridge/claudeConfig.js +24 -20
  60. package/dist/src/cli/bridge/client.js +28 -20
  61. package/dist/src/cli/bridge/codexConfig.js +16 -16
  62. package/dist/src/cli/bridge/doctor.js +47 -39
  63. package/dist/src/cli/bridge/host.js +58 -56
  64. package/dist/src/cli/browserConfig.js +102 -48
  65. package/dist/src/cli/browserDefaults.js +51 -26
  66. package/dist/src/cli/browserTabs.js +228 -0
  67. package/dist/src/cli/bundleWarnings.js +1 -1
  68. package/dist/src/cli/clipboard.js +11 -2
  69. package/dist/src/cli/detach.js +2 -2
  70. package/dist/src/cli/dryRun.js +62 -26
  71. package/dist/src/cli/duplicatePromptGuard.js +12 -4
  72. package/dist/src/cli/engine.js +9 -9
  73. package/dist/src/cli/errorUtils.js +1 -1
  74. package/dist/src/cli/fileSize.js +3 -3
  75. package/dist/src/cli/format.js +2 -2
  76. package/dist/src/cli/help.js +28 -28
  77. package/dist/src/cli/hiddenAliases.js +3 -3
  78. package/dist/src/cli/markdownBundle.js +7 -7
  79. package/dist/src/cli/markdownRenderer.js +15 -15
  80. package/dist/src/cli/notifier.js +77 -67
  81. package/dist/src/cli/options.js +131 -106
  82. package/dist/src/cli/oscUtils.js +1 -1
  83. package/dist/src/cli/projectSources.js +116 -0
  84. package/dist/src/cli/promptRequirement.js +2 -2
  85. package/dist/src/cli/renderOutput.js +1 -1
  86. package/dist/src/cli/rootAlias.js +1 -1
  87. package/dist/src/cli/runOptions.js +32 -28
  88. package/dist/src/cli/sessionCommand.js +82 -21
  89. package/dist/src/cli/sessionDisplay.js +213 -87
  90. package/dist/src/cli/sessionLineage.js +6 -2
  91. package/dist/src/cli/sessionRunner.js +149 -95
  92. package/dist/src/cli/sessionTable.js +26 -23
  93. package/dist/src/cli/stdin.js +22 -0
  94. package/dist/src/cli/tagline.js +121 -124
  95. package/dist/src/cli/tui/index.js +139 -128
  96. package/dist/src/cli/writeOutputPath.js +5 -5
  97. package/dist/src/config.js +7 -7
  98. package/dist/src/gemini-web/browserSessionManager.js +19 -15
  99. package/dist/src/gemini-web/client.js +76 -70
  100. package/dist/src/gemini-web/executionMode.js +6 -8
  101. package/dist/src/gemini-web/executor.js +98 -93
  102. package/dist/src/gemini-web/index.js +1 -1
  103. package/dist/src/mcp/consultPresets.js +19 -0
  104. package/dist/src/mcp/server.js +18 -12
  105. package/dist/src/mcp/tools/consult.js +246 -67
  106. package/dist/src/mcp/tools/projectSources.js +123 -0
  107. package/dist/src/mcp/tools/sessionResources.js +12 -12
  108. package/dist/src/mcp/tools/sessions.js +26 -17
  109. package/dist/src/mcp/types.js +12 -5
  110. package/dist/src/mcp/utils.js +21 -8
  111. package/dist/src/oracle/background.js +15 -15
  112. package/dist/src/oracle/claude.js +53 -25
  113. package/dist/src/oracle/client.js +50 -41
  114. package/dist/src/oracle/config.js +96 -66
  115. package/dist/src/oracle/errors.js +38 -38
  116. package/dist/src/oracle/files.js +55 -46
  117. package/dist/src/oracle/finishLine.js +10 -8
  118. package/dist/src/oracle/format.js +3 -3
  119. package/dist/src/oracle/gemini.js +37 -33
  120. package/dist/src/oracle/logging.js +7 -7
  121. package/dist/src/oracle/markdown.js +28 -28
  122. package/dist/src/oracle/modelResolver.js +16 -16
  123. package/dist/src/oracle/multiModelRunner.js +12 -12
  124. package/dist/src/oracle/oscProgress.js +8 -8
  125. package/dist/src/oracle/promptAssembly.js +6 -3
  126. package/dist/src/oracle/request.js +16 -13
  127. package/dist/src/oracle/run.js +160 -135
  128. package/dist/src/oracle/runUtils.js +8 -5
  129. package/dist/src/oracle/tokenEstimate.js +6 -6
  130. package/dist/src/oracle/tokenStats.js +5 -5
  131. package/dist/src/oracle/tokenStringifier.js +5 -5
  132. package/dist/src/oracle.js +12 -12
  133. package/dist/src/oracleHome.js +3 -3
  134. package/dist/src/projectSources/plan.js +27 -0
  135. package/dist/src/projectSources/url.js +23 -0
  136. package/dist/src/remote/client.js +25 -25
  137. package/dist/src/remote/health.js +20 -20
  138. package/dist/src/remote/remoteServiceConfig.js +9 -9
  139. package/dist/src/remote/server.js +129 -118
  140. package/dist/src/sessionManager.js +78 -75
  141. package/dist/src/sessionStore.js +3 -3
  142. package/dist/src/version.js +10 -10
  143. package/dist/vendor/oracle-notifier/README.md +2 -0
  144. package/package.json +67 -62
  145. package/vendor/oracle-notifier/README.md +2 -0
  146. package/dist/markdansi/types/index.js +0 -4
  147. package/dist/oracle/bin/oracle-cli.js +0 -472
  148. package/dist/oracle/src/browser/actions/assistantResponse.js +0 -471
  149. package/dist/oracle/src/browser/actions/attachments.js +0 -82
  150. package/dist/oracle/src/browser/actions/modelSelection.js +0 -190
  151. package/dist/oracle/src/browser/actions/navigation.js +0 -75
  152. package/dist/oracle/src/browser/actions/promptComposer.js +0 -167
  153. package/dist/oracle/src/browser/chromeLifecycle.js +0 -104
  154. package/dist/oracle/src/browser/config.js +0 -33
  155. package/dist/oracle/src/browser/constants.js +0 -40
  156. package/dist/oracle/src/browser/cookies.js +0 -210
  157. package/dist/oracle/src/browser/domDebug.js +0 -36
  158. package/dist/oracle/src/browser/index.js +0 -331
  159. package/dist/oracle/src/browser/pageActions.js +0 -5
  160. package/dist/oracle/src/browser/prompt.js +0 -88
  161. package/dist/oracle/src/browser/promptSummary.js +0 -20
  162. package/dist/oracle/src/browser/sessionRunner.js +0 -80
  163. package/dist/oracle/src/browser/utils.js +0 -62
  164. package/dist/oracle/src/browserMode.js +0 -1
  165. package/dist/oracle/src/cli/browserConfig.js +0 -44
  166. package/dist/oracle/src/cli/dryRun.js +0 -59
  167. package/dist/oracle/src/cli/engine.js +0 -17
  168. package/dist/oracle/src/cli/errorUtils.js +0 -9
  169. package/dist/oracle/src/cli/help.js +0 -70
  170. package/dist/oracle/src/cli/markdownRenderer.js +0 -15
  171. package/dist/oracle/src/cli/options.js +0 -103
  172. package/dist/oracle/src/cli/promptRequirement.js +0 -14
  173. package/dist/oracle/src/cli/rootAlias.js +0 -30
  174. package/dist/oracle/src/cli/sessionCommand.js +0 -77
  175. package/dist/oracle/src/cli/sessionDisplay.js +0 -270
  176. package/dist/oracle/src/cli/sessionRunner.js +0 -94
  177. package/dist/oracle/src/heartbeat.js +0 -43
  178. package/dist/oracle/src/oracle/client.js +0 -48
  179. package/dist/oracle/src/oracle/config.js +0 -29
  180. package/dist/oracle/src/oracle/errors.js +0 -101
  181. package/dist/oracle/src/oracle/files.js +0 -220
  182. package/dist/oracle/src/oracle/format.js +0 -33
  183. package/dist/oracle/src/oracle/fsAdapter.js +0 -7
  184. package/dist/oracle/src/oracle/oscProgress.js +0 -60
  185. package/dist/oracle/src/oracle/request.js +0 -48
  186. package/dist/oracle/src/oracle/run.js +0 -444
  187. package/dist/oracle/src/oracle/tokenStats.js +0 -39
  188. package/dist/oracle/src/oracle/types.js +0 -1
  189. package/dist/oracle/src/oracle.js +0 -9
  190. package/dist/oracle/src/sessionManager.js +0 -205
  191. package/dist/oracle/src/version.js +0 -39
  192. package/dist/scripts/chrome/browser-tools.js +0 -295
  193. package/dist/src/browser/profileSync.js +0 -141
  194. /package/dist/{oracle/src/browser → src/projectSources}/types.js +0 -0
@@ -1,23 +1,23 @@
1
- import path from 'node:path';
2
- import fs from 'node:fs/promises';
3
- import { createWriteStream } from 'node:fs';
4
- import net from 'node:net';
5
- import { DEFAULT_MODEL, formatElapsed } from './oracle.js';
6
- import { safeModelSlug } from './oracle/modelResolver.js';
7
- import { getOracleHomeDir } from './oracleHome.js';
1
+ import path from "node:path";
2
+ import fs from "node:fs/promises";
3
+ import { createWriteStream } from "node:fs";
4
+ import net from "node:net";
5
+ import { DEFAULT_MODEL, formatElapsed } from "./oracle.js";
6
+ import { safeModelSlug } from "./oracle/modelResolver.js";
7
+ import { getOracleHomeDir } from "./oracleHome.js";
8
8
  export function getSessionsDir() {
9
- return path.join(getOracleHomeDir(), 'sessions');
10
- }
11
- const METADATA_FILENAME = 'meta.json';
12
- const LEGACY_SESSION_FILENAME = 'session.json';
13
- const LEGACY_REQUEST_FILENAME = 'request.json';
14
- const MODELS_DIRNAME = 'models';
15
- const MODEL_JSON_EXTENSION = '.json';
16
- const MODEL_LOG_EXTENSION = '.log';
9
+ return path.join(getOracleHomeDir(), "sessions");
10
+ }
11
+ const METADATA_FILENAME = "meta.json";
12
+ const LEGACY_SESSION_FILENAME = "session.json";
13
+ const LEGACY_REQUEST_FILENAME = "request.json";
14
+ const MODELS_DIRNAME = "models";
15
+ const MODEL_JSON_EXTENSION = ".json";
16
+ const MODEL_LOG_EXTENSION = ".log";
17
17
  const MAX_STATUS_LIMIT = 1000;
18
18
  const ZOMBIE_MAX_AGE_MS = 60 * 60 * 1000; // 60 minutes
19
19
  const CHROME_RUNTIME_TIMEOUT_MS = 250;
20
- const DEFAULT_SLUG = 'session';
20
+ const DEFAULT_SLUG = "session";
21
21
  const MAX_SLUG_WORDS = 5;
22
22
  const MIN_CUSTOM_SLUG_WORDS = 3;
23
23
  const MAX_SLUG_WORD_LENGTH = 10;
@@ -28,15 +28,13 @@ export async function ensureSessionStorage() {
28
28
  await ensureDir(getSessionsDir());
29
29
  }
30
30
  function slugify(text, maxWords = MAX_SLUG_WORDS) {
31
- const normalized = text?.toLowerCase() ?? '';
31
+ const normalized = text?.toLowerCase() ?? "";
32
32
  const words = normalized.match(/[a-z0-9]+/g) ?? [];
33
- const trimmed = words
34
- .slice(0, maxWords)
35
- .map((word) => word.slice(0, MAX_SLUG_WORD_LENGTH));
36
- return trimmed.length > 0 ? trimmed.join('-') : DEFAULT_SLUG;
33
+ const trimmed = words.slice(0, maxWords).map((word) => word.slice(0, MAX_SLUG_WORD_LENGTH));
34
+ return trimmed.length > 0 ? trimmed.join("-") : DEFAULT_SLUG;
37
35
  }
38
36
  function countSlugWords(slug) {
39
- return slug.split('-').filter(Boolean).length;
37
+ return slug.split("-").filter(Boolean).length;
40
38
  }
41
39
  function normalizeCustomSlug(candidate) {
42
40
  const slug = slugify(candidate, MAX_SLUG_WORDS);
@@ -65,7 +63,7 @@ function legacySessionPath(id) {
65
63
  return path.join(sessionDir(id), LEGACY_SESSION_FILENAME);
66
64
  }
67
65
  function logPath(id) {
68
- return path.join(sessionDir(id), 'output.log');
66
+ return path.join(sessionDir(id), "output.log");
69
67
  }
70
68
  function modelsDir(id) {
71
69
  return path.join(sessionDir(id), MODELS_DIRNAME);
@@ -106,7 +104,7 @@ async function listModelRunFiles(sessionId) {
106
104
  }
107
105
  const jsonPath = path.join(dir, entry);
108
106
  try {
109
- const raw = await fs.readFile(jsonPath, 'utf8');
107
+ const raw = await fs.readFile(jsonPath, "utf8");
110
108
  const parsed = JSON.parse(raw);
111
109
  const normalized = ensureModelLogReference(sessionId, parsed);
112
110
  result.push(normalized);
@@ -126,7 +124,7 @@ function ensureModelLogReference(sessionId, record) {
126
124
  }
127
125
  async function readModelRunFile(sessionId, model) {
128
126
  try {
129
- const raw = await fs.readFile(modelJsonPath(sessionId, model), 'utf8');
127
+ const raw = await fs.readFile(modelJsonPath(sessionId, model), "utf8");
130
128
  const parsed = JSON.parse(raw);
131
129
  return ensureModelLogReference(sessionId, parsed);
132
130
  }
@@ -138,14 +136,14 @@ export async function updateModelRunMetadata(sessionId, model, updates) {
138
136
  await ensureDir(modelsDir(sessionId));
139
137
  const existing = (await readModelRunFile(sessionId, model)) ?? {
140
138
  model,
141
- status: 'pending',
139
+ status: "pending",
142
140
  };
143
141
  const next = ensureModelLogReference(sessionId, {
144
142
  ...existing,
145
143
  ...updates,
146
144
  model,
147
145
  });
148
- await fs.writeFile(modelJsonPath(sessionId, model), JSON.stringify(next, null, 2), 'utf8');
146
+ await fs.writeFile(modelJsonPath(sessionId, model), JSON.stringify(next, null, 2), "utf8");
149
147
  return next;
150
148
  }
151
149
  export async function readModelRunMetadata(sessionId, model) {
@@ -157,7 +155,7 @@ export async function initializeSession(options, cwd, notifications, baseSlugOve
157
155
  const sessionId = await ensureUniqueSessionId(baseSlug);
158
156
  const dir = sessionDir(sessionId);
159
157
  await ensureDir(dir);
160
- const mode = options.mode ?? 'api';
158
+ const mode = options.mode ?? "api";
161
159
  const browserConfig = options.browserConfig;
162
160
  const modelList = Array.isArray(options.models) && options.models.length > 0
163
161
  ? options.models
@@ -167,12 +165,12 @@ export async function initializeSession(options, cwd, notifications, baseSlugOve
167
165
  const metadata = {
168
166
  id: sessionId,
169
167
  createdAt: new Date().toISOString(),
170
- status: 'pending',
171
- promptPreview: (options.prompt || '').slice(0, 160),
168
+ status: "pending",
169
+ promptPreview: (options.prompt || "").slice(0, 160),
172
170
  model: modelList[0] ?? options.model,
173
171
  models: modelList.map((modelName) => ({
174
172
  model: modelName,
175
- status: 'pending',
173
+ status: "pending",
176
174
  })),
177
175
  cwd,
178
176
  mode,
@@ -215,24 +213,25 @@ export async function initializeSession(options, cwd, notifications, baseSlugOve
215
213
  generateImage: options.generateImage,
216
214
  editImage: options.editImage,
217
215
  outputPath: options.outputPath,
216
+ browserFollowUps: options.browserFollowUps,
218
217
  aspectRatio: options.aspectRatio,
219
218
  geminiShowThoughts: options.geminiShowThoughts,
220
219
  },
221
220
  };
222
221
  await ensureDir(modelsDir(sessionId));
223
- await fs.writeFile(metaPath(sessionId), JSON.stringify(metadata, null, 2), 'utf8');
222
+ await fs.writeFile(metaPath(sessionId), JSON.stringify(metadata, null, 2), "utf8");
224
223
  await Promise.all((modelList.length > 0 ? modelList : [metadata.model ?? DEFAULT_MODEL]).map(async (modelName) => {
225
224
  const jsonPath = modelJsonPath(sessionId, modelName);
226
225
  const logFilePath = modelLogPath(sessionId, modelName);
227
226
  const modelRecord = {
228
227
  model: modelName,
229
- status: 'pending',
228
+ status: "pending",
230
229
  log: { path: path.relative(sessionDir(sessionId), logFilePath) },
231
230
  };
232
- await fs.writeFile(jsonPath, JSON.stringify(modelRecord, null, 2), 'utf8');
233
- await fs.writeFile(logFilePath, '', 'utf8');
231
+ await fs.writeFile(jsonPath, JSON.stringify(modelRecord, null, 2), "utf8");
232
+ await fs.writeFile(logFilePath, "", "utf8");
234
233
  }));
235
- await fs.writeFile(logPath(sessionId), '', 'utf8');
234
+ await fs.writeFile(logPath(sessionId), "", "utf8");
236
235
  return metadata;
237
236
  }
238
237
  export async function readSessionMetadata(sessionId) {
@@ -251,12 +250,12 @@ export async function updateSessionMetadata(sessionId, updates) {
251
250
  (await readLegacySessionMetadata(sessionId)) ??
252
251
  { id: sessionId };
253
252
  const next = { ...existing, ...updates };
254
- await fs.writeFile(metaPath(sessionId), JSON.stringify(next, null, 2), 'utf8');
253
+ await fs.writeFile(metaPath(sessionId), JSON.stringify(next, null, 2), "utf8");
255
254
  return next;
256
255
  }
257
256
  async function readModernSessionMetadata(sessionId) {
258
257
  try {
259
- const raw = await fs.readFile(metaPath(sessionId), 'utf8');
258
+ const raw = await fs.readFile(metaPath(sessionId), "utf8");
260
259
  const parsed = JSON.parse(raw);
261
260
  if (!isSessionMetadataRecord(parsed)) {
262
261
  return null;
@@ -271,7 +270,7 @@ async function readModernSessionMetadata(sessionId) {
271
270
  }
272
271
  async function readLegacySessionMetadata(sessionId) {
273
272
  try {
274
- const raw = await fs.readFile(legacySessionPath(sessionId), 'utf8');
273
+ const raw = await fs.readFile(legacySessionPath(sessionId), "utf8");
275
274
  const parsed = JSON.parse(raw);
276
275
  const enriched = await attachModelRuns(parsed, sessionId);
277
276
  const runtimeChecked = await markDeadBrowser(enriched, { persist: false });
@@ -282,7 +281,7 @@ async function readLegacySessionMetadata(sessionId) {
282
281
  }
283
282
  }
284
283
  function isSessionMetadataRecord(value) {
285
- return Boolean(value && typeof value.id === 'string' && value.status);
284
+ return Boolean(value && typeof value.id === "string" && value.status);
286
285
  }
287
286
  async function attachModelRuns(meta, sessionId) {
288
287
  const runs = await listModelRunFiles(sessionId);
@@ -296,8 +295,8 @@ export function createSessionLogWriter(sessionId, model) {
296
295
  if (model) {
297
296
  void ensureDir(modelsDir(sessionId));
298
297
  }
299
- const stream = createWriteStream(targetPath, { flags: 'a' });
300
- const logLine = (line = '') => {
298
+ const stream = createWriteStream(targetPath, { flags: "a" });
299
+ const logLine = (line = "") => {
301
300
  stream.write(`${line}\n`);
302
301
  };
303
302
  const writeChunk = (chunk) => {
@@ -320,7 +319,7 @@ export async function listSessionsMetadata() {
320
319
  }
321
320
  return metas.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
322
321
  }
323
- export function filterSessionsByRange(metas, { hours = 24, includeAll = false, limit = 100 }) {
322
+ export function filterSessionsByRange(metas, { hours = 24, includeAll = false, limit = 100, }) {
324
323
  const maxLimit = Math.min(limit, MAX_STATUS_LIMIT);
325
324
  let filtered = metas;
326
325
  if (!includeAll) {
@@ -335,29 +334,31 @@ export async function readSessionLog(sessionId) {
335
334
  const runs = await listModelRunFiles(sessionId);
336
335
  if (runs.length === 0) {
337
336
  try {
338
- return await fs.readFile(logPath(sessionId), 'utf8');
337
+ return await fs.readFile(logPath(sessionId), "utf8");
339
338
  }
340
339
  catch {
341
- return '';
340
+ return "";
342
341
  }
343
342
  }
344
343
  const sections = [];
345
344
  let hasContent = false;
346
345
  const ordered = runs
347
346
  .slice()
348
- .sort((a, b) => (a.startedAt && b.startedAt ? a.startedAt.localeCompare(b.startedAt) : a.model.localeCompare(b.model)));
347
+ .sort((a, b) => a.startedAt && b.startedAt
348
+ ? a.startedAt.localeCompare(b.startedAt)
349
+ : a.model.localeCompare(b.model));
349
350
  for (const run of ordered) {
350
351
  const logFile = run.log?.path
351
352
  ? path.isAbsolute(run.log.path)
352
353
  ? run.log.path
353
354
  : path.join(sessionDir(sessionId), run.log.path)
354
355
  : modelLogPath(sessionId, run.model);
355
- let body = '';
356
+ let body = "";
356
357
  try {
357
- body = await fs.readFile(logFile, 'utf8');
358
+ body = await fs.readFile(logFile, "utf8");
358
359
  }
359
360
  catch {
360
- body = '';
361
+ body = "";
361
362
  }
362
363
  if (body.length > 0) {
363
364
  hasContent = true;
@@ -366,20 +367,20 @@ export async function readSessionLog(sessionId) {
366
367
  }
367
368
  if (!hasContent) {
368
369
  try {
369
- return await fs.readFile(logPath(sessionId), 'utf8');
370
+ return await fs.readFile(logPath(sessionId), "utf8");
370
371
  }
371
372
  catch {
372
373
  // ignore and return structured header-only log
373
374
  }
374
375
  }
375
- return sections.join('\n\n');
376
+ return sections.join("\n\n");
376
377
  }
377
378
  export async function readModelLog(sessionId, model) {
378
379
  try {
379
- return await fs.readFile(modelLogPath(sessionId, model), 'utf8');
380
+ return await fs.readFile(modelLogPath(sessionId, model), "utf8");
380
381
  }
381
382
  catch {
382
- return '';
383
+ return "";
383
384
  }
384
385
  }
385
386
  export async function readSessionRequest(sessionId) {
@@ -388,7 +389,7 @@ export async function readSessionRequest(sessionId) {
388
389
  return modern.options;
389
390
  }
390
391
  try {
391
- const raw = await fs.readFile(requestPath(sessionId), 'utf8');
392
+ const raw = await fs.readFile(requestPath(sessionId), "utf8");
392
393
  const parsed = JSON.parse(raw);
393
394
  if (isSessionMetadataRecord(parsed)) {
394
395
  return parsed.options ?? null;
@@ -452,7 +453,7 @@ export async function getSessionPaths(sessionId) {
452
453
  }
453
454
  }
454
455
  if (missing.length > 0) {
455
- throw new Error(`Session "${sessionId}" is missing: ${missing.join(', ')}`);
456
+ throw new Error(`Session "${sessionId}" is missing: ${missing.join(", ")}`);
456
457
  }
457
458
  return { dir, metadata, log, request };
458
459
  }
@@ -460,7 +461,7 @@ async function markZombie(meta, { persist }) {
460
461
  if (!(await isZombie(meta))) {
461
462
  return meta;
462
463
  }
463
- if (meta.mode === 'browser') {
464
+ if (meta.mode === "browser") {
464
465
  const runtime = meta.browser?.runtime;
465
466
  if (runtime) {
466
467
  const signals = [];
@@ -468,7 +469,7 @@ async function markZombie(meta, { persist }) {
468
469
  signals.push(isProcessAlive(runtime.chromePid));
469
470
  }
470
471
  if (runtime.chromePort) {
471
- const host = runtime.chromeHost ?? '127.0.0.1';
472
+ const host = runtime.chromeHost ?? "127.0.0.1";
472
473
  signals.push(await isPortOpen(host, runtime.chromePort));
473
474
  }
474
475
  if (signals.some(Boolean)) {
@@ -479,17 +480,17 @@ async function markZombie(meta, { persist }) {
479
480
  const maxAgeMs = resolveZombieMaxAgeMs(meta);
480
481
  const updated = {
481
482
  ...meta,
482
- status: 'error',
483
+ status: "error",
483
484
  errorMessage: `Session marked as zombie (> ${formatElapsed(maxAgeMs)} stale)`,
484
485
  completedAt: new Date().toISOString(),
485
486
  };
486
487
  if (persist) {
487
- await fs.writeFile(metaPath(meta.id), JSON.stringify(updated, null, 2), 'utf8');
488
+ await fs.writeFile(metaPath(meta.id), JSON.stringify(updated, null, 2), "utf8");
488
489
  }
489
490
  return updated;
490
491
  }
491
492
  async function markDeadBrowser(meta, { persist }) {
492
- if (meta.status !== 'running' || meta.mode !== 'browser') {
493
+ if (meta.status !== "running" || meta.mode !== "browser") {
493
494
  return meta;
494
495
  }
495
496
  const runtime = meta.browser?.runtime;
@@ -501,7 +502,7 @@ async function markDeadBrowser(meta, { persist }) {
501
502
  signals.push(isProcessAlive(runtime.chromePid));
502
503
  }
503
504
  if (runtime.chromePort) {
504
- const host = runtime.chromeHost ?? '127.0.0.1';
505
+ const host = runtime.chromeHost ?? "127.0.0.1";
505
506
  signals.push(await isPortOpen(host, runtime.chromePort));
506
507
  }
507
508
  if (signals.length === 0 || signals.some(Boolean)) {
@@ -510,24 +511,24 @@ async function markDeadBrowser(meta, { persist }) {
510
511
  const response = meta.response
511
512
  ? {
512
513
  ...meta.response,
513
- status: 'error',
514
- incompleteReason: meta.response.incompleteReason ?? 'chrome-disconnected',
514
+ status: "error",
515
+ incompleteReason: meta.response.incompleteReason ?? "chrome-disconnected",
515
516
  }
516
- : { status: 'error', incompleteReason: 'chrome-disconnected' };
517
+ : { status: "error", incompleteReason: "chrome-disconnected" };
517
518
  const updated = {
518
519
  ...meta,
519
- status: 'error',
520
- errorMessage: 'Browser session ended (Chrome is no longer reachable)',
520
+ status: "error",
521
+ errorMessage: "Browser session ended (Chrome is no longer reachable)",
521
522
  completedAt: new Date().toISOString(),
522
523
  response,
523
524
  };
524
525
  if (persist) {
525
- await fs.writeFile(metaPath(meta.id), JSON.stringify(updated, null, 2), 'utf8');
526
+ await fs.writeFile(metaPath(meta.id), JSON.stringify(updated, null, 2), "utf8");
526
527
  }
527
528
  return updated;
528
529
  }
529
530
  async function isZombie(meta) {
530
- if (meta.status !== 'running') {
531
+ if (meta.status !== "running") {
531
532
  return false;
532
533
  }
533
534
  const reference = meta.startedAt ?? meta.createdAt;
@@ -546,11 +547,13 @@ async function isZombie(meta) {
546
547
  }
547
548
  function resolveZombieMaxAgeMs(meta) {
548
549
  const explicit = meta.options?.zombieTimeoutMs;
549
- const hasExplicit = typeof explicit === 'number' && Number.isFinite(explicit) && explicit > 0;
550
+ const hasExplicit = typeof explicit === "number" && Number.isFinite(explicit) && explicit > 0;
550
551
  let maxAgeMs = hasExplicit ? explicit : ZOMBIE_MAX_AGE_MS;
551
552
  if (!hasExplicit) {
552
553
  const timeoutSeconds = meta.options?.timeoutSeconds;
553
- if (typeof timeoutSeconds === 'number' && Number.isFinite(timeoutSeconds) && timeoutSeconds > 0) {
554
+ if (typeof timeoutSeconds === "number" &&
555
+ Number.isFinite(timeoutSeconds) &&
556
+ timeoutSeconds > 0) {
554
557
  const timeoutMs = timeoutSeconds * 1000;
555
558
  if (timeoutMs > maxAgeMs) {
556
559
  maxAgeMs = timeoutMs;
@@ -563,7 +566,7 @@ async function getLastActivityMs(meta) {
563
566
  const candidates = new Set();
564
567
  candidates.add(logPath(meta.id));
565
568
  const modelNames = new Set();
566
- if (typeof meta.model === 'string' && meta.model.length > 0) {
569
+ if (typeof meta.model === "string" && meta.model.length > 0) {
567
570
  modelNames.add(meta.model);
568
571
  }
569
572
  if (Array.isArray(meta.models)) {
@@ -602,10 +605,10 @@ function isProcessAlive(pid) {
602
605
  }
603
606
  catch (error) {
604
607
  const code = error instanceof Error ? error.code : undefined;
605
- if (code === 'ESRCH' || code === 'EINVAL') {
608
+ if (code === "ESRCH" || code === "EINVAL") {
606
609
  return false;
607
610
  }
608
- if (code === 'EPERM') {
611
+ if (code === "EPERM") {
609
612
  return true;
610
613
  }
611
614
  return true;
@@ -629,11 +632,11 @@ async function isPortOpen(host, port) {
629
632
  resolve(result);
630
633
  };
631
634
  const timer = setTimeout(() => cleanup(false), CHROME_RUNTIME_TIMEOUT_MS);
632
- socket.once('connect', () => {
635
+ socket.once("connect", () => {
633
636
  clearTimeout(timer);
634
637
  cleanup(true);
635
638
  });
636
- socket.once('error', () => {
639
+ socket.once("error", () => {
637
640
  clearTimeout(timer);
638
641
  cleanup(false);
639
642
  });
@@ -1,4 +1,4 @@
1
- import { ensureSessionStorage, initializeSession, readSessionMetadata, updateSessionMetadata, createSessionLogWriter, readSessionLog, readModelLog, readSessionRequest, listSessionsMetadata, filterSessionsByRange, deleteSessionsOlderThan, updateModelRunMetadata, getSessionPaths, getSessionsDir, } from './sessionManager.js';
1
+ import { ensureSessionStorage, initializeSession, readSessionMetadata, updateSessionMetadata, createSessionLogWriter, readSessionLog, readModelLog, readSessionRequest, listSessionsMetadata, filterSessionsByRange, deleteSessionsOlderThan, updateModelRunMetadata, getSessionPaths, getSessionsDir, } from "./sessionManager.js";
2
2
  class FileSessionStore {
3
3
  ensureStorage() {
4
4
  return ensureSessionStorage();
@@ -44,9 +44,9 @@ class FileSessionStore {
44
44
  }
45
45
  }
46
46
  export const sessionStore = new FileSessionStore();
47
- export { wait } from './sessionManager.js';
47
+ export { wait } from "./sessionManager.js";
48
48
  export async function pruneOldSessions(hours, log) {
49
- if (typeof hours !== 'number' || Number.isNaN(hours) || hours <= 0) {
49
+ if (typeof hours !== "number" || Number.isNaN(hours) || hours <= 0) {
50
50
  return;
51
51
  }
52
52
  const result = await sessionStore.deleteOlderThan({ hours });
@@ -1,6 +1,6 @@
1
- import { readFileSync } from 'node:fs';
2
- import path from 'node:path';
3
- import { fileURLToPath } from 'node:url';
1
+ import { readFileSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
4
  let cachedVersion = null;
5
5
  export function getCliVersion() {
6
6
  if (cachedVersion) {
@@ -15,18 +15,18 @@ function readVersionFromPackage() {
15
15
  const filesystemRoot = path.parse(currentDir).root;
16
16
  // biome-ignore lint/nursery/noUnnecessaryConditions: deliberate sentinel loop to walk up directories
17
17
  while (true) {
18
- const candidate = path.join(currentDir, 'package.json');
18
+ const candidate = path.join(currentDir, "package.json");
19
19
  try {
20
- const raw = readFileSync(candidate, 'utf8');
20
+ const raw = readFileSync(candidate, "utf8");
21
21
  const parsed = JSON.parse(raw);
22
- const version = typeof parsed.version === 'string' && parsed.version.trim().length > 0
22
+ const version = typeof parsed.version === "string" && parsed.version.trim().length > 0
23
23
  ? parsed.version.trim()
24
- : '0.0.0';
24
+ : "0.0.0";
25
25
  return version;
26
26
  }
27
27
  catch (error) {
28
- const code = error instanceof Error && 'code' in error ? error.code : undefined;
29
- if (code && code !== 'ENOENT') {
28
+ const code = error instanceof Error && "code" in error ? error.code : undefined;
29
+ if (code && code !== "ENOENT") {
30
30
  break;
31
31
  }
32
32
  }
@@ -35,5 +35,5 @@ function readVersionFromPackage() {
35
35
  }
36
36
  currentDir = path.dirname(currentDir);
37
37
  }
38
- return '0.0.0';
38
+ return "0.0.0";
39
39
  }
@@ -18,7 +18,9 @@ export APP_STORE_CONNECT_ISSUER_ID=YYYYYYYY-YYYY-YYYY-YYYY-YYYYYYYYYYYY
18
18
  - Output: `OracleNotifier.app` (arm64 only), bundled with `OracleIcon.icns`.
19
19
 
20
20
  ## Usage
21
+
21
22
  The CLI prefers this helper on macOS; if it fails or is missing, it falls back to toasted-notifier/terminal-notifier.
22
23
 
23
24
  ## Permissions
25
+
24
26
  After first run, allow notifications for “Oracle Notifier” in System Settings → Notifications.