@steipete/oracle 0.7.6 → 0.8.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.
@@ -20,7 +20,7 @@ import { createFsAdapter } from './fsAdapter.js';
20
20
  import { resolveGeminiModelId } from './gemini.js';
21
21
  import { resolveClaudeModelId } from './claude.js';
22
22
  import { renderMarkdownAnsi } from '../cli/markdownRenderer.js';
23
- import { createLiveRenderer } from 'markdansi';
23
+ import { createMarkdownStreamer } from 'markdansi';
24
24
  import { executeBackgroundResponse } from './background.js';
25
25
  import { formatTokenEstimate, formatTokenValue, resolvePreviewMode } from './runUtils.js';
26
26
  import { estimateUsdCost } from 'tokentally';
@@ -312,8 +312,6 @@ export async function runOracle(options, deps = {}) {
312
312
  let response = null;
313
313
  let elapsedMs = 0;
314
314
  let sawTextDelta = false;
315
- let streamedText = '';
316
- let lastLiveFrameAtMs = 0;
317
315
  let answerHeaderPrinted = false;
318
316
  const allowAnswerHeader = options.suppressAnswerHeader !== true;
319
317
  const timeoutExceeded = () => now() - runStart >= timeoutMs;
@@ -381,14 +379,23 @@ export async function runOracle(options, deps = {}) {
381
379
  },
382
380
  });
383
381
  }
