@steipete/oracle 0.8.4 → 0.8.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.
Files changed (44) hide show
  1. package/README.md +30 -1
  2. package/dist/bin/oracle-cli.js +291 -16
  3. package/dist/scripts/debug/extract-chatgpt-response.js +53 -0
  4. package/dist/src/bridge/connection.js +103 -0
  5. package/dist/src/bridge/userConfigFile.js +28 -0
  6. package/dist/src/browser/actions/assistantResponse.js +85 -42
  7. package/dist/src/browser/actions/promptComposer.js +141 -32
  8. package/dist/src/browser/chromeLifecycle.js +78 -9
  9. package/dist/src/browser/config.js +14 -0
  10. package/dist/src/browser/detect.js +164 -0
  11. package/dist/src/browser/index.js +394 -24
  12. package/dist/src/browser/profileState.js +93 -0
  13. package/dist/src/cli/bridge/claudeConfig.js +54 -0
  14. package/dist/src/cli/bridge/client.js +73 -0
  15. package/dist/src/cli/bridge/codexConfig.js +43 -0
  16. package/dist/src/cli/bridge/doctor.js +107 -0
  17. package/dist/src/cli/bridge/host.js +259 -0
  18. package/dist/src/cli/browserConfig.js +21 -0
  19. package/dist/src/cli/browserDefaults.js +21 -0
  20. package/dist/src/cli/engine.js +17 -1
  21. package/dist/src/cli/options.js +14 -0
  22. package/dist/src/cli/runOptions.js +4 -0
  23. package/dist/src/cli/sessionRunner.js +149 -0
  24. package/dist/src/cli/tui/index.js +1 -0
  25. package/dist/src/mcp/tools/consult.js +81 -15
  26. package/dist/src/mcp/tools/sessions.js +15 -6
  27. package/dist/src/mcp/types.js +4 -0
  28. package/dist/src/mcp/utils.js +12 -2
  29. package/dist/src/oracle/background.js +1 -2
  30. package/dist/src/oracle/client.js +5 -2
  31. package/dist/src/oracle/files.js +2 -2
  32. package/dist/src/oracle/modelResolver.js +33 -1
  33. package/dist/src/oracle/run.js +1 -0
  34. package/dist/src/remote/client.js +6 -5
  35. package/dist/src/remote/health.js +113 -0
  36. package/dist/src/remote/remoteServiceConfig.js +31 -0
  37. package/dist/src/remote/server.js +28 -1
  38. package/dist/src/sessionManager.js +72 -7
  39. package/dist/src/sessionStore.js +2 -2
  40. package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
  41. package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
  42. package/package.json +21 -21
  43. package/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
  44. package/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
@@ -7,6 +7,7 @@ import { promisify } from 'node:util';
7
7
  import CDP from 'chrome-remote-interface';
8
8
  import { launch, Launcher } from 'chrome-launcher';
9
9
  import { cleanupStaleProfileState } from './profileState.js';
10
+ import { delay } from './utils.js';
10
11
  const execFileAsync = promisify(execFile);
