@steipete/oracle 0.5.6 → 0.6.1

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 (34) hide show
  1. package/README.md +3 -3
  2. package/dist/bin/oracle-cli.js +8 -2
  3. package/dist/src/browser/actions/assistantResponse.js +65 -6
  4. package/dist/src/browser/actions/modelSelection.js +22 -0
  5. package/dist/src/browser/actions/promptComposer.js +67 -3
  6. package/dist/src/browser/actions/thinkingTime.js +190 -0
  7. package/dist/src/browser/config.js +1 -0
  8. package/dist/src/browser/constants.js +1 -1
  9. package/dist/src/browser/index.js +106 -74
  10. package/dist/src/browser/pageActions.js +1 -1
  11. package/dist/src/browser/profileState.js +171 -0
  12. package/dist/src/browser/prompt.js +63 -19
  13. package/dist/src/browser/sessionRunner.js +10 -6
  14. package/dist/src/cli/browserConfig.js +6 -2
  15. package/dist/src/cli/sessionDisplay.js +8 -1
  16. package/dist/src/cli/sessionRunner.js +2 -8
  17. package/dist/src/cli/tui/index.js +1 -0
  18. package/dist/src/config.js +2 -3
  19. package/dist/src/oracle/config.js +38 -3
  20. package/dist/src/oracle/errors.js +3 -3
  21. package/dist/src/oracle/gemini.js +4 -1
  22. package/dist/src/oracle/run.js +4 -7
  23. package/dist/src/oracleHome.js +13 -0
  24. package/dist/src/remote/server.js +17 -11
  25. package/dist/src/sessionManager.js +10 -8
  26. package/dist/src/sessionStore.js +2 -2
  27. package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
  28. package/dist/vendor/oracle-notifier/build-notifier.sh +0 -0
  29. package/dist/vendor/oracle-notifier/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
  30. package/dist/vendor/oracle-notifier/oracle-notifier/build-notifier.sh +0 -0
  31. package/package.json +22 -38
  32. package/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
  33. package/vendor/oracle-notifier/build-notifier.sh +0 -0
  34. package/vendor/oracle-notifier/README.md +0 -24
@@ -19,7 +19,6 @@ import { formatElapsed } from '../oracle/format.js';
19
19
  import { sanitizeOscProgress } from './oscUtils.js';
20
20
  import { readFiles } from '../oracle/files.js';
21
21
  import { formatUSD } from '../oracle/format.js';
22
- import { SESSIONS_DIR } from '../sessionManager.js';
23
22
  import { cwd as getCwd } from 'node:process';
24
23
  const isTty = process.stdout.isTTY;
25
24
  const dim = (text) => (isTty ? kleur.dim(text) : text);
@@ -361,11 +360,6 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
361
360
  }
362
361
  : undefined,
363
362
  });
