@steipete/oracle 1.1.0 → 1.3.0
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 +40 -7
- package/assets-oracle-icon.png +0 -0
- package/dist/.DS_Store +0 -0
- package/dist/bin/oracle-cli.js +315 -47
- package/dist/bin/oracle-mcp.js +6 -0
- package/dist/src/browser/actions/modelSelection.js +117 -29
- package/dist/src/browser/config.js +6 -0
- package/dist/src/browser/cookies.js +50 -12
- package/dist/src/browser/index.js +19 -5
- package/dist/src/browser/prompt.js +6 -5
- package/dist/src/browser/sessionRunner.js +14 -3
- package/dist/src/cli/browserConfig.js +109 -2
- package/dist/src/cli/detach.js +12 -0
- package/dist/src/cli/dryRun.js +60 -8
- package/dist/src/cli/engine.js +7 -0
- package/dist/src/cli/help.js +3 -1
- package/dist/src/cli/hiddenAliases.js +17 -0
- package/dist/src/cli/markdownRenderer.js +79 -0
- package/dist/src/cli/notifier.js +223 -0
- package/dist/src/cli/options.js +22 -0
- package/dist/src/cli/promptRequirement.js +3 -0
- package/dist/src/cli/runOptions.js +43 -0
- package/dist/src/cli/sessionCommand.js +1 -1
- package/dist/src/cli/sessionDisplay.js +94 -7
- package/dist/src/cli/sessionRunner.js +32 -2
- package/dist/src/cli/tui/index.js +457 -0
- package/dist/src/config.js +27 -0
- package/dist/src/mcp/server.js +36 -0
- package/dist/src/mcp/tools/consult.js +158 -0
- package/dist/src/mcp/tools/sessionResources.js +64 -0
- package/dist/src/mcp/tools/sessions.js +106 -0
- package/dist/src/mcp/types.js +17 -0
- package/dist/src/mcp/utils.js +24 -0
- package/dist/src/oracle/client.js +24 -6
- package/dist/src/oracle/config.js +10 -0
- package/dist/src/oracle/files.js +151 -8
- package/dist/src/oracle/format.js +2 -7
- package/dist/src/oracle/fsAdapter.js +4 -1
- package/dist/src/oracle/gemini.js +161 -0
- package/dist/src/oracle/logging.js +36 -0
- package/dist/src/oracle/oscProgress.js +7 -1
- package/dist/src/oracle/run.js +148 -64
- package/dist/src/oracle/tokenEstimate.js +34 -0
- package/dist/src/oracle.js +1 -0
- package/dist/src/sessionManager.js +50 -3
- package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
- package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/Info.plist +20 -0
- package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
- package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/Resources/OracleIcon.icns +0 -0
- package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/_CodeSignature/CodeResources +128 -0
- package/dist/vendor/oracle-notifier/OracleNotifier.swift +45 -0
- package/dist/vendor/oracle-notifier/README.md +24 -0
- package/dist/vendor/oracle-notifier/build-notifier.sh +93 -0
- package/dist/vendor/oracle-notifier/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
- package/dist/vendor/oracle-notifier/oracle-notifier/OracleNotifier.app/Contents/Info.plist +20 -0
- package/dist/vendor/oracle-notifier/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
- package/dist/vendor/oracle-notifier/oracle-notifier/OracleNotifier.app/Contents/Resources/OracleIcon.icns +0 -0
- package/dist/vendor/oracle-notifier/oracle-notifier/OracleNotifier.app/Contents/_CodeSignature/CodeResources +128 -0
- package/dist/vendor/oracle-notifier/oracle-notifier/OracleNotifier.swift +45 -0
- package/dist/vendor/oracle-notifier/oracle-notifier/README.md +24 -0
- package/dist/vendor/oracle-notifier/oracle-notifier/build-notifier.sh +93 -0
- package/package.json +22 -6
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/Info.plist +20 -0
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/Resources/OracleIcon.icns +0 -0
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/_CodeSignature/CodeResources +128 -0
- package/vendor/oracle-notifier/OracleNotifier.swift +45 -0
- package/vendor/oracle-notifier/build-notifier.sh +93 -0
package/dist/bin/oracle-cli.js
CHANGED
|
@@ -2,43 +2,57 @@
|
|
|
2
2
|
import 'dotenv/config';
|
|
3
3
|
import { spawn } from 'node:child_process';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { once } from 'node:events';
|
|
5
6
|
import { Command, Option } from 'commander';
|
|
6
|
-
import { resolveEngine } from '../src/cli/engine.js';
|
|
7
|
+
import { resolveEngine, defaultWaitPreference } from '../src/cli/engine.js';
|
|
7
8
|
import { shouldRequirePrompt } from '../src/cli/promptRequirement.js';
|
|
8
9
|
import chalk from 'chalk';
|
|
9
10
|
import { ensureSessionStorage, initializeSession, readSessionMetadata, createSessionLogWriter, deleteSessionsOlderThan, } from '../src/sessionManager.js';
|
|
10
|
-
import {
|
|
11
|
+
import { renderPromptMarkdown, readFiles } from '../src/oracle.js';
|
|
11
12
|
import { CHATGPT_URL } from '../src/browserMode.js';
|
|
12
13
|
import { applyHelpStyling } from '../src/cli/help.js';
|
|
13
|
-
import { collectPaths, parseFloatOption, parseIntOption, parseSearchOption, usesDefaultStatusFilters, resolvePreviewMode, normalizeModelOption, resolveApiModel, inferModelFromLabel, parseHeartbeatOption, } from '../src/cli/options.js';
|
|
14
|
+
import { collectPaths, parseFloatOption, parseIntOption, parseSearchOption, usesDefaultStatusFilters, resolvePreviewMode, normalizeModelOption, normalizeBaseUrl, resolveApiModel, inferModelFromLabel, parseHeartbeatOption, parseTimeoutOption, } from '../src/cli/options.js';
|
|
15
|
+
import { shouldDetachSession } from '../src/cli/detach.js';
|
|
16
|
+
import { applyHiddenAliases } from '../src/cli/hiddenAliases.js';
|
|
14
17
|
import { buildBrowserConfig, resolveBrowserModelLabel } from '../src/cli/browserConfig.js';
|
|
15
18
|
import { performSessionRun } from '../src/cli/sessionRunner.js';
|
|
16
|
-
import { attachSession, showStatus } from '../src/cli/sessionDisplay.js';
|
|
19
|
+
import { attachSession, showStatus, formatCompletionSummary } from '../src/cli/sessionDisplay.js';
|
|
20
|
+
import { resolveGeminiModelId } from '../src/oracle/gemini.js';
|
|
17
21
|
import { handleSessionCommand, formatSessionCleanupMessage } from '../src/cli/sessionCommand.js';
|
|
18
22
|
import { isErrorLogged } from '../src/cli/errorUtils.js';
|
|
19
23
|
import { handleSessionAlias, handleStatusFlag } from '../src/cli/rootAlias.js';
|
|
20
24
|
import { getCliVersion } from '../src/version.js';
|
|
21
|
-
import { runDryRunSummary } from '../src/cli/dryRun.js';
|
|
25
|
+
import { runDryRunSummary, runBrowserPreview } from '../src/cli/dryRun.js';
|
|
26
|
+
import { launchTui } from '../src/cli/tui/index.js';
|
|
27
|
+
import { resolveNotificationSettings, deriveNotificationSettingsFromMetadata, } from '../src/cli/notifier.js';
|
|
28
|
+
import { loadUserConfig } from '../src/config.js';
|
|
22
29
|
const VERSION = getCliVersion();
|
|
23
30
|
const CLI_ENTRYPOINT = fileURLToPath(import.meta.url);
|
|
24
31
|
const rawCliArgs = process.argv.slice(2);
|
|
32
|
+
const userCliArgs = rawCliArgs[0] === CLI_ENTRYPOINT ? rawCliArgs.slice(1) : rawCliArgs;
|
|
25
33
|
const isTty = process.stdout.isTTY;
|
|
34
|
+
const tuiEnabled = () => isTty && process.env.ORACLE_NO_TUI !== '1';
|
|
26
35
|
const program = new Command();
|
|
27
36
|
applyHelpStyling(program, VERSION, isTty);
|
|
28
37
|
program.hook('preAction', (thisCommand) => {
|
|
29
38
|
if (thisCommand !== program) {
|
|
30
39
|
return;
|
|
31
40
|
}
|
|
32
|
-
if (
|
|
41
|
+
if (userCliArgs.some((arg) => arg === '--help' || arg === '-h')) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (userCliArgs.length === 0 && tuiEnabled()) {
|
|
45
|
+
// Skip prompt enforcement; runRootCommand will launch the TUI.
|
|
33
46
|
return;
|
|
34
47
|
}
|
|
35
48
|
const opts = thisCommand.optsWithGlobals();
|
|
49
|
+
applyHiddenAliases(opts, (key, value) => thisCommand.setOptionValue(key, value));
|
|
36
50
|
const positional = thisCommand.args?.[0];
|
|
37
51
|
if (!opts.prompt && positional) {
|
|
38
52
|
opts.prompt = positional;
|
|
39
53
|
thisCommand.setOptionValue('prompt', positional);
|
|
40
54
|
}
|
|
41
|
-
if (shouldRequirePrompt(
|
|
55
|
+
if (shouldRequirePrompt(userCliArgs, opts)) {
|
|
42
56
|
console.log(chalk.yellow('Prompt is required. Provide it via --prompt "<text>" or positional [prompt].'));
|
|
43
57
|
thisCommand.help({ error: false });
|
|
44
58
|
process.exitCode = 1;
|
|
@@ -51,16 +65,31 @@ program
|
|
|
51
65
|
.version(VERSION)
|
|
52
66
|
.argument('[prompt]', 'Prompt text (shorthand for --prompt).')
|
|
53
67
|
.option('-p, --prompt <text>', 'User prompt to send to the model.')
|
|
68
|
+
.addOption(new Option('--message <text>', 'Alias for --prompt.').hideHelp())
|
|
54
69
|
.option('-f, --file <paths...>', 'Files/directories or glob patterns to attach (prefix with !pattern to exclude). Files larger than 1 MB are rejected automatically.', collectPaths, [])
|
|
70
|
+
.addOption(new Option('--include <paths...>', 'Alias for --file.')
|
|
71
|
+
.argParser(collectPaths)
|
|
72
|
+
.default([])
|
|
73
|
+
.hideHelp())
|
|
55
74
|
.option('-s, --slug <words>', 'Custom session slug (3-5 words).')
|
|
56
75
|
.option('-m, --model <model>', 'Model to target (gpt-5-pro | gpt-5.1, or ChatGPT labels like "5.1 Instant" for browser runs).', normalizeModelOption, 'gpt-5-pro')
|
|
57
|
-
.addOption(new Option('-e, --engine <mode>', 'Execution engine (api | browser). If omitted,
|
|
76
|
+
.addOption(new Option('-e, --engine <mode>', 'Execution engine (api | browser). If omitted, oracle picks api when OPENAI_API_KEY is set, otherwise browser.').choices(['api', 'browser']))
|
|
58
77
|
.option('--files-report', 'Show token usage per attached file (also prints automatically when files exceed the token budget).', false)
|
|
59
78
|
.option('-v, --verbose', 'Enable verbose logging for all operations.', false)
|
|
60
|
-
.
|
|
61
|
-
.
|
|
79
|
+
.addOption(new Option('--[no-]notify', 'Desktop notification when a session finishes (default on unless CI/SSH).')
|
|
80
|
+
.default(undefined))
|
|
81
|
+
.addOption(new Option('--[no-]notify-sound', 'Play a notification sound on completion (default off).').default(undefined))
|
|
82
|
+
.addOption(new Option('--timeout <seconds|auto>', 'Overall timeout before aborting the API call (auto = 20m for gpt-5-pro, 30s otherwise).')
|
|
83
|
+
.argParser(parseTimeoutOption)
|
|
84
|
+
.default('auto'))
|
|
85
|
+
.addOption(new Option('--preview [mode]', '(alias) Preview the request without calling the model (summary | json | full). Deprecated: use --dry-run instead.')
|
|
86
|
+
.hideHelp()
|
|
62
87
|
.choices(['summary', 'json', 'full'])
|
|
63
88
|
.preset('summary'))
|
|
89
|
+
.addOption(new Option('--dry-run [mode]', 'Preview without calling the model (summary | json | full).')
|
|
90
|
+
.choices(['summary', 'json', 'full'])
|
|
91
|
+
.preset('summary')
|
|
92
|
+
.default(false))
|
|
64
93
|
.addOption(new Option('--exec-session <id>').hideHelp())
|
|
65
94
|
.addOption(new Option('--session <id>').hideHelp())
|
|
66
95
|
.addOption(new Option('--status', 'Show stored sessions (alias for `oracle status`).').default(false).hideHelp())
|
|
@@ -75,20 +104,30 @@ program
|
|
|
75
104
|
.addOption(new Option('--max-output <tokens>', 'Override the max output tokens for the selected model.')
|
|
76
105
|
.argParser(parseIntOption)
|
|
77
106
|
.hideHelp())
|
|
107
|
+
.option('--base-url <url>', 'Override the OpenAI-compatible base URL for API runs (e.g. LiteLLM proxy endpoint).')
|
|
108
|
+
.option('--azure-endpoint <url>', 'Azure OpenAI Endpoint (e.g. https://resource.openai.azure.com/).')
|
|
109
|
+
.option('--azure-deployment <name>', 'Azure OpenAI Deployment Name.')
|
|
110
|
+
.option('--azure-api-version <version>', 'Azure OpenAI API Version.')
|
|
78
111
|
.addOption(new Option('--browser', '(deprecated) Use --engine browser instead.').default(false).hideHelp())
|
|
79
112
|
.addOption(new Option('--browser-chrome-profile <name>', 'Chrome profile name/path for cookie reuse.').hideHelp())
|
|
80
113
|
.addOption(new Option('--browser-chrome-path <path>', 'Explicit Chrome or Chromium executable path.').hideHelp())
|
|
81
114
|
.addOption(new Option('--browser-url <url>', `Override the ChatGPT URL (default ${CHATGPT_URL}).`).hideHelp())
|
|
82
115
|
.addOption(new Option('--browser-timeout <ms|s|m>', 'Maximum time to wait for an answer (default 900s).').hideHelp())
|
|
83
116
|
.addOption(new Option('--browser-input-timeout <ms|s|m>', 'Maximum time to wait for the prompt textarea (default 30s).').hideHelp())
|
|
117
|
+
.addOption(new Option('--browser-cookie-names <names>', 'Comma-separated cookie allowlist for sync.').hideHelp())
|
|
118
|
+
.addOption(new Option('--browser-inline-cookies <jsonOrBase64>', 'Inline cookies payload (JSON array or base64-encoded JSON).').hideHelp())
|
|
119
|
+
.addOption(new Option('--browser-inline-cookies-file <path>', 'Load inline cookies from file (JSON or base64 JSON).').hideHelp())
|
|
84
120
|
.addOption(new Option('--browser-no-cookie-sync', 'Skip copying cookies from Chrome.').hideHelp())
|
|
85
121
|
.addOption(new Option('--browser-headless', 'Launch Chrome in headless mode.').hideHelp())
|
|
86
122
|
.addOption(new Option('--browser-hide-window', 'Hide the Chrome window after launch (macOS headful only).').hideHelp())
|
|
87
123
|
.addOption(new Option('--browser-keep-browser', 'Keep Chrome running after completion.').hideHelp())
|
|
88
124
|
.addOption(new Option('--browser-allow-cookie-errors', 'Continue even if Chrome cookies cannot be copied.').hideHelp())
|
|
89
125
|
.addOption(new Option('--browser-inline-files', 'Paste files directly into the ChatGPT composer instead of uploading attachments.').default(false))
|
|
126
|
+
.addOption(new Option('--browser-bundle-files', 'Bundle all attachments into a single archive before uploading.').default(false))
|
|
90
127
|
.option('--debug-help', 'Show the advanced/debug option set and exit.', false)
|
|
91
128
|
.option('--heartbeat <seconds>', 'Emit periodic in-progress updates (0 to disable).', parseHeartbeatOption, 30)
|
|
129
|
+
.addOption(new Option('--wait').default(undefined))
|
|
130
|
+
.addOption(new Option('--no-wait').default(undefined).hideHelp())
|
|
92
131
|
.showHelpAfterError('(use --help for usage)');
|
|
93
132
|
program.addHelpText('after', `
|
|
94
133
|
Examples:
|
|
@@ -106,6 +145,7 @@ const sessionCommand = program
|
|
|
106
145
|
.option('--limit <count>', 'Maximum sessions to show when listing (max 1000).', parseIntOption, 100)
|
|
107
146
|
.option('--all', 'Include all stored sessions regardless of age.', false)
|
|
108
147
|
.option('--clear', 'Delete stored sessions older than the provided window (24h default).', false)
|
|
148
|
+
.option('--hide-prompt', 'Hide stored prompt when displaying a session.', false)
|
|
109
149
|
.option('--render', 'Render completed session output as markdown (rich TTY only).', false)
|
|
110
150
|
.option('--render-markdown', 'Alias for --render.', false)
|
|
111
151
|
.option('--path', 'Print the stored session paths instead of attaching.', false)
|
|
@@ -122,6 +162,7 @@ const statusCommand = program
|
|
|
122
162
|
.option('--clear', 'Delete stored sessions older than the provided window (24h default).', false)
|
|
123
163
|
.option('--render', 'Render completed session output as markdown (rich TTY only).', false)
|
|
124
164
|
.option('--render-markdown', 'Alias for --render.', false)
|
|
165
|
+
.option('--hide-prompt', 'Hide stored prompt when displaying a session.', false)
|
|
125
166
|
.addOption(new Option('--clean', 'Deprecated alias for --clear.').default(false).hideHelp())
|
|
126
167
|
.action(async (sessionId, _options, command) => {
|
|
127
168
|
const statusOptions = command.opts();
|
|
@@ -145,7 +186,11 @@ const statusCommand = program
|
|
|
145
186
|
return;
|
|
146
187
|
}
|
|
147
188
|
if (sessionId) {
|
|
148
|
-
|
|
189
|
+
const autoRender = !command.getOptionValueSource?.('render') && !command.getOptionValueSource?.('renderMarkdown')
|
|
190
|
+
? process.stdout.isTTY
|
|
191
|
+
: false;
|
|
192
|
+
const renderMarkdown = Boolean(statusOptions.render || statusOptions.renderMarkdown || autoRender);
|
|
193
|
+
await attachSession(sessionId, { renderMarkdown, renderPrompt: !statusOptions.hidePrompt });
|
|
149
194
|
return;
|
|
150
195
|
}
|
|
151
196
|
const showExamples = usesDefaultStatusFilters(command);
|
|
@@ -160,27 +205,46 @@ function buildRunOptions(options, overrides = {}) {
|
|
|
160
205
|
if (!options.prompt) {
|
|
161
206
|
throw new Error('Prompt is required.');
|
|
162
207
|
}
|
|
208
|
+
const normalizedBaseUrl = normalizeBaseUrl(overrides.baseUrl ?? options.baseUrl);
|
|
209
|
+
const azure = options.azureEndpoint || overrides.azure?.endpoint
|
|
210
|
+
? {
|
|
211
|
+
endpoint: overrides.azure?.endpoint ?? options.azureEndpoint,
|
|
212
|
+
deployment: overrides.azure?.deployment ?? options.azureDeployment,
|
|
213
|
+
apiVersion: overrides.azure?.apiVersion ?? options.azureApiVersion,
|
|
214
|
+
}
|
|
215
|
+
: undefined;
|
|
163
216
|
return {
|
|
164
217
|
prompt: options.prompt,
|
|
165
218
|
model: options.model,
|
|
219
|
+
effectiveModelId: overrides.effectiveModelId ?? options.effectiveModelId ?? options.model,
|
|
166
220
|
file: overrides.file ?? options.file ?? [],
|
|
167
221
|
slug: overrides.slug ?? options.slug,
|
|
168
222
|
filesReport: overrides.filesReport ?? options.filesReport,
|
|
169
223
|
maxInput: overrides.maxInput ?? options.maxInput,
|
|
170
224
|
maxOutput: overrides.maxOutput ?? options.maxOutput,
|
|
171
225
|
system: overrides.system ?? options.system,
|
|
226
|
+
timeoutSeconds: overrides.timeoutSeconds ?? options.timeout,
|
|
172
227
|
silent: overrides.silent ?? options.silent,
|
|
173
228
|
search: overrides.search ?? options.search,
|
|
174
229
|
preview: overrides.preview ?? undefined,
|
|
175
230
|
previewMode: overrides.previewMode ?? options.previewMode,
|
|
176
231
|
apiKey: overrides.apiKey ?? options.apiKey,
|
|
232
|
+
baseUrl: normalizedBaseUrl,
|
|
233
|
+
azure,
|
|
177
234
|
sessionId: overrides.sessionId ?? options.sessionId,
|
|
178
235
|
verbose: overrides.verbose ?? options.verbose,
|
|
179
236
|
heartbeatIntervalMs: overrides.heartbeatIntervalMs ?? resolveHeartbeatIntervalMs(options.heartbeat),
|
|
180
237
|
browserInlineFiles: overrides.browserInlineFiles ?? options.browserInlineFiles ?? false,
|
|
238
|
+
browserBundleFiles: overrides.browserBundleFiles ?? options.browserBundleFiles ?? false,
|
|
181
239
|
background: overrides.background ?? undefined,
|
|
182
240
|
};
|
|
183
241
|
}
|
|
242
|
+
export function enforceBrowserSearchFlag(runOptions, sessionMode, logFn = console.log) {
|
|
243
|
+
if (sessionMode === 'browser' && runOptions.search === false) {
|
|
244
|
+
logFn(chalk.dim('Note: search is not available in browser engine; ignoring search=false.'));
|
|
245
|
+
runOptions.search = undefined;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
184
248
|
function resolveHeartbeatIntervalMs(seconds) {
|
|
185
249
|
if (typeof seconds !== 'number' || seconds <= 0) {
|
|
186
250
|
return undefined;
|
|
@@ -192,6 +256,7 @@ function buildRunOptionsFromMetadata(metadata) {
|
|
|
192
256
|
return {
|
|
193
257
|
prompt: stored.prompt ?? '',
|
|
194
258
|
model: stored.model ?? 'gpt-5-pro',
|
|
259
|
+
effectiveModelId: stored.effectiveModelId ?? stored.model,
|
|
195
260
|
file: stored.file ?? [],
|
|
196
261
|
slug: stored.slug,
|
|
197
262
|
filesReport: stored.filesReport,
|
|
@@ -199,14 +264,17 @@ function buildRunOptionsFromMetadata(metadata) {
|
|
|
199
264
|
maxOutput: stored.maxOutput,
|
|
200
265
|
system: stored.system,
|
|
201
266
|
silent: stored.silent,
|
|
202
|
-
search:
|
|
267
|
+
search: stored.search,
|
|
203
268
|
preview: false,
|
|
204
269
|
previewMode: undefined,
|
|
205
270
|
apiKey: undefined,
|
|
271
|
+
baseUrl: normalizeBaseUrl(stored.baseUrl),
|
|
272
|
+
azure: stored.azure,
|
|
206
273
|
sessionId: metadata.id,
|
|
207
274
|
verbose: stored.verbose,
|
|
208
275
|
heartbeatIntervalMs: stored.heartbeatIntervalMs,
|
|
209
276
|
browserInlineFiles: stored.browserInlineFiles,
|
|
277
|
+
browserBundleFiles: stored.browserBundleFiles,
|
|
210
278
|
background: stored.background,
|
|
211
279
|
};
|
|
212
280
|
}
|
|
@@ -217,7 +285,18 @@ function getBrowserConfigFromMetadata(metadata) {
|
|
|
217
285
|
return metadata.options?.browserConfig ?? metadata.browser?.config;
|
|
218
286
|
}
|
|
219
287
|
async function runRootCommand(options) {
|
|
288
|
+
if (process.env.ORACLE_FORCE_TUI === '1') {
|
|
289
|
+
await ensureSessionStorage();
|
|
290
|
+
await launchTui({ version: VERSION });
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
const userConfig = (await loadUserConfig()).config;
|
|
220
294
|
const helpRequested = rawCliArgs.some((arg) => arg === '--help' || arg === '-h');
|
|
295
|
+
const optionUsesDefault = (name) => {
|
|
296
|
+
// Commander reports undefined for untouched options, so treat undefined/default the same
|
|
297
|
+
const source = program.getOptionValueSource?.(name);
|
|
298
|
+
return source == null || source === 'default';
|
|
299
|
+
};
|
|
221
300
|
if (helpRequested) {
|
|
222
301
|
if (options.verbose) {
|
|
223
302
|
console.log('');
|
|
@@ -227,8 +306,12 @@ async function runRootCommand(options) {
|
|
|
227
306
|
program.help({ error: false });
|
|
228
307
|
return;
|
|
229
308
|
}
|
|
230
|
-
const previewMode = resolvePreviewMode(options.preview);
|
|
231
|
-
if (
|
|
309
|
+
const previewMode = resolvePreviewMode(options.dryRun || options.preview);
|
|
310
|
+
if (userCliArgs.length === 0) {
|
|
311
|
+
if (tuiEnabled()) {
|
|
312
|
+
await launchTui({ version: VERSION });
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
232
315
|
console.log(chalk.yellow('No prompt or subcommand supplied. See `oracle --help` for usage.'));
|
|
233
316
|
program.help({ error: false });
|
|
234
317
|
return;
|
|
@@ -237,19 +320,77 @@ async function runRootCommand(options) {
|
|
|
237
320
|
printDebugHelp(program.name());
|
|
238
321
|
return;
|
|
239
322
|
}
|
|
240
|
-
if (options.dryRun && previewMode) {
|
|
241
|
-
throw new Error('--dry-run cannot be combined with --preview.');
|
|
242
|
-
}
|
|
243
323
|
if (options.dryRun && options.renderMarkdown) {
|
|
244
324
|
throw new Error('--dry-run cannot be combined with --render-markdown.');
|
|
245
325
|
}
|
|
246
|
-
const
|
|
326
|
+
const preferredEngine = options.engine ?? userConfig.engine;
|
|
327
|
+
let engine = resolveEngine({ engine: preferredEngine, browserFlag: options.browser, env: process.env });
|
|
247
328
|
if (options.browser) {
|
|
248
329
|
console.log(chalk.yellow('`--browser` is deprecated; use `--engine browser` instead.'));
|
|
249
330
|
}
|
|
331
|
+
if (optionUsesDefault('model') && userConfig.model) {
|
|
332
|
+
options.model = userConfig.model;
|
|
333
|
+
}
|
|
334
|
+
if (optionUsesDefault('search') && userConfig.search) {
|
|
335
|
+
options.search = userConfig.search === 'on';
|
|
336
|
+
}
|
|
337
|
+
if (optionUsesDefault('filesReport') && userConfig.filesReport != null) {
|
|
338
|
+
options.filesReport = Boolean(userConfig.filesReport);
|
|
339
|
+
}
|
|
340
|
+
if (optionUsesDefault('heartbeat') && typeof userConfig.heartbeatSeconds === 'number') {
|
|
341
|
+
options.heartbeat = userConfig.heartbeatSeconds;
|
|
342
|
+
}
|
|
343
|
+
if (optionUsesDefault('baseUrl') && userConfig.apiBaseUrl) {
|
|
344
|
+
options.baseUrl = userConfig.apiBaseUrl;
|
|
345
|
+
}
|
|
346
|
+
if (optionUsesDefault('azureEndpoint')) {
|
|
347
|
+
if (process.env.AZURE_OPENAI_ENDPOINT) {
|
|
348
|
+
options.azureEndpoint = process.env.AZURE_OPENAI_ENDPOINT;
|
|
349
|
+
}
|
|
350
|
+
else if (userConfig.azure?.endpoint) {
|
|
351
|
+
options.azureEndpoint = userConfig.azure.endpoint;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
if (optionUsesDefault('azureDeployment')) {
|
|
355
|
+
if (process.env.AZURE_OPENAI_DEPLOYMENT) {
|
|
356
|
+
options.azureDeployment = process.env.AZURE_OPENAI_DEPLOYMENT;
|
|
357
|
+
}
|
|
358
|
+
else if (userConfig.azure?.deployment) {
|
|
359
|
+
options.azureDeployment = userConfig.azure.deployment;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
if (optionUsesDefault('azureApiVersion')) {
|
|
363
|
+
if (process.env.AZURE_OPENAI_API_VERSION) {
|
|
364
|
+
options.azureApiVersion = process.env.AZURE_OPENAI_API_VERSION;
|
|
365
|
+
}
|
|
366
|
+
else if (userConfig.azure?.apiVersion) {
|
|
367
|
+
options.azureApiVersion = userConfig.azure.apiVersion;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
250
370
|
const cliModelArg = normalizeModelOption(options.model) || 'gpt-5-pro';
|
|
251
|
-
const
|
|
371
|
+
const resolvedModelCandidate = engine === 'browser' ? inferModelFromLabel(cliModelArg) : resolveApiModel(cliModelArg);
|
|
372
|
+
const isGemini = resolvedModelCandidate.startsWith('gemini');
|
|
373
|
+
const userForcedBrowser = options.browser || options.engine === 'browser';
|
|
374
|
+
if (isGemini && userForcedBrowser) {
|
|
375
|
+
throw new Error('Gemini is only supported via API. Use --engine api.');
|
|
376
|
+
}
|
|
377
|
+
if (isGemini && engine === 'browser') {
|
|
378
|
+
engine = 'api';
|
|
379
|
+
}
|
|
380
|
+
const resolvedModel = isGemini ? resolveApiModel(cliModelArg) : resolvedModelCandidate;
|
|
381
|
+
const effectiveModelId = resolvedModel.startsWith('gemini') ? resolveGeminiModelId(resolvedModel) : resolvedModel;
|
|
382
|
+
const resolvedBaseUrl = normalizeBaseUrl(options.baseUrl ?? process.env.OPENAI_BASE_URL);
|
|
252
383
|
const resolvedOptions = { ...options, model: resolvedModel };
|
|
384
|
+
resolvedOptions.baseUrl = resolvedBaseUrl;
|
|
385
|
+
// Decide whether to block until completion:
|
|
386
|
+
// - explicit --wait / --no-wait wins
|
|
387
|
+
// - otherwise block for fast models (gpt-5.1, browser) and detach by default for gpt-5-pro API
|
|
388
|
+
const waitPreference = resolveWaitFlag({
|
|
389
|
+
waitFlag: options.wait,
|
|
390
|
+
noWaitFlag: options.noWait,
|
|
391
|
+
model: resolvedModel,
|
|
392
|
+
engine,
|
|
393
|
+
});
|
|
253
394
|
if (await handleStatusFlag(options, { attachSession, showStatus })) {
|
|
254
395
|
return;
|
|
255
396
|
}
|
|
@@ -269,77 +410,146 @@ async function runRootCommand(options) {
|
|
|
269
410
|
return;
|
|
270
411
|
}
|
|
271
412
|
if (previewMode) {
|
|
413
|
+
if (!options.prompt) {
|
|
414
|
+
throw new Error('Prompt is required when using --dry-run/preview.');
|
|
415
|
+
}
|
|
416
|
+
if (userConfig.promptSuffix) {
|
|
417
|
+
options.prompt = `${options.prompt.trim()}\n${userConfig.promptSuffix}`;
|
|
418
|
+
}
|
|
419
|
+
resolvedOptions.prompt = options.prompt;
|
|
420
|
+
const runOptions = buildRunOptions(resolvedOptions, { preview: true, previewMode, baseUrl: resolvedBaseUrl });
|
|
272
421
|
if (engine === 'browser') {
|
|
273
|
-
|
|
422
|
+
await runBrowserPreview({
|
|
423
|
+
runOptions,
|
|
424
|
+
cwd: process.cwd(),
|
|
425
|
+
version: VERSION,
|
|
426
|
+
previewMode,
|
|
427
|
+
log: console.log,
|
|
428
|
+
}, {});
|
|
429
|
+
return;
|
|
274
430
|
}
|
|
275
|
-
|
|
276
|
-
|
|
431
|
+
// API dry-run/preview path
|
|
432
|
+
if (previewMode === 'summary') {
|
|
433
|
+
await runDryRunSummary({
|
|
434
|
+
engine,
|
|
435
|
+
runOptions,
|
|
436
|
+
cwd: process.cwd(),
|
|
437
|
+
version: VERSION,
|
|
438
|
+
log: console.log,
|
|
439
|
+
}, {});
|
|
440
|
+
return;
|
|
277
441
|
}
|
|
278
|
-
const runOptions = buildRunOptions(resolvedOptions, { preview: true, previewMode });
|
|
279
|
-
await runOracle(runOptions, { log: console.log, write: (chunk) => process.stdout.write(chunk) });
|
|
280
|
-
return;
|
|
281
|
-
}
|
|
282
|
-
if (!options.prompt) {
|
|
283
|
-
throw new Error('Prompt is required when starting a new session.');
|
|
284
|
-
}
|
|
285
|
-
if (options.dryRun) {
|
|
286
|
-
const baseRunOptions = buildRunOptions(resolvedOptions, { preview: false, previewMode: undefined });
|
|
287
442
|
await runDryRunSummary({
|
|
288
443
|
engine,
|
|
289
|
-
runOptions
|
|
444
|
+
runOptions,
|
|
290
445
|
cwd: process.cwd(),
|
|
291
446
|
version: VERSION,
|
|
292
447
|
log: console.log,
|
|
293
448
|
}, {});
|
|
294
449
|
return;
|
|
295
450
|
}
|
|
451
|
+
if (!options.prompt) {
|
|
452
|
+
throw new Error('Prompt is required when starting a new session.');
|
|
453
|
+
}
|
|
454
|
+
if (userConfig.promptSuffix) {
|
|
455
|
+
options.prompt = `${options.prompt.trim()}\n${userConfig.promptSuffix}`;
|
|
456
|
+
}
|
|
457
|
+
resolvedOptions.prompt = options.prompt;
|
|
296
458
|
if (options.file && options.file.length > 0) {
|
|
297
459
|
await readFiles(options.file, { cwd: process.cwd() });
|
|
298
460
|
}
|
|
461
|
+
applyBrowserDefaultsFromConfig(options, userConfig);
|
|
462
|
+
const notifications = resolveNotificationSettings({
|
|
463
|
+
cliNotify: options.notify,
|
|
464
|
+
cliNotifySound: options.notifySound,
|
|
465
|
+
env: process.env,
|
|
466
|
+
config: userConfig.notify,
|
|
467
|
+
});
|
|
299
468
|
const sessionMode = engine === 'browser' ? 'browser' : 'api';
|
|
300
469
|
const browserModelLabelOverride = sessionMode === 'browser' ? resolveBrowserModelLabel(cliModelArg, resolvedModel) : undefined;
|
|
301
470
|
const browserConfig = sessionMode === 'browser'
|
|
302
|
-
? buildBrowserConfig({
|
|
471
|
+
? await buildBrowserConfig({
|
|
303
472
|
...options,
|
|
304
473
|
model: resolvedModel,
|
|
305
474
|
browserModelLabel: browserModelLabelOverride,
|
|
306
475
|
})
|
|
307
476
|
: undefined;
|
|
477
|
+
if (options.dryRun) {
|
|
478
|
+
const baseRunOptions = buildRunOptions(resolvedOptions, {
|
|
479
|
+
preview: false,
|
|
480
|
+
previewMode: undefined,
|
|
481
|
+
baseUrl: resolvedBaseUrl,
|
|
482
|
+
});
|
|
483
|
+
await runDryRunSummary({
|
|
484
|
+
engine,
|
|
485
|
+
runOptions: baseRunOptions,
|
|
486
|
+
cwd: process.cwd(),
|
|
487
|
+
version: VERSION,
|
|
488
|
+
log: console.log,
|
|
489
|
+
browserConfig,
|
|
490
|
+
}, {});
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
308
493
|
await ensureSessionStorage();
|
|
309
|
-
const baseRunOptions = buildRunOptions(resolvedOptions, {
|
|
494
|
+
const baseRunOptions = buildRunOptions(resolvedOptions, {
|
|
495
|
+
preview: false,
|
|
496
|
+
previewMode: undefined,
|
|
497
|
+
background: userConfig.background ?? resolvedOptions.background,
|
|
498
|
+
baseUrl: resolvedBaseUrl,
|
|
499
|
+
});
|
|
500
|
+
enforceBrowserSearchFlag(baseRunOptions, sessionMode, console.log);
|
|
501
|
+
if (sessionMode === 'browser' && baseRunOptions.search === false) {
|
|
502
|
+
console.log(chalk.dim('Note: search is not available in browser engine; ignoring search=false.'));
|
|
503
|
+
baseRunOptions.search = undefined;
|
|
504
|
+
}
|
|
310
505
|
const sessionMeta = await initializeSession({
|
|
311
506
|
...baseRunOptions,
|
|
312
507
|
mode: sessionMode,
|
|
313
508
|
browserConfig,
|
|
314
|
-
}, process.cwd());
|
|
315
|
-
const
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
const
|
|
509
|
+
}, process.cwd(), notifications);
|
|
510
|
+
const liveRunOptions = {
|
|
511
|
+
...baseRunOptions,
|
|
512
|
+
sessionId: sessionMeta.id,
|
|
513
|
+
effectiveModelId,
|
|
514
|
+
};
|
|
515
|
+
const disableDetachEnv = process.env.ORACLE_NO_DETACH === '1';
|
|
516
|
+
const detachAllowed = shouldDetachSession({
|
|
517
|
+
engine,
|
|
518
|
+
model: resolvedModel,
|
|
519
|
+
waitPreference,
|
|
520
|
+
disableDetachEnv,
|
|
521
|
+
});
|
|
522
|
+
const detached = !detachAllowed
|
|
321
523
|
? false
|
|
322
524
|
: await launchDetachedSession(sessionMeta.id).catch((error) => {
|
|
323
525
|
const message = error instanceof Error ? error.message : String(error);
|
|
324
526
|
console.log(chalk.yellow(`Unable to detach session runner (${message}). Running inline...`));
|
|
325
527
|
return false;
|
|
326
528
|
});
|
|
529
|
+
if (!waitPreference) {
|
|
530
|
+
if (!detached) {
|
|
531
|
+
console.log(chalk.red('Unable to start in background; use --wait to run inline.'));
|
|
532
|
+
process.exitCode = 1;
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
console.log(chalk.blue(`Session running in background. Reattach via: oracle session ${sessionMeta.id}`));
|
|
536
|
+
console.log(chalk.dim('Pro runs can take up to 10 minutes. Add --wait to stay attached.'));
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
327
539
|
if (detached === false) {
|
|
328
|
-
await runInteractiveSession(sessionMeta, liveRunOptions, sessionMode, browserConfig, true);
|
|
329
|
-
console.log(chalk.bold(`Session ${sessionMeta.id} completed`));
|
|
540
|
+
await runInteractiveSession(sessionMeta, liveRunOptions, sessionMode, browserConfig, false, notifications, userConfig, true);
|
|
330
541
|
return;
|
|
331
542
|
}
|
|
332
543
|
if (detached) {
|
|
333
544
|
console.log(chalk.blue(`Reattach via: oracle session ${sessionMeta.id}`));
|
|
334
545
|
await attachSession(sessionMeta.id, { suppressMetadata: true });
|
|
335
|
-
console.log(chalk.bold(`Session ${sessionMeta.id} completed`));
|
|
336
546
|
}
|
|
337
547
|
}
|
|
338
|
-
async function runInteractiveSession(sessionMeta, runOptions, mode, browserConfig, showReattachHint = true) {
|
|
548
|
+
async function runInteractiveSession(sessionMeta, runOptions, mode, browserConfig, showReattachHint = true, notifications, userConfig, suppressSummary = false) {
|
|
339
549
|
const { logLine, writeChunk, stream } = createSessionLogWriter(sessionMeta.id);
|
|
340
550
|
let headerAugmented = false;
|
|
341
551
|
const combinedLog = (message = '') => {
|
|
342
|
-
if (!headerAugmented && message.startsWith('
|
|
552
|
+
if (!headerAugmented && message.startsWith('oracle (')) {
|
|
343
553
|
headerAugmented = true;
|
|
344
554
|
if (showReattachHint) {
|
|
345
555
|
console.log(`${message}\n${chalk.blue(`Reattach via: oracle session ${sessionMeta.id}`)}`);
|
|
@@ -367,7 +577,16 @@ async function runInteractiveSession(sessionMeta, runOptions, mode, browserConfi
|
|
|
367
577
|
log: combinedLog,
|
|
368
578
|
write: combinedWrite,
|
|
369
579
|
version: VERSION,
|
|
580
|
+
notifications: notifications ?? deriveNotificationSettingsFromMetadata(sessionMeta, process.env, userConfig?.notify),
|
|
370
581
|
});
|
|
582
|
+
const latest = await readSessionMetadata(sessionMeta.id);
|
|
583
|
+
if (!suppressSummary) {
|
|
584
|
+
const summary = latest ? formatCompletionSummary(latest, { includeSlug: true }) : null;
|
|
585
|
+
if (summary) {
|
|
586
|
+
console.log('\n' + chalk.green.bold(summary));
|
|
587
|
+
logLine(summary); // plain text in log, colored on stdout
|
|
588
|
+
}
|
|
589
|
+
}
|
|
371
590
|
}
|
|
372
591
|
catch (error) {
|
|
373
592
|
throw error;
|
|
@@ -407,6 +626,8 @@ async function executeSession(sessionId) {
|
|
|
407
626
|
const sessionMode = getSessionMode(metadata);
|
|
408
627
|
const browserConfig = getBrowserConfigFromMetadata(metadata);
|
|
409
628
|
const { logLine, writeChunk, stream } = createSessionLogWriter(sessionId);
|
|
629
|
+
const userConfig = (await loadUserConfig()).config;
|
|
630
|
+
const notifications = deriveNotificationSettingsFromMetadata(metadata, process.env, userConfig.notify);
|
|
410
631
|
try {
|
|
411
632
|
await performSessionRun({
|
|
412
633
|
sessionMeta: metadata,
|
|
@@ -417,6 +638,7 @@ async function executeSession(sessionId) {
|
|
|
417
638
|
log: logLine,
|
|
418
639
|
write: writeChunk,
|
|
419
640
|
version: VERSION,
|
|
641
|
+
notifications,
|
|
420
642
|
});
|
|
421
643
|
}
|
|
422
644
|
catch {
|
|
@@ -456,11 +678,57 @@ function printDebugOptionGroup(entries) {
|
|
|
456
678
|
console.log(` ${label}${description}`);
|
|
457
679
|
});
|
|
458
680
|
}
|
|
681
|
+
function resolveWaitFlag({ waitFlag, noWaitFlag, model, engine, }) {
|
|
682
|
+
if (waitFlag === true)
|
|
683
|
+
return true;
|
|
684
|
+
if (noWaitFlag === true)
|
|
685
|
+
return false;
|
|
686
|
+
return defaultWaitPreference(model, engine);
|
|
687
|
+
}
|
|
688
|
+
function applyBrowserDefaultsFromConfig(options, config) {
|
|
689
|
+
const browser = config.browser;
|
|
690
|
+
if (!browser)
|
|
691
|
+
return;
|
|
692
|
+
const source = (key) => program.getOptionValueSource?.(key);
|
|
693
|
+
if (source('browserChromeProfile') === 'default' && browser.chromeProfile !== undefined) {
|
|
694
|
+
options.browserChromeProfile = browser.chromeProfile ?? undefined;
|
|
695
|
+
}
|
|
696
|
+
if (source('browserChromePath') === 'default' && browser.chromePath !== undefined) {
|
|
697
|
+
options.browserChromePath = browser.chromePath ?? undefined;
|
|
698
|
+
}
|
|
699
|
+
if (source('browserUrl') === 'default' && browser.url !== undefined) {
|
|
700
|
+
options.browserUrl = browser.url;
|
|
701
|
+
}
|
|
702
|
+
if (source('browserTimeout') === 'default' && typeof browser.timeoutMs === 'number') {
|
|
703
|
+
options.browserTimeout = String(browser.timeoutMs);
|
|
704
|
+
}
|
|
705
|
+
if (source('browserInputTimeout') === 'default' && typeof browser.inputTimeoutMs === 'number') {
|
|
706
|
+
options.browserInputTimeout = String(browser.inputTimeoutMs);
|
|
707
|
+
}
|
|
708
|
+
if (source('browserHeadless') === 'default' && browser.headless !== undefined) {
|
|
709
|
+
options.browserHeadless = browser.headless;
|
|
710
|
+
}
|
|
711
|
+
if (source('browserHideWindow') === 'default' && browser.hideWindow !== undefined) {
|
|
712
|
+
options.browserHideWindow = browser.hideWindow;
|
|
713
|
+
}
|
|
714
|
+
if (source('browserKeepBrowser') === 'default' && browser.keepBrowser !== undefined) {
|
|
715
|
+
options.browserKeepBrowser = browser.keepBrowser;
|
|
716
|
+
}
|
|
717
|
+
}
|
|
459
718
|
program.action(async function () {
|
|
460
719
|
const options = this.optsWithGlobals();
|
|
461
720
|
await runRootCommand(options);
|
|
462
721
|
});
|
|
463
|
-
|
|
722
|
+
async function main() {
|
|
723
|
+
const parsePromise = program.parseAsync(process.argv);
|
|
724
|
+
const sigintPromise = once(process, 'SIGINT').then(() => 'sigint');
|
|
725
|
+
const result = await Promise.race([parsePromise.then(() => 'parsed'), sigintPromise]);
|
|
726
|
+
if (result === 'sigint') {
|
|
727
|
+
console.log(chalk.yellow('\nCancelled.'));
|
|
728
|
+
process.exitCode = 130;
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
void main().catch((error) => {
|
|
464
732
|
if (error instanceof Error) {
|
|
465
733
|
if (!isErrorLogged(error)) {
|
|
466
734
|
console.error(chalk.red('✖'), error.message);
|