@steipete/oracle 0.5.4 → 0.5.6

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.
@@ -135,9 +135,27 @@ export async function uploadAttachmentFile(deps, attachment, logger) {
135
135
  await runtime.evaluate({ expression: `(function(){${dispatchEvents} return true;})()`, returnByValue: true });
136
136
  };
137
137
  await tryFileInput();
138
+ // Snapshot the attachment state immediately after setting files so we can detect silent failures.
139
+ const snapshotExpr = `(() => {
140
+ const chips = Array.from(document.querySelectorAll('[data-testid*="attachment"],[data-testid*="chip"],[data-testid*="upload"],[aria-label="Remove file"]'))
141
+ .map((node) => (node?.textContent || '').trim())
142
+ .filter(Boolean);
143
+ const inputs = Array.from(document.querySelectorAll('input[type="file"]')).map((el) => ({
144
+ files: Array.from(el.files || []).map((f) => f?.name ?? ''),
145
+ }));
146
+ return { chips, inputs };
147
+ })()`;
148
+ const snapshot = await runtime
149
+ .evaluate({ expression: snapshotExpr, returnByValue: true })
150
+ .then((res) => res?.result?.value)
151
+ .catch(() => undefined);
152
+ if (snapshot) {
153
+ logger?.(`Attachment snapshot after setFileInputFiles: chips=${JSON.stringify(snapshot.chips || [])} inputs=${JSON.stringify(snapshot.inputs || [])}`);
154
+ }
155
+ const inputHasFile = snapshot?.inputs?.some((entry) => (entry.files || []).some((name) => name?.toLowerCase?.().includes(expectedName.toLowerCase()))) ?? false;
138
156
  if (await waitForAttachmentAnchored(runtime, expectedName, 20_000)) {
139
157
  await waitForAttachmentVisible(runtime, expectedName, 20_000, logger);
140
- logger('Attachment queued (file input)');
158
+ logger(inputHasFile ? 'Attachment queued (file input, confirmed present)' : 'Attachment queued (file input)');
141
159
  return;
142
160
  }
143
161
  await logDomFailure(runtime, logger, 'file-upload-missing');
@@ -189,10 +207,6 @@ export async function waitForAttachmentCompletion(Runtime, timeoutMs, expectedNa
189
207
  btn?.parentElement?.parentElement?.innerText?.toLowerCase?.() ?? '',
190
208
  );
191
209
  attachedNames.push(...cardTexts.filter(Boolean));
192
- const filesPills = Array.from(document.querySelectorAll('button,div'))
193
- .map((node) => (node?.textContent || '').toLowerCase())
194
- .filter((text) => /\bfiles\b/.test(text));
195
- attachedNames.push(...filesPills);
196
210
  const filesAttached = attachedNames.length > 0;
197
211
  return { state: button ? (disabled ? 'disabled' : 'ready') : 'missing', uploading, filesAttached, attachedNames };
198
212
  })()`;
@@ -274,13 +288,6 @@ export async function waitForAttachmentVisible(Runtime, expectedName, timeoutMs,
274
288
  return { found: true, userTurns: userTurns.length, source: 'attachment-cards' };
275
289
  }
276
290
 
277
- const filesPills = Array.from(document.querySelectorAll('button,div')).map((node) =>
278
- (node?.textContent || '').toLowerCase(),
279
- );
280
- if (filesPills.some((text) => /\bfiles\b/.test(text))) {
281
- return { found: true, userTurns: userTurns.length, source: 'files-pill' };
282
- }
283
-
284
291
  const attrMatch = Array.from(document.querySelectorAll('[aria-label], [title], [data-testid]')).some(matchNode);
285
292
  if (attrMatch) {
286
293
  return { found: true, userTurns: userTurns.length, source: 'attrs' };
@@ -328,13 +335,6 @@ async function waitForAttachmentAnchored(Runtime, expectedName, timeoutMs) {
328
335
  return { found: true, text: cards.find((t) => t.includes(normalized)) };
329
336
  }
330
337
 
331
- const filesPills = Array.from(document.querySelectorAll('button,div')).map((node) =>
332
- (node?.textContent || '').toLowerCase(),
333
- );
334
- if (filesPills.some((text) => /\bfiles\b/.test(text))) {
335
- return { found: true, text: filesPills.find((t) => /\bfiles\b/.test(t)) };
336
- }
337
-
338
338
  // As a last resort, treat file inputs that hold the target name as anchored. Some UIs delay chip rendering.
339
339
  const inputHit = Array.from(document.querySelectorAll('input[type="file"]')).some((el) =>
340
340
  Array.from(el.files || []).some((file) => file?.name?.toLowerCase?.().includes(normalized)),
@@ -231,24 +231,37 @@ async function verifyPromptCommitted(Runtime, prompt, timeoutMs, logger) {
231
231
  const fallback = document.querySelector(${fallbackSelectorLiteral});
232
232
  const normalize = (value) => value?.toLowerCase?.().replace(/\\s+/g, ' ').trim() ?? '';
233
233
  const normalizedPrompt = normalize(${encodedPrompt});
234
+ const normalizedPromptPrefix = normalizedPrompt.slice(0, 120);
234
235
  const CONVERSATION_SELECTOR = ${JSON.stringify(CONVERSATION_TURN_SELECTOR)};
235
236
  const articles = Array.from(document.querySelectorAll(CONVERSATION_SELECTOR));
236
- const userMatched = articles.some((node) => normalize(node?.innerText).includes(normalizedPrompt));
237
+ const normalizedTurns = articles.map((node) => normalize(node?.innerText));
238
+ const userMatched = normalizedTurns.some((text) => text.includes(normalizedPrompt));
239
+ const prefixMatched =
240
+ normalizedPromptPrefix.length > 30 &&
241
+ normalizedTurns.some((text) => text.includes(normalizedPromptPrefix));
242
+ const lastTurn = normalizedTurns[normalizedTurns.length - 1] ?? '';
237
243
  return {
238
244
  userMatched,
245
+ prefixMatched,
239
246
  fallbackValue: fallback?.value ?? '',
240
247
  editorValue: editor?.innerText ?? '',
248
+ lastTurn,
249
+ turnsCount: normalizedTurns.length,
241
250
  };
242
251
  })()`;
