@steipete/oracle 1.1.0 → 1.2.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 +29 -4
- package/assets-oracle-icon.png +0 -0
- package/dist/bin/oracle-cli.js +169 -18
- package/dist/bin/oracle-mcp.js +6 -0
- package/dist/src/browser/actions/modelSelection.js +117 -29
- package/dist/src/browser/cookies.js +1 -1
- package/dist/src/browser/index.js +2 -1
- package/dist/src/browser/prompt.js +6 -5
- package/dist/src/browser/sessionRunner.js +4 -2
- package/dist/src/cli/dryRun.js +41 -5
- package/dist/src/cli/engine.js +7 -0
- package/dist/src/cli/help.js +1 -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/promptRequirement.js +3 -0
- package/dist/src/cli/runOptions.js +29 -0
- package/dist/src/cli/sessionCommand.js +1 -1
- package/dist/src/cli/sessionDisplay.js +94 -7
- package/dist/src/cli/sessionRunner.js +21 -2
- package/dist/src/cli/tui/index.js +436 -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/files.js +143 -6
- package/dist/src/oracle/run.js +41 -20
- package/dist/src/oracle/tokenEstimate.js +34 -0
- package/dist/src/sessionManager.js +48 -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 +39 -13
- 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/README.md +24 -0
- package/vendor/oracle-notifier/build-notifier.sh +93 -0
- package/dist/.DS_Store +0 -0
package/README.md
CHANGED
|
@@ -20,6 +20,9 @@ Oracle gives your agents a simple, reliable way to **bundle a prompt plus the ri
|
|
|
20
20
|
|
|
21
21
|
If you omit `--engine`, Oracle prefers the API engine when `OPENAI_API_KEY` is present; otherwise it falls back to browser mode. Switch explicitly with `-e, --engine {api|browser}` when you want to override the auto choice. Everything else (prompt assembly, file handling, session logging) stays the same.
|
|
22
22
|
|
|
23
|
+
Note: Browser engine is considered experimental, requires an OpenAI Pro account and only works on macOS with Chrome.
|
|
24
|
+
Your system password is needed to copy cookies. API engine is stable and should be preferred.
|
|
25
|
+
|
|
23
26
|
## Quick start
|
|
24
27
|
|
|
25
28
|
```bash
|
|
@@ -43,8 +46,23 @@ oracle session <id> # replay a run locally
|
|
|
43
46
|
|
|
44
47
|
## How do I integrate this?
|
|
45
48
|
|
|
46
|
-
|
|
47
|
-
-
|
|
49
|
+
**CLI** (direct calls; great for CI or scripted tasks)
|
|
50
|
+
- One-liner in CI: `OPENAI_API_KEY=sk-... npx -y @steipete/oracle --prompt "Smoke-check latest PR" --file src/ docs/ --preview summary`.
|
|
51
|
+
- Package script: add `"oracle": "oracle --prompt \"Review the diff\" --file ."` to `package.json`, then run `OPENAI_API_KEY=... pnpm oracle`.
|
|
52
|
+
- Don’t want to export the key? Inline works: `OPENAI_API_KEY=sk-... oracle -p "Quick check" --file src/`.
|
|
53
|
+
|
|
54
|
+
**MCP** (tools + resources; mix-and-match with the CLI sessions)
|
|
55
|
+
- Run the bundled stdio server: `pnpm mcp` (or `oracle-mcp`) after `pnpm build`. Tools: `consult`, `sessions`; resources: `oracle-session://{id}/{metadata|log|request}`. Details in [docs/mcp.md](docs/mcp.md).
|
|
56
|
+
- mcporter config (stdio):
|
|
57
|
+
```json
|
|
58
|
+
{
|
|
59
|
+
"name": "oracle",
|
|
60
|
+
"type": "stdio",
|
|
61
|
+
"command": "npx",
|
|
62
|
+
"args": ["-y", "@steipete/oracle", "oracle-mcp"]
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
- You can call the MCP tools against sessions created by the CLI (shared `~/.oracle/sessions`), and vice versa.
|
|
48
66
|
|
|
49
67
|
## Highlights
|
|
50
68
|
|
|
@@ -56,17 +74,22 @@ oracle session <id> # replay a run locally
|
|
|
56
74
|
- **File safety** — Per-file token accounting and size guards; `--files-report` shows exactly what you’re sending.
|
|
57
75
|
- **Readable previews** — `--preview` / `--render-markdown` let you inspect the bundle before spending.
|
|
58
76
|
|
|
77
|
+
## Configuration
|
|
78
|
+
|
|
79
|
+
Put per-user defaults in `~/.oracle/config.json` (parsed as JSON5, so comments/trailing commas are fine). Example settings cover default engine/model, notifications, browser defaults, and prompt suffixes. See `docs/configuration.md` for a complete example and precedence.
|
|
80
|
+
|
|
59
81
|
## Flags you’ll actually use
|
|
60
82
|
|
|
61
83
|
| Flag | Purpose |
|
|
62
84
|
| --- | --- |
|
|
63
85
|
| `-p, --prompt <text>` | Required prompt. |
|
|
64
86
|
| `-f, --file <paths...>` | Attach files/dirs (supports globs and `!` excludes). |
|
|
65
|
-
| `-e, --engine <api
|
|
87
|
+
| `-e, --engine <api\|browser>` | Choose API or browser automation. Omitted: API when `OPENAI_API_KEY` is set, otherwise browser. |
|
|
66
88
|
| `-m, --model <name>` | `gpt-5-pro` (default) or `gpt-5.1`. |
|
|
67
89
|
| `--files-report` | Print per-file token usage. |
|
|
68
|
-
| `--preview [summary
|
|
90
|
+
| `--preview [summary\|json\|full]` | Inspect the request without sending. |
|
|
69
91
|
| `--render-markdown` | Print the assembled `[SYSTEM]/[USER]/[FILE]` bundle. |
|
|
92
|
+
| `--wait` / `--no-wait` | Block until completion. Default: `wait` for gpt-5.1/browser; `no-wait` for gpt-5-pro API (reattach later). |
|
|
70
93
|
| `-v, --verbose` | Extra logging (also surfaces advanced flags with `--help`). |
|
|
71
94
|
|
|
72
95
|
More knobs (`--max-input`, cookie sync controls for browser mode, etc.) live behind `oracle --help --verbose`.
|
|
@@ -78,6 +101,8 @@ Add `--render` (alias `--render-markdown`) when attaching to pretty-print the st
|
|
|
78
101
|
|
|
79
102
|
**Recommendation:** Prefer the API engine when you have an API key (`--engine api` or just set `OPENAI_API_KEY`). The API delivers more reliable results and supports longer, uninterrupted runs than the browser engine in most cases.
|
|
80
103
|
|
|
104
|
+
**Wait vs no-wait:** gpt-5-pro API runs default to detaching (shows a reattach hint); add `--wait` to stay attached. gpt-5.1 and browser runs block by default. You can reattach anytime via `oracle session <id>`.
|
|
105
|
+
|
|
81
106
|
## Testing
|
|
82
107
|
|
|
83
108
|
```bash
|
|
Binary file
|
package/dist/bin/oracle-cli.js
CHANGED
|
@@ -3,7 +3,7 @@ import 'dotenv/config';
|
|
|
3
3
|
import { spawn } from 'node:child_process';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
5
|
import { Command, Option } from 'commander';
|
|
6
|
-
import { resolveEngine } from '../src/cli/engine.js';
|
|
6
|
+
import { resolveEngine, defaultWaitPreference } from '../src/cli/engine.js';
|
|
7
7
|
import { shouldRequirePrompt } from '../src/cli/promptRequirement.js';
|
|
8
8
|
import chalk from 'chalk';
|
|
9
9
|
import { ensureSessionStorage, initializeSession, readSessionMetadata, createSessionLogWriter, deleteSessionsOlderThan, } from '../src/sessionManager.js';
|
|
@@ -11,34 +11,45 @@ import { runOracle, renderPromptMarkdown, readFiles } from '../src/oracle.js';
|
|
|
11
11
|
import { CHATGPT_URL } from '../src/browserMode.js';
|
|
12
12
|
import { applyHelpStyling } from '../src/cli/help.js';
|
|
13
13
|
import { collectPaths, parseFloatOption, parseIntOption, parseSearchOption, usesDefaultStatusFilters, resolvePreviewMode, normalizeModelOption, resolveApiModel, inferModelFromLabel, parseHeartbeatOption, } from '../src/cli/options.js';
|
|
14
|
+
import { applyHiddenAliases } from '../src/cli/hiddenAliases.js';
|
|
14
15
|
import { buildBrowserConfig, resolveBrowserModelLabel } from '../src/cli/browserConfig.js';
|
|
15
16
|
import { performSessionRun } from '../src/cli/sessionRunner.js';
|
|
16
|
-
import { attachSession, showStatus } from '../src/cli/sessionDisplay.js';
|
|
17
|
+
import { attachSession, showStatus, formatCompletionSummary } from '../src/cli/sessionDisplay.js';
|
|
17
18
|
import { handleSessionCommand, formatSessionCleanupMessage } from '../src/cli/sessionCommand.js';
|
|
18
19
|
import { isErrorLogged } from '../src/cli/errorUtils.js';
|
|
19
20
|
import { handleSessionAlias, handleStatusFlag } from '../src/cli/rootAlias.js';
|
|
20
21
|
import { getCliVersion } from '../src/version.js';
|
|
21
|
-
import { runDryRunSummary } from '../src/cli/dryRun.js';
|
|
22
|
+
import { runDryRunSummary, runBrowserPreview } from '../src/cli/dryRun.js';
|
|
23
|
+
import { launchTui } from '../src/cli/tui/index.js';
|
|
24
|
+
import { resolveNotificationSettings, deriveNotificationSettingsFromMetadata, } from '../src/cli/notifier.js';
|
|
25
|
+
import { loadUserConfig } from '../src/config.js';
|
|
22
26
|
const VERSION = getCliVersion();
|
|
23
27
|
const CLI_ENTRYPOINT = fileURLToPath(import.meta.url);
|
|
24
28
|
const rawCliArgs = process.argv.slice(2);
|
|
29
|
+
const userCliArgs = rawCliArgs[0] === CLI_ENTRYPOINT ? rawCliArgs.slice(1) : rawCliArgs;
|
|
25
30
|
const isTty = process.stdout.isTTY;
|
|
31
|
+
const tuiEnabled = () => isTty && process.env.ORACLE_NO_TUI !== '1';
|
|
26
32
|
const program = new Command();
|
|
27
33
|
applyHelpStyling(program, VERSION, isTty);
|
|
28
34
|
program.hook('preAction', (thisCommand) => {
|
|
29
35
|
if (thisCommand !== program) {
|
|
30
36
|
return;
|
|
31
37
|
}
|
|
32
|
-
if (
|
|
38
|
+
if (userCliArgs.some((arg) => arg === '--help' || arg === '-h')) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
if (userCliArgs.length === 0 && tuiEnabled()) {
|
|
42
|
+
// Skip prompt enforcement; runRootCommand will launch the TUI.
|
|
33
43
|
return;
|
|
34
44
|
}
|
|
35
45
|
const opts = thisCommand.optsWithGlobals();
|
|
46
|
+
applyHiddenAliases(opts, (key, value) => thisCommand.setOptionValue(key, value));
|
|
36
47
|
const positional = thisCommand.args?.[0];
|
|
37
48
|
if (!opts.prompt && positional) {
|
|
38
49
|
opts.prompt = positional;
|
|
39
50
|
thisCommand.setOptionValue('prompt', positional);
|
|
40
51
|
}
|
|
41
|
-
if (shouldRequirePrompt(
|
|
52
|
+
if (shouldRequirePrompt(userCliArgs, opts)) {
|
|
42
53
|
console.log(chalk.yellow('Prompt is required. Provide it via --prompt "<text>" or positional [prompt].'));
|
|
43
54
|
thisCommand.help({ error: false });
|
|
44
55
|
process.exitCode = 1;
|
|
@@ -51,12 +62,20 @@ program
|
|
|
51
62
|
.version(VERSION)
|
|
52
63
|
.argument('[prompt]', 'Prompt text (shorthand for --prompt).')
|
|
53
64
|
.option('-p, --prompt <text>', 'User prompt to send to the model.')
|
|
65
|
+
.addOption(new Option('--message <text>', 'Alias for --prompt.').hideHelp())
|
|
54
66
|
.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, [])
|
|
67
|
+
.addOption(new Option('--include <paths...>', 'Alias for --file.')
|
|
68
|
+
.argParser(collectPaths)
|
|
69
|
+
.default([])
|
|
70
|
+
.hideHelp())
|
|
55
71
|
.option('-s, --slug <words>', 'Custom session slug (3-5 words).')
|
|
56
72
|
.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,
|
|
73
|
+
.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
74
|
.option('--files-report', 'Show token usage per attached file (also prints automatically when files exceed the token budget).', false)
|
|
59
75
|
.option('-v, --verbose', 'Enable verbose logging for all operations.', false)
|
|
76
|
+
.addOption(new Option('--[no-]notify', 'Desktop notification when a session finishes (default on unless CI/SSH).')
|
|
77
|
+
.default(undefined))
|
|
78
|
+
.addOption(new Option('--[no-]notify-sound', 'Play a notification sound on completion (default off).').default(undefined))
|
|
60
79
|
.option('--dry-run', 'Validate inputs and show token estimates without calling the model.', false)
|
|
61
80
|
.addOption(new Option('--preview [mode]', 'Preview the request without calling the API (summary | json | full).')
|
|
62
81
|
.choices(['summary', 'json', 'full'])
|
|
@@ -87,8 +106,11 @@ program
|
|
|
87
106
|
.addOption(new Option('--browser-keep-browser', 'Keep Chrome running after completion.').hideHelp())
|
|
88
107
|
.addOption(new Option('--browser-allow-cookie-errors', 'Continue even if Chrome cookies cannot be copied.').hideHelp())
|
|
89
108
|
.addOption(new Option('--browser-inline-files', 'Paste files directly into the ChatGPT composer instead of uploading attachments.').default(false))
|
|
109
|
+
.addOption(new Option('--browser-bundle-files', 'Bundle all attachments into a single archive before uploading.').default(false))
|
|
90
110
|
.option('--debug-help', 'Show the advanced/debug option set and exit.', false)
|
|
91
111
|
.option('--heartbeat <seconds>', 'Emit periodic in-progress updates (0 to disable).', parseHeartbeatOption, 30)
|
|
112
|
+
.addOption(new Option('--wait').default(undefined))
|
|
113
|
+
.addOption(new Option('--no-wait').default(undefined).hideHelp())
|
|
92
114
|
.showHelpAfterError('(use --help for usage)');
|
|
93
115
|
program.addHelpText('after', `
|
|
94
116
|
Examples:
|
|
@@ -106,6 +128,7 @@ const sessionCommand = program
|
|
|
106
128
|
.option('--limit <count>', 'Maximum sessions to show when listing (max 1000).', parseIntOption, 100)
|
|
107
129
|
.option('--all', 'Include all stored sessions regardless of age.', false)
|
|
108
130
|
.option('--clear', 'Delete stored sessions older than the provided window (24h default).', false)
|
|
131
|
+
.option('--hide-prompt', 'Hide stored prompt when displaying a session.', false)
|
|
109
132
|
.option('--render', 'Render completed session output as markdown (rich TTY only).', false)
|
|
110
133
|
.option('--render-markdown', 'Alias for --render.', false)
|
|
111
134
|
.option('--path', 'Print the stored session paths instead of attaching.', false)
|
|
@@ -122,6 +145,7 @@ const statusCommand = program
|
|
|
122
145
|
.option('--clear', 'Delete stored sessions older than the provided window (24h default).', false)
|
|
123
146
|
.option('--render', 'Render completed session output as markdown (rich TTY only).', false)
|
|
124
147
|
.option('--render-markdown', 'Alias for --render.', false)
|
|
148
|
+
.option('--hide-prompt', 'Hide stored prompt when displaying a session.', false)
|
|
125
149
|
.addOption(new Option('--clean', 'Deprecated alias for --clear.').default(false).hideHelp())
|
|
126
150
|
.action(async (sessionId, _options, command) => {
|
|
127
151
|
const statusOptions = command.opts();
|
|
@@ -145,7 +169,11 @@ const statusCommand = program
|
|
|
145
169
|
return;
|
|
146
170
|
}
|
|
147
171
|
if (sessionId) {
|
|
148
|
-
|
|
172
|
+
const autoRender = !command.getOptionValueSource?.('render') && !command.getOptionValueSource?.('renderMarkdown')
|
|
173
|
+
? process.stdout.isTTY
|
|
174
|
+
: false;
|
|
175
|
+
const renderMarkdown = Boolean(statusOptions.render || statusOptions.renderMarkdown || autoRender);
|
|
176
|
+
await attachSession(sessionId, { renderMarkdown, renderPrompt: !statusOptions.hidePrompt });
|
|
149
177
|
return;
|
|
150
178
|
}
|
|
151
179
|
const showExamples = usesDefaultStatusFilters(command);
|
|
@@ -178,9 +206,16 @@ function buildRunOptions(options, overrides = {}) {
|
|
|
178
206
|
verbose: overrides.verbose ?? options.verbose,
|
|
179
207
|
heartbeatIntervalMs: overrides.heartbeatIntervalMs ?? resolveHeartbeatIntervalMs(options.heartbeat),
|
|
180
208
|
browserInlineFiles: overrides.browserInlineFiles ?? options.browserInlineFiles ?? false,
|
|
209
|
+
browserBundleFiles: overrides.browserBundleFiles ?? options.browserBundleFiles ?? false,
|
|
181
210
|
background: overrides.background ?? undefined,
|
|
182
211
|
};
|
|
183
212
|
}
|
|
213
|
+
export function enforceBrowserSearchFlag(runOptions, sessionMode, logFn = console.log) {
|
|
214
|
+
if (sessionMode === 'browser' && runOptions.search === false) {
|
|
215
|
+
logFn(chalk.dim('Note: search is not available in browser engine; ignoring search=false.'));
|
|
216
|
+
runOptions.search = undefined;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
184
219
|
function resolveHeartbeatIntervalMs(seconds) {
|
|
185
220
|
if (typeof seconds !== 'number' || seconds <= 0) {
|
|
186
221
|
return undefined;
|
|
@@ -199,7 +234,7 @@ function buildRunOptionsFromMetadata(metadata) {
|
|
|
199
234
|
maxOutput: stored.maxOutput,
|
|
200
235
|
system: stored.system,
|
|
201
236
|
silent: stored.silent,
|
|
202
|
-
search:
|
|
237
|
+
search: stored.search,
|
|
203
238
|
preview: false,
|
|
204
239
|
previewMode: undefined,
|
|
205
240
|
apiKey: undefined,
|
|
@@ -207,6 +242,7 @@ function buildRunOptionsFromMetadata(metadata) {
|
|
|
207
242
|
verbose: stored.verbose,
|
|
208
243
|
heartbeatIntervalMs: stored.heartbeatIntervalMs,
|
|
209
244
|
browserInlineFiles: stored.browserInlineFiles,
|
|
245
|
+
browserBundleFiles: stored.browserBundleFiles,
|
|
210
246
|
background: stored.background,
|
|
211
247
|
};
|
|
212
248
|
}
|
|
@@ -217,6 +253,7 @@ function getBrowserConfigFromMetadata(metadata) {
|
|
|
217
253
|
return metadata.options?.browserConfig ?? metadata.browser?.config;
|
|
218
254
|
}
|
|
219
255
|
async function runRootCommand(options) {
|
|
256
|
+
const userConfig = (await loadUserConfig()).config;
|
|
220
257
|
const helpRequested = rawCliArgs.some((arg) => arg === '--help' || arg === '-h');
|
|
221
258
|
if (helpRequested) {
|
|
222
259
|
if (options.verbose) {
|
|
@@ -228,7 +265,11 @@ async function runRootCommand(options) {
|
|
|
228
265
|
return;
|
|
229
266
|
}
|
|
230
267
|
const previewMode = resolvePreviewMode(options.preview);
|
|
231
|
-
if (
|
|
268
|
+
if (userCliArgs.length === 0) {
|
|
269
|
+
if (tuiEnabled()) {
|
|
270
|
+
await launchTui({ version: VERSION });
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
232
273
|
console.log(chalk.yellow('No prompt or subcommand supplied. See `oracle --help` for usage.'));
|
|
233
274
|
program.help({ error: false });
|
|
234
275
|
return;
|
|
@@ -243,13 +284,35 @@ async function runRootCommand(options) {
|
|
|
243
284
|
if (options.dryRun && options.renderMarkdown) {
|
|
244
285
|
throw new Error('--dry-run cannot be combined with --render-markdown.');
|
|
245
286
|
}
|
|
246
|
-
const
|
|
287
|
+
const preferredEngine = options.engine ?? userConfig.engine;
|
|
288
|
+
const engine = resolveEngine({ engine: preferredEngine, browserFlag: options.browser, env: process.env });
|
|
247
289
|
if (options.browser) {
|
|
248
290
|
console.log(chalk.yellow('`--browser` is deprecated; use `--engine browser` instead.'));
|
|
249
291
|
}
|
|
292
|
+
if (program.getOptionValueSource?.('model') === 'default' && userConfig.model) {
|
|
293
|
+
options.model = userConfig.model;
|
|
294
|
+
}
|
|
295
|
+
if (program.getOptionValueSource?.('search') === 'default' && userConfig.search) {
|
|
296
|
+
options.search = userConfig.search === 'on';
|
|
297
|
+
}
|
|
298
|
+
if (program.getOptionValueSource?.('filesReport') === 'default' && userConfig.filesReport != null) {
|
|
299
|
+
options.filesReport = Boolean(userConfig.filesReport);
|
|
300
|
+
}
|
|
301
|
+
if (program.getOptionValueSource?.('heartbeat') === 'default' && typeof userConfig.heartbeatSeconds === 'number') {
|
|
302
|
+
options.heartbeat = userConfig.heartbeatSeconds;
|
|
303
|
+
}
|
|
250
304
|
const cliModelArg = normalizeModelOption(options.model) || 'gpt-5-pro';
|
|
251
305
|
const resolvedModel = engine === 'browser' ? inferModelFromLabel(cliModelArg) : resolveApiModel(cliModelArg);
|
|
252
306
|
const resolvedOptions = { ...options, model: resolvedModel };
|
|
307
|
+
// Decide whether to block until completion:
|
|
308
|
+
// - explicit --wait / --no-wait wins
|
|
309
|
+
// - otherwise block for fast models (gpt-5.1, browser) and detach by default for gpt-5-pro API
|
|
310
|
+
const waitPreference = resolveWaitFlag({
|
|
311
|
+
waitFlag: options.wait,
|
|
312
|
+
noWaitFlag: options.noWait,
|
|
313
|
+
model: resolvedModel,
|
|
314
|
+
engine,
|
|
315
|
+
});
|
|
253
316
|
if (await handleStatusFlag(options, { attachSession, showStatus })) {
|
|
254
317
|
return;
|
|
255
318
|
}
|
|
@@ -269,19 +332,34 @@ async function runRootCommand(options) {
|
|
|
269
332
|
return;
|
|
270
333
|
}
|
|
271
334
|
if (previewMode) {
|
|
272
|
-
if (engine === 'browser') {
|
|
273
|
-
throw new Error('--engine browser cannot be combined with --preview.');
|
|
274
|
-
}
|
|
275
335
|
if (!options.prompt) {
|
|
276
336
|
throw new Error('Prompt is required when using --preview.');
|
|
277
337
|
}
|
|
338
|
+
if (userConfig.promptSuffix) {
|
|
339
|
+
options.prompt = `${options.prompt.trim()}\n${userConfig.promptSuffix}`;
|
|
340
|
+
}
|
|
341
|
+
resolvedOptions.prompt = options.prompt;
|
|
278
342
|
const runOptions = buildRunOptions(resolvedOptions, { preview: true, previewMode });
|
|
343
|
+
if (engine === 'browser') {
|
|
344
|
+
await runBrowserPreview({
|
|
345
|
+
runOptions,
|
|
346
|
+
cwd: process.cwd(),
|
|
347
|
+
version: VERSION,
|
|
348
|
+
previewMode,
|
|
349
|
+
log: console.log,
|
|
350
|
+
}, {});
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
279
353
|
await runOracle(runOptions, { log: console.log, write: (chunk) => process.stdout.write(chunk) });
|
|
280
354
|
return;
|
|
281
355
|
}
|
|
282
356
|
if (!options.prompt) {
|
|
283
357
|
throw new Error('Prompt is required when starting a new session.');
|
|
284
358
|
}
|
|
359
|
+
if (userConfig.promptSuffix) {
|
|
360
|
+
options.prompt = `${options.prompt.trim()}\n${userConfig.promptSuffix}`;
|
|
361
|
+
}
|
|
362
|
+
resolvedOptions.prompt = options.prompt;
|
|
285
363
|
if (options.dryRun) {
|
|
286
364
|
const baseRunOptions = buildRunOptions(resolvedOptions, { preview: false, previewMode: undefined });
|
|
287
365
|
await runDryRunSummary({
|
|
@@ -296,6 +374,13 @@ async function runRootCommand(options) {
|
|
|
296
374
|
if (options.file && options.file.length > 0) {
|
|
297
375
|
await readFiles(options.file, { cwd: process.cwd() });
|
|
298
376
|
}
|
|
377
|
+
applyBrowserDefaultsFromConfig(options, userConfig);
|
|
378
|
+
const notifications = resolveNotificationSettings({
|
|
379
|
+
cliNotify: options.notify,
|
|
380
|
+
cliNotifySound: options.notifySound,
|
|
381
|
+
env: process.env,
|
|
382
|
+
config: userConfig.notify,
|
|
383
|
+
});
|
|
299
384
|
const sessionMode = engine === 'browser' ? 'browser' : 'api';
|
|
300
385
|
const browserModelLabelOverride = sessionMode === 'browser' ? resolveBrowserModelLabel(cliModelArg, resolvedModel) : undefined;
|
|
301
386
|
const browserConfig = sessionMode === 'browser'
|
|
@@ -306,12 +391,21 @@ async function runRootCommand(options) {
|
|
|
306
391
|
})
|
|
307
392
|
: undefined;
|
|
308
393
|
await ensureSessionStorage();
|
|
309
|
-
const baseRunOptions = buildRunOptions(resolvedOptions, {
|
|
394
|
+
const baseRunOptions = buildRunOptions(resolvedOptions, {
|
|
395
|
+
preview: false,
|
|
396
|
+
previewMode: undefined,
|
|
397
|
+
background: userConfig.background ?? resolvedOptions.background,
|
|
398
|
+
});
|
|
399
|
+
enforceBrowserSearchFlag(baseRunOptions, sessionMode, console.log);
|
|
400
|
+
if (sessionMode === 'browser' && baseRunOptions.search === false) {
|
|
401
|
+
console.log(chalk.dim('Note: search is not available in browser engine; ignoring search=false.'));
|
|
402
|
+
baseRunOptions.search = undefined;
|
|
403
|
+
}
|
|
310
404
|
const sessionMeta = await initializeSession({
|
|
311
405
|
...baseRunOptions,
|
|
312
406
|
mode: sessionMode,
|
|
313
407
|
browserConfig,
|
|
314
|
-
}, process.cwd());
|
|
408
|
+
}, process.cwd(), notifications);
|
|
315
409
|
const reattachCommand = `pnpm oracle session ${sessionMeta.id}`;
|
|
316
410
|
console.log(chalk.bold(`Reattach later with: ${chalk.cyan(reattachCommand)}`));
|
|
317
411
|
console.log('');
|
|
@@ -324,8 +418,18 @@ async function runRootCommand(options) {
|
|
|
324
418
|
console.log(chalk.yellow(`Unable to detach session runner (${message}). Running inline...`));
|
|
325
419
|
return false;
|
|
326
420
|
});
|
|
421
|
+
if (!waitPreference) {
|
|
422
|
+
if (!detached) {
|
|
423
|
+
console.log(chalk.red('Unable to start in background; use --wait to run inline.'));
|
|
424
|
+
process.exitCode = 1;
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
console.log(chalk.blue(`Session running in background. Reattach via: oracle session ${sessionMeta.id}`));
|
|
428
|
+
console.log(chalk.dim('Pro runs can take up to 10 minutes. Add --wait to stay attached.'));
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
327
431
|
if (detached === false) {
|
|
328
|
-
await runInteractiveSession(sessionMeta, liveRunOptions, sessionMode, browserConfig, true);
|
|
432
|
+
await runInteractiveSession(sessionMeta, liveRunOptions, sessionMode, browserConfig, true, notifications, userConfig);
|
|
329
433
|
console.log(chalk.bold(`Session ${sessionMeta.id} completed`));
|
|
330
434
|
return;
|
|
331
435
|
}
|
|
@@ -335,11 +439,11 @@ async function runRootCommand(options) {
|
|
|
335
439
|
console.log(chalk.bold(`Session ${sessionMeta.id} completed`));
|
|
336
440
|
}
|
|
337
441
|
}
|
|
338
|
-
async function runInteractiveSession(sessionMeta, runOptions, mode, browserConfig, showReattachHint = true) {
|
|
442
|
+
async function runInteractiveSession(sessionMeta, runOptions, mode, browserConfig, showReattachHint = true, notifications, userConfig) {
|
|
339
443
|
const { logLine, writeChunk, stream } = createSessionLogWriter(sessionMeta.id);
|
|
340
444
|
let headerAugmented = false;
|
|
341
445
|
const combinedLog = (message = '') => {
|
|
342
|
-
if (!headerAugmented && message.startsWith('
|
|
446
|
+
if (!headerAugmented && message.startsWith('oracle (')) {
|
|
343
447
|
headerAugmented = true;
|
|
344
448
|
if (showReattachHint) {
|
|
345
449
|
console.log(`${message}\n${chalk.blue(`Reattach via: oracle session ${sessionMeta.id}`)}`);
|
|
@@ -367,7 +471,14 @@ async function runInteractiveSession(sessionMeta, runOptions, mode, browserConfi
|
|
|
367
471
|
log: combinedLog,
|
|
368
472
|
write: combinedWrite,
|
|
369
473
|
version: VERSION,
|
|
474
|
+
notifications: notifications ?? deriveNotificationSettingsFromMetadata(sessionMeta, process.env, userConfig?.notify),
|
|
370
475
|
});
|
|
476
|
+
const latest = await readSessionMetadata(sessionMeta.id);
|
|
477
|
+
const summary = latest ? formatCompletionSummary(latest, { includeSlug: true }) : null;
|
|
478
|
+
if (summary) {
|
|
479
|
+
console.log('\n' + chalk.green.bold(summary));
|
|
480
|
+
logLine(summary); // plain text in log, colored on stdout
|
|
481
|
+
}
|
|
371
482
|
}
|
|
372
483
|
catch (error) {
|
|
373
484
|
throw error;
|
|
@@ -407,6 +518,8 @@ async function executeSession(sessionId) {
|
|
|
407
518
|
const sessionMode = getSessionMode(metadata);
|
|
408
519
|
const browserConfig = getBrowserConfigFromMetadata(metadata);
|
|
409
520
|
const { logLine, writeChunk, stream } = createSessionLogWriter(sessionId);
|
|
521
|
+
const userConfig = (await loadUserConfig()).config;
|
|
522
|
+
const notifications = deriveNotificationSettingsFromMetadata(metadata, process.env, userConfig.notify);
|
|
410
523
|
try {
|
|
411
524
|
await performSessionRun({
|
|
412
525
|
sessionMeta: metadata,
|
|
@@ -417,6 +530,7 @@ async function executeSession(sessionId) {
|
|
|
417
530
|
log: logLine,
|
|
418
531
|
write: writeChunk,
|
|
419
532
|
version: VERSION,
|
|
533
|
+
notifications,
|
|
420
534
|
});
|
|
421
535
|
}
|
|
422
536
|
catch {
|
|
@@ -456,6 +570,43 @@ function printDebugOptionGroup(entries) {
|
|
|
456
570
|
console.log(` ${label}${description}`);
|
|
457
571
|
});
|
|
458
572
|
}
|
|
573
|
+
function resolveWaitFlag({ waitFlag, noWaitFlag, model, engine, }) {
|
|
574
|
+
if (waitFlag === true)
|
|
575
|
+
return true;
|
|
576
|
+
if (noWaitFlag === true)
|
|
577
|
+
return false;
|
|
578
|
+
return defaultWaitPreference(model, engine);
|
|
579
|
+
}
|
|
580
|
+
function applyBrowserDefaultsFromConfig(options, config) {
|
|
581
|
+
const browser = config.browser;
|
|
582
|
+
if (!browser)
|
|
583
|
+
return;
|
|
584
|
+
const source = (key) => program.getOptionValueSource?.(key);
|
|
585
|
+
if (source('browserChromeProfile') === 'default' && browser.chromeProfile !== undefined) {
|
|
586
|
+
options.browserChromeProfile = browser.chromeProfile ?? undefined;
|
|
587
|
+
}
|
|
588
|
+
if (source('browserChromePath') === 'default' && browser.chromePath !== undefined) {
|
|
589
|
+
options.browserChromePath = browser.chromePath ?? undefined;
|
|
590
|
+
}
|
|
591
|
+
if (source('browserUrl') === 'default' && browser.url !== undefined) {
|
|
592
|
+
options.browserUrl = browser.url;
|
|
593
|
+
}
|
|
594
|
+
if (source('browserTimeout') === 'default' && typeof browser.timeoutMs === 'number') {
|
|
595
|
+
options.browserTimeout = String(browser.timeoutMs);
|
|
596
|
+
}
|
|
597
|
+
if (source('browserInputTimeout') === 'default' && typeof browser.inputTimeoutMs === 'number') {
|
|
598
|
+
options.browserInputTimeout = String(browser.inputTimeoutMs);
|
|
599
|
+
}
|
|
600
|
+
if (source('browserHeadless') === 'default' && browser.headless !== undefined) {
|
|
601
|
+
options.browserHeadless = browser.headless;
|
|
602
|
+
}
|
|
603
|
+
if (source('browserHideWindow') === 'default' && browser.hideWindow !== undefined) {
|
|
604
|
+
options.browserHideWindow = browser.hideWindow;
|
|
605
|
+
}
|
|
606
|
+
if (source('browserKeepBrowser') === 'default' && browser.keepBrowser !== undefined) {
|
|
607
|
+
options.browserKeepBrowser = browser.keepBrowser;
|
|
608
|
+
}
|
|
609
|
+
}
|
|
459
610
|
program.action(async function () {
|
|
460
611
|
const options = this.optsWithGlobals();
|
|
461
612
|
await runRootCommand(options);
|