@steipete/oracle 1.0.8 → 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 +32 -4
- package/assets-oracle-icon.png +0 -0
- package/dist/bin/oracle-cli.js +178 -21
- package/dist/bin/oracle-mcp.js +6 -0
- package/dist/markdansi/types/index.js +4 -0
- package/dist/oracle/bin/oracle-cli.js +472 -0
- package/dist/oracle/src/browser/actions/assistantResponse.js +471 -0
- package/dist/oracle/src/browser/actions/attachments.js +82 -0
- package/dist/oracle/src/browser/actions/modelSelection.js +190 -0
- package/dist/oracle/src/browser/actions/navigation.js +75 -0
- package/dist/oracle/src/browser/actions/promptComposer.js +167 -0
- package/dist/oracle/src/browser/chromeLifecycle.js +104 -0
- package/dist/oracle/src/browser/config.js +33 -0
- package/dist/oracle/src/browser/constants.js +40 -0
- package/dist/oracle/src/browser/cookies.js +210 -0
- package/dist/oracle/src/browser/domDebug.js +36 -0
- package/dist/oracle/src/browser/index.js +331 -0
- package/dist/oracle/src/browser/pageActions.js +5 -0
- package/dist/oracle/src/browser/prompt.js +88 -0
- package/dist/oracle/src/browser/promptSummary.js +20 -0
- package/dist/oracle/src/browser/sessionRunner.js +80 -0
- package/dist/oracle/src/browser/types.js +1 -0
- package/dist/oracle/src/browser/utils.js +62 -0
- package/dist/oracle/src/browserMode.js +1 -0
- package/dist/oracle/src/cli/browserConfig.js +44 -0
- package/dist/oracle/src/cli/dryRun.js +59 -0
- package/dist/oracle/src/cli/engine.js +17 -0
- package/dist/oracle/src/cli/errorUtils.js +9 -0
- package/dist/oracle/src/cli/help.js +70 -0
- package/dist/oracle/src/cli/markdownRenderer.js +15 -0
- package/dist/oracle/src/cli/options.js +103 -0
- package/dist/oracle/src/cli/promptRequirement.js +14 -0
- package/dist/oracle/src/cli/rootAlias.js +30 -0
- package/dist/oracle/src/cli/sessionCommand.js +77 -0
- package/dist/oracle/src/cli/sessionDisplay.js +270 -0
- package/dist/oracle/src/cli/sessionRunner.js +94 -0
- package/dist/oracle/src/heartbeat.js +43 -0
- package/dist/oracle/src/oracle/client.js +48 -0
- package/dist/oracle/src/oracle/config.js +29 -0
- package/dist/oracle/src/oracle/errors.js +101 -0
- package/dist/oracle/src/oracle/files.js +220 -0
- package/dist/oracle/src/oracle/format.js +33 -0
- package/dist/oracle/src/oracle/fsAdapter.js +7 -0
- package/dist/oracle/src/oracle/oscProgress.js +60 -0
- package/dist/oracle/src/oracle/request.js +48 -0
- package/dist/oracle/src/oracle/run.js +444 -0
- package/dist/oracle/src/oracle/tokenStats.js +39 -0
- package/dist/oracle/src/oracle/types.js +1 -0
- package/dist/oracle/src/oracle.js +9 -0
- package/dist/oracle/src/sessionManager.js +205 -0
- package/dist/oracle/src/version.js +39 -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 +97 -0
- package/dist/src/cli/notifier.js +223 -0
- package/dist/src/cli/promptRequirement.js +3 -0
- package/dist/src/cli/rootAlias.js +14 -0
- package/dist/src/cli/runOptions.js +29 -0
- package/dist/src/cli/sessionCommand.js +60 -2
- package/dist/src/cli/sessionDisplay.js +222 -10
- 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/oscProgress.js +60 -0
- package/dist/src/oracle/run.js +104 -71
- package/dist/src/oracle/tokenEstimate.js +34 -0
- package/dist/src/sessionManager.js +65 -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 +27 -9
- 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/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`.
|
|
@@ -74,6 +97,11 @@ More knobs (`--max-input`, cookie sync controls for browser mode, etc.) live beh
|
|
|
74
97
|
## Sessions & background runs
|
|
75
98
|
|
|
76
99
|
Every non-preview run writes to `~/.oracle/sessions/<slug>` with usage, cost hints, and logs. Use `oracle status` to list sessions, `oracle session <id>` to replay, and `oracle status --clear --hours 168` to prune. Set `ORACLE_HOME_DIR` to relocate storage.
|
|
100
|
+
Add `--render` (alias `--render-markdown`) when attaching to pretty-print the stored markdown if your terminal supports color; falls back to raw text otherwise.
|
|
101
|
+
|
|
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.
|
|
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>`.
|
|
77
105
|
|
|
78
106
|
## Testing
|
|
79
107
|
|
|
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
|
-
import { handleStatusFlag } from '../src/cli/rootAlias.js';
|
|
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,19 +62,29 @@ 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'])
|
|
63
82
|
.preset('summary'))
|
|
64
83
|
.addOption(new Option('--exec-session <id>').hideHelp())
|
|
84
|
+
.addOption(new Option('--session <id>').hideHelp())
|
|
65
85
|
.addOption(new Option('--status', 'Show stored sessions (alias for `oracle status`).').default(false).hideHelp())
|
|
66
86
|
.option('--render-markdown', 'Emit the assembled markdown bundle for prompt + files and exit.', false)
|
|
87
|
+
.option('--verbose-render', 'Show render/TTY diagnostics when replaying sessions.', false)
|
|
67
88
|
.addOption(new Option('--search <mode>', 'Set server-side search behavior (on/off).')
|
|
68
89
|
.argParser(parseSearchOption)
|
|
69
90
|
.hideHelp())
|
|
@@ -85,8 +106,11 @@ program
|
|
|
85
106
|
.addOption(new Option('--browser-keep-browser', 'Keep Chrome running after completion.').hideHelp())
|
|
86
107
|
.addOption(new Option('--browser-allow-cookie-errors', 'Continue even if Chrome cookies cannot be copied.').hideHelp())
|
|
87
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))
|
|
88
110
|
.option('--debug-help', 'Show the advanced/debug option set and exit.', false)
|
|
89
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())
|
|
90
114
|
.showHelpAfterError('(use --help for usage)');
|
|
91
115
|
program.addHelpText('after', `
|
|
92
116
|
Examples:
|
|
@@ -104,6 +128,10 @@ const sessionCommand = program
|
|
|
104
128
|
.option('--limit <count>', 'Maximum sessions to show when listing (max 1000).', parseIntOption, 100)
|
|
105
129
|
.option('--all', 'Include all stored sessions regardless of age.', false)
|
|
106
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)
|
|
132
|
+
.option('--render', 'Render completed session output as markdown (rich TTY only).', false)
|
|
133
|
+
.option('--render-markdown', 'Alias for --render.', false)
|
|
134
|
+
.option('--path', 'Print the stored session paths instead of attaching.', false)
|
|
107
135
|
.addOption(new Option('--clean', 'Deprecated alias for --clear.').default(false).hideHelp())
|
|
108
136
|
.action(async (sessionId, _options, cmd) => {
|
|
109
137
|
await handleSessionCommand(sessionId, cmd);
|
|
@@ -115,6 +143,9 @@ const statusCommand = program
|
|
|
115
143
|
.option('--limit <count>', 'Maximum sessions to show (max 1000).', parseIntOption, 100)
|
|
116
144
|
.option('--all', 'Include all stored sessions regardless of age.', false)
|
|
117
145
|
.option('--clear', 'Delete stored sessions older than the provided window (24h default).', false)
|
|
146
|
+
.option('--render', 'Render completed session output as markdown (rich TTY only).', false)
|
|
147
|
+
.option('--render-markdown', 'Alias for --render.', false)
|
|
148
|
+
.option('--hide-prompt', 'Hide stored prompt when displaying a session.', false)
|
|
118
149
|
.addOption(new Option('--clean', 'Deprecated alias for --clear.').default(false).hideHelp())
|
|
119
150
|
.action(async (sessionId, _options, command) => {
|
|
120
151
|
const statusOptions = command.opts();
|
|
@@ -138,7 +169,11 @@ const statusCommand = program
|
|
|
138
169
|
return;
|
|
139
170
|
}
|
|
140
171
|
if (sessionId) {
|
|
141
|
-
|
|
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 });
|
|
142
177
|
return;
|
|
143
178
|
}
|
|
144
179
|
const showExamples = usesDefaultStatusFilters(command);
|
|
@@ -171,9 +206,16 @@ function buildRunOptions(options, overrides = {}) {
|
|
|
171
206
|
verbose: overrides.verbose ?? options.verbose,
|
|
172
207
|
heartbeatIntervalMs: overrides.heartbeatIntervalMs ?? resolveHeartbeatIntervalMs(options.heartbeat),
|
|
173
208
|
browserInlineFiles: overrides.browserInlineFiles ?? options.browserInlineFiles ?? false,
|
|
209
|
+
browserBundleFiles: overrides.browserBundleFiles ?? options.browserBundleFiles ?? false,
|
|
174
210
|
background: overrides.background ?? undefined,
|
|
175
211
|
};
|
|
176
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
|
+
}
|
|
177
219
|
function resolveHeartbeatIntervalMs(seconds) {
|
|
178
220
|
if (typeof seconds !== 'number' || seconds <= 0) {
|
|
179
221
|
return undefined;
|
|
@@ -192,7 +234,7 @@ function buildRunOptionsFromMetadata(metadata) {
|
|
|
192
234
|
maxOutput: stored.maxOutput,
|
|
193
235
|
system: stored.system,
|
|
194
236
|
silent: stored.silent,
|
|
195
|
-
search:
|
|
237
|
+
search: stored.search,
|
|
196
238
|
preview: false,
|
|
197
239
|
previewMode: undefined,
|
|
198
240
|
apiKey: undefined,
|
|
@@ -200,6 +242,7 @@ function buildRunOptionsFromMetadata(metadata) {
|
|
|
200
242
|
verbose: stored.verbose,
|
|
201
243
|
heartbeatIntervalMs: stored.heartbeatIntervalMs,
|
|
202
244
|
browserInlineFiles: stored.browserInlineFiles,
|
|
245
|
+
browserBundleFiles: stored.browserBundleFiles,
|
|
203
246
|
background: stored.background,
|
|
204
247
|
};
|
|
205
248
|
}
|
|
@@ -210,6 +253,7 @@ function getBrowserConfigFromMetadata(metadata) {
|
|
|
210
253
|
return metadata.options?.browserConfig ?? metadata.browser?.config;
|
|
211
254
|
}
|
|
212
255
|
async function runRootCommand(options) {
|
|
256
|
+
const userConfig = (await loadUserConfig()).config;
|
|
213
257
|
const helpRequested = rawCliArgs.some((arg) => arg === '--help' || arg === '-h');
|
|
214
258
|
if (helpRequested) {
|
|
215
259
|
if (options.verbose) {
|
|
@@ -221,7 +265,11 @@ async function runRootCommand(options) {
|
|
|
221
265
|
return;
|
|
222
266
|
}
|
|
223
267
|
const previewMode = resolvePreviewMode(options.preview);
|
|
224
|
-
if (
|
|
268
|
+
if (userCliArgs.length === 0) {
|
|
269
|
+
if (tuiEnabled()) {
|
|
270
|
+
await launchTui({ version: VERSION });
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
225
273
|
console.log(chalk.yellow('No prompt or subcommand supplied. See `oracle --help` for usage.'));
|
|
226
274
|
program.help({ error: false });
|
|
227
275
|
return;
|
|
@@ -236,18 +284,39 @@ async function runRootCommand(options) {
|
|
|
236
284
|
if (options.dryRun && options.renderMarkdown) {
|
|
237
285
|
throw new Error('--dry-run cannot be combined with --render-markdown.');
|
|
238
286
|
}
|
|
239
|
-
const
|
|
287
|
+
const preferredEngine = options.engine ?? userConfig.engine;
|
|
288
|
+
const engine = resolveEngine({ engine: preferredEngine, browserFlag: options.browser, env: process.env });
|
|
240
289
|
if (options.browser) {
|
|
241
290
|
console.log(chalk.yellow('`--browser` is deprecated; use `--engine browser` instead.'));
|
|
242
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
|
+
}
|
|
243
304
|
const cliModelArg = normalizeModelOption(options.model) || 'gpt-5-pro';
|
|
244
305
|
const resolvedModel = engine === 'browser' ? inferModelFromLabel(cliModelArg) : resolveApiModel(cliModelArg);
|
|
245
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
|
+
});
|
|
246
316
|
if (await handleStatusFlag(options, { attachSession, showStatus })) {
|
|
247
317
|
return;
|
|
248
318
|
}
|
|
249
|
-
if (options
|
|
250
|
-
await attachSession(options.session);
|
|
319
|
+
if (await handleSessionAlias(options, { attachSession })) {
|
|
251
320
|
return;
|
|
252
321
|
}
|
|
253
322
|
if (options.execSession) {
|
|
@@ -263,19 +332,34 @@ async function runRootCommand(options) {
|
|
|
263
332
|
return;
|
|
264
333
|
}
|
|
265
334
|
if (previewMode) {
|
|
266
|
-
if (engine === 'browser') {
|
|
267
|
-
throw new Error('--engine browser cannot be combined with --preview.');
|
|
268
|
-
}
|
|
269
335
|
if (!options.prompt) {
|
|
270
336
|
throw new Error('Prompt is required when using --preview.');
|
|
271
337
|
}
|
|
338
|
+
if (userConfig.promptSuffix) {
|
|
339
|
+
options.prompt = `${options.prompt.trim()}\n${userConfig.promptSuffix}`;
|
|
340
|
+
}
|
|
341
|
+
resolvedOptions.prompt = options.prompt;
|
|
272
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
|
+
}
|
|
273
353
|
await runOracle(runOptions, { log: console.log, write: (chunk) => process.stdout.write(chunk) });
|
|
274
354
|
return;
|
|
275
355
|
}
|
|
276
356
|
if (!options.prompt) {
|
|
277
357
|
throw new Error('Prompt is required when starting a new session.');
|
|
278
358
|
}
|
|
359
|
+
if (userConfig.promptSuffix) {
|
|
360
|
+
options.prompt = `${options.prompt.trim()}\n${userConfig.promptSuffix}`;
|
|
361
|
+
}
|
|
362
|
+
resolvedOptions.prompt = options.prompt;
|
|
279
363
|
if (options.dryRun) {
|
|
280
364
|
const baseRunOptions = buildRunOptions(resolvedOptions, { preview: false, previewMode: undefined });
|
|
281
365
|
await runDryRunSummary({
|
|
@@ -290,6 +374,13 @@ async function runRootCommand(options) {
|
|
|
290
374
|
if (options.file && options.file.length > 0) {
|
|
291
375
|
await readFiles(options.file, { cwd: process.cwd() });
|
|
292
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
|
+
});
|
|
293
384
|
const sessionMode = engine === 'browser' ? 'browser' : 'api';
|
|
294
385
|
const browserModelLabelOverride = sessionMode === 'browser' ? resolveBrowserModelLabel(cliModelArg, resolvedModel) : undefined;
|
|
295
386
|
const browserConfig = sessionMode === 'browser'
|
|
@@ -300,12 +391,21 @@ async function runRootCommand(options) {
|
|
|
300
391
|
})
|
|
301
392
|
: undefined;
|
|
302
393
|
await ensureSessionStorage();
|
|
303
|
-
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
|
+
}
|
|
304
404
|
const sessionMeta = await initializeSession({
|
|
305
405
|
...baseRunOptions,
|
|
306
406
|
mode: sessionMode,
|
|
307
407
|
browserConfig,
|
|
308
|
-
}, process.cwd());
|
|
408
|
+
}, process.cwd(), notifications);
|
|
309
409
|
const reattachCommand = `pnpm oracle session ${sessionMeta.id}`;
|
|
310
410
|
console.log(chalk.bold(`Reattach later with: ${chalk.cyan(reattachCommand)}`));
|
|
311
411
|
console.log('');
|
|
@@ -318,8 +418,18 @@ async function runRootCommand(options) {
|
|
|
318
418
|
console.log(chalk.yellow(`Unable to detach session runner (${message}). Running inline...`));
|
|
319
419
|
return false;
|
|
320
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
|
+
}
|
|
321
431
|
if (detached === false) {
|
|
322
|
-
await runInteractiveSession(sessionMeta, liveRunOptions, sessionMode, browserConfig, true);
|
|
432
|
+
await runInteractiveSession(sessionMeta, liveRunOptions, sessionMode, browserConfig, true, notifications, userConfig);
|
|
323
433
|
console.log(chalk.bold(`Session ${sessionMeta.id} completed`));
|
|
324
434
|
return;
|
|
325
435
|
}
|
|
@@ -329,11 +439,11 @@ async function runRootCommand(options) {
|
|
|
329
439
|
console.log(chalk.bold(`Session ${sessionMeta.id} completed`));
|
|
330
440
|
}
|
|
331
441
|
}
|
|
332
|
-
async function runInteractiveSession(sessionMeta, runOptions, mode, browserConfig, showReattachHint = true) {
|
|
442
|
+
async function runInteractiveSession(sessionMeta, runOptions, mode, browserConfig, showReattachHint = true, notifications, userConfig) {
|
|
333
443
|
const { logLine, writeChunk, stream } = createSessionLogWriter(sessionMeta.id);
|
|
334
444
|
let headerAugmented = false;
|
|
335
445
|
const combinedLog = (message = '') => {
|
|
336
|
-
if (!headerAugmented && message.startsWith('
|
|
446
|
+
if (!headerAugmented && message.startsWith('oracle (')) {
|
|
337
447
|
headerAugmented = true;
|
|
338
448
|
if (showReattachHint) {
|
|
339
449
|
console.log(`${message}\n${chalk.blue(`Reattach via: oracle session ${sessionMeta.id}`)}`);
|
|
@@ -361,7 +471,14 @@ async function runInteractiveSession(sessionMeta, runOptions, mode, browserConfi
|
|
|
361
471
|
log: combinedLog,
|
|
362
472
|
write: combinedWrite,
|
|
363
473
|
version: VERSION,
|
|
474
|
+
notifications: notifications ?? deriveNotificationSettingsFromMetadata(sessionMeta, process.env, userConfig?.notify),
|
|
364
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
|
+
}
|
|
365
482
|
}
|
|
366
483
|
catch (error) {
|
|
367
484
|
throw error;
|
|
@@ -401,6 +518,8 @@ async function executeSession(sessionId) {
|
|
|
401
518
|
const sessionMode = getSessionMode(metadata);
|
|
402
519
|
const browserConfig = getBrowserConfigFromMetadata(metadata);
|
|
403
520
|
const { logLine, writeChunk, stream } = createSessionLogWriter(sessionId);
|
|
521
|
+
const userConfig = (await loadUserConfig()).config;
|
|
522
|
+
const notifications = deriveNotificationSettingsFromMetadata(metadata, process.env, userConfig.notify);
|
|
404
523
|
try {
|
|
405
524
|
await performSessionRun({
|
|
406
525
|
sessionMeta: metadata,
|
|
@@ -411,6 +530,7 @@ async function executeSession(sessionId) {
|
|
|
411
530
|
log: logLine,
|
|
412
531
|
write: writeChunk,
|
|
413
532
|
version: VERSION,
|
|
533
|
+
notifications,
|
|
414
534
|
});
|
|
415
535
|
}
|
|
416
536
|
catch {
|
|
@@ -450,6 +570,43 @@ function printDebugOptionGroup(entries) {
|
|
|
450
570
|
console.log(` ${label}${description}`);
|
|
451
571
|
});
|
|
452
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
|
+
}
|
|
453
610
|
program.action(async function () {
|
|
454
611
|
const options = this.optsWithGlobals();
|
|
455
612
|
await runRootCommand(options);
|