@steipete/oracle 0.8.0 → 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.
- package/README.md +3 -1
- package/dist/src/browser/actions/thinkingTime.js +5 -0
- package/dist/src/browser/chromeLifecycle.js +9 -1
- package/dist/src/browser/config.js +2 -0
- package/dist/src/browser/index.js +21 -4
- package/dist/src/browser/profileState.js +16 -0
- package/dist/src/browser/reattach.js +8 -2
- package/dist/src/cli/browserConfig.js +2 -1
- package/dist/src/cli/browserDefaults.js +9 -0
- package/dist/src/oracle/oscProgress.js +7 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -112,6 +112,8 @@ npx -y @steipete/oracle oracle-mcp
|
|
|
112
112
|
| `--base-url <url>` | Point API runs at LiteLLM/Azure/OpenRouter/etc. |
|
|
113
113
|
| `--chatgpt-url <url>` | Target a ChatGPT workspace/folder (browser). |
|
|
114
114
|
| `--browser-model-strategy <select\|current\|ignore>` | Control ChatGPT model selection in browser mode (current keeps the active model; ignore skips the picker). |
|
|
115
|
+
| `--browser-manual-login` | Skip cookie copy; reuse a persistent automation profile and wait for manual ChatGPT login. |
|
|
116
|
+
| `--browser-thinking-time <light\|standard\|extended\|heavy>` | Set ChatGPT thinking-time intensity (browser; Thinking/Pro models only). |
|
|
115
117
|
| `--browser-port <port>` | Pin the Chrome DevTools port (WSL/Windows firewall helper). |
|
|
116
118
|
| `--browser-inline-cookies[(-file)] <payload|path>` | Supply cookies without Chrome/Keychain (browser). |
|
|
117
119
|
| `--browser-timeout`, `--browser-input-timeout` | Control overall/browser input timeouts (supports h/m/s/ms). |
|
|
@@ -147,7 +149,7 @@ Advanced flags
|
|
|
147
149
|
|
|
148
150
|
| Area | Flags |
|
|
149
151
|
| --- | --- |
|
|
150
|
-
| Browser | `--browser-timeout`, `--browser-input-timeout`, `--browser-inline-cookies[(-file)]`, `--browser-attachments`, `--browser-inline-files`, `--browser-bundle-files`, `--browser-keep-browser`, `--browser-headless`, `--browser-hide-window`, `--browser-no-cookie-sync`, `--browser-allow-cookie-errors`, `--browser-chrome-path`, `--browser-cookie-path`, `--chatgpt-url` |
|
|
152
|
+
| Browser | `--browser-manual-login`, `--browser-thinking-time`, `--browser-timeout`, `--browser-input-timeout`, `--browser-inline-cookies[(-file)]`, `--browser-attachments`, `--browser-inline-files`, `--browser-bundle-files`, `--browser-keep-browser`, `--browser-headless`, `--browser-hide-window`, `--browser-no-cookie-sync`, `--browser-allow-cookie-errors`, `--browser-chrome-path`, `--browser-cookie-path`, `--chatgpt-url` |
|
|
151
153
|
| Azure/OpenAI | `--azure-endpoint`, `--azure-deployment`, `--azure-api-version`, `--base-url` |
|
|
152
154
|
|
|
153
155
|
Remote browser example
|
|
@@ -115,6 +115,11 @@ function buildThinkingTimeExpression(level) {
|
|
|
115
115
|
if (aria.includes('thinking') || text.includes('thinking')) {
|
|
116
116
|
return btn;
|
|
117
117
|
}
|
|
118
|
+
|
|
119
|
+
// In some cases the pill is labeled "Pro".
|
|
120
|
+
if (aria.includes('pro') || text.includes('pro')) {
|
|
121
|
+
return btn;
|
|
122
|
+
}
|
|
118
123
|
}
|
|
119
124
|
}
|
|
120
125
|
return null;
|
|
@@ -6,6 +6,7 @@ import { execFile } from 'node:child_process';
|
|
|
6
6
|
import { promisify } from 'node:util';
|
|
7
7
|
import CDP from 'chrome-remote-interface';
|
|
8
8
|
import { launch, Launcher } from 'chrome-launcher';
|
|
9
|
+
import { cleanupStaleProfileState } from './profileState.js';
|
|
9
10
|
const execFileAsync = promisify(execFile);
|
|
10
11
|
export async function launchChrome(config, userDataDir, logger) {
|
|
11
12
|
const connectHost = resolveRemoteDebugHost();
|
|
@@ -64,7 +65,14 @@ export function registerTerminationHooks(chrome, userDataDir, keepBrowser, logge
|
|
|
64
65
|
catch {
|
|
65
66
|
// ignore kill failures
|
|
66
67
|
}
|
|
67
|
-
|
|
68
|
+
if (opts?.preserveUserDataDir) {
|
|
69
|
+
// Preserve the profile directory (manual login), but clear reattach hints so we don't
|
|
70
|
+
// try to reuse a dead DevTools port on the next run.
|
|
71
|
+
await cleanupStaleProfileState(userDataDir, logger, { lockRemovalMode: 'never' }).catch(() => undefined);
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
await rm(userDataDir, { recursive: true, force: true }).catch(() => undefined);
|
|
75
|
+
}
|
|
68
76
|
}
|
|
69
77
|
})().finally(() => {
|
|
70
78
|
const exitCode = signal === 'SIGINT' ? 130 : 1;
|
|
@@ -26,6 +26,7 @@ export const DEFAULT_BROWSER_CONFIG = {
|
|
|
26
26
|
remoteChrome: null,
|
|
27
27
|
manualLogin: false,
|
|
28
28
|
manualLoginProfileDir: null,
|
|
29
|
+
manualLoginCookieSync: false,
|
|
29
30
|
};
|
|
30
31
|
export function resolveBrowserConfig(config) {
|
|
31
32
|
const debugPortEnv = parseDebugPort(process.env.ORACLE_BROWSER_PORT ?? process.env.ORACLE_BROWSER_DEBUG_PORT);
|
|
@@ -72,6 +73,7 @@ export function resolveBrowserConfig(config) {
|
|
|
72
73
|
thinkingTime: config?.thinkingTime,
|
|
73
74
|
manualLogin,
|
|
74
75
|
manualLoginProfileDir: manualLogin ? resolvedProfileDir : null,
|
|
76
|
+
manualLoginCookieSync: config?.manualLoginCookieSync ?? DEFAULT_BROWSER_CONFIG.manualLoginCookieSync,
|
|
75
77
|
};
|
|
76
78
|
}
|
|
77
79
|
function parseDebugPort(raw) {
|
|
@@ -13,7 +13,7 @@ import { formatElapsed } from '../oracle/format.js';
|
|
|
13
13
|
import { CHATGPT_URL, CONVERSATION_TURN_SELECTOR, DEFAULT_MODEL_STRATEGY } from './constants.js';
|
|
14
14
|
import { BrowserAutomationError } from '../oracle/errors.js';
|
|
15
15
|
import { alignPromptEchoPair, buildPromptEchoMatcher } from './reattachHelpers.js';
|
|
16
|
-
import { cleanupStaleProfileState, readChromePid, readDevToolsPort, verifyDevToolsReachable, writeChromePid, writeDevToolsActivePort, } from './profileState.js';
|
|
16
|
+
import { cleanupStaleProfileState, readChromePid, readDevToolsPort, shouldCleanupManualLoginProfileState, verifyDevToolsReachable, writeChromePid, writeDevToolsActivePort, } from './profileState.js';
|
|
17
17
|
export { CHATGPT_URL, DEFAULT_MODEL_STRATEGY, DEFAULT_MODEL_TARGET } from './constants.js';
|
|
18
18
|
export { parseDuration, delay, normalizeChatgptUrl, isTemporaryChatUrl } from './utils.js';
|
|
19
19
|
export async function runBrowserMode(options) {
|
|
@@ -95,7 +95,7 @@ export async function runBrowserMode(options) {
|
|
|
95
95
|
else {
|
|
96
96
|
logger(`Created temporary Chrome profile at ${userDataDir}`);
|
|
97
97
|
}
|
|
98
|
-
const effectiveKeepBrowser = config.keepBrowser
|
|
98
|
+
const effectiveKeepBrowser = Boolean(config.keepBrowser);
|
|
99
99
|
const reusedChrome = manualLogin ? await maybeReuseRunningChrome(userDataDir, logger) : null;
|
|
100
100
|
const chrome = reusedChrome ??
|
|
101
101
|
(await launchChrome({
|
|
@@ -115,6 +115,7 @@ export async function runBrowserMode(options) {
|
|
|
115
115
|
removeTerminationHooks = registerTerminationHooks(chrome, userDataDir, effectiveKeepBrowser, logger, {
|
|
116
116
|
isInFlight: () => runStatus !== 'complete',
|
|
117
117
|
emitRuntimeHint,
|
|
118
|
+
preserveUserDataDir: manualLogin,
|
|
118
119
|
});
|
|
119
120
|
}
|
|
120
121
|
catch {
|
|
@@ -160,8 +161,12 @@ export async function runBrowserMode(options) {
|
|
|
160
161
|
if (!manualLogin) {
|
|
161
162
|
await Network.clearBrowserCookies();
|
|
162
163
|
}
|
|
163
|
-
const
|
|
164
|
+
const manualLoginCookieSync = manualLogin && Boolean(config.manualLoginCookieSync);
|
|
165
|
+
const cookieSyncEnabled = config.cookieSync && (!manualLogin || manualLoginCookieSync);
|
|
164
166
|
if (cookieSyncEnabled) {
|
|
167
|
+
if (manualLoginCookieSync) {
|
|
168
|
+
logger('Manual login mode: seeding persistent profile with cookies from your Chrome profile.');
|
|
169
|
+
}
|
|
165
170
|
if (!config.inlineCookies) {
|
|
166
171
|
logger('Heads-up: macOS may prompt for your Keychain password to read Chrome cookies; use --copy or --render for manual flow.');
|
|
167
172
|
}
|
|
@@ -623,7 +628,19 @@ export async function runBrowserMode(options) {
|
|
|
623
628
|
// ignore kill failures
|
|
624
629
|
}
|
|
625
630
|
}
|
|
626
|
-
|
|
631
|
+
if (manualLogin) {
|
|
632
|
+
const shouldCleanup = await shouldCleanupManualLoginProfileState(userDataDir, logger.verbose ? logger : undefined, {
|
|
633
|
+
connectionClosedUnexpectedly,
|
|
634
|
+
host: chromeHost,
|
|
635
|
+
});
|
|
636
|
+
if (shouldCleanup) {
|
|
637
|
+
// Preserve the persistent manual-login profile, but clear stale reattach hints.
|
|
638
|
+
await cleanupStaleProfileState(userDataDir, logger, { lockRemovalMode: 'never' }).catch(() => undefined);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
else {
|
|
642
|
+
await rm(userDataDir, { recursive: true, force: true }).catch(() => undefined);
|
|
643
|
+
}
|
|
627
644
|
if (!connectionClosedUnexpectedly) {
|
|
628
645
|
const totalSeconds = (Date.now() - startedAt) / 1000;
|
|
629
646
|
logger(`Cleanup ${runStatus} • ${totalSeconds.toFixed(1)}s total`);
|
|
@@ -105,6 +105,22 @@ export async function verifyDevToolsReachable({ port, host = '127.0.0.1', attemp
|
|
|
105
105
|
}
|
|
106
106
|
return { ok: false, error: 'unreachable' };
|
|
107
107
|
}
|
|
108
|
+
export async function shouldCleanupManualLoginProfileState(userDataDir, logger, options = {}) {
|
|
109
|
+
if (!options.connectionClosedUnexpectedly) {
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
const port = await readDevToolsPort(userDataDir);
|
|
113
|
+
if (!port) {
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
const probe = await (options.probe ?? verifyDevToolsReachable)({ port, host: options.host });
|
|
117
|
+
if (probe.ok) {
|
|
118
|
+
logger?.(`DevTools port ${port} still reachable; preserving manual-login profile state`);
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
logger?.(`DevTools port ${port} unreachable (${probe.error}); clearing stale profile state`);
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
108
124
|
export async function cleanupStaleProfileState(userDataDir, logger, options = {}) {
|
|
109
125
|
for (const candidate of getDevToolsActivePortPaths(userDataDir)) {
|
|
110
126
|
try {
|
|
@@ -7,6 +7,7 @@ import { launchChrome, connectToChrome, hideChromeWindow } from './chromeLifecyc
|
|
|
7
7
|
import { resolveBrowserConfig } from './config.js';
|
|
8
8
|
import { syncCookies } from './cookies.js';
|
|
9
9
|
import { CHATGPT_URL } from './constants.js';
|
|
10
|
+
import { cleanupStaleProfileState } from './profileState.js';
|
|
10
11
|
import { pickTarget, extractConversationIdFromUrl, buildConversationUrl, withTimeout, openConversationFromSidebar, openConversationFromSidebarWithRetry, waitForLocationChange, readConversationTurnIndex, buildPromptEchoMatcher, recoverPromptEcho, alignPromptEchoMarkdown, } from './reattachHelpers.js';
|
|
11
12
|
export async function resumeBrowserSession(runtime, config, logger, deps = {}) {
|
|
12
13
|
const recoverSession = deps.recoverSession ??
|
|
@@ -159,14 +160,19 @@ async function resumeBrowserSessionViaNewChrome(runtime, config, logger, deps) {
|
|
|
159
160
|
// ignore
|
|
160
161
|
}
|
|
161
162
|
}
|
|
162
|
-
if (!resolved.keepBrowser
|
|
163
|
+
if (!resolved.keepBrowser) {
|
|
163
164
|
try {
|
|
164
165
|
await chrome.kill();
|
|
165
166
|
}
|
|
166
167
|
catch {
|
|
167
168
|
// ignore
|
|
168
169
|
}
|
|
169
|
-
|
|
170
|
+
if (manualLogin) {
|
|
171
|
+
await cleanupStaleProfileState(userDataDir, logger, { lockRemovalMode: 'never' }).catch(() => undefined);
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
await rm(userDataDir, { recursive: true, force: true }).catch(() => undefined);
|
|
175
|
+
}
|
|
170
176
|
}
|
|
171
177
|
return { answerText: aligned.answerText, answerMarkdown: aligned.answerMarkdown };
|
|
172
178
|
}
|
|
@@ -88,7 +88,8 @@ export async function buildBrowserConfig(options) {
|
|
|
88
88
|
inlineCookiesSource: inline?.source ?? null,
|
|
89
89
|
headless: undefined, // disable headless; Cloudflare blocks it
|
|
90
90
|
keepBrowser: options.browserKeepBrowser ? true : undefined,
|
|
91
|
-
manualLogin: options.browserManualLogin ?
|
|
91
|
+
manualLogin: options.browserManualLogin === undefined ? undefined : options.browserManualLogin,
|
|
92
|
+
manualLoginProfileDir: options.browserManualLoginProfileDir ?? undefined,
|
|
92
93
|
hideWindow: options.browserHideWindow ? true : undefined,
|
|
93
94
|
desiredModel,
|
|
94
95
|
modelStrategy,
|
|
@@ -45,4 +45,13 @@ export function applyBrowserDefaultsFromConfig(options, config, getSource) {
|
|
|
45
45
|
if (isUnset('browserModelStrategy') && browser.modelStrategy !== undefined) {
|
|
46
46
|
options.browserModelStrategy = browser.modelStrategy;
|
|
47
47
|
}
|
|
48
|
+
if (isUnset('browserThinkingTime') && browser.thinkingTime !== undefined) {
|
|
49
|
+
options.browserThinkingTime = browser.thinkingTime;
|
|
50
|
+
}
|
|
51
|
+
if (isUnset('browserManualLogin') && browser.manualLogin !== undefined) {
|
|
52
|
+
options.browserManualLogin = browser.manualLogin;
|
|
53
|
+
}
|
|
54
|
+
if (isUnset('browserManualLoginProfileDir') && browser.manualLoginProfileDir !== undefined) {
|
|
55
|
+
options.browserManualLoginProfileDir = browser.manualLoginProfileDir;
|
|
56
|
+
}
|
|
48
57
|
}
|
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
import process from 'node:process';
|
|
2
2
|
import { startOscProgress as startOscProgressShared, supportsOscProgress as supportsOscProgressShared, } from 'osc-progress';
|
|
3
3
|
export function supportsOscProgress(env = process.env, isTty = process.stdout.isTTY) {
|
|
4
|
+
if (env.CODEX_MANAGED_BY_NPM === '1' && env.ORACLE_FORCE_OSC_PROGRESS !== '1') {
|
|
5
|
+
return false;
|
|
6
|
+
}
|
|
4
7
|
return supportsOscProgressShared(env, isTty, {
|
|
5
8
|
disableEnvVar: 'ORACLE_NO_OSC_PROGRESS',
|
|
6
9
|
forceEnvVar: 'ORACLE_FORCE_OSC_PROGRESS',
|
|
7
10
|
});
|
|
8
11
|
}
|
|
9
12
|
export function startOscProgress(options = {}) {
|
|
13
|
+
const env = options.env ?? process.env;
|
|
14
|
+
if (env.CODEX_MANAGED_BY_NPM === '1' && env.ORACLE_FORCE_OSC_PROGRESS !== '1') {
|
|
15
|
+
return () => { };
|
|
16
|
+
}
|
|
10
17
|
return startOscProgressShared({
|
|
11
18
|
...options,
|
|
12
19
|
// Preserve Oracle's previous default: progress emits to stdout.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@steipete/oracle",
|
|
3
|
-
"version": "0.8.
|
|
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",
|