11
12
  export async function launchChrome(config, userDataDir, logger) {
12
13
  const connectHost = resolveRemoteDebugHost();
@@ -124,15 +125,14 @@ export async function connectToChrome(port, logger, host) {
124
125
  }
125
126
  export async function connectToRemoteChrome(host, port, logger, targetUrl) {
126
127
  if (targetUrl) {
127
- try {
128
- const target = await CDP.New({ host, port, url: targetUrl });
129
- const client = await CDP({ host, port, target: target.id });
130
- logger(`Opened dedicated remote Chrome tab targeting ${targetUrl}`);
131
- return { client, targetId: target.id };
132
- }
133
- catch (error) {
134
- const message = error instanceof Error ? error.message : String(error);
135
- logger(`Failed to open dedicated remote Chrome tab (${message}); falling back to first target.`);
128
+ const targetConnection = await connectToNewTarget(host, port, targetUrl, logger, {
129
+ opened: () => `Opened dedicated remote Chrome tab targeting ${targetUrl}`,
130
+ openFailed: (message) => `Failed to open dedicated remote Chrome tab (${message}); falling back to first target.`,
131
+ attachFailed: (targetId, message) => `Failed to attach to dedicated remote Chrome tab ${targetId} (${message}); falling back to first target.`,
132
+ closeFailed: (targetId, message) => `Failed to close unused remote Chrome tab ${targetId}: ${message}`,
133
+ });
134
+ if (targetConnection) {
135
+ return { client: targetConnection.client, targetId: targetConnection.targetId };
136
136
  }
137
137
  }
138
138
  const fallbackClient = await CDP({ host, port });
@@ -154,6 +154,75 @@ export async function closeRemoteChromeTarget(host, port, targetId, logger) {
154
154
  logger(`Failed to close remote Chrome tab ${targetId}: ${message}`);
155
155
  }
156
156
  }
157
+ async function connectToNewTarget(host, port, url, logger, messages) {
158
+ try {
159
+ const target = await CDP.New({ host, port, url });
160
+ try {
161
+ const client = await CDP({ host, port, target: target.id });
162
+ if (messages.opened) {
163
+ logger(messages.opened(target.id));
164
+ }
165
+ return { client, targetId: target.id };
166
+ }
167
+ catch (error) {
168
+ const message = error instanceof Error ? error.message : String(error);
169
+ logger(messages.attachFailed(target.id, message));
170
+ try {
171
+ await CDP.Close({ host, port, id: target.id });
172
+ }
173
+ catch (closeError) {
174
+ const closeMessage = closeError instanceof Error ? closeError.message : String(closeError);
175
+ logger(messages.closeFailed(target.id, closeMessage));
176
+ }
177
+ }
178
+ }
179
+ catch (error) {
180
+ const message = error instanceof Error ? error.message : String(error);
181
+ logger(messages.openFailed(message));
182
+ }
183
+ return null;
184
+ }
185
+ export async function connectWithNewTab(port, logger, initialUrl, host, options) {
186
+ const effectiveHost = host ?? '127.0.0.1';
187
+ const url = initialUrl ?? 'about:blank';
188
+ const fallbackToDefault = options?.fallbackToDefault ?? true;
189
+ const retries = Math.max(0, options?.retries ?? 0);
190
+ const retryDelayMs = Math.max(0, options?.retryDelayMs ?? 250);
191
+ const fallbackLabel = fallbackToDefault ? 'falling back to default target.' : 'strict mode: not falling back.';
192
+ let attempt = 0;
193
+ while (attempt <= retries) {
194
+ const targetConnection = await connectToNewTarget(effectiveHost, port, url, logger, {
195
+ opened: (targetId) => `Opened isolated browser tab (target=${targetId})`,
196
+ openFailed: (message) => `Failed to open isolated browser tab (${message}); ${fallbackLabel}`,
197
+ attachFailed: (targetId, message) => `Failed to attach to isolated browser tab ${targetId} (${message}); ${fallbackLabel}`,
198
+ closeFailed: (targetId, message) => `Failed to close unused browser tab ${targetId}: ${message}`,
199
+ });
200
+ if (targetConnection) {
201
+ return targetConnection;
202
+ }
203
+ if (attempt >= retries) {
204
+ break;
205
+ }
206
+ attempt += 1;
207
+ await delay(retryDelayMs * attempt);
208
+ }
209
+ if (!fallbackToDefault) {
210
+ throw new Error('Failed to open isolated browser tab; refusing to attach to default target.');
211
+ }
212
+ const client = await connectToChrome(port, logger, effectiveHost);
213
+ return { client };
214
+ }
215
+ export async function closeTab(port, targetId, logger, host) {
216
+ const effectiveHost = host ?? '127.0.0.1';
217
+ try {
218
+ await CDP.Close({ host: effectiveHost, port, id: targetId });
219
+ logger(`Closed isolated browser tab (target=${targetId})`);
220
+ }
221
+ catch (error) {
222
+ const message = error instanceof Error ? error.message : String(error);
223
+ logger(`Failed to close browser tab ${targetId}: ${message}`);
224
+ }
225
+ }
157
226
  function buildChromeFlags(headless, debugBindAddress) {
158
227
  const flags = [
159
228
  '--disable-background-networking',
@@ -12,6 +12,13 @@ export const DEFAULT_BROWSER_CONFIG = {
12
12
  timeoutMs: 1_200_000,
13
13
  debugPort: null,
14
14
  inputTimeoutMs: 60_000,
15
+ assistantRecheckDelayMs: 0,
16
+ assistantRecheckTimeoutMs: 120_000,
17
+ reuseChromeWaitMs: 10_000,
18
+ profileLockTimeoutMs: 300_000,
19
+ autoReattachDelayMs: 0,
20
+ autoReattachIntervalMs: 0,
21
+ autoReattachTimeoutMs: 120_000,
15
22
  cookieSync: true,
16
23
  cookieNames: null,
17
24
  cookieSyncWaitMs: 0,
@@ -57,6 +64,13 @@ export function resolveBrowserConfig(config) {
57
64
  timeoutMs: config?.timeoutMs ?? DEFAULT_BROWSER_CONFIG.timeoutMs,
58
65
  debugPort: config?.debugPort ?? debugPortEnv ?? DEFAULT_BROWSER_CONFIG.debugPort,
59
66
  inputTimeoutMs: config?.inputTimeoutMs ?? DEFAULT_BROWSER_CONFIG.inputTimeoutMs,
67
+ assistantRecheckDelayMs: config?.assistantRecheckDelayMs ?? DEFAULT_BROWSER_CONFIG.assistantRecheckDelayMs,
68
+ assistantRecheckTimeoutMs: config?.assistantRecheckTimeoutMs ?? DEFAULT_BROWSER_CONFIG.assistantRecheckTimeoutMs,
69
+ reuseChromeWaitMs: config?.reuseChromeWaitMs ?? DEFAULT_BROWSER_CONFIG.reuseChromeWaitMs,
70
+ profileLockTimeoutMs: config?.profileLockTimeoutMs ?? DEFAULT_BROWSER_CONFIG.profileLockTimeoutMs,
71
+ autoReattachDelayMs: config?.autoReattachDelayMs ?? DEFAULT_BROWSER_CONFIG.autoReattachDelayMs,
72
+ autoReattachIntervalMs: config?.autoReattachIntervalMs ?? DEFAULT_BROWSER_CONFIG.autoReattachIntervalMs,
73
+ autoReattachTimeoutMs: config?.autoReattachTimeoutMs ?? DEFAULT_BROWSER_CONFIG.autoReattachTimeoutMs,
60
74
  cookieSync: config?.cookieSync ?? cookieSyncDefault,
61
75
  cookieNames: config?.cookieNames ?? DEFAULT_BROWSER_CONFIG.cookieNames,
62
76
  cookieSyncWaitMs: config?.cookieSyncWaitMs ?? DEFAULT_BROWSER_CONFIG.cookieSyncWaitMs,
@@ -0,0 +1,164 @@
1
+ import fs from 'node:fs/promises';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import { Launcher } from 'chrome-launcher';
5
+ export async function detectChromeBinary() {
6
+ const envPath = (process.env.CHROME_PATH ?? '').trim();
7
+ if (envPath) {
8
+ const ok = await isExecutable(envPath);
9
+ if (ok) {
10
+ return { path: envPath };
11
+ }
12
+ }
13
+ const launcherDetected = Launcher.getFirstInstallation();
14
+ if (launcherDetected) {
15
+ return { path: launcherDetected };
16
+ }
17
+ const candidates = platformChromeCandidates();
18
+ for (const candidate of candidates.absolutePaths) {
19
+ if (await isExecutable(candidate)) {
20
+ return { path: candidate };
21
+ }
22
+ }
23
+ const fromPath = await findOnPath(candidates.binaryNames);
24
+ if (fromPath) {
25
+ return { path: fromPath };
26
+ }
27
+ return { path: null };
28
+ }
29
+ export async function detectChromeCookieDb({ profile }) {
30
+ const profileName = profile?.trim() ? profile.trim() : 'Default';
31
+ if (process.platform === 'win32') {
32
+ return null;
33
+ }
34
+ const roots = platformProfileRoots();
35
+ for (const root of roots) {
36
+ const dir = path.join(root, profileName);
37
+ const direct = path.join(dir, 'Cookies');
38
+ if (await isFile(direct))
39
+ return direct;
40
+ const network = path.join(dir, 'Network', 'Cookies');
41
+ if (await isFile(network))
42
+ return network;
43
+ }
44
+ return null;
45
+ }
46
+ function platformChromeCandidates() {
47
+ if (process.platform === 'linux') {
48
+ return {
49
+ binaryNames: [
50
+ 'google-chrome',
51
+ 'google-chrome-stable',
52
+ 'chromium',
53
+ 'chromium-browser',
54
+ 'brave-browser',
55
+ 'microsoft-edge',
56
+ 'microsoft-edge-stable',
57
+ ],
58
+ absolutePaths: [
59
+ '/usr/bin/google-chrome',
60
+ '/usr/bin/google-chrome-stable',
61
+ '/usr/bin/google-chrome-beta',
62
+ '/usr/bin/google-chrome-unstable',
63
+ '/usr/bin/chromium',
64
+ '/usr/bin/chromium-browser',
65
+ '/usr/bin/brave-browser',
66
+ '/usr/bin/microsoft-edge',
67
+ '/usr/bin/microsoft-edge-stable',
68
+ '/snap/bin/chromium',
69
+ '/snap/bin/brave',
70
+ '/snap/bin/brave-browser',
71
+ '/snap/bin/microsoft-edge',
72
+ '/opt/google/chrome/chrome',
73
+ ],
74
+ };
75
+ }
76
+ if (process.platform === 'darwin') {
77
+ return {
78
+ binaryNames: [],
79
+ absolutePaths: [
80
+ '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
81
+ '/Applications/Chromium.app/Contents/MacOS/Chromium',
82
+ '/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge',
83
+ '/Applications/Brave Browser.app/Contents/MacOS/Brave Browser',
84
+ ],
85
+ };
86
+ }
87
+ if (process.platform === 'win32') {
88
+ const programFiles = process.env.ProgramFiles ?? 'C:\\Program Files';
89
+ const programFilesX86 = process.env['ProgramFiles(x86)'] ?? 'C:\\Program Files (x86)';
90
+ const localAppData = process.env.LOCALAPPDATA ?? path.join(os.homedir(), 'AppData', 'Local');
91
+ return {
92
+ binaryNames: [],
93
+ absolutePaths: [
94
+ path.join(programFiles, 'Google', 'Chrome', 'Application', 'chrome.exe'),
95
+ path.join(programFilesX86, 'Google', 'Chrome', 'Application', 'chrome.exe'),
96
+ path.join(localAppData, 'Google', 'Chrome', 'Application', 'chrome.exe'),
97
+ path.join(programFiles, 'Microsoft', 'Edge', 'Application', 'msedge.exe'),
98
+ path.join(programFilesX86, 'Microsoft', 'Edge', 'Application', 'msedge.exe'),
99
+ ],
100
+ };
101
+ }
102
+ return { binaryNames: [], absolutePaths: [] };
103
+ }
104
+ function platformProfileRoots() {
105
+ const home = os.homedir();
106
+ if (process.platform === 'darwin') {
107
+ return [
108
+ path.join(home, 'Library', 'Application Support', 'Google', 'Chrome'),
109
+ path.join(home, 'Library', 'Application Support', 'Chromium'),
110
+ path.join(home, 'Library', 'Application Support', 'Microsoft Edge'),
111
+ path.join(home, 'Library', 'Application Support', 'BraveSoftware', 'Brave-Browser'),
112
+ ];
113
+ }
114
+ if (process.platform === 'linux') {
115
+ return [
116
+ path.join(home, '.config', 'google-chrome'),
117
+ path.join(home, '.config', 'google-chrome-beta'),
118
+ path.join(home, '.config', 'google-chrome-unstable'),
119
+ path.join(home, '.config', 'chromium'),
120
+ path.join(home, '.config', 'microsoft-edge'),
121
+ path.join(home, '.config', 'BraveSoftware', 'Brave-Browser'),
122
+ // Snap Chromium profiles
123
+ path.join(home, 'snap', 'chromium', 'common', 'chromium'),
124
+ path.join(home, 'snap', 'chromium', 'current', 'chromium'),
125
+ ];
126
+ }
127
+ return [];
128
+ }
129
+ async function isExecutable(candidate) {
130
+ try {
131
+ const stat = await fs.stat(candidate);
132
+ if (!stat.isFile())
133
+ return false;
134
+ if (process.platform === 'win32')
135
+ return true;
136
+ // eslint-disable-next-line no-bitwise
137
+ return (stat.mode & 0o111) !== 0;
138
+ }
139
+ catch {
140
+ return false;
141
+ }
142
+ }
143
+ async function isFile(candidate) {
144
+ try {
145
+ const stat = await fs.stat(candidate);
146
+ return stat.isFile();
147
+ }
148
+ catch {
149
+ return false;
150
+ }
151
+ }
152
+ async function findOnPath(names) {
153
+ const rawPath = process.env.PATH ?? '';
154
+ const dirs = rawPath.split(path.delimiter).filter(Boolean);
155
+ for (const name of names) {
156
+ for (const dir of dirs) {
157
+ const candidate = path.join(dir, name);
158
+ if (await isExecutable(candidate)) {
159
+ return candidate;
160
+ }
161
+ }
162
+ }
163
+ return null;
164
+ }