384
- let liveRenderer = null;
382
+ let markdownStreamer = null;
383
+ const flushMarkdownStreamer = () => {
384
+ if (!markdownStreamer)
385
+ return;
386
+ const rendered = markdownStreamer.finish();
387
+ markdownStreamer = null;
388
+ if (rendered) {
389
+ stdoutWrite(rendered);
390
+ }
391
+ };
385
392
  try {
386
- liveRenderer =
393
+ markdownStreamer =
387
394
  isTty && !renderPlain
388
- ? createLiveRenderer({
389
- write: stdoutWrite,
390
- width: process.stdout.columns ?? 80,
391
- renderFrame: renderMarkdownAnsi,
395
+ ? createMarkdownStreamer({
396
+ render: renderMarkdownAnsi,
397
+ spacing: 'single',
398
+ mode: 'hybrid',
392
399
  })
393
400
  : null;
394
401
  for await (const event of stream) {
@@ -409,38 +416,27 @@ export async function runOracle(options, deps = {}) {
409
416
  stdoutWrite(event.delta);
410
417
  continue;
411
418
  }
412
- if (liveRenderer) {
413
- streamedText += event.delta;
414
- const currentMs = now();
415
- const due = currentMs - lastLiveFrameAtMs >= 120;
416
- const hasNewline = event.delta.includes('\n');
417
- if (hasNewline || due) {
418
- liveRenderer.render(streamedText);
419
- lastLiveFrameAtMs = currentMs;
419
+ if (markdownStreamer) {
420
+ const rendered = markdownStreamer.push(event.delta);
421
+ if (rendered) {
422
+ stdoutWrite(rendered);
420
423
  }
421
424
  continue;
422
425
  }
423
426
  // Non-TTY streams should still surface output; fall back to raw stdout.
424
427
  stdoutWrite(event.delta);
425
428
  }
426
- if (liveRenderer) {
427
- streamedText = streamedText.trim();
428
- if (streamedText.length > 0) {
429
- liveRenderer.render(streamedText);
430
- }
431
- }
429
+ flushMarkdownStreamer();
432
430
  throwIfTimedOut();
433
431
  }
434
432
  catch (streamError) {
435
433
  // stream.abort() is not available on the interface
434
+ flushMarkdownStreamer();
436
435
  stopHeartbeatNow();
437
436
  const transportError = toTransportError(streamError, requestBody.model);
438
437
  log(chalk.yellow(describeTransportError(transportError, timeoutMs)));
439
438
  throw transportError;
440
439
  }
441
- finally {
442
- liveRenderer?.finish();
443
- }
444
440
  response = await stream.finalResponse();
445
441
  throwIfTimedOut();
446
442
  stopHeartbeatNow();
@@ -455,17 +451,12 @@ export async function runOracle(options, deps = {}) {
455
451
  }
456
452
  // We only add spacing when streamed text was printed.
457
453
  if (sawTextDelta && !options.silent) {
458
- const shouldRenderAfterStream = isTty && !renderPlain && streamedText.length > 0;
459
454
  if (renderPlain) {
460
455
  // Plain streaming already wrote chunks; ensure clean separation.
461
456
  stdoutWrite('\n');
462
457
  }
463
- else if (!shouldRenderAfterStream) {
464
- // Non-TTY streams should still surface output; ensure separation.
465
- log('');
466
- }
467
458
  else {
468
- // Live-rendered mode already drew the final frame; only separate from logs.
459
+ // Separate streamed output from logs.
469
460
  log('');
470
461
  }
471
462
  }
@@ -7,7 +7,7 @@ import { spawn, spawnSync } from 'node:child_process';
7
7
  import { mkdtemp, rm, mkdir, writeFile } from 'node:fs/promises';
8
8
  import chalk from 'chalk';
9
9
  import { runBrowserMode } from '../browserMode.js';
10
- import { loadChromeCookies } from '../browser/chromeCookies.js';
10
+ import { getCookies } from '@steipete/sweet-cookie';
11
11
  import { CHATGPT_URL } from '../browser/constants.js';
12
12
  import { cleanupStaleProfileState, readDevToolsPort, verifyDevToolsReachable, writeChromePid, writeDevToolsActivePort, } from '../browser/profileState.js';
13
13
  import { normalizeChatgptUrl } from '../browser/utils.js';
@@ -331,13 +331,17 @@ function formatReachableAddresses(bindAddress, port) {
331
331
  async function loadLocalChatgptCookies(logger, targetUrl) {
332
332
  try {
333
333
  logger('Loading ChatGPT cookies from this host\'s Chrome profile...');
334
- const cookies = await Promise.resolve(loadChromeCookies({
335
- targetUrl,
336
- profile: 'Default',
337
- })).catch((error) => {
338
- logger(`Unable to load local ChatGPT cookies on this host: ${error instanceof Error ? error.message : String(error)}`);
339
- return [];
334
+ const { cookies: rawCookies, warnings } = await getCookies({
335
+ url: targetUrl,
336
+ browsers: ['chrome'],
337
+ mode: 'merge',
338
+ chromeProfile: 'Default',
339
+ timeoutMs: 5_000,
340
340
  });
341
+ if (warnings.length) {
342
+ logger(`Cookie warnings:\n- ${warnings.join('\n- ')}`);
343
+ }
344
+ const cookies = rawCookies.map(toCdpCookie).filter((c) => Boolean(c));
341
345
  if (!cookies || cookies.length === 0) {
342
346
  logger('No local ChatGPT cookies found on this host. Please log in once; opening ChatGPT...');
343
347
  const opened = triggerLocalLoginPrompt(logger, targetUrl);
@@ -348,14 +352,7 @@ async function loadLocalChatgptCookies(logger, targetUrl) {
348
352
  }
349
353
  catch (error) {
350
354
  const message = error instanceof Error ? error.message : String(error);
351
- const missingDbMatch = message.match(/Unable to locate Chrome cookie DB at (.+)/);
352
- if (missingDbMatch) {
353
- const lookedPath = missingDbMatch[1];
354
- logger(`Chrome cookies not found at ${lookedPath}. Set --browser-cookie-path to your Chrome profile or log in manually.`);
355
- }
356
- else {
357
- logger(`Unable to load local ChatGPT cookies on this host: ${message}`);
358
- }
355
+ logger(`Unable to load local ChatGPT cookies on this host: ${message}`);
359
356
  if (process.platform === 'linux' && isWsl()) {
360
357
  logger('WSL hint: Chrome lives under /mnt/c/Users/<you>/AppData/Local/Google/Chrome/User Data/Default; pass --browser-cookie-path to that directory if auto-detect fails.');
361
358
  }
@@ -363,6 +360,24 @@ async function loadLocalChatgptCookies(logger, targetUrl) {
363
360
  return { cookies: null, opened };
364
361
  }
365
362
  }
363
+ function toCdpCookie(cookie) {
364
+ if (!cookie?.name)
365
+ return null;
366
+ const out = {
367
+ name: cookie.name,
368
+ value: cookie.value,
369
+ domain: cookie.domain,
370
+ path: cookie.path ?? '/',
371
+ secure: cookie.secure ?? true,
372
+ httpOnly: cookie.httpOnly ?? false,
373
+ };
374
+ if (typeof cookie.expires === 'number')
375
+ out.expires = cookie.expires;
376
+ if (cookie.sameSite === 'Lax' || cookie.sameSite === 'Strict' || cookie.sameSite === 'None') {
377
+ out.sameSite = cookie.sameSite;
378
+ }
379
+ return out;
380
+ }
366
381
  function triggerLocalLoginPrompt(logger, url) {
367
382
  const verbose = process.argv.includes('--verbose') || process.env.ORACLE_SERVE_VERBOSE === '1';
368
383
  const openers = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@steipete/oracle",
3
- "version": "0.7.6",
3
+ "version": "0.8.1",
4
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",
@@ -42,13 +42,13 @@
42
42
  "url": "git+https://github.com/steipete/oracle.git"
43
43
  },
44
44
  "engines": {
45
- "node": ">=20"
45
+ "node": ">=22"
46
46
  },
47
47
  "devEngines": {
48
48
  "runtime": [
49
49
  {
50
50
  "name": "node",
51
- "version": ">=20"
51
+ "version": ">=22"
52
52
  }
53
53
  ]
54
54
  },
@@ -64,8 +64,8 @@
64
64
  "@google/genai": "^1.34.0",
65
65
  "@google/generative-ai": "^0.24.1",
66
66
  "@modelcontextprotocol/sdk": "^1.25.1",
67
+ "@steipete/sweet-cookie": "^0.1.0",
67
68
  "chalk": "^5.6.2",
68
- "chrome-cookies-secure": "3.0.0",
69
69
  "chrome-launcher": "^1.2.1",
70
70
  "chrome-remote-interface": "^0.33.3",
71
71
  "clipboardy": "^5.0.2",
@@ -75,13 +75,11 @@
75
75
  "gpt-tokenizer": "^3.4.0",
76
76
  "inquirer": "13.1.0",
77
77
  "json5": "^2.2.3",
78
- "keytar": "^7.9.0",
79
78
  "kleur": "^4.1.5",
80
- "markdansi": "0.1.7",
79
+ "markdansi": "0.2.0",
81
80
  "openai": "^6.15.0",
82
- "osc-progress": "^0.1.0",
81
+ "osc-progress": "^0.2.0",
83
82
  "shiki": "^3.20.0",
84
- "sqlite3": "^5.1.7",
85
83
  "toasted-notifier": "^10.1.0",
86
84
  "tokentally": "^0.1.1",
87
85
  "zod": "^4.2.1"
@@ -102,20 +100,13 @@
102
100
  "typescript": "^5.9.3",
103
101
  "vitest": "^4.0.16"
104
102
  },
105
- "optionalDependencies": {
106
- "win-dpapi": "npm:@primno/dpapi@2.0.1"
107
- },
108
103
  "pnpm": {
109
104
  "overrides": {
110
- "devtools-protocol": "0.0.1561482",
111
- "win-dpapi": "npm:@primno/dpapi@2.0.1"
105
+ "devtools-protocol": "0.0.1561482"
112
106
  },
113
107
  "onlyBuiltDependencies": [
114
108
  "@cdktf/node-pty-prebuilt-multiarch",
115
- "esbuild",
116
- "keytar",
117
- "sqlite3",
118
- "win-dpapi"
109
+ "esbuild"
119
110
  ]
120
111
  },
121
112
  "packageManager": "pnpm@10.23.0+sha512.21c4e5698002ade97e4efe8b8b4a89a8de3c85a37919f957e7a0f30f38fbc5bbdd05980ffe29179b2fb6e6e691242e098d945d1601772cad0fef5fb6411e2a4b"