@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,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,
@@ -181,8 +179,12 @@ export async function initializeSession(options, cwd, notifications, baseSlugOve
181
179
  options: {
182
180
  prompt: options.prompt,
183
181
  file: options.file ?? [],
182
+ maxFileSizeBytes: options.maxFileSizeBytes,
184
183
  model: options.model,
185
184
  models: modelList,
185
+ previousResponseId: options.previousResponseId,
186
+ followupSessionId: options.followupSessionId,
187
+ followupModel: options.followupModel,
186
188
  effectiveModelId: options.effectiveModelId,
187
189
  maxInput: options.maxInput,
188
190
  system: options.system,
@@ -216,19 +218,19 @@ export async function initializeSession(options, cwd, notifications, baseSlugOve
216
218
  },
217
219
  };
218
220
  await ensureDir(modelsDir(sessionId));
219
- await fs.writeFile(metaPath(sessionId), JSON.stringify(metadata, null, 2), 'utf8');
221
+ await fs.writeFile(metaPath(sessionId), JSON.stringify(metadata, null, 2), "utf8");
220
222
  await Promise.all((modelList.length > 0 ? modelList : [metadata.model ?? DEFAULT_MODEL]).map(async (modelName) => {
221
223
  const jsonPath = modelJsonPath(sessionId, modelName);
222
224
  const logFilePath = modelLogPath(sessionId, modelName);
223
225
  const modelRecord = {
224
226
  model: modelName,
225
- status: 'pending',
227
+ status: "pending",
226
228
  log: { path: path.relative(sessionDir(sessionId), logFilePath) },
227
229
  };
228
- await fs.writeFile(jsonPath, JSON.stringify(modelRecord, null, 2), 'utf8');
229
- await fs.writeFile(logFilePath, '', 'utf8');
230
+ await fs.writeFile(jsonPath, JSON.stringify(modelRecord, null, 2), "utf8");
231
+ await fs.writeFile(logFilePath, "", "utf8");
230
232
  }));
231
- await fs.writeFile(logPath(sessionId), '', 'utf8');
233
+ await fs.writeFile(logPath(sessionId), "", "utf8");
232
234
  return metadata;
233
235
  }