364
- if (mode === 'browser') {
365
- log(dim('Next steps (browser fallback):')); // guides users when automation breaks
366
- log(dim('- Rerun with --engine api to bypass Chrome entirely.'));
367
- log(dim('- Or rerun with --engine api --render-markdown [--file …] to generate a single markdown bundle you can paste into ChatGPT manually (add --browser-bundle-files if you still want attachments).'));
368
- }
369
363
  if (modelForStatus) {
370
364
  await sessionStore.updateModelRun(sessionMeta.id, modelForStatus, {
371
365
  status: 'error',
@@ -386,7 +380,7 @@ async function writeAssistantOutput(targetPath, content, log) {
386
380
  return;
387
381
  }
388
382
  const normalizedTarget = path.resolve(targetPath);
389
- const normalizedSessionsDir = path.resolve(SESSIONS_DIR);
383
+ const normalizedSessionsDir = path.resolve(sessionStore.sessionsDir());
390
384
  if (normalizedTarget === normalizedSessionsDir ||
391
385
  normalizedTarget.startsWith(`${normalizedSessionsDir}${path.sep}`)) {
392
386
  log(dim(`write-output skipped: refusing to write inside session storage (${normalizedSessionsDir}).`));
@@ -442,7 +436,7 @@ function buildFallbackPath(original) {
442
436
  const dir = getCwd();
443
437
  const candidate = ext ? `${stem}.fallback${ext}` : `${stem}.fallback`;
444
438
  const fallback = path.join(dir, candidate);
445
- const normalizedSessionsDir = path.resolve(SESSIONS_DIR);
439
+ const normalizedSessionsDir = path.resolve(sessionStore.sessionsDir());
446
440
  const normalizedFallback = path.resolve(fallback);
447
441
  if (normalizedFallback === normalizedSessionsDir ||
448
442
  normalizedFallback.startsWith(`${normalizedSessionsDir}${path.sep}`)) {
@@ -388,6 +388,7 @@ async function askOracleFlow(version, userConfig) {
388
388
  sessionId: undefined,
389
389
  verbose: false,
390
390
  heartbeatIntervalMs: undefined,
391
+ browserAttachments: 'auto',
391
392
  browserInlineFiles: false,
392
393
  browserBundleFiles: false,
393
394
  background: undefined,
@@ -1,10 +1,9 @@
1
1
  import fs from 'node:fs/promises';
2
- import os from 'node:os';
3
2
  import path from 'node:path';
4
3
  import JSON5 from 'json5';
4
+ import { getOracleHomeDir } from './oracleHome.js';
5
5
  function resolveConfigPath() {
6
- const oracleHome = process.env.ORACLE_HOME_DIR ?? path.join(os.homedir(), '.oracle');
7
- return path.join(oracleHome, 'config.json');
6
+ return path.join(getOracleHomeDir(), 'config.json');
8
7
  }
9
8
  export async function loadUserConfig() {
10
9
  const CONFIG_PATH = resolveConfigPath();
@@ -3,17 +3,18 @@ import { countTokens as countTokensGpt5Pro } from 'gpt-tokenizer/model/gpt-5-pro
3
3
  import { countTokens as countTokensAnthropicRaw } from '@anthropic-ai/tokenizer';
4
4
  import { stringifyTokenizerInput } from './tokenStringifier.js';
5
5
  export const DEFAULT_MODEL = 'gpt-5.1-pro';
6
- export const PRO_MODELS = new Set(['gpt-5.1-pro', 'gpt-5-pro', 'claude-4.5-sonnet', 'claude-4.1-opus']);
6
+ export const PRO_MODELS = new Set(['gpt-5.1-pro', 'gpt-5-pro', 'gpt-5.2-pro', 'claude-4.5-sonnet', 'claude-4.1-opus']);
7
7
  const countTokensAnthropic = (input) => countTokensAnthropicRaw(stringifyTokenizerInput(input));
8
8
  export const MODEL_CONFIGS = {
9
9
  'gpt-5.1-pro': {
10
10
  model: 'gpt-5.1-pro',
11
+ apiModel: 'gpt-5.2-pro',
11
12
  provider: 'openai',
12
13
  tokenizer: countTokensGpt5Pro,
13
14
  inputLimit: 196000,
14
15
  pricing: {
15
- inputPerToken: 15 / 1_000_000,
16
- outputPerToken: 120 / 1_000_000,
16
+ inputPerToken: 21 / 1_000_000,
17
+ outputPerToken: 168 / 1_000_000,
17
18
  },
18
19
  reasoning: null,
19
20
  },
@@ -50,6 +51,40 @@ export const MODEL_CONFIGS = {
50
51
  },
51
52
  reasoning: { effort: 'high' },
52
53
  },
54
+ 'gpt-5.2': {
55
+ model: 'gpt-5.2',
56
+ provider: 'openai',
57
+ tokenizer: countTokensGpt5,
58
+ inputLimit: 196000,
59
+ pricing: {
60
+ inputPerToken: 1.75 / 1_000_000,
61
+ outputPerToken: 14 / 1_000_000,
62
+ },
63
+ reasoning: { effort: 'xhigh' },
64
+ },
65
+ 'gpt-5.2-instant': {
66
+ model: 'gpt-5.2-instant',
67
+ apiModel: 'gpt-5.2-chat-latest',
68
+ provider: 'openai',
69
+ tokenizer: countTokensGpt5,
70
+ inputLimit: 196000,
71
+ pricing: {
72
+ inputPerToken: 1.75 / 1_000_000,
73
+ outputPerToken: 14 / 1_000_000,
74
+ },
75
+ reasoning: null,
76
+ },
77
+ 'gpt-5.2-pro': {
78
+ model: 'gpt-5.2-pro',
79
+ provider: 'openai',
80
+ tokenizer: countTokensGpt5Pro,
81
+ inputLimit: 196000,
82
+ pricing: {
83
+ inputPerToken: 21 / 1_000_000,
84
+ outputPerToken: 168 / 1_000_000,
85
+ },
86
+ reasoning: { effort: 'xhigh' },
87
+ },
53
88
  'gemini-3-pro': {
54
89
  model: 'gemini-3-pro',
55
90
  provider: 'google',
@@ -95,13 +95,13 @@ export function toTransportError(error, model) {
95
95
  const apiMessage = apiError.error?.message ||
96
96
  apiError.message ||
97
97
  (apiError.status ? `${apiError.status} OpenAI API error` : 'OpenAI API error');
98
- // TODO: Remove once gpt-5.1-pro is available via the Responses API.
99
- if (model === 'gpt-5.1-pro' &&
98
+ // Friendly guidance when a pro-tier model isn't available on this base URL / API key.
99
+ if (model === 'gpt-5.2-pro' &&
100
100
  (code === 'model_not_found' ||
101
101
  messageText.includes('does not exist') ||
102
102
  messageText.includes('unknown model') ||
103
103
  messageText.includes('model_not_found'))) {
104
- return new OracleTransportError('model-unavailable', 'gpt-5.1-pro is not yet available on this API base. Using gpt-5-pro until OpenAI enables it. // TODO: Remove once gpt-5.1-pro is available', apiError);
104
+ return new OracleTransportError('model-unavailable', 'gpt-5.2-pro is not available on this API base/key. Try gpt-5-pro or gpt-5.2, or switch to the browser engine.', apiError);
105
105
  }
106
106
  if (apiError.status === 404 || apiError.status === 405) {
107
107
  return new OracleTransportError('unsupported-endpoint', 'HTTP 404/405 from the Responses API; this base URL or gateway likely does not expose /v1/responses. Set OPENAI_BASE_URL to api.openai.com/v1, update your Azure API version/deployment, or use the browser engine.', apiError);
@@ -1,10 +1,13 @@
1
1
  import { GoogleGenAI, HarmCategory, HarmBlockThreshold } from '@google/genai';
2
2
  const MODEL_ID_MAP = {
3
3
  'gemini-3-pro': 'gemini-3-pro-preview',
4
- 'gpt-5.1-pro': 'gpt-5.1-pro', // unused, normalize TS map
4
+ 'gpt-5.1-pro': 'gpt-5.1-pro',
5
5
  'gpt-5-pro': 'gpt-5-pro',
6
6
  'gpt-5.1': 'gpt-5.1',
7
7
  'gpt-5.1-codex': 'gpt-5.1-codex',
8
+ 'gpt-5.2': 'gpt-5.2',
9
+ 'gpt-5.2-instant': 'gpt-5.2-instant',
10
+ 'gpt-5.2-pro': 'gpt-5.2-pro',
8
11
  'claude-4.5-sonnet': 'claude-4.5-sonnet',
9
12
  'claude-4.1-opus': 'claude-4.1-opus',
10
13
  'grok-4.1': 'grok-4.1',
@@ -186,17 +186,14 @@ export async function runOracle(options, deps = {}) {
186
186
  : DEFAULT_TIMEOUT_NON_PRO_MS / 1000
187
187
  : options.timeoutSeconds;
188
188
  const timeoutMs = timeoutSeconds * 1000;
189
- const apiModelFromConfig = modelConfig.apiModel ?? modelConfig.model;
190
- const modelDowngraded = apiModelFromConfig === 'gpt-5.1-pro';
191
- const resolvedApiModelId = modelDowngraded ? 'gpt-5-pro' : apiModelFromConfig;
192
189
  // Track the concrete model id we dispatch to (especially for Gemini preview aliases)
193
190
  const effectiveModelId = options.effectiveModelId ??
194
191
  (options.model.startsWith('gemini')
195
192
  ? resolveGeminiModelId(options.model)
196
- : resolvedApiModelId);
193
+ : (modelConfig.apiModel ?? modelConfig.model));
197
194
  const headerModelLabel = richTty ? chalk.cyan(modelConfig.model) : modelConfig.model;
198
195
  const requestBody = buildRequestBody({
199
- modelConfig: { ...modelConfig, apiModel: resolvedApiModelId },
196
+ modelConfig,
200
197
  systemPrompt,
201
198
  userPrompt: promptWithFiles,
202
199
  searchEnabled,
@@ -222,8 +219,8 @@ export async function runOracle(options, deps = {}) {
222
219
  if (baseUrl) {
223
220
  log(dim(`Base URL: ${formatBaseUrlForLog(baseUrl)}`));
224
221
  }
225
- if (modelDowngraded) {
226
- log(dim('gpt-5.1-pro is not yet available via API; sending request with gpt-5-pro instead.'));
222
+ if (effectiveModelId !== modelConfig.model) {
223
+ log(dim(`Resolved model: ${modelConfig.model} ${effectiveModelId}`));
227
224
  }
228
225
  if (options.background && !supportsBackground) {
229
226
  log(dim('Background runs are not supported for this model; streaming in foreground instead.'));
@@ -0,0 +1,13 @@
1
+ import os from 'node:os';
2
+ import path from 'node:path';
3
+ let oracleHomeDirOverride = null;
4
+ /**
5
+ * Test-only hook: avoid mutating process.env (shared across Vitest worker threads).
6
+ * This override is scoped to the current Node worker.
7
+ */
8
+ export function setOracleHomeDirOverrideForTest(dir) {
9
+ oracleHomeDirOverride = dir;
10
+ }
11
+ export function getOracleHomeDir() {
12
+ return oracleHomeDirOverride ?? process.env.ORACLE_HOME_DIR ?? path.join(os.homedir(), '.oracle');
13
+ }
@@ -4,12 +4,12 @@ import path from 'node:path';
4
4
  import net from 'node:net';
5
5
  import { randomBytes, randomUUID } from 'node:crypto';
6
6
  import { spawn, spawnSync } from 'node:child_process';
7
- import { existsSync } from 'node:fs';
8
7
  import { mkdtemp, rm, mkdir, writeFile } from 'node:fs/promises';
9
8
  import chalk from 'chalk';
10
9
  import { runBrowserMode } from '../browserMode.js';
11
10
  import { loadChromeCookies } from '../browser/chromeCookies.js';
12
11
  import { CHATGPT_URL } from '../browser/constants.js';
12
+ import { cleanupStaleProfileState, readDevToolsPort, verifyDevToolsReachable, writeChromePid, writeDevToolsActivePort, } from '../browser/profileState.js';
13
13
  import { normalizeChatgptUrl } from '../browser/utils.js';
14
14
  async function findAvailablePort() {
15
15
  return await new Promise((resolve, reject) => {
@@ -209,10 +209,17 @@ export async function serveRemote(options = {}) {
209
209
  if (preferManualLogin) {
210
210
  await mkdir(manualProfileDir, { recursive: true });
211
211
  console.log(`Cookie extraction is unavailable on this platform. Using manual-login Chrome profile at ${manualProfileDir}. Remote runs will reuse this profile; sign in once when the browser opens.`);
212
- const devtoolsPortFile = path.join(manualProfileDir, 'DevToolsActivePort');
213
- const alreadyRunning = existsSync(devtoolsPortFile);
214
- if (alreadyRunning) {
215
- console.log('Detected an existing automation Chrome session; will reuse it for manual login.');
212
+ const existingPort = await readDevToolsPort(manualProfileDir);
213
+ if (existingPort) {
214
+ const reachable = await verifyDevToolsReachable({ port: existingPort });
215
+ if (reachable.ok) {
216
+ console.log('Detected an existing automation Chrome session; will reuse it for manual login.');
217
+ }
218
+ else {
219
+ console.log(`Found stale DevToolsActivePort (port ${existingPort}, ${reachable.error}); launching a fresh manual-login Chrome.`);
220
+ await cleanupStaleProfileState(manualProfileDir, console.log, { lockRemovalMode: 'never' });
221
+ void launchManualLoginChrome(manualProfileDir, CHATGPT_URL, console.log);
222
+ }
216
223
  }
217
224
  else {
218
225
  void launchManualLoginChrome(manualProfileDir, CHATGPT_URL, console.log);
@@ -459,12 +466,11 @@ async function launchManualLoginChrome(profileDir, url, logger) {
459
466
  });
460
467
  const chosenPort = chrome?.port ?? debugPort ?? null;
461
468
  if (chosenPort) {
462
- // Write DevToolsActivePort eagerly so maybeReuseRunningChrome can attach on the next run
463
- const devtoolsFile = path.join(profileDir, 'DevToolsActivePort');
464
- const devtoolsFileDefault = path.join(profileDir, 'Default', 'DevToolsActivePort');
465
- const contents = `${chosenPort}\n/devtools/browser`;
466
- await writeFile(devtoolsFile, contents).catch(() => undefined);
467
- await writeFile(devtoolsFileDefault, contents).catch(() => undefined);
469
+ // Persist DevToolsActivePort eagerly so future runs can attach/reuse this Chrome.
470
+ await writeDevToolsActivePort(profileDir, chosenPort);
471
+ if (chrome?.pid) {
472
+ await writeChromePid(profileDir, chrome.pid);
473
+ }
468
474
  logger(`Manual-login Chrome DevTools port: ${chosenPort}`);
469
475
  logger(`If needed, DevTools JSON at http://127.0.0.1:${chosenPort}/json/version`);
470
476
  }
@@ -1,11 +1,12 @@
1
- import os from 'node:os';
2
1
  import path from 'node:path';
3
2
  import fs from 'node:fs/promises';
4
3
  import { createWriteStream } from 'node:fs';
5
4
  import { DEFAULT_MODEL } from './oracle.js';
6
5
  import { safeModelSlug } from './oracle/modelResolver.js';
7
- const ORACLE_HOME = process.env.ORACLE_HOME_DIR ?? path.join(os.homedir(), '.oracle');
8
- const SESSIONS_DIR = path.join(ORACLE_HOME, 'sessions');
6
+ import { getOracleHomeDir } from './oracleHome.js';
7
+ export function getSessionsDir() {
8
+ return path.join(getOracleHomeDir(), 'sessions');
9
+ }
9
10
  const METADATA_FILENAME = 'meta.json';
10
11
  const LEGACY_SESSION_FILENAME = 'session.json';
11
12
  const LEGACY_REQUEST_FILENAME = 'request.json';
@@ -22,7 +23,7 @@ async function ensureDir(dirPath) {
22
23
  await fs.mkdir(dirPath, { recursive: true });
23
24
  }
24
25
  export async function ensureSessionStorage() {
25
- await ensureDir(SESSIONS_DIR);
26
+ await ensureDir(getSessionsDir());
26
27
  }
27
28
  function slugify(text, maxWords = MAX_SLUG_WORDS) {
28
29
  const normalized = text?.toLowerCase() ?? '';
@@ -50,7 +51,7 @@ export function createSessionId(prompt, customSlug) {
50
51
  return slugify(prompt);
51
52
  }
52
53
  function sessionDir(id) {
53
- return path.join(SESSIONS_DIR, id);
54
+ return path.join(getSessionsDir(), id);
54
55
  }
55
56
  function metaPath(id) {
56
57
  return path.join(sessionDir(id), METADATA_FILENAME);
@@ -191,6 +192,7 @@ export async function initializeSession(options, cwd, notifications) {
191
192
  browserConfig,
192
193
  verbose: options.verbose,
193
194
  heartbeatIntervalMs: options.heartbeatIntervalMs,
195
+ browserAttachments: options.browserAttachments,
194
196
  browserInlineFiles: options.browserInlineFiles,
195
197
  browserBundleFiles: options.browserBundleFiles,
196
198
  background: options.background,
@@ -287,7 +289,7 @@ export function createSessionLogWriter(sessionId, model) {
287
289
  }
288
290
  export async function listSessionsMetadata() {
289
291
  await ensureSessionStorage();
290
- const entries = await fs.readdir(SESSIONS_DIR).catch(() => []);
292
+ const entries = await fs.readdir(getSessionsDir()).catch(() => []);
291
293
  const metas = [];
292
294
  for (const entry of entries) {
293
295
  let meta = await readSessionMetadata(entry);
@@ -379,7 +381,7 @@ export async function readSessionRequest(sessionId) {
379
381
  }
380
382
  export async function deleteSessionsOlderThan({ hours = 24, includeAll = false, } = {}) {
381
383
  await ensureSessionStorage();
382
- const entries = await fs.readdir(SESSIONS_DIR).catch(() => []);
384
+ const entries = await fs.readdir(getSessionsDir()).catch(() => []);
383
385
  if (!entries.length) {
384
386
  return { deleted: 0, remaining: 0 };
385
387
  }
@@ -415,7 +417,7 @@ export async function deleteSessionsOlderThan({ hours = 24, includeAll = false,
415
417
  export async function wait(ms) {
416
418
  return new Promise((resolve) => setTimeout(resolve, ms));
417
419
  }
418
- export { ORACLE_HOME, SESSIONS_DIR, MAX_STATUS_LIMIT };
420
+ export { MAX_STATUS_LIMIT };
419
421
  export { ZOMBIE_MAX_AGE_MS };
420
422
  export async function getSessionPaths(sessionId) {
421
423
  const dir = sessionDir(sessionId);
@@ -1,4 +1,4 @@
1
- import { ensureSessionStorage, initializeSession, readSessionMetadata, updateSessionMetadata, createSessionLogWriter, readSessionLog, readModelLog, readSessionRequest, listSessionsMetadata, filterSessionsByRange, deleteSessionsOlderThan, updateModelRunMetadata, getSessionPaths, SESSIONS_DIR, } 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();
@@ -40,7 +40,7 @@ class FileSessionStore {
40
40
  return getSessionPaths(sessionId);
41
41
  }
42
42
  sessionsDir() {
43
- return SESSIONS_DIR;
43
+ return getSessionsDir();
44
44
  }
45
45
  }
46
46
  export const sessionStore = new FileSessionStore();
File without changes
package/package.json CHANGED
@@ -1,33 +1,13 @@
1
1
  {
2
2
  "name": "@steipete/oracle",
3
- "version": "0.5.6",
4
- "description": "CLI wrapper around OpenAI Responses API with GPT-5.1 Pro, GPT-5.1, and GPT-5.1 Codex high reasoning modes.",
3
+ "version": "0.6.1",
4
+ "description": "CLI wrapper around OpenAI Responses API with GPT-5.2 Pro (via gpt-5.1-pro alias), GPT-5.2, GPT-5.1, and GPT-5.1 Codex high reasoning modes.",
5
5
  "type": "module",
6
6
  "main": "dist/bin/oracle-cli.js",
7
7
  "bin": {
8
8
  "oracle": "dist/bin/oracle-cli.js",
9
9
  "oracle-mcp": "dist/bin/oracle-mcp.js"
10
10
  },
11
- "scripts": {
12
- "docs:list": "tsx scripts/docs-list.ts",
13
- "build": "tsc -p tsconfig.build.json && pnpm run build:vendor",
14
- "build:vendor": "node -e \"const fs=require('fs'); const path=require('path'); const src=path.join('vendor','oracle-notifier'); const dest=path.join('dist','vendor','oracle-notifier'); fs.mkdirSync(dest,{recursive:true}); if(fs.existsSync(src)){ fs.cpSync(src,dest,{recursive:true,force:true}); }\"",
15
- "start": "pnpm run build && node ./dist/scripts/run-cli.js",
16
- "oracle": "pnpm start",
17
- "check": "pnpm run typecheck",
18
- "typecheck": "tsc --noEmit",
19
- "lint": "pnpm run typecheck && biome lint .",
20
- "test": "vitest run",
21
- "test:mcp": "pnpm run build && pnpm run test:mcp:unit && pnpm run test:mcp:mcporter",
22
- "test:mcp:unit": "vitest run tests/mcp*.test.ts tests/mcp/**/*.test.ts",
23
- "test:mcp:mcporter": "npx -y mcporter list oracle-local --schema --config config/mcporter.json && npx -y mcporter call oracle-local.sessions limit:1 --config config/mcporter.json",
24
- "test:browser": "pnpm run build && tsx scripts/test-browser.ts && ./scripts/browser-smoke.sh",
25
- "test:live": "ORACLE_LIVE_TEST=1 vitest run tests/live --exclude tests/live/openai-live.test.ts",
26
- "test:pro": "ORACLE_LIVE_TEST=1 vitest run tests/live/openai-live.test.ts",
27
- "test:coverage": "vitest run --coverage",
28
- "prepare": "pnpm run build",
29
- "mcp": "pnpm run build && node ./dist/bin/oracle-mcp.js"
30
- },
31
11
  "files": [
32
12
  "dist/**/*",
33
13
  "assets-oracle-icon.png",
@@ -103,19 +83,23 @@
103
83
  "optionalDependencies": {
104
84
  "win-dpapi": "npm:@primno/dpapi@2.0.1"
105
85
  },
106
- "pnpm": {
107
- "overrides": {
108
- "zod": "4.1.13",
109
- "win-dpapi": "npm:@primno/dpapi@2.0.1",
110
- "zod-to-json-schema": "3.25.0",
111
- "devtools-protocol": "0.0.1551306"
112
- },
113
- "onlyBuiltDependencies": [
114
- "@cdktf/node-pty-prebuilt-multiarch",
115
- "keytar",
116
- "sqlite3",
117
- "win-dpapi"
118
- ]
119
- },
120
- "packageManager": "pnpm@10.23.0+sha512.21c4e5698002ade97e4efe8b8b4a89a8de3c85a37919f957e7a0f30f38fbc5bbdd05980ffe29179b2fb6e6e691242e098d945d1601772cad0fef5fb6411e2a4b"
121
- }
86
+ "scripts": {
87
+ "docs:list": "tsx scripts/docs-list.ts",
88
+ "build": "tsc -p tsconfig.build.json && pnpm run build:vendor",
89
+ "build:vendor": "node -e \"const fs=require('fs'); const path=require('path'); const src=path.join('vendor','oracle-notifier'); const dest=path.join('dist','vendor','oracle-notifier'); fs.mkdirSync(dest,{recursive:true}); if(fs.existsSync(src)){ fs.cpSync(src,dest,{recursive:true,force:true}); }\"",
90
+ "start": "pnpm run build && node ./dist/scripts/run-cli.js",
91
+ "oracle": "pnpm start",
92
+ "check": "pnpm run typecheck",
93
+ "typecheck": "tsc --noEmit",
94
+ "lint": "pnpm run typecheck && biome lint .",
95
+ "test": "vitest run",
96
+ "test:mcp": "pnpm run build && pnpm run test:mcp:unit && pnpm run test:mcp:mcporter",
97
+ "test:mcp:unit": "vitest run tests/mcp*.test.ts tests/mcp/**/*.test.ts",
98
+ "test:mcp:mcporter": "npx -y mcporter list oracle-local --schema --config config/mcporter.json && npx -y mcporter call oracle-local.sessions limit:1 --config config/mcporter.json",
99
+ "test:browser": "pnpm run build && tsx scripts/test-browser.ts && ./scripts/browser-smoke.sh",
100
+ "test:live": "ORACLE_LIVE_TEST=1 vitest run tests/live --exclude tests/live/openai-live.test.ts",
101
+ "test:pro": "ORACLE_LIVE_TEST=1 vitest run tests/live/openai-live.test.ts",
102
+ "test:coverage": "vitest run --coverage",
103
+ "mcp": "pnpm run build && node ./dist/bin/oracle-mcp.js"
104
+ }
105
+ }
File without changes
@@ -1,24 +0,0 @@
1
- # Oracle Notifier helper (macOS, arm64)
2
-
3
- Builds a tiny signed helper app for macOS notifications with the Oracle icon.
4
-
5
- ## Build
6
-
7
- ```bash
8
- cd vendor/oracle-notifier
9
- # Optional: notarize by setting App Store Connect key credentials
10
- export APP_STORE_CONNECT_API_KEY_P8="$(cat AuthKey_XXXXXX.p8)" # with literal newlines or \n escaped
11
- export APP_STORE_CONNECT_KEY_ID=XXXXXX
12
- export APP_STORE_CONNECT_ISSUER_ID=YYYYYYYY-YYYY-YYYY-YYYY-YYYYYYYYYYYY
13
- ./build-notifier.sh
14
- ```
15
-
16
- - Requires Xcode command line tools (swiftc) and a macOS Developer ID certificate. Without a valid cert, the build fails (no ad-hoc fallback).
17
- - If `APP_STORE_CONNECT_*` vars are set, the script notarizes and staples the ticket.
18
- - Output: `OracleNotifier.app` (arm64 only), bundled with `OracleIcon.icns`.
19
-
20
- ## Usage
21
- The CLI prefers this helper on macOS; if it fails or is missing, it falls back to toasted-notifier/terminal-notifier.
22
-
23
- ## Permissions
24
- After first run, allow notifications for “Oracle Notifier” in System Settings → Notifications.