243
252
  while (Date.now() < deadline) {
244
253
  const { result } = await Runtime.evaluate({ expression: script, returnByValue: true });
245
254
  const info = result.value;
246
- if (info?.userMatched) {
255
+ if (info?.userMatched || info?.prefixMatched) {
247
256
  return;
248
257
  }
249
258
  await delay(100);
250
259
  }
251
260
  if (logger) {
261
+ logger(`Prompt commit check failed; latest state: ${await Runtime.evaluate({
262
+ expression: script,
263
+ returnByValue: true,
264
+ }).then((res) => JSON.stringify(res?.result?.value)).catch(() => 'unavailable')}`);
252
265
  await logDomFailure(Runtime, logger, 'prompt-commit');
253
266
  }
254
267
  throw new Error('Prompt did not appear in conversation before timeout (send may have failed)');
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@steipete/oracle",
3
- "version": "0.5.4",
3
+ "version": "0.5.6",
4
4
  "description": "CLI wrapper around OpenAI Responses API with GPT-5.1 Pro, GPT-5.1, and GPT-5.1 Codex high reasoning modes.",
5
5
  "type": "module",
6
6
  "main": "dist/bin/oracle-cli.js",
@@ -8,6 +8,26 @@
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
+ },
11
31
  "files": [
12
32
  "dist/**/*",
13
33
  "assets-oracle-icon.png",
@@ -30,11 +50,7 @@
30
50
  "name": "node",
31
51
  "version": ">=20"
32
52
  }
33
- ],
34
- "packageManager": {
35
- "name": "pnpm",
36
- "version": ">=8"
37
- }
53
+ ]
38
54
  },
39
55
  "keywords": [],
40
56
  "author": "",
@@ -87,23 +103,19 @@
87
103
  "optionalDependencies": {
88
104
  "win-dpapi": "npm:@primno/dpapi@2.0.1"
89
105
  },
90
- "scripts": {
91
- "docs:list": "tsx scripts/docs-list.ts",
92
- "build": "tsc -p tsconfig.build.json && pnpm run build:vendor",
93
- "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}); }\"",
94
- "start": "pnpm run build && node ./dist/scripts/run-cli.js",
95
- "oracle": "pnpm start",
96
- "check": "pnpm run typecheck",
97
- "typecheck": "tsc --noEmit",
98
- "lint": "pnpm run typecheck && biome lint .",
99
- "test": "vitest run",
100
- "test:mcp": "pnpm run build && pnpm run test:mcp:unit && pnpm run test:mcp:mcporter",
101
- "test:mcp:unit": "vitest run tests/mcp*.test.ts tests/mcp/**/*.test.ts",
102
- "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",
103
- "test:browser": "pnpm run build && tsx scripts/test-browser.ts && ./scripts/browser-smoke.sh",
104
- "test:live": "ORACLE_LIVE_TEST=1 vitest run tests/live --exclude tests/live/openai-live.test.ts",
105
- "test:pro": "ORACLE_LIVE_TEST=1 vitest run tests/live/openai-live.test.ts",
106
- "test:coverage": "vitest run --coverage",
107
- "mcp": "pnpm run build && node ./dist/bin/oracle-mcp.js"
108
- }
109
- }
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
+ }
@@ -0,0 +1,24 @@
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.
File without changes
package/dist/.DS_Store DELETED
Binary file