234
236
  export async function readSessionMetadata(sessionId) {
@@ -247,12 +249,12 @@ export async function updateSessionMetadata(sessionId, updates) {
247
249
  (await readLegacySessionMetadata(sessionId)) ??
248
250
  { id: sessionId };
249
251
  const next = { ...existing, ...updates };
250
- await fs.writeFile(metaPath(sessionId), JSON.stringify(next, null, 2), 'utf8');
252
+ await fs.writeFile(metaPath(sessionId), JSON.stringify(next, null, 2), "utf8");
251
253
  return next;
252
254
  }
253
255
  async function readModernSessionMetadata(sessionId) {
254
256
  try {
255
- const raw = await fs.readFile(metaPath(sessionId), 'utf8');
257
+ const raw = await fs.readFile(metaPath(sessionId), "utf8");
256
258
  const parsed = JSON.parse(raw);
257
259
  if (!isSessionMetadataRecord(parsed)) {
258
260
  return null;
@@ -267,7 +269,7 @@ async function readModernSessionMetadata(sessionId) {
267
269
  }
268
270
  async function readLegacySessionMetadata(sessionId) {
269
271
  try {
270
- const raw = await fs.readFile(legacySessionPath(sessionId), 'utf8');
272
+ const raw = await fs.readFile(legacySessionPath(sessionId), "utf8");
271
273
  const parsed = JSON.parse(raw);
272
274
  const enriched = await attachModelRuns(parsed, sessionId);
273
275
  const runtimeChecked = await markDeadBrowser(enriched, { persist: false });
@@ -278,7 +280,7 @@ async function readLegacySessionMetadata(sessionId) {
278
280
  }
279
281
  }
280
282
  function isSessionMetadataRecord(value) {
281
- return Boolean(value && typeof value.id === 'string' && value.status);
283
+ return Boolean(value && typeof value.id === "string" && value.status);
282
284
  }
283
285
  async function attachModelRuns(meta, sessionId) {
284
286
  const runs = await listModelRunFiles(sessionId);
@@ -292,8 +294,8 @@ export function createSessionLogWriter(sessionId, model) {
292
294
  if (model) {
293
295
  void ensureDir(modelsDir(sessionId));
294
296
  }
295
- const stream = createWriteStream(targetPath, { flags: 'a' });
296
- const logLine = (line = '') => {
297
+ const stream = createWriteStream(targetPath, { flags: "a" });
298
+ const logLine = (line = "") => {
297
299
  stream.write(`${line}\n`);
298
300
  };
299
301
  const writeChunk = (chunk) => {
@@ -316,7 +318,7 @@ export async function listSessionsMetadata() {
316
318
  }
317
319
  return metas.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
318
320
  }
319
- export function filterSessionsByRange(metas, { hours = 24, includeAll = false, limit = 100 }) {
321
+ export function filterSessionsByRange(metas, { hours = 24, includeAll = false, limit = 100, }) {
320
322
  const maxLimit = Math.min(limit, MAX_STATUS_LIMIT);
321
323
  let filtered = metas;
322
324
  if (!includeAll) {
@@ -331,29 +333,31 @@ export async function readSessionLog(sessionId) {
331
333
  const runs = await listModelRunFiles(sessionId);
332
334
  if (runs.length === 0) {
333
335
  try {
334
- return await fs.readFile(logPath(sessionId), 'utf8');
336
+ return await fs.readFile(logPath(sessionId), "utf8");
335
337
  }
336
338
  catch {
337
- return '';
339
+ return "";
338
340
  }
339
341
  }
340
342
  const sections = [];
341
343
  let hasContent = false;
342
344
  const ordered = runs
343
345
  .slice()
344
- .sort((a, b) => (a.startedAt && b.startedAt ? a.startedAt.localeCompare(b.startedAt) : a.model.localeCompare(b.model)));
346
+ .sort((a, b) => a.startedAt && b.startedAt
347
+ ? a.startedAt.localeCompare(b.startedAt)
348
+ : a.model.localeCompare(b.model));
345
349
  for (const run of ordered) {
346
350
  const logFile = run.log?.path
347
351
  ? path.isAbsolute(run.log.path)
348
352
  ? run.log.path
349
353
  : path.join(sessionDir(sessionId), run.log.path)
350
354
  : modelLogPath(sessionId, run.model);
351
- let body = '';
355
+ let body = "";
352
356
  try {
353
- body = await fs.readFile(logFile, 'utf8');
357
+ body = await fs.readFile(logFile, "utf8");
354
358
  }
355
359
  catch {
356
- body = '';
360
+ body = "";
357
361
  }
358
362
  if (body.length > 0) {
359
363
  hasContent = true;
@@ -362,20 +366,20 @@ export async function readSessionLog(sessionId) {
362
366
  }
363
367
  if (!hasContent) {
364
368
  try {
365
- return await fs.readFile(logPath(sessionId), 'utf8');
369
+ return await fs.readFile(logPath(sessionId), "utf8");
366
370
  }
367
371
  catch {
368
372
  // ignore and return structured header-only log
369
373
  }
370
374
  }
371
- return sections.join('\n\n');
375
+ return sections.join("\n\n");
372
376
  }
373
377
  export async function readModelLog(sessionId, model) {
374
378
  try {
375
- return await fs.readFile(modelLogPath(sessionId, model), 'utf8');
379
+ return await fs.readFile(modelLogPath(sessionId, model), "utf8");
376
380
  }
377
381
  catch {
378
- return '';
382
+ return "";
379
383
  }
380
384
  }
381
385
  export async function readSessionRequest(sessionId) {
@@ -384,7 +388,7 @@ export async function readSessionRequest(sessionId) {
384
388
  return modern.options;
385
389
  }
386
390
  try {
387
- const raw = await fs.readFile(requestPath(sessionId), 'utf8');
391
+ const raw = await fs.readFile(requestPath(sessionId), "utf8");
388
392
  const parsed = JSON.parse(raw);
389
393
  if (isSessionMetadataRecord(parsed)) {
390
394
  return parsed.options ?? null;
@@ -448,7 +452,7 @@ export async function getSessionPaths(sessionId) {
448
452
  }
449
453
  }
450
454
  if (missing.length > 0) {
451
- throw new Error(`Session "${sessionId}" is missing: ${missing.join(', ')}`);
455
+ throw new Error(`Session "${sessionId}" is missing: ${missing.join(", ")}`);
452
456
  }
453
457
  return { dir, metadata, log, request };
454
458
  }
@@ -456,7 +460,7 @@ async function markZombie(meta, { persist }) {
456
460
  if (!(await isZombie(meta))) {
457
461
  return meta;
458
462
  }
459
- if (meta.mode === 'browser') {
463
+ if (meta.mode === "browser") {
460
464
  const runtime = meta.browser?.runtime;
461
465
  if (runtime) {
462
466
  const signals = [];
@@ -464,7 +468,7 @@ async function markZombie(meta, { persist }) {
464
468
  signals.push(isProcessAlive(runtime.chromePid));
465
469
  }
466
470
  if (runtime.chromePort) {
467
- const host = runtime.chromeHost ?? '127.0.0.1';
471
+ const host = runtime.chromeHost ?? "127.0.0.1";
468
472
  signals.push(await isPortOpen(host, runtime.chromePort));
469
473
  }
470
474
  if (signals.some(Boolean)) {
@@ -475,17 +479,17 @@ async function markZombie(meta, { persist }) {
475
479
  const maxAgeMs = resolveZombieMaxAgeMs(meta);
476
480
  const updated = {
477
481
  ...meta,
478
- status: 'error',
482
+ status: "error",
479
483
  errorMessage: `Session marked as zombie (> ${formatElapsed(maxAgeMs)} stale)`,
480
484
  completedAt: new Date().toISOString(),
481
485
  };
482
486
  if (persist) {
483
- await fs.writeFile(metaPath(meta.id), JSON.stringify(updated, null, 2), 'utf8');
487
+ await fs.writeFile(metaPath(meta.id), JSON.stringify(updated, null, 2), "utf8");
484
488
  }
485
489
  return updated;
486
490
  }
487
491
  async function markDeadBrowser(meta, { persist }) {
488
- if (meta.status !== 'running' || meta.mode !== 'browser') {
492
+ if (meta.status !== "running" || meta.mode !== "browser") {
489
493
  return meta;
490
494
  }
491
495
  const runtime = meta.browser?.runtime;
@@ -497,7 +501,7 @@ async function markDeadBrowser(meta, { persist }) {
497
501
  signals.push(isProcessAlive(runtime.chromePid));
498
502
  }
499
503
  if (runtime.chromePort) {
500
- const host = runtime.chromeHost ?? '127.0.0.1';
504
+ const host = runtime.chromeHost ?? "127.0.0.1";
501
505
  signals.push(await isPortOpen(host, runtime.chromePort));
502
506
  }
503
507
  if (signals.length === 0 || signals.some(Boolean)) {
@@ -506,24 +510,24 @@ async function markDeadBrowser(meta, { persist }) {
506
510
  const response = meta.response
507
511
  ? {
508
512
  ...meta.response,
509
- status: 'error',
510
- incompleteReason: meta.response.incompleteReason ?? 'chrome-disconnected',
513
+ status: "error",
514
+ incompleteReason: meta.response.incompleteReason ?? "chrome-disconnected",
511
515
  }
512
- : { status: 'error', incompleteReason: 'chrome-disconnected' };
516
+ : { status: "error", incompleteReason: "chrome-disconnected" };
513
517
  const updated = {
514
518
  ...meta,
515
- status: 'error',
516
- errorMessage: 'Browser session ended (Chrome is no longer reachable)',
519
+ status: "error",
520
+ errorMessage: "Browser session ended (Chrome is no longer reachable)",
517
521
  completedAt: new Date().toISOString(),
518
522
  response,
519
523
  };
520
524
  if (persist) {
521
- await fs.writeFile(metaPath(meta.id), JSON.stringify(updated, null, 2), 'utf8');
525
+ await fs.writeFile(metaPath(meta.id), JSON.stringify(updated, null, 2), "utf8");
522
526
  }
523
527
  return updated;
524
528
  }
525
529
  async function isZombie(meta) {
526
- if (meta.status !== 'running') {
530
+ if (meta.status !== "running") {
527
531
  return false;
528
532
  }
529
533
  const reference = meta.startedAt ?? meta.createdAt;
@@ -542,11 +546,13 @@ async function isZombie(meta) {
542
546
  }
543
547
  function resolveZombieMaxAgeMs(meta) {
544
548
  const explicit = meta.options?.zombieTimeoutMs;
545
- const hasExplicit = typeof explicit === 'number' && Number.isFinite(explicit) && explicit > 0;
549
+ const hasExplicit = typeof explicit === "number" && Number.isFinite(explicit) && explicit > 0;
546
550
  let maxAgeMs = hasExplicit ? explicit : ZOMBIE_MAX_AGE_MS;
547
551
  if (!hasExplicit) {
548
552
  const timeoutSeconds = meta.options?.timeoutSeconds;
549
- if (typeof timeoutSeconds === 'number' && Number.isFinite(timeoutSeconds) && timeoutSeconds > 0) {
553
+ if (typeof timeoutSeconds === "number" &&
554
+ Number.isFinite(timeoutSeconds) &&
555
+ timeoutSeconds > 0) {
550
556
  const timeoutMs = timeoutSeconds * 1000;
551
557
  if (timeoutMs > maxAgeMs) {
552
558
  maxAgeMs = timeoutMs;
@@ -559,7 +565,7 @@ async function getLastActivityMs(meta) {
559
565
  const candidates = new Set();
560
566
  candidates.add(logPath(meta.id));
561
567
  const modelNames = new Set();
562
- if (typeof meta.model === 'string' && meta.model.length > 0) {
568
+ if (typeof meta.model === "string" && meta.model.length > 0) {
563
569
  modelNames.add(meta.model);
564
570
  }
565
571
  if (Array.isArray(meta.models)) {
@@ -598,10 +604,10 @@ function isProcessAlive(pid) {
598
604
  }
599
605
  catch (error) {
600
606
  const code = error instanceof Error ? error.code : undefined;
601
- if (code === 'ESRCH' || code === 'EINVAL') {
607
+ if (code === "ESRCH" || code === "EINVAL") {
602
608
  return false;
603
609
  }
604
- if (code === 'EPERM') {
610
+ if (code === "EPERM") {
605
611
  return true;
606
612
  }
607
613
  return true;
@@ -625,11 +631,11 @@ async function isPortOpen(host, port) {
625
631
  resolve(result);
626
632
  };
627
633
  const timer = setTimeout(() => cleanup(false), CHROME_RUNTIME_TIMEOUT_MS);
628
- socket.once('connect', () => {
634
+ socket.once("connect", () => {
629
635
  clearTimeout(timer);
630
636
  cleanup(true);
631
637
  });
632
- socket.once('error', () => {
638
+ socket.once("error", () => {
633
639
  clearTimeout(timer);
634
640
  cleanup(false);
635
641
  });
@@ -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.