@steipete/oracle 0.9.0 → 0.10.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/LICENSE +1 -1
- package/README.md +61 -48
- package/dist/bin/oracle-cli.js +455 -402
- package/dist/bin/oracle-mcp.js +2 -2
- package/dist/bin/oracle.js +165 -279
- package/dist/scripts/agent-send.js +31 -31
- package/dist/scripts/check.js +6 -6
- package/dist/scripts/debug/extract-chatgpt-response.js +10 -10
- package/dist/scripts/docs-list.js +30 -30
- package/dist/scripts/git-policy.js +25 -23
- package/dist/scripts/run-cli.js +8 -8
- package/dist/scripts/runner.js +203 -195
- package/dist/scripts/test-browser.js +21 -18
- package/dist/scripts/test-remote-chrome.js +20 -20
- package/dist/src/bridge/connection.js +18 -18
- package/dist/src/bridge/userConfigFile.js +7 -7
- package/dist/src/browser/actions/assistantResponse.js +149 -101
- package/dist/src/browser/actions/attachmentDataTransfer.js +49 -47
- package/dist/src/browser/actions/attachments.js +246 -150
- package/dist/src/browser/actions/domEvents.js +2 -2
- package/dist/src/browser/actions/modelSelection.js +275 -117
- package/dist/src/browser/actions/navigation.js +161 -137
- package/dist/src/browser/actions/promptComposer.js +100 -64
- package/dist/src/browser/actions/remoteFileTransfer.js +10 -10
- package/dist/src/browser/actions/thinkingTime.js +207 -110
- package/dist/src/browser/chromeLifecycle.js +62 -60
- package/dist/src/browser/config.js +34 -15
- package/dist/src/browser/constants.js +17 -12
- package/dist/src/browser/cookies.js +19 -19
- package/dist/src/browser/detect.js +62 -62
- package/dist/src/browser/domDebug.js +1 -1
- package/dist/src/browser/index.js +390 -295
- package/dist/src/browser/modelStrategy.js +1 -1
- package/dist/src/browser/pageActions.js +5 -5
- package/dist/src/browser/policies.js +16 -13
- package/dist/src/browser/profileState.js +44 -39
- package/dist/src/browser/prompt.js +72 -42
- package/dist/src/browser/promptSummary.js +5 -5
- package/dist/src/browser/providerDomFlow.js +1 -1
- package/dist/src/browser/providers/chatgptDomProvider.js +9 -9
- package/dist/src/browser/providers/geminiDeepThinkDomProvider.js +51 -42
- package/dist/src/browser/providers/index.js +2 -2
- package/dist/src/browser/reattach.js +67 -34
- package/dist/src/browser/reattachHelpers.js +31 -26
- package/dist/src/browser/sessionRunner.js +37 -25
- package/dist/src/browser/utils.js +9 -9
- package/dist/src/browserMode.js +1 -1
- package/dist/src/cli/bridge/claudeConfig.js +16 -16
- package/dist/src/cli/bridge/client.js +28 -20
- package/dist/src/cli/bridge/codexConfig.js +16 -16
- package/dist/src/cli/bridge/doctor.js +47 -39
- package/dist/src/cli/bridge/host.js +58 -56
- package/dist/src/cli/browserConfig.js +62 -48
- package/dist/src/cli/browserDefaults.js +27 -26
- package/dist/src/cli/bundleWarnings.js +1 -1
- package/dist/src/cli/clipboard.js +11 -2
- package/dist/src/cli/detach.js +2 -2
- package/dist/src/cli/dryRun.js +29 -25
- package/dist/src/cli/duplicatePromptGuard.js +3 -3
- package/dist/src/cli/engine.js +9 -9
- package/dist/src/cli/errorUtils.js +1 -1
- package/dist/src/cli/fileSize.js +3 -3
- package/dist/src/cli/format.js +2 -2
- package/dist/src/cli/help.js +28 -28
- package/dist/src/cli/hiddenAliases.js +3 -3
- package/dist/src/cli/markdownBundle.js +7 -7
- package/dist/src/cli/markdownRenderer.js +15 -15
- package/dist/src/cli/notifier.js +77 -67
- package/dist/src/cli/options.js +127 -106
- package/dist/src/cli/oscUtils.js +1 -1
- package/dist/src/cli/promptRequirement.js +2 -2
- package/dist/src/cli/renderOutput.js +1 -1
- package/dist/src/cli/rootAlias.js +1 -1
- package/dist/src/cli/runOptions.js +32 -28
- package/dist/src/cli/sessionCommand.js +31 -21
- package/dist/src/cli/sessionDisplay.js +95 -81
- package/dist/src/cli/sessionLineage.js +6 -2
- package/dist/src/cli/sessionRunner.js +103 -93
- package/dist/src/cli/sessionTable.js +26 -23
- package/dist/src/cli/stdin.js +22 -0
- package/dist/src/cli/tagline.js +121 -124
- package/dist/src/cli/tui/index.js +139 -128
- package/dist/src/cli/writeOutputPath.js +5 -5
- package/dist/src/config.js +7 -7
- package/dist/src/gemini-web/browserSessionManager.js +19 -15
- package/dist/src/gemini-web/client.js +76 -70
- package/dist/src/gemini-web/executionMode.js +6 -8
- package/dist/src/gemini-web/executor.js +98 -93
- package/dist/src/gemini-web/index.js +1 -1
- package/dist/src/mcp/server.js +16 -12
- package/dist/src/mcp/tools/consult.js +51 -47
- package/dist/src/mcp/tools/sessionResources.js +12 -12
- package/dist/src/mcp/tools/sessions.js +26 -17
- package/dist/src/mcp/types.js +5 -5
- package/dist/src/mcp/utils.js +15 -7
- package/dist/src/oracle/background.js +15 -15
- package/dist/src/oracle/claude.js +53 -25
- package/dist/src/oracle/client.js +50 -41
- package/dist/src/oracle/config.js +96 -66
- package/dist/src/oracle/errors.js +38 -38
- package/dist/src/oracle/files.js +55 -46
- package/dist/src/oracle/finishLine.js +10 -8
- package/dist/src/oracle/format.js +3 -3
- package/dist/src/oracle/gemini.js +37 -33
- package/dist/src/oracle/logging.js +7 -7
- package/dist/src/oracle/markdown.js +28 -28
- package/dist/src/oracle/modelResolver.js +16 -16
- package/dist/src/oracle/multiModelRunner.js +12 -12
- package/dist/src/oracle/oscProgress.js +8 -8
- package/dist/src/oracle/promptAssembly.js +6 -3
- package/dist/src/oracle/request.js +16 -13
- package/dist/src/oracle/run.js +156 -134
- package/dist/src/oracle/runUtils.js +8 -5
- package/dist/src/oracle/tokenEstimate.js +6 -6
- package/dist/src/oracle/tokenStats.js +5 -5
- package/dist/src/oracle/tokenStringifier.js +5 -5
- package/dist/src/oracle.js +12 -12
- package/dist/src/oracleHome.js +3 -3
- package/dist/src/remote/client.js +25 -25
- package/dist/src/remote/health.js +20 -20
- package/dist/src/remote/remoteServiceConfig.js +9 -9
- package/dist/src/remote/server.js +129 -118
- package/dist/src/sessionManager.js +77 -75
- package/dist/src/sessionStore.js +3 -3
- package/dist/src/version.js +10 -10
- package/dist/vendor/oracle-notifier/README.md +2 -0
- package/package.json +66 -62
- package/vendor/oracle-notifier/README.md +2 -0
- package/dist/markdansi/types/index.js +0 -4
- package/dist/oracle/bin/oracle-cli.js +0 -472
- package/dist/oracle/src/browser/actions/assistantResponse.js +0 -471
- package/dist/oracle/src/browser/actions/attachments.js +0 -82
- package/dist/oracle/src/browser/actions/modelSelection.js +0 -190
- package/dist/oracle/src/browser/actions/navigation.js +0 -75
- package/dist/oracle/src/browser/actions/promptComposer.js +0 -167
- package/dist/oracle/src/browser/chromeLifecycle.js +0 -104
- package/dist/oracle/src/browser/config.js +0 -33
- package/dist/oracle/src/browser/constants.js +0 -40
- package/dist/oracle/src/browser/cookies.js +0 -210
- package/dist/oracle/src/browser/domDebug.js +0 -36
- package/dist/oracle/src/browser/index.js +0 -331
- package/dist/oracle/src/browser/pageActions.js +0 -5
- package/dist/oracle/src/browser/prompt.js +0 -88
- package/dist/oracle/src/browser/promptSummary.js +0 -20
- package/dist/oracle/src/browser/sessionRunner.js +0 -80
- package/dist/oracle/src/browser/types.js +0 -1
- package/dist/oracle/src/browser/utils.js +0 -62
- package/dist/oracle/src/browserMode.js +0 -1
- package/dist/oracle/src/cli/browserConfig.js +0 -44
- package/dist/oracle/src/cli/dryRun.js +0 -59
- package/dist/oracle/src/cli/engine.js +0 -17
- package/dist/oracle/src/cli/errorUtils.js +0 -9
- package/dist/oracle/src/cli/help.js +0 -70
- package/dist/oracle/src/cli/markdownRenderer.js +0 -15
- package/dist/oracle/src/cli/options.js +0 -103
- package/dist/oracle/src/cli/promptRequirement.js +0 -14
- package/dist/oracle/src/cli/rootAlias.js +0 -30
- package/dist/oracle/src/cli/sessionCommand.js +0 -77
- package/dist/oracle/src/cli/sessionDisplay.js +0 -270
- package/dist/oracle/src/cli/sessionRunner.js +0 -94
- package/dist/oracle/src/heartbeat.js +0 -43
- package/dist/oracle/src/oracle/client.js +0 -48
- package/dist/oracle/src/oracle/config.js +0 -29
- package/dist/oracle/src/oracle/errors.js +0 -101
- package/dist/oracle/src/oracle/files.js +0 -220
- package/dist/oracle/src/oracle/format.js +0 -33
- package/dist/oracle/src/oracle/fsAdapter.js +0 -7
- package/dist/oracle/src/oracle/oscProgress.js +0 -60
- package/dist/oracle/src/oracle/request.js +0 -48
- package/dist/oracle/src/oracle/run.js +0 -444
- package/dist/oracle/src/oracle/tokenStats.js +0 -39
- package/dist/oracle/src/oracle/types.js +0 -1
- package/dist/oracle/src/oracle.js +0 -9
- package/dist/oracle/src/sessionManager.js +0 -205
- package/dist/oracle/src/version.js +0 -39
- package/dist/scripts/chrome/browser-tools.js +0 -295
- package/dist/src/browser/profileSync.js +0 -141
package/dist/bin/oracle-cli.js
CHANGED
|
@@ -1,59 +1,60 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import
|
|
3
|
-
import { spawn } from
|
|
4
|
-
import { fileURLToPath } from
|
|
5
|
-
import { once } from
|
|
6
|
-
import { Command, Option } from
|
|
2
|
+
import "dotenv/config";
|
|
3
|
+
import { spawn } from "node:child_process";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { once } from "node:events";
|
|
6
|
+
import { Command, Option } from "commander";
|
|
7
7
|
// Allow `npx @steipete/oracle oracle-mcp` to resolve the MCP server even though npx runs the default binary.
|
|
8
|
-
if (process.argv[2] ===
|
|
9
|
-
const { startMcpServer } = await import(
|
|
8
|
+
if (process.argv[2] === "oracle-mcp") {
|
|
9
|
+
const { startMcpServer } = await import("../src/mcp/server.js");
|
|
10
10
|
await startMcpServer();
|
|
11
11
|
process.exit(0);
|
|
12
12
|
}
|
|
13
|
-
import { resolveEngine, defaultWaitPreference } from
|
|
14
|
-
import { shouldRequirePrompt } from
|
|
15
|
-
import
|
|
16
|
-
import
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
30
|
-
import {
|
|
31
|
-
import {
|
|
32
|
-
import {
|
|
33
|
-
import {
|
|
34
|
-
import {
|
|
35
|
-
import {
|
|
36
|
-
import {
|
|
37
|
-
import {
|
|
38
|
-
import {
|
|
39
|
-
import {
|
|
40
|
-
import {
|
|
41
|
-
import {
|
|
42
|
-
import {
|
|
43
|
-
import {
|
|
44
|
-
import {
|
|
45
|
-
import {
|
|
46
|
-
import {
|
|
47
|
-
import {
|
|
48
|
-
import {
|
|
49
|
-
import {
|
|
50
|
-
import {
|
|
13
|
+
import { resolveEngine, defaultWaitPreference } from "../src/cli/engine.js";
|
|
14
|
+
import { shouldRequirePrompt } from "../src/cli/promptRequirement.js";
|
|
15
|
+
import { resolveDashPrompt } from "../src/cli/stdin.js";
|
|
16
|
+
import chalk from "chalk";
|
|
17
|
+
import { sessionStore, pruneOldSessions } from "../src/sessionStore.js";
|
|
18
|
+
import { DEFAULT_MODEL, MODEL_CONFIGS, readFiles, estimateRequestTokens, buildRequestBody, } from "../src/oracle.js";
|
|
19
|
+
import { isKnownModel } from "../src/oracle/modelResolver.js";
|
|
20
|
+
import { CHATGPT_URL } from "../src/browserMode.js";
|
|
21
|
+
import { createRemoteBrowserExecutor } from "../src/remote/client.js";
|
|
22
|
+
import { createGeminiWebExecutor } from "../src/gemini-web/index.js";
|
|
23
|
+
import { applyHelpStyling } from "../src/cli/help.js";
|
|
24
|
+
import { collectPaths, collectModelList, parseFloatOption, parseIntOption, parseSearchOption, usesDefaultStatusFilters, resolvePreviewMode, normalizeModelOption, normalizeBaseUrl, resolveApiModel, inferModelFromLabel, parseHeartbeatOption, parseTimeoutOption, parseDurationOption, mergePathLikeOptions, dedupePathInputs, } from "../src/cli/options.js";
|
|
25
|
+
import { copyToClipboard } from "../src/cli/clipboard.js";
|
|
26
|
+
import { buildMarkdownBundle } from "../src/cli/markdownBundle.js";
|
|
27
|
+
import { shouldDetachSession } from "../src/cli/detach.js";
|
|
28
|
+
import { applyHiddenAliases } from "../src/cli/hiddenAliases.js";
|
|
29
|
+
import { buildBrowserConfig, resolveBrowserModelLabel } from "../src/cli/browserConfig.js";
|
|
30
|
+
import { performSessionRun } from "../src/cli/sessionRunner.js";
|
|
31
|
+
import { isMediaFile } from "../src/browser/prompt.js";
|
|
32
|
+
import { attachSession, showStatus, formatCompletionSummary } from "../src/cli/sessionDisplay.js";
|
|
33
|
+
import { formatCompactNumber } from "../src/cli/format.js";
|
|
34
|
+
import { formatIntroLine } from "../src/cli/tagline.js";
|
|
35
|
+
import { warnIfOversizeBundle } from "../src/cli/bundleWarnings.js";
|
|
36
|
+
import { formatRenderedMarkdown } from "../src/cli/renderOutput.js";
|
|
37
|
+
import { resolveRenderFlag, resolveRenderPlain } from "../src/cli/renderFlags.js";
|
|
38
|
+
import { resolveGeminiModelId } from "../src/oracle/gemini.js";
|
|
39
|
+
import { handleSessionCommand, formatSessionCleanupMessage, } from "../src/cli/sessionCommand.js";
|
|
40
|
+
import { isErrorLogged } from "../src/cli/errorUtils.js";
|
|
41
|
+
import { handleSessionAlias, handleStatusFlag } from "../src/cli/rootAlias.js";
|
|
42
|
+
import { resolveOutputPath } from "../src/cli/writeOutputPath.js";
|
|
43
|
+
import { getCliVersion } from "../src/version.js";
|
|
44
|
+
import { runDryRunSummary, runBrowserPreview } from "../src/cli/dryRun.js";
|
|
45
|
+
import { launchTui } from "../src/cli/tui/index.js";
|
|
46
|
+
import { resolveNotificationSettings, deriveNotificationSettingsFromMetadata, } from "../src/cli/notifier.js";
|
|
47
|
+
import { loadUserConfig } from "../src/config.js";
|
|
48
|
+
import { applyBrowserDefaultsFromConfig } from "../src/cli/browserDefaults.js";
|
|
49
|
+
import { shouldBlockDuplicatePrompt } from "../src/cli/duplicatePromptGuard.js";
|
|
50
|
+
import { resolveRemoteServiceConfig } from "../src/remote/remoteServiceConfig.js";
|
|
51
|
+
import { resolveConfiguredMaxFileSizeBytes } from "../src/cli/fileSize.js";
|
|
51
52
|
const VERSION = getCliVersion();
|
|
52
53
|
const CLI_ENTRYPOINT = fileURLToPath(import.meta.url);
|
|
53
54
|
const LEGACY_FLAG_ALIASES = new Map([
|
|
54
|
-
[
|
|
55
|
-
[
|
|
56
|
-
[
|
|
55
|
+
["--[no-]notify", "--notify"],
|
|
56
|
+
["--[no-]notify-sound", "--notify-sound"],
|
|
57
|
+
["--[no-]background", "--background"],
|
|
57
58
|
]);
|
|
58
59
|
const normalizedArgv = process.argv.map((arg, index) => {
|
|
59
60
|
if (index < 2)
|
|
@@ -65,18 +66,18 @@ const userCliArgs = rawCliArgs[0] === CLI_ENTRYPOINT ? rawCliArgs.slice(1) : raw
|
|
|
65
66
|
const isTty = process.stdout.isTTY;
|
|
66
67
|
const program = new Command();
|
|
67
68
|
let introPrinted = false;
|
|
68
|
-
program.hook(
|
|
69
|
+
program.hook("preAction", () => {
|
|
69
70
|
if (introPrinted)
|
|
70
71
|
return;
|
|
71
72
|
console.log(formatIntroLine(VERSION, { env: process.env, richTty: isTty }));
|
|
72
73
|
introPrinted = true;
|
|
73
74
|
});
|
|
74
75
|
applyHelpStyling(program, VERSION, isTty);
|
|
75
|
-
program.hook(
|
|
76
|
+
program.hook("preAction", async (thisCommand) => {
|
|
76
77
|
if (thisCommand !== program) {
|
|
77
78
|
return;
|
|
78
79
|
}
|
|
79
|
-
if (userCliArgs.some((arg) => arg ===
|
|
80
|
+
if (userCliArgs.some((arg) => arg === "--help" || arg === "-h")) {
|
|
80
81
|
return;
|
|
81
82
|
}
|
|
82
83
|
if (userCliArgs.length === 0) {
|
|
@@ -88,7 +89,12 @@ program.hook('preAction', (thisCommand) => {
|
|
|
88
89
|
const positional = thisCommand.args?.[0];
|
|
89
90
|
if (!opts.prompt && positional) {
|
|
90
91
|
opts.prompt = positional;
|
|
91
|
-
thisCommand.setOptionValue(
|
|
92
|
+
thisCommand.setOptionValue("prompt", positional);
|
|
93
|
+
}
|
|
94
|
+
const resolvedPrompt = await resolveDashPrompt(opts.prompt);
|
|
95
|
+
if (resolvedPrompt !== opts.prompt) {
|
|
96
|
+
opts.prompt = resolvedPrompt;
|
|
97
|
+
thisCommand.setOptionValue("prompt", resolvedPrompt);
|
|
92
98
|
}
|
|
93
99
|
if (shouldRequirePrompt(userCliArgs, opts)) {
|
|
94
100
|
console.log(chalk.yellow('Prompt is required. Provide it via --prompt "<text>" or positional [prompt].'));
|
|
@@ -98,141 +104,146 @@ program.hook('preAction', (thisCommand) => {
|
|
|
98
104
|
}
|
|
99
105
|
});
|
|
100
106
|
program
|
|
101
|
-
.name(
|
|
102
|
-
.description(
|
|
107
|
+
.name("oracle")
|
|
108
|
+
.description("One-shot GPT-5.5 Pro / GPT-5.5 / GPT-5.1 Codex tool for hard questions that benefit from large file context and server-side search.")
|
|
103
109
|
.version(VERSION)
|
|
104
|
-
.argument(
|
|
105
|
-
.option(
|
|
106
|
-
.addOption(new Option(
|
|
107
|
-
.option(
|
|
108
|
-
.option(
|
|
109
|
-
.option(
|
|
110
|
-
.addOption(new Option(
|
|
110
|
+
.argument("[prompt]", "Prompt text (shorthand for --prompt).")
|
|
111
|
+
.option("-p, --prompt <text>", "User prompt to send to the model.")
|
|
112
|
+
.addOption(new Option("--message <text>", "Alias for --prompt.").hideHelp())
|
|
113
|
+
.option("--followup <sessionId|responseId>", "Continue an OpenAI/Azure Responses API run from a stored response id (resp_...) or from a stored oracle session id.")
|
|
114
|
+
.option("--followup-model <model>", "When following up a multi-model session, choose which model response to continue from.")
|
|
115
|
+
.option("-f, --file <paths...>", "Files/directories or glob patterns to attach (prefix with !pattern to exclude). Oversized files are rejected automatically (default cap: 1 MB; configurable via ORACLE_MAX_FILE_SIZE_BYTES or config.maxFileSizeBytes).", collectPaths, [])
|
|
116
|
+
.addOption(new Option("--include <paths...>", "Alias for --file.")
|
|
111
117
|
.argParser(collectPaths)
|
|
112
118
|
.default([])
|
|
113
119
|
.hideHelp())
|
|
114
|
-
.addOption(new Option(
|
|
120
|
+
.addOption(new Option("--files <paths...>", "Alias for --file.")
|
|
115
121
|
.argParser(collectPaths)
|
|
116
122
|
.default([])
|
|
117
123
|
.hideHelp())
|
|
118
|
-
.addOption(new Option(
|
|
124
|
+
.addOption(new Option("--path <paths...>", "Alias for --file.")
|
|
119
125
|
.argParser(collectPaths)
|
|
120
126
|
.default([])
|
|
121
127
|
.hideHelp())
|
|
122
|
-
.addOption(new Option(
|
|
128
|
+
.addOption(new Option("--paths <paths...>", "Alias for --file.")
|
|
123
129
|
.argParser(collectPaths)
|
|
124
130
|
.default([])
|
|
125
131
|
.hideHelp())
|
|
126
|
-
.addOption(new Option(
|
|
127
|
-
.addOption(new Option(
|
|
128
|
-
.option(
|
|
129
|
-
.option(
|
|
130
|
-
.addOption(new Option(
|
|
132
|
+
.addOption(new Option("--copy-markdown", "Copy the assembled markdown bundle to the clipboard; pair with --render to print it too.").default(false))
|
|
133
|
+
.addOption(new Option("--copy").hideHelp().default(false))
|
|
134
|
+
.option("-s, --slug <words>", "Custom session slug (3-5 words).")
|
|
135
|
+
.option("-m, --model <model>", 'Model to target (gpt-5.5-pro default). Also gpt-5.5, gpt-5.4-pro, gpt-5.4, gpt-5.1-pro, gpt-5-pro, gpt-5.1, gpt-5.1-codex API-only, gpt-5.2, gpt-5.2-instant, gpt-5.2-pro, gemini-3.1-pro API-only, gemini-3-pro, claude-4.6-sonnet, claude-4.1-opus, or ChatGPT labels like "5.5 Pro" / "5.2 Thinking" for browser runs).', normalizeModelOption)
|
|
136
|
+
.addOption(new Option("--models <models>", 'Comma-separated API model list to query in parallel (e.g., "gpt-5.5-pro,gemini-3-pro").')
|
|
131
137
|
.argParser(collectModelList)
|
|
132
138
|
.default([]))
|
|
133
|
-
.addOption(new Option(
|
|
134
|
-
.addOption(new Option(
|
|
135
|
-
.
|
|
136
|
-
.
|
|
137
|
-
.
|
|
138
|
-
.
|
|
139
|
-
.addOption(new Option(
|
|
140
|
-
.addOption(new Option(
|
|
141
|
-
.addOption(new Option(
|
|
139
|
+
.addOption(new Option("-e, --engine <mode>", "Execution engine (api | browser). Browser engine: GPT models automate ChatGPT; Gemini models use a cookie-based client for gemini.google.com. If omitted, oracle picks api when OPENAI_API_KEY is set, otherwise browser.").choices(["api", "browser"]))
|
|
140
|
+
.addOption(new Option("--mode <mode>", "Alias for --engine (api | browser).")
|
|
141
|
+
.choices(["api", "browser"])
|
|
142
|
+
.hideHelp())
|
|
143
|
+
.option("--files-report", "Show token usage per attached file (also prints automatically when files exceed the token budget).", false)
|
|
144
|
+
.option("-v, --verbose", "Enable verbose logging for all operations.", false)
|
|
145
|
+
.addOption(new Option("--notify", "Desktop notification when a session finishes (default on unless CI/SSH).").default(undefined))
|
|
146
|
+
.addOption(new Option("--no-notify", "Disable desktop notifications.").default(undefined))
|
|
147
|
+
.addOption(new Option("--notify-sound", "Play a notification sound on completion (default off).").default(undefined))
|
|
148
|
+
.addOption(new Option("--no-notify-sound", "Disable notification sounds.").default(undefined))
|
|
149
|
+
.addOption(new Option("--timeout <seconds|auto>", "Overall timeout before aborting the API call (auto = 60m for Pro models, 120s otherwise).")
|
|
142
150
|
.argParser(parseTimeoutOption)
|
|
143
|
-
.default(
|
|
144
|
-
.addOption(new Option(
|
|
145
|
-
.addOption(new Option(
|
|
146
|
-
.addOption(new Option(
|
|
147
|
-
.argParser((value) => parseDurationOption(value,
|
|
151
|
+
.default("auto"))
|
|
152
|
+
.addOption(new Option("--background", "Use Responses API background mode (create + retrieve) for API runs.").default(undefined))
|
|
153
|
+
.addOption(new Option("--no-background", "Disable Responses API background mode.").default(undefined))
|
|
154
|
+
.addOption(new Option("--http-timeout <ms|s|m|h>", "HTTP client timeout for API requests (default 20m).")
|
|
155
|
+
.argParser((value) => parseDurationOption(value, "HTTP timeout"))
|
|
148
156
|
.default(undefined))
|
|
149
|
-
.addOption(new Option(
|
|
150
|
-
.argParser((value) => parseDurationOption(value,
|
|
157
|
+
.addOption(new Option("--zombie-timeout <ms|s|m|h>", "Override stale-session cutoff used by `oracle status` (default 60m).")
|
|
158
|
+
.argParser((value) => parseDurationOption(value, "Zombie timeout"))
|
|
151
159
|
.default(undefined))
|
|
152
|
-
.option(
|
|
153
|
-
.addOption(new Option(
|
|
160
|
+
.option("--zombie-last-activity", "Base stale-session detection on last log activity instead of start time.", false)
|
|
161
|
+
.addOption(new Option("--preview [mode]", "(alias) Preview the request without calling the model (summary | json | full). Deprecated: use --dry-run instead.")
|
|
154
162
|
.hideHelp()
|
|
155
|
-
.choices([
|
|
156
|
-
.preset(
|
|
157
|
-
.addOption(new Option(
|
|
158
|
-
.choices([
|
|
159
|
-
.preset(
|
|
163
|
+
.choices(["summary", "json", "full"])
|
|
164
|
+
.preset("summary"))
|
|
165
|
+
.addOption(new Option("--dry-run [mode]", "Preview without calling the model (summary | json | full).")
|
|
166
|
+
.choices(["summary", "json", "full"])
|
|
167
|
+
.preset("summary")
|
|
160
168
|
.default(false))
|
|
161
|
-
.addOption(new Option(
|
|
162
|
-
.addOption(new Option(
|
|
163
|
-
.addOption(new Option(
|
|
164
|
-
.
|
|
165
|
-
.
|
|
166
|
-
.option(
|
|
167
|
-
.option(
|
|
168
|
-
.option(
|
|
169
|
-
.
|
|
169
|
+
.addOption(new Option("--exec-session <id>").hideHelp())
|
|
170
|
+
.addOption(new Option("--session <id>").hideHelp())
|
|
171
|
+
.addOption(new Option("--status", "Show stored sessions (alias for `oracle status`).")
|
|
172
|
+
.default(false)
|
|
173
|
+
.hideHelp())
|
|
174
|
+
.option("--render-markdown", "Print the assembled markdown bundle for prompt + files and exit; pair with --copy to put it on the clipboard.", false)
|
|
175
|
+
.option("--render", "Alias for --render-markdown.", false)
|
|
176
|
+
.option("--render-plain", "Render markdown without ANSI/highlighting (use plain text even in a TTY).", false)
|
|
177
|
+
.option("--write-output <path>", "Write only the final assistant message to this file (overwrites; multi-model appends .<model> before the extension).")
|
|
178
|
+
.option("--verbose-render", "Show render/TTY diagnostics when replaying sessions.", false)
|
|
179
|
+
.addOption(new Option("--search <mode>", "Set server-side search behavior (on/off).")
|
|
170
180
|
.argParser(parseSearchOption)
|
|
171
181
|
.hideHelp())
|
|
172
|
-
.addOption(new Option(
|
|
182
|
+
.addOption(new Option("--max-input <tokens>", "Override the input token budget for the selected model.")
|
|
173
183
|
.argParser(parseIntOption)
|
|
174
184
|
.hideHelp())
|
|
175
|
-
.addOption(new Option(
|
|
185
|
+
.addOption(new Option("--max-output <tokens>", "Override the max output tokens for the selected model.")
|
|
176
186
|
.argParser(parseIntOption)
|
|
177
187
|
.hideHelp())
|
|
178
|
-
.option(
|
|
179
|
-
.option(
|
|
180
|
-
.option(
|
|
181
|
-
.option(
|
|
182
|
-
.addOption(new Option(
|
|
183
|
-
.addOption(new Option(
|
|
184
|
-
.addOption(new Option(
|
|
185
|
-
.addOption(new Option(
|
|
186
|
-
.addOption(new Option(
|
|
187
|
-
.addOption(new Option(
|
|
188
|
-
.addOption(new Option(
|
|
189
|
-
.addOption(new Option(
|
|
190
|
-
.addOption(new Option(
|
|
191
|
-
.addOption(new Option(
|
|
192
|
-
.addOption(new Option(
|
|
193
|
-
.addOption(new Option(
|
|
194
|
-
.addOption(new Option(
|
|
195
|
-
.addOption(new Option(
|
|
196
|
-
.addOption(new Option(
|
|
197
|
-
.addOption(new Option(
|
|
198
|
-
.addOption(new Option(
|
|
199
|
-
.
|
|
200
|
-
.
|
|
201
|
-
.
|
|
202
|
-
.addOption(new Option(
|
|
203
|
-
.addOption(new Option(
|
|
204
|
-
.addOption(new Option(
|
|
205
|
-
.addOption(new Option(
|
|
206
|
-
.addOption(new Option(
|
|
207
|
-
.addOption(new Option(
|
|
208
|
-
.addOption(new Option(
|
|
209
|
-
.addOption(new Option(
|
|
210
|
-
.addOption(new Option(
|
|
211
|
-
.
|
|
188
|
+
.option("--base-url <url>", "Override the OpenAI-compatible base URL for API runs (e.g. LiteLLM proxy endpoint).")
|
|
189
|
+
.option("--azure-endpoint <url>", "Azure OpenAI Endpoint (e.g. https://resource.openai.azure.com/).")
|
|
190
|
+
.option("--azure-deployment <name>", "Azure OpenAI Deployment Name.")
|
|
191
|
+
.option("--azure-api-version <version>", "Azure OpenAI API Version.")
|
|
192
|
+
.addOption(new Option("--browser", "(deprecated) Use --engine browser instead.").default(false).hideHelp())
|
|
193
|
+
.addOption(new Option("--browser-chrome-profile <name>", "Chrome profile name/path for cookie reuse.").hideHelp())
|
|
194
|
+
.addOption(new Option("--browser-chrome-path <path>", "Explicit Chrome or Chromium executable path.").hideHelp())
|
|
195
|
+
.addOption(new Option("--browser-cookie-path <path>", "Explicit Chrome/Chromium cookie DB path for session reuse."))
|
|
196
|
+
.addOption(new Option("--chatgpt-url <url>", `Override the ChatGPT web URL (e.g., workspace/folder like https://chatgpt.com/g/.../project; default ${CHATGPT_URL}).`))
|
|
197
|
+
.addOption(new Option("--browser-url <url>", `Alias for --chatgpt-url (default ${CHATGPT_URL}).`).hideHelp())
|
|
198
|
+
.addOption(new Option("--browser-timeout <ms|s|m>", "Maximum time to wait for an answer (default 1200s / 20m).").hideHelp())
|
|
199
|
+
.addOption(new Option("--browser-input-timeout <ms|s|m>", "Maximum time to wait for the prompt textarea (default 60s).").hideHelp())
|
|
200
|
+
.addOption(new Option("--browser-recheck-delay <ms|s|m|h>", "After an assistant timeout, wait this long then revisit the conversation to retry capture.").hideHelp())
|
|
201
|
+
.addOption(new Option("--browser-recheck-timeout <ms|s|m|h>", "Time budget for the delayed recheck attempt (default 120s).").hideHelp())
|
|
202
|
+
.addOption(new Option("--browser-reuse-wait <ms|s|m|h>", "Wait for a shared Chrome profile to appear before launching a new one (helps parallel runs).").hideHelp())
|
|
203
|
+
.addOption(new Option("--browser-profile-lock-timeout <ms|s|m|h>", "Wait for the shared manual-login profile lock before sending (serializes parallel runs).").hideHelp())
|
|
204
|
+
.addOption(new Option("--browser-auto-reattach-delay <ms|s|m|h>", "Delay before starting periodic auto-reattach attempts after a timeout.").hideHelp())
|
|
205
|
+
.addOption(new Option("--browser-auto-reattach-interval <ms|s|m|h>", "Interval between auto-reattach attempts (0 disables).").hideHelp())
|
|
206
|
+
.addOption(new Option("--browser-auto-reattach-timeout <ms|s|m|h>", "Time budget for each auto-reattach attempt (default 120s).").hideHelp())
|
|
207
|
+
.addOption(new Option("--browser-cookie-wait <ms|s|m>", "Wait before retrying cookie sync when Chrome cookies are empty or locked.").hideHelp())
|
|
208
|
+
.addOption(new Option("--browser-port <port>", "Use a fixed Chrome DevTools port (helpful on WSL firewalls).").argParser(parseIntOption))
|
|
209
|
+
.addOption(new Option("--browser-debug-port <port>", "(alias) Use a fixed Chrome DevTools port.")
|
|
210
|
+
.argParser(parseIntOption)
|
|
211
|
+
.hideHelp())
|
|
212
|
+
.addOption(new Option("--browser-cookie-names <names>", "Comma-separated cookie allowlist for sync.").hideHelp())
|
|
213
|
+
.addOption(new Option("--browser-inline-cookies <jsonOrBase64>", "Inline cookies payload (JSON array or base64-encoded JSON).").hideHelp())
|
|
214
|
+
.addOption(new Option("--browser-inline-cookies-file <path>", "Load inline cookies from file (JSON or base64 JSON).").hideHelp())
|
|
215
|
+
.addOption(new Option("--browser-no-cookie-sync", "Skip copying cookies from Chrome.").hideHelp())
|
|
216
|
+
.addOption(new Option("--browser-manual-login", "Skip cookie copy; reuse a persistent automation profile and wait for manual ChatGPT login.").hideHelp())
|
|
217
|
+
.addOption(new Option("--browser-headless", "Launch Chrome in headless mode.").hideHelp())
|
|
218
|
+
.addOption(new Option("--browser-hide-window", "Hide the Chrome window after launch (macOS headful only).").hideHelp())
|
|
219
|
+
.addOption(new Option("--browser-keep-browser", "Keep Chrome running after completion.").hideHelp())
|
|
220
|
+
.addOption(new Option("--browser-model-strategy <mode>", "ChatGPT model picker strategy: select (default) switches to the requested model, current keeps the active model, ignore skips the picker entirely.").choices(["select", "current", "ignore"]))
|
|
221
|
+
.addOption(new Option("--browser-thinking-time <level>", "Thinking time intensity for Thinking/Pro models: light, standard, extended, heavy.")
|
|
222
|
+
.choices(["light", "standard", "extended", "heavy"])
|
|
212
223
|
.hideHelp())
|
|
213
|
-
.addOption(new Option(
|
|
214
|
-
.addOption(new Option(
|
|
215
|
-
.choices([
|
|
216
|
-
.default(
|
|
217
|
-
.addOption(new Option(
|
|
218
|
-
.addOption(new Option(
|
|
219
|
-
.addOption(new Option(
|
|
220
|
-
.addOption(new Option(
|
|
221
|
-
.addOption(new Option(
|
|
222
|
-
.addOption(new Option(
|
|
223
|
-
.addOption(new Option(
|
|
224
|
-
.addOption(new Option(
|
|
225
|
-
.addOption(new Option(
|
|
226
|
-
.addOption(new Option(
|
|
227
|
-
.addOption(new Option(
|
|
228
|
-
.option(
|
|
229
|
-
.option(
|
|
230
|
-
.option(
|
|
231
|
-
.option(
|
|
232
|
-
.addOption(new Option(
|
|
233
|
-
.addOption(new Option(
|
|
234
|
-
.showHelpAfterError(
|
|
235
|
-
program.addHelpText(
|
|
224
|
+
.addOption(new Option("--browser-allow-cookie-errors", "Continue even if Chrome cookies cannot be copied.").hideHelp())
|
|
225
|
+
.addOption(new Option("--browser-attachments <mode>", "How to deliver --file inputs in browser mode: auto (default) pastes inline up to ~60k chars then uploads; never always paste inline; always always upload.")
|
|
226
|
+
.choices(["auto", "never", "always"])
|
|
227
|
+
.default("auto"))
|
|
228
|
+
.addOption(new Option("--remote-chrome <host:port>", "Connect to remote Chrome DevTools Protocol (e.g., 192.168.1.10:9222 or [2001:db8::1]:9222 for IPv6)."))
|
|
229
|
+
.addOption(new Option("--remote-host <host:port>", "Delegate browser runs to a remote `oracle serve` instance."))
|
|
230
|
+
.addOption(new Option("--remote-token <token>", "Access token for the remote `oracle serve` instance."))
|
|
231
|
+
.addOption(new Option("--browser-inline-files", "Alias for --browser-attachments never (force pasting file contents inline).").default(false))
|
|
232
|
+
.addOption(new Option("--browser-bundle-files", "Bundle all attachments into a single archive before uploading.").default(false))
|
|
233
|
+
.addOption(new Option("--youtube <url>", "YouTube video URL to analyze (Gemini web/cookie mode only; uses your signed-in Chrome cookies for gemini.google.com)."))
|
|
234
|
+
.addOption(new Option("--generate-image <file>", "Generate image and save to file (Gemini web/cookie mode only; requires gemini.google.com Chrome cookies)."))
|
|
235
|
+
.addOption(new Option("--edit-image <file>", "Edit existing image (use with --output, Gemini web/cookie mode only)."))
|
|
236
|
+
.addOption(new Option("--output <file>", "Output file path for image operations (Gemini web/cookie mode only)."))
|
|
237
|
+
.addOption(new Option("--aspect <ratio>", "Aspect ratio for image generation: 16:9, 1:1, 4:3, 3:4 (Gemini web/cookie mode only)."))
|
|
238
|
+
.addOption(new Option("--gemini-show-thoughts", "Display Gemini thinking process (Gemini web/cookie mode only).").default(false))
|
|
239
|
+
.option("--retain-hours <hours>", "Prune stored sessions older than this many hours before running (set 0 to disable).", parseFloatOption)
|
|
240
|
+
.option("--force", "Force start a new session even if an identical prompt is already running.", false)
|
|
241
|
+
.option("--debug-help", "Show the advanced/debug option set and exit.", false)
|
|
242
|
+
.option("--heartbeat <seconds>", "Emit periodic in-progress updates (0 to disable).", parseHeartbeatOption, 30)
|
|
243
|
+
.addOption(new Option("--wait").default(undefined))
|
|
244
|
+
.addOption(new Option("--no-wait").default(undefined).hideHelp())
|
|
245
|
+
.showHelpAfterError("(use --help for usage)");
|
|
246
|
+
program.addHelpText("after", `
|
|
236
247
|
Examples:
|
|
237
248
|
# Quick API run with two files
|
|
238
249
|
oracle --prompt "Summarize the risk register" --file docs/risk-register.md docs/risk-matrix.md
|
|
@@ -245,15 +256,15 @@ Examples:
|
|
|
245
256
|
oracle --render --copy -p "Review the TS data layer" --file "src/**/*.ts" --file "!src/**/*.test.ts"
|
|
246
257
|
`);
|
|
247
258
|
program
|
|
248
|
-
.command(
|
|
249
|
-
.description(
|
|
250
|
-
.option(
|
|
251
|
-
.option(
|
|
252
|
-
.option(
|
|
253
|
-
.option(
|
|
254
|
-
.option(
|
|
259
|
+
.command("serve")
|
|
260
|
+
.description("Run Oracle browser automation as a remote service for other machines.")
|
|
261
|
+
.option("--host <address>", "Interface to bind (default 0.0.0.0).")
|
|
262
|
+
.option("--port <number>", "Port to listen on (default random).", parseIntOption)
|
|
263
|
+
.option("--token <value>", "Access token clients must provide (random if omitted).")
|
|
264
|
+
.option("--manual-login", "Use a dedicated Chrome profile for manual login (recommended when cookie sync is unavailable).", false)
|
|
265
|
+
.option("--manual-login-profile-dir <path>", "Chrome profile directory for manual login (default ~/.oracle/browser-profile).")
|
|
255
266
|
.action(async (commandOptions) => {
|
|
256
|
-
const { serveRemote } = await import(
|
|
267
|
+
const { serveRemote } = await import("../src/remote/server.js");
|
|
257
268
|
await serveRemote({
|
|
258
269
|
host: commandOptions.host,
|
|
259
270
|
port: commandOptions.port,
|
|
@@ -262,119 +273,122 @@ program
|
|
|
262
273
|
manualLoginProfileDir: commandOptions.manualLoginProfileDir,
|
|
263
274
|
});
|
|
264
275
|
});
|
|
265
|
-
const bridgeCommand = program
|
|
276
|
+
const bridgeCommand = program
|
|
277
|
+
.command("bridge")
|
|
278
|
+
.description("Bridge a Windows-hosted ChatGPT session to Linux clients.");
|
|
266
279
|
bridgeCommand
|
|
267
|
-
.command(
|
|
268
|
-
.description(
|
|
269
|
-
.option(
|
|
270
|
-
.option(
|
|
271
|
-
.option(
|
|
272
|
-
.option(
|
|
273
|
-
.option(
|
|
274
|
-
.option(
|
|
275
|
-
.option(
|
|
276
|
-
.option(
|
|
277
|
-
.option(
|
|
278
|
-
.option(
|
|
279
|
-
.option(
|
|
280
|
+
.command("host")
|
|
281
|
+
.description("Start a secure oracle serve host (optionally with an SSH reverse tunnel).")
|
|
282
|
+
.option("--bind <host:port>", "Local bind address for the host service (default 127.0.0.1:9473).")
|
|
283
|
+
.option("--token <token|auto>", "Service access token (default auto).", "auto")
|
|
284
|
+
.option("--write-connection <path>", "Write a connection artifact JSON (default ~/.oracle/bridge-connection.json).")
|
|
285
|
+
.option("--ssh <user@host>", "Maintain an SSH reverse tunnel to the Linux host (ssh -N -R ...).")
|
|
286
|
+
.option("--ssh-remote-port <port>", "Remote port to bind on the Linux host (default matches --bind port).", parseIntOption)
|
|
287
|
+
.option("--ssh-identity <path>", "SSH identity file (ssh -i).")
|
|
288
|
+
.option("--ssh-extra-args <args>", "Extra args passed to ssh (quoted string).")
|
|
289
|
+
.option("--background", "Run the host in the background and write pid/log files.", false)
|
|
290
|
+
.option("--foreground", "Run the host in the foreground (default).", false)
|
|
291
|
+
.option("--print", "Print the client connection string (includes token).", false)
|
|
292
|
+
.option("--print-token", "Print only the token.", false)
|
|
280
293
|
.action(async (commandOptions) => {
|
|
281
|
-
const { runBridgeHost } = await import(
|
|
294
|
+
const { runBridgeHost } = await import("../src/cli/bridge/host.js");
|
|
282
295
|
await runBridgeHost(commandOptions);
|
|
283
296
|
});
|
|
284
297
|
bridgeCommand
|
|
285
|
-
.command(
|
|
286
|
-
.description(
|
|
287
|
-
.requiredOption(
|
|
288
|
-
.option(
|
|
289
|
-
.option(
|
|
290
|
-
.option(
|
|
291
|
-
.option(
|
|
298
|
+
.command("client")
|
|
299
|
+
.description("Configure this machine to use a remote oracle serve host.")
|
|
300
|
+
.requiredOption("--connect <connection>", "Connection string or path to bridge-connection.json.")
|
|
301
|
+
.option("--config <path>", "Override the oracle config file location (default ~/.oracle/config.json).")
|
|
302
|
+
.option("--no-write-config", "Do not write ~/.oracle/config.json (just validate).")
|
|
303
|
+
.option("--no-test", "Skip remote /health check.")
|
|
304
|
+
.option("--print-env", "Print env var exports (includes token).", false)
|
|
292
305
|
.action(async (commandOptions) => {
|
|
293
|
-
const { runBridgeClient } = await import(
|
|
306
|
+
const { runBridgeClient } = await import("../src/cli/bridge/client.js");
|
|
294
307
|
await runBridgeClient(commandOptions);
|
|
295
308
|
});
|
|
296
309
|
bridgeCommand
|
|
297
|
-
.command(
|
|
298
|
-
.description(
|
|
299
|
-
.option(
|
|
310
|
+
.command("doctor")
|
|
311
|
+
.description("Diagnose bridge connectivity and browser engine prerequisites.")
|
|
312
|
+
.option("--verbose", "Show extra diagnostics.", false)
|
|
300
313
|
.action(async (commandOptions) => {
|
|
301
|
-
const { runBridgeDoctor } = await import(
|
|
314
|
+
const { runBridgeDoctor } = await import("../src/cli/bridge/doctor.js");
|
|
302
315
|
await runBridgeDoctor(commandOptions);
|
|
303
316
|
});
|
|
304
317
|
bridgeCommand
|
|
305
|
-
.command(
|
|
306
|
-
.description(
|
|
307
|
-
.option(
|
|
318
|
+
.command("codex-config")
|
|
319
|
+
.description("Print a Codex CLI MCP server config snippet for oracle-mcp.")
|
|
320
|
+
.option("--print-token", "Include ORACLE_REMOTE_TOKEN in the snippet.", false)
|
|
308
321
|
.action(async (commandOptions) => {
|
|
309
|
-
const { runBridgeCodexConfig } = await import(
|
|
322
|
+
const { runBridgeCodexConfig } = await import("../src/cli/bridge/codexConfig.js");
|
|
310
323
|
await runBridgeCodexConfig(commandOptions);
|
|
311
324
|
});
|
|
312
325
|
bridgeCommand
|
|
313
|
-
.command(
|
|
314
|
-
.description(
|
|
315
|
-
.option(
|
|
326
|
+
.command("claude-config")
|
|
327
|
+
.description("Print a Claude Code MCP config snippet (.mcp.json) for oracle-mcp.")
|
|
328
|
+
.option("--print-token", "Include ORACLE_REMOTE_TOKEN in the snippet.", false)
|
|
316
329
|
.action(async (commandOptions) => {
|
|
317
|
-
const { runBridgeClaudeConfig } = await import(
|
|
330
|
+
const { runBridgeClaudeConfig } = await import("../src/cli/bridge/claudeConfig.js");
|
|
318
331
|
await runBridgeClaudeConfig(commandOptions);
|
|
319
332
|
});
|
|
320
333
|
program
|
|
321
|
-
.command(
|
|
322
|
-
.description(
|
|
334
|
+
.command("tui")
|
|
335
|
+
.description("Launch the interactive terminal UI for humans (no automation).")
|
|
323
336
|
.action(async () => {
|
|
324
337
|
await sessionStore.ensureStorage();
|
|
325
338
|
await launchTui({ version: VERSION, printIntro: false });
|
|
326
339
|
});
|
|
327
|
-
|
|
328
|
-
.command(
|
|
329
|
-
.description(
|
|
330
|
-
.option(
|
|
331
|
-
.option(
|
|
332
|
-
.option(
|
|
333
|
-
.option(
|
|
334
|
-
.option(
|
|
335
|
-
.option(
|
|
336
|
-
.option(
|
|
337
|
-
.option(
|
|
338
|
-
.option(
|
|
339
|
-
.addOption(new Option(
|
|
340
|
+
program
|
|
341
|
+
.command("session [id]")
|
|
342
|
+
.description("Attach to a stored session or list recent sessions when no ID is provided.")
|
|
343
|
+
.option("--hours <hours>", "Look back this many hours when listing sessions (default 24).", parseFloatOption, 24)
|
|
344
|
+
.option("--limit <count>", "Maximum sessions to show when listing (max 1000).", parseIntOption, 100)
|
|
345
|
+
.option("--all", "Include all stored sessions regardless of age.", false)
|
|
346
|
+
.option("--clear", "Delete stored sessions older than the provided window (24h default).", false)
|
|
347
|
+
.option("--hide-prompt", "Hide stored prompt when displaying a session.", false)
|
|
348
|
+
.option("--render", "Render completed session output as markdown (rich TTY only).", false)
|
|
349
|
+
.option("--render-markdown", "Alias for --render.", false)
|
|
350
|
+
.option("--model <name>", "Filter sessions/output for a specific model.", "")
|
|
351
|
+
.option("--path", "Print the stored session paths instead of attaching.", false)
|
|
352
|
+
.addOption(new Option("--clean", "Deprecated alias for --clear.").default(false).hideHelp())
|
|
340
353
|
.action(async (sessionId, _options, cmd) => {
|
|
341
354
|
await handleSessionCommand(sessionId, cmd);
|
|
342
355
|
});
|
|
343
|
-
|
|
344
|
-
.command(
|
|
345
|
-
.description(
|
|
346
|
-
.option(
|
|
347
|
-
.option(
|
|
348
|
-
.option(
|
|
349
|
-
.option(
|
|
350
|
-
.option(
|
|
351
|
-
.option(
|
|
352
|
-
.option(
|
|
353
|
-
.option(
|
|
354
|
-
.addOption(new Option(
|
|
356
|
+
program
|
|
357
|
+
.command("status [id]")
|
|
358
|
+
.description("List recent sessions (24h window by default) or attach to a session when an ID is provided.")
|
|
359
|
+
.option("--hours <hours>", "Look back this many hours (default 24).", parseFloatOption, 24)
|
|
360
|
+
.option("--limit <count>", "Maximum sessions to show (max 1000).", parseIntOption, 100)
|
|
361
|
+
.option("--all", "Include all stored sessions regardless of age.", false)
|
|
362
|
+
.option("--clear", "Delete stored sessions older than the provided window (24h default).", false)
|
|
363
|
+
.option("--render", "Render completed session output as markdown (rich TTY only).", false)
|
|
364
|
+
.option("--render-markdown", "Alias for --render.", false)
|
|
365
|
+
.option("--model <name>", "Filter sessions/output for a specific model.", "")
|
|
366
|
+
.option("--hide-prompt", "Hide stored prompt when displaying a session.", false)
|
|
367
|
+
.addOption(new Option("--clean", "Deprecated alias for --clear.").default(false).hideHelp())
|
|
355
368
|
.action(async (sessionId, _options, command) => {
|
|
356
369
|
const statusOptions = command.opts();
|
|
357
370
|
const clearRequested = Boolean(statusOptions.clear || statusOptions.clean);
|
|
358
371
|
if (clearRequested) {
|
|
359
372
|
if (sessionId) {
|
|
360
|
-
console.error(
|
|
373
|
+
console.error("Cannot combine a session ID with --clear. Remove the ID to delete cached sessions.");
|
|
361
374
|
process.exitCode = 1;
|
|
362
375
|
return;
|
|
363
376
|
}
|
|
364
377
|
const hours = statusOptions.hours;
|
|
365
378
|
const includeAll = statusOptions.all;
|
|
366
379
|
const result = await sessionStore.deleteOlderThan({ hours, includeAll });
|
|
367
|
-
const scope = includeAll ?
|
|
380
|
+
const scope = includeAll ? "all stored sessions" : `sessions older than ${hours}h`;
|
|
368
381
|
console.log(formatSessionCleanupMessage(result, scope));
|
|
369
382
|
return;
|
|
370
383
|
}
|
|
371
|
-
if (sessionId ===
|
|
384
|
+
if (sessionId === "clear" || sessionId === "clean") {
|
|
372
385
|
console.error('Session cleanup now uses --clear. Run "oracle status --clear --hours <n>" instead.');
|
|
373
386
|
process.exitCode = 1;
|
|
374
387
|
return;
|
|
375
388
|
}
|
|
376
389
|
if (sessionId) {
|
|
377
|
-
const autoRender = !command.getOptionValueSource?.(
|
|
390
|
+
const autoRender = !command.getOptionValueSource?.("render") &&
|
|
391
|
+
!command.getOptionValueSource?.("renderMarkdown")
|
|
378
392
|
? process.stdout.isTTY
|
|
379
393
|
: false;
|
|
380
394
|
const renderMarkdown = Boolean(statusOptions.render || statusOptions.renderMarkdown || autoRender);
|
|
@@ -390,19 +404,19 @@ const statusCommand = program
|
|
|
390
404
|
});
|
|
391
405
|
});
|
|
392
406
|
program
|
|
393
|
-
.command(
|
|
394
|
-
.description(
|
|
395
|
-
.addOption(new Option(
|
|
396
|
-
.addOption(new Option(
|
|
397
|
-
.option(
|
|
398
|
-
.option(
|
|
407
|
+
.command("restart <id>")
|
|
408
|
+
.description("Re-run a stored session as a new session (clones options).")
|
|
409
|
+
.addOption(new Option("--wait").default(undefined))
|
|
410
|
+
.addOption(new Option("--no-wait").default(undefined).hideHelp())
|
|
411
|
+
.option("--remote-host <host:port>", "Delegate browser runs to a remote `oracle serve` instance.")
|
|
412
|
+
.option("--remote-token <token>", "Access token for the remote `oracle serve` instance.")
|
|
399
413
|
.action(async (sessionId, _options, cmd) => {
|
|
400
414
|
const restartOptions = cmd.opts();
|
|
401
415
|
await restartSession(sessionId, restartOptions);
|
|
402
416
|
});
|
|
403
417
|
function buildRunOptions(options, overrides = {}) {
|
|
404
418
|
if (!options.prompt) {
|
|
405
|
-
throw new Error(
|
|
419
|
+
throw new Error("Prompt is required.");
|
|
406
420
|
}
|
|
407
421
|
const normalizedBaseUrl = normalizeBaseUrl(overrides.baseUrl ?? options.baseUrl);
|
|
408
422
|
const azure = options.azureEndpoint || overrides.azure?.endpoint
|
|
@@ -439,7 +453,9 @@ function buildRunOptions(options, overrides = {}) {
|
|
|
439
453
|
sessionId: overrides.sessionId ?? options.sessionId,
|
|
440
454
|
verbose: overrides.verbose ?? options.verbose,
|
|
441
455
|
heartbeatIntervalMs: overrides.heartbeatIntervalMs ?? resolveHeartbeatIntervalMs(options.heartbeat),
|
|
442
|
-
browserAttachments: overrides.browserAttachments ??
|
|
456
|
+
browserAttachments: overrides.browserAttachments ??
|
|
457
|
+
options.browserAttachments ??
|
|
458
|
+
"auto",
|
|
443
459
|
browserInlineFiles: overrides.browserInlineFiles ?? options.browserInlineFiles ?? false,
|
|
444
460
|
browserBundleFiles: overrides.browserBundleFiles ?? options.browserBundleFiles ?? false,
|
|
445
461
|
background: overrides.background ?? undefined,
|
|
@@ -448,26 +464,26 @@ function buildRunOptions(options, overrides = {}) {
|
|
|
448
464
|
};
|
|
449
465
|
}
|
|
450
466
|
export function enforceBrowserSearchFlag(runOptions, sessionMode, logFn = console.log) {
|
|
451
|
-
if (sessionMode ===
|
|
452
|
-
logFn(chalk.dim(
|
|
467
|
+
if (sessionMode === "browser" && runOptions.search === false) {
|
|
468
|
+
logFn(chalk.dim("Note: search is not available in browser engine; ignoring search=false."));
|
|
453
469
|
runOptions.search = undefined;
|
|
454
470
|
}
|
|
455
471
|
}
|
|
456
472
|
function resolveHeartbeatIntervalMs(seconds) {
|
|
457
|
-
if (typeof seconds !==
|
|
473
|
+
if (typeof seconds !== "number" || seconds <= 0) {
|
|
458
474
|
return undefined;
|
|
459
475
|
}
|
|
460
476
|
return Math.round(seconds * 1000);
|
|
461
477
|
}
|
|
462
478
|
function assertFollowupSupported({ engine, model, baseUrl, azureEndpoint, }) {
|
|
463
|
-
if (engine !==
|
|
464
|
-
throw new Error(
|
|
479
|
+
if (engine !== "api") {
|
|
480
|
+
throw new Error("--followup requires --engine api.");
|
|
465
481
|
}
|
|
466
|
-
if (model.startsWith(
|
|
482
|
+
if (model.startsWith("gemini") || model.startsWith("claude")) {
|
|
467
483
|
throw new Error(`--followup is only supported for OpenAI Responses API runs. Model ${model} uses a provider client without previous_response_id support.`);
|
|
468
484
|
}
|
|
469
485
|
if (baseUrl && !azureEndpoint) {
|
|
470
|
-
throw new Error(
|
|
486
|
+
throw new Error("--followup is only supported for the default OpenAI Responses API or Azure OpenAI Responses. Custom --base-url providers are not supported.");
|
|
471
487
|
}
|
|
472
488
|
}
|
|
473
489
|
function levenshteinDistance(a, b) {
|
|
@@ -477,8 +493,8 @@ function levenshteinDistance(a, b) {
|
|
|
477
493
|
return b.length;
|
|
478
494
|
if (b.length === 0)
|
|
479
495
|
return a.length;
|
|
480
|
-
const previous =
|
|
481
|
-
const current =
|
|
496
|
+
const previous = Array.from({ length: b.length + 1 });
|
|
497
|
+
const current = Array.from({ length: b.length + 1 });
|
|
482
498
|
for (let j = 0; j <= b.length; j += 1) {
|
|
483
499
|
previous[j] = j;
|
|
484
500
|
}
|
|
@@ -515,7 +531,7 @@ async function suggestFollowupSessionIds(input, limit = 3) {
|
|
|
515
531
|
const seen = new Set();
|
|
516
532
|
const ranked = sessions
|
|
517
533
|
.map((meta) => meta.id)
|
|
518
|
-
.filter((id) => typeof id ===
|
|
534
|
+
.filter((id) => typeof id === "string" && id.length > 0)
|
|
519
535
|
.filter((id) => {
|
|
520
536
|
if (seen.has(id))
|
|
521
537
|
return false;
|
|
@@ -531,16 +547,18 @@ async function suggestFollowupSessionIds(input, limit = 3) {
|
|
|
531
547
|
async function resolveFollowupReference(value, followupModel) {
|
|
532
548
|
const trimmed = value.trim();
|
|
533
549
|
if (trimmed.length === 0) {
|
|
534
|
-
throw new Error(
|
|
550
|
+
throw new Error("--followup requires a session id or response id.");
|
|
535
551
|
}
|
|
536
|
-
if (trimmed.startsWith(
|
|
552
|
+
if (trimmed.startsWith("resp_")) {
|
|
537
553
|
return { responseId: trimmed };
|
|
538
554
|
}
|
|
539
555
|
// Treat as oracle session id (slug).
|
|
540
556
|
const meta = await sessionStore.readSession(trimmed);
|
|
541
557
|
if (!meta) {
|
|
542
558
|
const suggestions = await suggestFollowupSessionIds(trimmed);
|
|
543
|
-
const suggestionText = suggestions.length > 0
|
|
559
|
+
const suggestionText = suggestions.length > 0
|
|
560
|
+
? ` Did you mean: ${suggestions.map((id) => `"${id}"`).join(", ")}?`
|
|
561
|
+
: "";
|
|
544
562
|
throw new Error(`No session found with ID ${trimmed}.${suggestionText} Run "oracle status --hours 72 --limit 20" to list recent sessions.`);
|
|
545
563
|
}
|
|
546
564
|
const fromMetadata = extractResponseIdFromSession(meta, followupModel);
|
|
@@ -548,7 +566,7 @@ async function resolveFollowupReference(value, followupModel) {
|
|
|
548
566
|
return { responseId: fromMetadata, sessionId: meta.id };
|
|
549
567
|
}
|
|
550
568
|
// Fallback: scrape the log for a response id (covers older sessions / edge cases).
|
|
551
|
-
const logText = await sessionStore.readLog(trimmed).catch(() =>
|
|
569
|
+
const logText = await sessionStore.readLog(trimmed).catch(() => "");
|
|
552
570
|
const matches = logText.match(/resp_[A-Za-z0-9]+/g) ?? [];
|
|
553
571
|
const last = matches.length > 0 ? matches[matches.length - 1] : null;
|
|
554
572
|
if (last) {
|
|
@@ -560,7 +578,7 @@ function extractResponseIdFromSession(meta, followupModel) {
|
|
|
560
578
|
// Single-model sessions store response metadata at the session root.
|
|
561
579
|
const rootResponse = meta.response ?? null;
|
|
562
580
|
const rootResponseId = rootResponse?.responseId ?? rootResponse?.id;
|
|
563
|
-
if (rootResponseId && rootResponseId.startsWith(
|
|
581
|
+
if (rootResponseId && rootResponseId.startsWith("resp_")) {
|
|
564
582
|
return rootResponseId;
|
|
565
583
|
}
|
|
566
584
|
const runs = Array.isArray(meta.models) ? meta.models : [];
|
|
@@ -575,19 +593,19 @@ function extractResponseIdFromSession(meta, followupModel) {
|
|
|
575
593
|
};
|
|
576
594
|
const chosen = pickRun();
|
|
577
595
|
if (!chosen) {
|
|
578
|
-
const models = runs.map((r) => r.model).join(
|
|
596
|
+
const models = runs.map((r) => r.model).join(", ");
|
|
579
597
|
throw new Error(followupModel
|
|
580
598
|
? `Session ${meta.id} has no model named ${followupModel}. Available: ${models}`
|
|
581
599
|
: `Session ${meta.id} has multiple model runs. Re-run with --followup-model. Available: ${models}`);
|
|
582
600
|
}
|
|
583
601
|
const runResponse = chosen.response ?? null;
|
|
584
602
|
const runResponseId = runResponse?.responseId ?? runResponse?.id;
|
|
585
|
-
return runResponseId && runResponseId.startsWith(
|
|
603
|
+
return runResponseId && runResponseId.startsWith("resp_") ? runResponseId : null;
|
|
586
604
|
}
|
|
587
605
|
function buildRunOptionsFromMetadata(metadata) {
|
|
588
606
|
const stored = metadata.options ?? {};
|
|
589
607
|
return {
|
|
590
|
-
prompt: stored.prompt ??
|
|
608
|
+
prompt: stored.prompt ?? "",
|
|
591
609
|
model: stored.model ?? DEFAULT_MODEL,
|
|
592
610
|
models: stored.models,
|
|
593
611
|
previousResponseId: stored.previousResponseId,
|
|
@@ -622,36 +640,36 @@ function buildRunOptionsFromMetadata(metadata) {
|
|
|
622
640
|
};
|
|
623
641
|
}
|
|
624
642
|
function getSessionMode(metadata) {
|
|
625
|
-
return metadata.mode ?? metadata.options?.mode ??
|
|
643
|
+
return metadata.mode ?? metadata.options?.mode ?? "api";
|
|
626
644
|
}
|
|
627
645
|
function getBrowserConfigFromMetadata(metadata) {
|
|
628
646
|
return metadata.options?.browserConfig ?? metadata.browser?.config;
|
|
629
647
|
}
|
|
630
648
|
async function runRootCommand(options) {
|
|
631
|
-
if (process.env.ORACLE_FORCE_TUI ===
|
|
649
|
+
if (process.env.ORACLE_FORCE_TUI === "1") {
|
|
632
650
|
await sessionStore.ensureStorage();
|
|
633
651
|
await launchTui({ version: VERSION, printIntro: false });
|
|
634
652
|
return;
|
|
635
653
|
}
|
|
636
654
|
const userConfig = (await loadUserConfig()).config;
|
|
637
|
-
const helpRequested = rawCliArgs.some((arg) => arg ===
|
|
655
|
+
const helpRequested = rawCliArgs.some((arg) => arg === "--help" || arg === "-h");
|
|
638
656
|
const multiModelProvided = Array.isArray(options.models) && options.models.length > 0;
|
|
639
657
|
if (multiModelProvided) {
|
|
640
|
-
const modelFromConfigOrCli = normalizeModelOption(options.model ?? userConfig.model ??
|
|
658
|
+
const modelFromConfigOrCli = normalizeModelOption(options.model ?? userConfig.model ?? "");
|
|
641
659
|
if (modelFromConfigOrCli) {
|
|
642
|
-
throw new Error(
|
|
660
|
+
throw new Error("--models cannot be combined with --model.");
|
|
643
661
|
}
|
|
644
662
|
}
|
|
645
663
|
const optionUsesDefault = (name) => {
|
|
646
664
|
// Commander reports undefined for untouched options, so treat undefined/default the same
|
|
647
665
|
const source = program.getOptionValueSource?.(name);
|
|
648
|
-
return source == null || source ===
|
|
666
|
+
return source == null || source === "default";
|
|
649
667
|
};
|
|
650
668
|
if (helpRequested) {
|
|
651
669
|
if (options.verbose) {
|
|
652
|
-
console.log(
|
|
670
|
+
console.log("");
|
|
653
671
|
printDebugHelp(program.name());
|
|
654
|
-
console.log(
|
|
672
|
+
console.log("");
|
|
655
673
|
}
|
|
656
674
|
program.help({ error: false });
|
|
657
675
|
return;
|
|
@@ -661,8 +679,8 @@ async function runRootCommand(options) {
|
|
|
661
679
|
if (mergedFileInputs.length > 0) {
|
|
662
680
|
const { deduped, duplicates } = dedupePathInputs(mergedFileInputs, { cwd: process.cwd() });
|
|
663
681
|
if (duplicates.length > 0) {
|
|
664
|
-
const preview = duplicates.slice(0, 8).join(
|
|
665
|
-
const suffix = duplicates.length > 8 ? ` (+${duplicates.length - 8} more)` :
|
|
682
|
+
const preview = duplicates.slice(0, 8).join(", ");
|
|
683
|
+
const suffix = duplicates.length > 8 ? ` (+${duplicates.length - 8} more)` : "";
|
|
666
684
|
console.log(chalk.dim(`Ignoring duplicate --file inputs: ${preview}${suffix}`));
|
|
667
685
|
}
|
|
668
686
|
options.file = deduped;
|
|
@@ -671,11 +689,11 @@ async function runRootCommand(options) {
|
|
|
671
689
|
const renderMarkdown = resolveRenderFlag(options.render, options.renderMarkdown);
|
|
672
690
|
const renderPlain = resolveRenderPlain(options.renderPlain, options.render, options.renderMarkdown);
|
|
673
691
|
const applyRetentionOption = () => {
|
|
674
|
-
if (optionUsesDefault(
|
|
692
|
+
if (optionUsesDefault("retainHours") && typeof userConfig.sessionRetentionHours === "number") {
|
|
675
693
|
options.retainHours = userConfig.sessionRetentionHours;
|
|
676
694
|
}
|
|
677
695
|
const envRetention = process.env.ORACLE_RETAIN_HOURS;
|
|
678
|
-
if (optionUsesDefault(
|
|
696
|
+
if (optionUsesDefault("retainHours") && envRetention) {
|
|
679
697
|
const parsed = Number.parseFloat(envRetention);
|
|
680
698
|
if (!Number.isNaN(parsed)) {
|
|
681
699
|
options.retainHours = parsed;
|
|
@@ -695,11 +713,11 @@ async function runRootCommand(options) {
|
|
|
695
713
|
console.log(chalk.dim(`Remote browser host detected: ${remoteHost}`));
|
|
696
714
|
}
|
|
697
715
|
if (userCliArgs.length === 0) {
|
|
698
|
-
console.log(chalk.yellow(
|
|
716
|
+
console.log(chalk.yellow("No prompt or subcommand supplied. Run `oracle --help` or `oracle tui` for the TUI."));
|
|
699
717
|
program.outputHelp();
|
|
700
718
|
return;
|
|
701
719
|
}
|
|
702
|
-
const retentionHours = typeof options.retainHours ===
|
|
720
|
+
const retentionHours = typeof options.retainHours === "number" ? options.retainHours : undefined;
|
|
703
721
|
await sessionStore.ensureStorage();
|
|
704
722
|
await pruneOldSessions(retentionHours, (message) => console.log(chalk.dim(message)));
|
|
705
723
|
if (options.debugHelp) {
|
|
@@ -707,35 +725,39 @@ async function runRootCommand(options) {
|
|
|
707
725
|
return;
|
|
708
726
|
}
|
|
709
727
|
if (options.dryRun && options.renderMarkdown) {
|
|
710
|
-
throw new Error(
|
|
728
|
+
throw new Error("--dry-run cannot be combined with --render-markdown.");
|
|
711
729
|
}
|
|
712
730
|
const preferredEngine = options.engine ?? userConfig.engine;
|
|
713
|
-
let engine = resolveEngine({
|
|
731
|
+
let engine = resolveEngine({
|
|
732
|
+
engine: preferredEngine,
|
|
733
|
+
browserFlag: options.browser,
|
|
734
|
+
env: process.env,
|
|
735
|
+
});
|
|
714
736
|
if (options.browser) {
|
|
715
|
-
console.log(chalk.yellow(
|
|
737
|
+
console.log(chalk.yellow("`--browser` is deprecated; use `--engine browser` instead."));
|
|
716
738
|
}
|
|
717
|
-
if (optionUsesDefault(
|
|
739
|
+
if (optionUsesDefault("model") && userConfig.model) {
|
|
718
740
|
options.model = userConfig.model;
|
|
719
741
|
}
|
|
720
|
-
if (optionUsesDefault(
|
|
721
|
-
options.search = userConfig.search ===
|
|
742
|
+
if (optionUsesDefault("search") && userConfig.search) {
|
|
743
|
+
options.search = userConfig.search === "on";
|
|
722
744
|
}
|
|
723
|
-
if (optionUsesDefault(
|
|
745
|
+
if (optionUsesDefault("filesReport") && userConfig.filesReport != null) {
|
|
724
746
|
options.filesReport = Boolean(userConfig.filesReport);
|
|
725
747
|
}
|
|
726
|
-
if (optionUsesDefault(
|
|
748
|
+
if (optionUsesDefault("heartbeat") && typeof userConfig.heartbeatSeconds === "number") {
|
|
727
749
|
options.heartbeat = userConfig.heartbeatSeconds;
|
|
728
750
|
}
|
|
729
|
-
if (optionUsesDefault(
|
|
751
|
+
if (optionUsesDefault("baseUrl") && userConfig.apiBaseUrl) {
|
|
730
752
|
options.baseUrl = userConfig.apiBaseUrl;
|
|
731
753
|
}
|
|
732
|
-
if (remoteHost && engine !==
|
|
733
|
-
throw new Error(
|
|
754
|
+
if (remoteHost && engine !== "browser") {
|
|
755
|
+
throw new Error("--remote-host requires --engine browser.");
|
|
734
756
|
}
|
|
735
757
|
if (remoteHost && options.remoteChrome) {
|
|
736
|
-
throw new Error(
|
|
758
|
+
throw new Error("--remote-host cannot be combined with --remote-chrome.");
|
|
737
759
|
}
|
|
738
|
-
if (optionUsesDefault(
|
|
760
|
+
if (optionUsesDefault("azureEndpoint")) {
|
|
739
761
|
if (process.env.AZURE_OPENAI_ENDPOINT) {
|
|
740
762
|
options.azureEndpoint = process.env.AZURE_OPENAI_ENDPOINT;
|
|
741
763
|
}
|
|
@@ -743,7 +765,7 @@ async function runRootCommand(options) {
|
|
|
743
765
|
options.azureEndpoint = userConfig.azure.endpoint;
|
|
744
766
|
}
|
|
745
767
|
}
|
|
746
|
-
if (optionUsesDefault(
|
|
768
|
+
if (optionUsesDefault("azureDeployment")) {
|
|
747
769
|
if (process.env.AZURE_OPENAI_DEPLOYMENT) {
|
|
748
770
|
options.azureDeployment = process.env.AZURE_OPENAI_DEPLOYMENT;
|
|
749
771
|
}
|
|
@@ -751,7 +773,7 @@ async function runRootCommand(options) {
|
|
|
751
773
|
options.azureDeployment = userConfig.azure.deployment;
|
|
752
774
|
}
|
|
753
775
|
}
|
|
754
|
-
if (optionUsesDefault(
|
|
776
|
+
if (optionUsesDefault("azureApiVersion")) {
|
|
755
777
|
if (process.env.AZURE_OPENAI_API_VERSION) {
|
|
756
778
|
options.azureApiVersion = process.env.AZURE_OPENAI_API_VERSION;
|
|
757
779
|
}
|
|
@@ -762,52 +784,52 @@ async function runRootCommand(options) {
|
|
|
762
784
|
const normalizedMultiModels = multiModelProvided
|
|
763
785
|
? Array.from(new Set(options.models.map((entry) => resolveApiModel(entry))))
|
|
764
786
|
: [];
|
|
765
|
-
const cliModelArg = normalizeModelOption(options.model) || (multiModelProvided ?
|
|
787
|
+
const cliModelArg = normalizeModelOption(options.model) || (multiModelProvided ? "" : DEFAULT_MODEL);
|
|
766
788
|
const resolvedModelCandidate = multiModelProvided
|
|
767
789
|
? normalizedMultiModels[0]
|
|
768
|
-
: engine ===
|
|
790
|
+
: engine === "browser"
|
|
769
791
|
? inferModelFromLabel(cliModelArg || DEFAULT_MODEL)
|
|
770
792
|
: resolveApiModel(cliModelArg || DEFAULT_MODEL);
|
|
771
793
|
const primaryModelCandidate = normalizedMultiModels[0] ?? resolvedModelCandidate;
|
|
772
|
-
const isGemini = primaryModelCandidate.startsWith(
|
|
773
|
-
const isCodex = primaryModelCandidate.startsWith(
|
|
774
|
-
const isClaude = primaryModelCandidate.startsWith(
|
|
775
|
-
const userForcedBrowser = options.browser || options.engine ===
|
|
776
|
-
const isBrowserCompatible = (model) => model.startsWith(
|
|
777
|
-
const hasNonBrowserCompatibleTarget = (engine ===
|
|
794
|
+
const isGemini = primaryModelCandidate.startsWith("gemini");
|
|
795
|
+
const isCodex = primaryModelCandidate.startsWith("gpt-5.1-codex");
|
|
796
|
+
const isClaude = primaryModelCandidate.startsWith("claude");
|
|
797
|
+
const userForcedBrowser = options.browser || options.engine === "browser";
|
|
798
|
+
const isBrowserCompatible = (model) => model.startsWith("gpt-") || model.startsWith("gemini");
|
|
799
|
+
const hasNonBrowserCompatibleTarget = (engine === "browser" || userForcedBrowser) &&
|
|
778
800
|
(normalizedMultiModels.length > 0
|
|
779
801
|
? normalizedMultiModels.some((model) => !isBrowserCompatible(model))
|
|
780
802
|
: !isBrowserCompatible(resolvedModelCandidate));
|
|
781
803
|
if (hasNonBrowserCompatibleTarget) {
|
|
782
|
-
throw new Error(
|
|
804
|
+
throw new Error("Browser engine only supports GPT and Gemini models. Re-run with --engine api for Grok, Claude, or other models.");
|
|
783
805
|
}
|
|
784
|
-
if (isClaude && engine ===
|
|
785
|
-
console.log(chalk.dim(
|
|
786
|
-
engine =
|
|
806
|
+
if (isClaude && engine === "browser") {
|
|
807
|
+
console.log(chalk.dim("Browser engine is not supported for Claude models; switching to API."));
|
|
808
|
+
engine = "api";
|
|
787
809
|
}
|
|
788
|
-
if (isCodex && engine ===
|
|
789
|
-
console.log(chalk.dim(
|
|
790
|
-
engine =
|
|
810
|
+
if (isCodex && engine === "browser") {
|
|
811
|
+
console.log(chalk.dim("Browser engine is not supported for gpt-5.1-codex; switching to API."));
|
|
812
|
+
engine = "api";
|
|
791
813
|
}
|
|
792
814
|
if (normalizedMultiModels.length > 0) {
|
|
793
|
-
engine =
|
|
815
|
+
engine = "api";
|
|
794
816
|
}
|
|
795
817
|
if (remoteHost && normalizedMultiModels.length > 0) {
|
|
796
|
-
throw new Error(
|
|
818
|
+
throw new Error("--remote-host does not support --models yet. Use API engine locally instead.");
|
|
797
819
|
}
|
|
798
820
|
const resolvedModel = normalizedMultiModels[0] ?? (isGemini ? resolveApiModel(cliModelArg) : resolvedModelCandidate);
|
|
799
|
-
const includesGeminiApiOnly = (normalizedMultiModels.length > 0 ? normalizedMultiModels : [resolvedModel]).some((model) => model ===
|
|
800
|
-
if ((userForcedBrowser || userConfig.engine ===
|
|
801
|
-
throw new Error(
|
|
821
|
+
const includesGeminiApiOnly = (normalizedMultiModels.length > 0 ? normalizedMultiModels : [resolvedModel]).some((model) => model === "gemini-3.1-pro");
|
|
822
|
+
if ((userForcedBrowser || userConfig.engine === "browser") && includesGeminiApiOnly) {
|
|
823
|
+
throw new Error("gemini-3.1-pro is API-only today. Use --engine api or switch to gemini-3-pro for Gemini web.");
|
|
802
824
|
}
|
|
803
|
-
if (engine ===
|
|
804
|
-
console.log(chalk.dim(
|
|
805
|
-
engine =
|
|
825
|
+
if (engine === "browser" && includesGeminiApiOnly) {
|
|
826
|
+
console.log(chalk.dim("gemini-3.1-pro is API-only today; switching to API."));
|
|
827
|
+
engine = "api";
|
|
806
828
|
}
|
|
807
|
-
const effectiveModelId = resolvedModel.startsWith(
|
|
829
|
+
const effectiveModelId = resolvedModel.startsWith("gemini")
|
|
808
830
|
? resolveGeminiModelId(resolvedModel)
|
|
809
831
|
: isKnownModel(resolvedModel)
|
|
810
|
-
? MODEL_CONFIGS[resolvedModel].apiModel ?? resolvedModel
|
|
832
|
+
? (MODEL_CONFIGS[resolvedModel].apiModel ?? resolvedModel)
|
|
811
833
|
: resolvedModel;
|
|
812
834
|
const resolvedBaseUrl = normalizeBaseUrl(options.baseUrl ?? (isClaude ? process.env.ANTHROPIC_BASE_URL : process.env.OPENAI_BASE_URL));
|
|
813
835
|
const { models: _rawModels, ...optionsWithoutModels } = options;
|
|
@@ -828,7 +850,7 @@ async function runRootCommand(options) {
|
|
|
828
850
|
engine,
|
|
829
851
|
});
|
|
830
852
|
if (remoteHost && waitPreference === false) {
|
|
831
|
-
console.log(chalk.dim(
|
|
853
|
+
console.log(chalk.dim("Remote browser runs require --wait; ignoring --no-wait."));
|
|
832
854
|
waitPreference = true;
|
|
833
855
|
}
|
|
834
856
|
if (await handleStatusFlag(options, { attachSession, showStatus })) {
|
|
@@ -843,10 +865,12 @@ async function runRootCommand(options) {
|
|
|
843
865
|
}
|
|
844
866
|
if (renderMarkdown || copyMarkdown) {
|
|
845
867
|
if (!options.prompt) {
|
|
846
|
-
throw new Error(
|
|
868
|
+
throw new Error("Prompt is required when using --render-markdown or --copy-markdown.");
|
|
847
869
|
}
|
|
848
870
|
const bundle = await buildMarkdownBundle({ prompt: options.prompt, file: options.file, system: options.system }, { cwd: process.cwd() });
|
|
849
|
-
const modelConfig = isKnownModel(resolvedModel)
|
|
871
|
+
const modelConfig = isKnownModel(resolvedModel)
|
|
872
|
+
? MODEL_CONFIGS[resolvedModel]
|
|
873
|
+
: MODEL_CONFIGS["gpt-5.1"];
|
|
850
874
|
const requestBody = buildRequestBody({
|
|
851
875
|
modelConfig,
|
|
852
876
|
systemPrompt: bundle.systemPrompt,
|
|
@@ -863,17 +887,19 @@ async function runRootCommand(options) {
|
|
|
863
887
|
? bundle.markdown
|
|
864
888
|
: await formatRenderedMarkdown(bundle.markdown, { richTty: isTty });
|
|
865
889
|
// Trim trailing newlines from the rendered bundle so we print exactly one blank before the summary line.
|
|
866
|
-
console.log(output.replace(/\n+$/u,
|
|
890
|
+
console.log(output.replace(/\n+$/u, ""));
|
|
867
891
|
}
|
|
868
892
|
if (copyMarkdown) {
|
|
869
893
|
const result = await copyToClipboard(bundle.markdown);
|
|
870
894
|
if (result.success) {
|
|
871
|
-
const filesPart = bundle.files.length > 0 ? `; ${bundle.files.length} files` :
|
|
895
|
+
const filesPart = bundle.files.length > 0 ? `; ${bundle.files.length} files` : "";
|
|
872
896
|
const summary = `Copied markdown to clipboard (~${formatCompactNumber(estimatedTokens)} tokens${filesPart}).`;
|
|
873
897
|
console.log(chalk.green(summary));
|
|
874
898
|
}
|
|
875
899
|
else {
|
|
876
|
-
const reason = result.error instanceof Error
|
|
900
|
+
const reason = result.error instanceof Error
|
|
901
|
+
? result.error.message
|
|
902
|
+
: String(result.error ?? "unknown error");
|
|
877
903
|
console.log(chalk.dim(`Copy failed (${reason}); markdown not printed. Re-run with --render-markdown if you need the content.`));
|
|
878
904
|
}
|
|
879
905
|
}
|
|
@@ -881,7 +907,7 @@ async function runRootCommand(options) {
|
|
|
881
907
|
}
|
|
882
908
|
if (previewMode) {
|
|
883
909
|
if (!options.prompt) {
|
|
884
|
-
throw new Error(
|
|
910
|
+
throw new Error("Prompt is required when using --dry-run/preview.");
|
|
885
911
|
}
|
|
886
912
|
if (userConfig.promptSuffix) {
|
|
887
913
|
options.prompt = `${options.prompt.trim()}\n${userConfig.promptSuffix}`;
|
|
@@ -895,15 +921,19 @@ async function runRootCommand(options) {
|
|
|
895
921
|
azureEndpoint: resolvedOptions.azure?.endpoint,
|
|
896
922
|
});
|
|
897
923
|
if (normalizedMultiModels.length > 0) {
|
|
898
|
-
throw new Error(
|
|
924
|
+
throw new Error("--followup cannot be combined with --models.");
|
|
899
925
|
}
|
|
900
926
|
const followup = await resolveFollowupReference(options.followup, options.followupModel);
|
|
901
927
|
resolvedOptions.previousResponseId = followup.responseId;
|
|
902
928
|
resolvedOptions.followupSessionId = followup.sessionId;
|
|
903
929
|
resolvedOptions.followupModel = options.followupModel;
|
|
904
930
|
}
|
|
905
|
-
const runOptions = buildRunOptions(resolvedOptions, {
|
|
906
|
-
|
|
931
|
+
const runOptions = buildRunOptions(resolvedOptions, {
|
|
932
|
+
preview: true,
|
|
933
|
+
previewMode,
|
|
934
|
+
baseUrl: resolvedBaseUrl,
|
|
935
|
+
});
|
|
936
|
+
if (engine === "browser") {
|
|
907
937
|
await runBrowserPreview({
|
|
908
938
|
runOptions,
|
|
909
939
|
cwd: process.cwd(),
|
|
@@ -914,7 +944,7 @@ async function runRootCommand(options) {
|
|
|
914
944
|
return;
|
|
915
945
|
}
|
|
916
946
|
// API dry-run/preview path
|
|
917
|
-
if (previewMode ===
|
|
947
|
+
if (previewMode === "summary") {
|
|
918
948
|
await runDryRunSummary({
|
|
919
949
|
engine,
|
|
920
950
|
runOptions,
|
|
@@ -934,7 +964,7 @@ async function runRootCommand(options) {
|
|
|
934
964
|
return;
|
|
935
965
|
}
|
|
936
966
|
if (!options.prompt) {
|
|
937
|
-
throw new Error(
|
|
967
|
+
throw new Error("Prompt is required when starting a new session.");
|
|
938
968
|
}
|
|
939
969
|
if (userConfig.promptSuffix) {
|
|
940
970
|
options.prompt = `${options.prompt.trim()}\n${userConfig.promptSuffix}`;
|
|
@@ -948,7 +978,7 @@ async function runRootCommand(options) {
|
|
|
948
978
|
azureEndpoint: resolvedOptions.azure?.endpoint,
|
|
949
979
|
});
|
|
950
980
|
if (normalizedMultiModels.length > 0) {
|
|
951
|
-
throw new Error(
|
|
981
|
+
throw new Error("--followup cannot be combined with --models.");
|
|
952
982
|
}
|
|
953
983
|
const followup = await resolveFollowupReference(options.followup, options.followupModel);
|
|
954
984
|
resolvedOptions.previousResponseId = followup.responseId;
|
|
@@ -966,8 +996,10 @@ async function runRootCommand(options) {
|
|
|
966
996
|
return;
|
|
967
997
|
}
|
|
968
998
|
if (options.file && options.file.length > 0) {
|
|
969
|
-
const isBrowserMode = engine ===
|
|
970
|
-
const filesToValidate = isBrowserMode
|
|
999
|
+
const isBrowserMode = engine === "browser" || userForcedBrowser;
|
|
1000
|
+
const filesToValidate = isBrowserMode
|
|
1001
|
+
? options.file.filter((f) => !isMediaFile(f))
|
|
1002
|
+
: options.file;
|
|
971
1003
|
if (filesToValidate.length > 0) {
|
|
972
1004
|
await readFiles(filesToValidate, {
|
|
973
1005
|
cwd: process.cwd(),
|
|
@@ -983,9 +1015,9 @@ async function runRootCommand(options) {
|
|
|
983
1015
|
env: process.env,
|
|
984
1016
|
config: userConfig.notify,
|
|
985
1017
|
});
|
|
986
|
-
const sessionMode = engine ===
|
|
987
|
-
const browserModelLabelOverride = sessionMode ===
|
|
988
|
-
const browserConfig = sessionMode ===
|
|
1018
|
+
const sessionMode = engine === "browser" ? "browser" : "api";
|
|
1019
|
+
const browserModelLabelOverride = sessionMode === "browser" ? resolveBrowserModelLabel(cliModelArg, resolvedModel) : undefined;
|
|
1020
|
+
const browserConfig = sessionMode === "browser"
|
|
989
1021
|
? await buildBrowserConfig({
|
|
990
1022
|
...options,
|
|
991
1023
|
model: resolvedModel,
|
|
@@ -999,7 +1031,7 @@ async function runRootCommand(options) {
|
|
|
999
1031
|
};
|
|
1000
1032
|
console.log(chalk.dim(`Routing browser automation to remote host ${remoteHost}`));
|
|
1001
1033
|
}
|
|
1002
|
-
else if (browserConfig && resolvedModel.startsWith(
|
|
1034
|
+
else if (browserConfig && resolvedModel.startsWith("gemini")) {
|
|
1003
1035
|
browserDeps = {
|
|
1004
1036
|
executeBrowser: createGeminiWebExecutor({
|
|
1005
1037
|
youtube: options.youtube,
|
|
@@ -1010,9 +1042,9 @@ async function runRootCommand(options) {
|
|
|
1010
1042
|
showThoughts: options.geminiShowThoughts,
|
|
1011
1043
|
}),
|
|
1012
1044
|
};
|
|
1013
|
-
console.log(chalk.dim(
|
|
1014
|
-
if (browserConfig.modelStrategy && browserConfig.modelStrategy !==
|
|
1015
|
-
console.log(chalk.dim(
|
|
1045
|
+
console.log(chalk.dim("Using Gemini web client for browser automation"));
|
|
1046
|
+
if (browserConfig.modelStrategy && browserConfig.modelStrategy !== "select") {
|
|
1047
|
+
console.log(chalk.dim("Browser model strategy is ignored for Gemini web runs."));
|
|
1016
1048
|
}
|
|
1017
1049
|
}
|
|
1018
1050
|
const remoteExecutionActive = Boolean(browserDeps);
|
|
@@ -1040,8 +1072,8 @@ async function runRootCommand(options) {
|
|
|
1040
1072
|
baseUrl: resolvedBaseUrl,
|
|
1041
1073
|
});
|
|
1042
1074
|
enforceBrowserSearchFlag(baseRunOptions, sessionMode, console.log);
|
|
1043
|
-
if (sessionMode ===
|
|
1044
|
-
console.log(chalk.dim(
|
|
1075
|
+
if (sessionMode === "browser" && baseRunOptions.search === false) {
|
|
1076
|
+
console.log(chalk.dim("Note: search is not available in browser engine; ignoring search=false."));
|
|
1045
1077
|
baseRunOptions.search = undefined;
|
|
1046
1078
|
}
|
|
1047
1079
|
const sessionMeta = await sessionStore.createSession({
|
|
@@ -1063,7 +1095,7 @@ async function runRootCommand(options) {
|
|
|
1063
1095
|
sessionId: sessionMeta.id,
|
|
1064
1096
|
effectiveModelId,
|
|
1065
1097
|
};
|
|
1066
|
-
const disableDetachEnv = process.env.ORACLE_NO_DETACH ===
|
|
1098
|
+
const disableDetachEnv = process.env.ORACLE_NO_DETACH === "1";
|
|
1067
1099
|
const detachAllowed = remoteExecutionActive
|
|
1068
1100
|
? false
|
|
1069
1101
|
: shouldDetachSession({
|
|
@@ -1081,12 +1113,12 @@ async function runRootCommand(options) {
|
|
|
1081
1113
|
});
|
|
1082
1114
|
if (!waitPreference) {
|
|
1083
1115
|
if (!detached) {
|
|
1084
|
-
console.log(chalk.red(
|
|
1116
|
+
console.log(chalk.red("Unable to start in background; use --wait to run inline."));
|
|
1085
1117
|
process.exitCode = 1;
|
|
1086
1118
|
return;
|
|
1087
1119
|
}
|
|
1088
1120
|
console.log(chalk.blue(`Session running in background. Reattach via: oracle session ${sessionMeta.id}`));
|
|
1089
|
-
console.log(chalk.dim(
|
|
1121
|
+
console.log(chalk.dim("Pro runs can take up to 60 minutes (usually 10-15). Add --wait to stay attached."));
|
|
1090
1122
|
return;
|
|
1091
1123
|
}
|
|
1092
1124
|
if (detached === false) {
|
|
@@ -1101,8 +1133,8 @@ async function runRootCommand(options) {
|
|
|
1101
1133
|
async function runInteractiveSession(sessionMeta, runOptions, mode, browserConfig, showReattachHint = true, notifications, userConfig, suppressSummary = false, browserDeps, cwd = process.cwd()) {
|
|
1102
1134
|
const { logLine, writeChunk, stream } = sessionStore.createLogWriter(sessionMeta.id);
|
|
1103
1135
|
let headerAugmented = false;
|
|
1104
|
-
const combinedLog = (message =
|
|
1105
|
-
if (!headerAugmented && message.startsWith(
|
|
1136
|
+
const combinedLog = (message = "") => {
|
|
1137
|
+
if (!headerAugmented && message.startsWith("oracle (")) {
|
|
1106
1138
|
headerAugmented = true;
|
|
1107
1139
|
if (showReattachHint) {
|
|
1108
1140
|
console.log(`${message}\n${chalk.blue(`Reattach via: oracle session ${sessionMeta.id}`)}`);
|
|
@@ -1131,21 +1163,19 @@ async function runInteractiveSession(sessionMeta, runOptions, mode, browserConfi
|
|
|
1131
1163
|
log: combinedLog,
|
|
1132
1164
|
write: combinedWrite,
|
|
1133
1165
|
version: VERSION,
|
|
1134
|
-
notifications: notifications ??
|
|
1166
|
+
notifications: notifications ??
|
|
1167
|
+
deriveNotificationSettingsFromMetadata(sessionMeta, process.env, userConfig?.notify),
|
|
1135
1168
|
browserDeps,
|
|
1136
1169
|
});
|
|
1137
1170
|
const latest = await sessionStore.readSession(sessionMeta.id);
|
|
1138
1171
|
if (!suppressSummary) {
|
|
1139
1172
|
const summary = latest ? formatCompletionSummary(latest, { includeSlug: true }) : null;
|
|
1140
1173
|
if (summary) {
|
|
1141
|
-
console.log(
|
|
1174
|
+
console.log("\n" + chalk.green.bold(summary));
|
|
1142
1175
|
logLine(summary); // plain text in log, colored on stdout
|
|
1143
1176
|
}
|
|
1144
1177
|
}
|
|
1145
1178
|
}
|
|
1146
|
-
catch (error) {
|
|
1147
|
-
throw error;
|
|
1148
|
-
}
|
|
1149
1179
|
finally {
|
|
1150
1180
|
stream.end();
|
|
1151
1181
|
}
|
|
@@ -1153,14 +1183,14 @@ async function runInteractiveSession(sessionMeta, runOptions, mode, browserConfi
|
|
|
1153
1183
|
async function launchDetachedSession(sessionId) {
|
|
1154
1184
|
return new Promise((resolve, reject) => {
|
|
1155
1185
|
try {
|
|
1156
|
-
const args = [
|
|
1186
|
+
const args = ["--", CLI_ENTRYPOINT, "--exec-session", sessionId];
|
|
1157
1187
|
const child = spawn(process.execPath, args, {
|
|
1158
1188
|
detached: true,
|
|
1159
|
-
stdio:
|
|
1189
|
+
stdio: "ignore",
|
|
1160
1190
|
env: process.env,
|
|
1161
1191
|
});
|
|
1162
|
-
child.once(
|
|
1163
|
-
child.once(
|
|
1192
|
+
child.once("error", reject);
|
|
1193
|
+
child.once("spawn", () => {
|
|
1164
1194
|
child.unref();
|
|
1165
1195
|
resolve(true);
|
|
1166
1196
|
});
|
|
@@ -1184,9 +1214,9 @@ async function restartSession(sessionId, options) {
|
|
|
1184
1214
|
return;
|
|
1185
1215
|
}
|
|
1186
1216
|
const sessionMode = getSessionMode(metadata);
|
|
1187
|
-
const engine = sessionMode ===
|
|
1217
|
+
const engine = sessionMode === "browser" ? "browser" : "api";
|
|
1188
1218
|
const browserConfig = getBrowserConfigFromMetadata(metadata);
|
|
1189
|
-
if (sessionMode ===
|
|
1219
|
+
if (sessionMode === "browser" && !browserConfig) {
|
|
1190
1220
|
console.error(chalk.red(`Session ${sessionId} is missing browser config; cannot restart.`));
|
|
1191
1221
|
process.exitCode = 1;
|
|
1192
1222
|
return;
|
|
@@ -1195,8 +1225,10 @@ async function restartSession(sessionId, options) {
|
|
|
1195
1225
|
const cwd = metadata.cwd ?? process.cwd();
|
|
1196
1226
|
const storedOptions = metadata.options ?? {};
|
|
1197
1227
|
if (runOptions.file && runOptions.file.length > 0) {
|
|
1198
|
-
const isBrowserMode = engine ===
|
|
1199
|
-
const filesToValidate = isBrowserMode
|
|
1228
|
+
const isBrowserMode = engine === "browser";
|
|
1229
|
+
const filesToValidate = isBrowserMode
|
|
1230
|
+
? runOptions.file.filter((f) => !isMediaFile(f))
|
|
1231
|
+
: runOptions.file;
|
|
1200
1232
|
if (filesToValidate.length > 0) {
|
|
1201
1233
|
await readFiles(filesToValidate, {
|
|
1202
1234
|
cwd,
|
|
@@ -1219,14 +1251,14 @@ async function restartSession(sessionId, options) {
|
|
|
1219
1251
|
});
|
|
1220
1252
|
const remoteHost = remoteConfig.host;
|
|
1221
1253
|
const remoteToken = remoteConfig.token;
|
|
1222
|
-
if (remoteHost && engine !==
|
|
1223
|
-
throw new Error(
|
|
1254
|
+
if (remoteHost && engine !== "browser") {
|
|
1255
|
+
throw new Error("--remote-host requires a browser session.");
|
|
1224
1256
|
}
|
|
1225
1257
|
if (remoteHost) {
|
|
1226
1258
|
console.log(chalk.dim(`Remote browser host detected: ${remoteHost}`));
|
|
1227
1259
|
}
|
|
1228
1260
|
if (remoteHost && waitPreference === false) {
|
|
1229
|
-
console.log(chalk.dim(
|
|
1261
|
+
console.log(chalk.dim("Remote browser runs require --wait; ignoring --no-wait."));
|
|
1230
1262
|
waitPreference = true;
|
|
1231
1263
|
}
|
|
1232
1264
|
let browserDeps;
|
|
@@ -1236,7 +1268,7 @@ async function restartSession(sessionId, options) {
|
|
|
1236
1268
|
};
|
|
1237
1269
|
console.log(chalk.dim(`Routing browser automation to remote host ${remoteHost}`));
|
|
1238
1270
|
}
|
|
1239
|
-
else if (browserConfig && runOptions.model.startsWith(
|
|
1271
|
+
else if (browserConfig && runOptions.model.startsWith("gemini")) {
|
|
1240
1272
|
browserDeps = {
|
|
1241
1273
|
executeBrowser: createGeminiWebExecutor({
|
|
1242
1274
|
youtube: storedOptions.youtube,
|
|
@@ -1247,9 +1279,9 @@ async function restartSession(sessionId, options) {
|
|
|
1247
1279
|
showThoughts: storedOptions.geminiShowThoughts,
|
|
1248
1280
|
}),
|
|
1249
1281
|
};
|
|
1250
|
-
console.log(chalk.dim(
|
|
1251
|
-
if (browserConfig.modelStrategy && browserConfig.modelStrategy !==
|
|
1252
|
-
console.log(chalk.dim(
|
|
1282
|
+
console.log(chalk.dim("Using Gemini web client for browser automation"));
|
|
1283
|
+
if (browserConfig.modelStrategy && browserConfig.modelStrategy !== "select") {
|
|
1284
|
+
console.log(chalk.dim("Browser model strategy is ignored for Gemini web runs."));
|
|
1253
1285
|
}
|
|
1254
1286
|
}
|
|
1255
1287
|
const remoteExecutionActive = Boolean(browserDeps);
|
|
@@ -1274,7 +1306,7 @@ async function restartSession(sessionId, options) {
|
|
|
1274
1306
|
sessionId: sessionMeta.id,
|
|
1275
1307
|
effectiveModelId: resolveEffectiveModelIdForRun(runOptions.model, runOptions.effectiveModelId),
|
|
1276
1308
|
};
|
|
1277
|
-
const disableDetachEnv = process.env.ORACLE_NO_DETACH ===
|
|
1309
|
+
const disableDetachEnv = process.env.ORACLE_NO_DETACH === "1";
|
|
1278
1310
|
const detachAllowed = remoteExecutionActive
|
|
1279
1311
|
? false
|
|
1280
1312
|
: shouldDetachSession({
|
|
@@ -1292,12 +1324,12 @@ async function restartSession(sessionId, options) {
|
|
|
1292
1324
|
});
|
|
1293
1325
|
if (!waitPreference) {
|
|
1294
1326
|
if (!detached) {
|
|
1295
|
-
console.log(chalk.red(
|
|
1327
|
+
console.log(chalk.red("Unable to start in background; use --wait to run inline."));
|
|
1296
1328
|
process.exitCode = 1;
|
|
1297
1329
|
return;
|
|
1298
1330
|
}
|
|
1299
1331
|
console.log(chalk.blue(`Session running in background. Reattach via: oracle session ${sessionMeta.id}`));
|
|
1300
|
-
console.log(chalk.dim(
|
|
1332
|
+
console.log(chalk.dim("Pro runs can take up to 60 minutes (usually 10-15). Add --wait to stay attached."));
|
|
1301
1333
|
return;
|
|
1302
1334
|
}
|
|
1303
1335
|
if (detached === false) {
|
|
@@ -1343,37 +1375,58 @@ async function executeSession(sessionId) {
|
|
|
1343
1375
|
}
|
|
1344
1376
|
}
|
|
1345
1377
|
function printDebugHelp(cliName) {
|
|
1346
|
-
console.log(chalk.bold(
|
|
1378
|
+
console.log(chalk.bold("Advanced Options"));
|
|
1347
1379
|
printDebugOptionGroup([
|
|
1348
|
-
[
|
|
1349
|
-
[
|
|
1350
|
-
[
|
|
1380
|
+
["--search <on|off>", "Enable or disable the server-side search tool (default on)."],
|
|
1381
|
+
["--max-input <tokens>", "Override the input token budget."],
|
|
1382
|
+
["--max-output <tokens>", "Override the max output tokens (model default otherwise)."],
|
|
1351
1383
|
]);
|
|
1352
|
-
console.log(
|
|
1353
|
-
console.log(chalk.bold(
|
|
1384
|
+
console.log("");
|
|
1385
|
+
console.log(chalk.bold("Browser Options"));
|
|
1354
1386
|
printDebugOptionGroup([
|
|
1355
|
-
[
|
|
1356
|
-
[
|
|
1357
|
-
[
|
|
1358
|
-
[
|
|
1359
|
-
[
|
|
1360
|
-
[
|
|
1361
|
-
[
|
|
1362
|
-
[
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
[
|
|
1367
|
-
[
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
[
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1387
|
+
["--chatgpt-url <url>", "Override the ChatGPT web URL (workspace/folder targets)."],
|
|
1388
|
+
["--browser-chrome-profile <name>", "Reuse cookies from a specific Chrome profile."],
|
|
1389
|
+
["--browser-chrome-path <path>", "Point to a custom Chrome/Chromium binary."],
|
|
1390
|
+
["--browser-cookie-path <path>", "Use a specific Chrome/Chromium cookie store file."],
|
|
1391
|
+
["--browser-url <url>", "Alias for --chatgpt-url."],
|
|
1392
|
+
["--browser-timeout <ms|s|m>", "Cap total wait time for the assistant response."],
|
|
1393
|
+
["--browser-input-timeout <ms|s|m>", "Cap how long we wait for the composer textarea."],
|
|
1394
|
+
[
|
|
1395
|
+
"--browser-recheck-delay <ms|s|m|h>",
|
|
1396
|
+
"After timeout, wait then revisit the conversation to retry capture.",
|
|
1397
|
+
],
|
|
1398
|
+
["--browser-recheck-timeout <ms|s|m|h>", "Time budget for the delayed recheck attempt."],
|
|
1399
|
+
[
|
|
1400
|
+
"--browser-reuse-wait <ms|s|m|h>",
|
|
1401
|
+
"Wait for a shared Chrome profile before launching (parallel runs).",
|
|
1402
|
+
],
|
|
1403
|
+
[
|
|
1404
|
+
"--browser-profile-lock-timeout <ms|s|m|h>",
|
|
1405
|
+
"Wait for the manual-login profile lock before sending.",
|
|
1406
|
+
],
|
|
1407
|
+
[
|
|
1408
|
+
"--browser-auto-reattach-delay <ms|s|m|h>",
|
|
1409
|
+
"Delay before periodic auto-reattach attempts after a timeout.",
|
|
1410
|
+
],
|
|
1411
|
+
[
|
|
1412
|
+
"--browser-auto-reattach-interval <ms|s|m|h>",
|
|
1413
|
+
"Interval between auto-reattach attempts (0 disables).",
|
|
1414
|
+
],
|
|
1415
|
+
["--browser-auto-reattach-timeout <ms|s|m|h>", "Time budget for each auto-reattach attempt."],
|
|
1416
|
+
[
|
|
1417
|
+
"--browser-cookie-wait <ms|s|m>",
|
|
1418
|
+
"Wait before retrying cookie sync when Chrome cookies are empty or locked.",
|
|
1419
|
+
],
|
|
1420
|
+
["--browser-no-cookie-sync", "Skip copying cookies from your main profile."],
|
|
1421
|
+
[
|
|
1422
|
+
"--browser-manual-login",
|
|
1423
|
+
"Skip cookie copy; reuse a persistent automation profile and log in manually.",
|
|
1424
|
+
],
|
|
1425
|
+
["--browser-headless", "Launch Chrome in headless mode."],
|
|
1426
|
+
["--browser-hide-window", "Hide the Chrome window (macOS headful only)."],
|
|
1427
|
+
["--browser-keep-browser", "Leave Chrome running after completion."],
|
|
1375
1428
|
]);
|
|
1376
|
-
console.log(
|
|
1429
|
+
console.log("");
|
|
1377
1430
|
console.log(chalk.dim(`Tip: run \`${cliName} --help\` to see the primary option set.`));
|
|
1378
1431
|
}
|
|
1379
1432
|
function printDebugOptionGroup(entries) {
|
|
@@ -1395,17 +1448,17 @@ function resolveRestartWaitPreference({ waitFlag, storedPreference, model, engin
|
|
|
1395
1448
|
return true;
|
|
1396
1449
|
if (waitFlag === false)
|
|
1397
1450
|
return false;
|
|
1398
|
-
if (typeof storedPreference ===
|
|
1451
|
+
if (typeof storedPreference === "boolean")
|
|
1399
1452
|
return storedPreference;
|
|
1400
1453
|
return defaultWaitPreference(model, engine);
|
|
1401
1454
|
}
|
|
1402
1455
|
function resolveEffectiveModelIdForRun(model, stored) {
|
|
1403
1456
|
if (stored)
|
|
1404
1457
|
return stored;
|
|
1405
|
-
if (model.startsWith(
|
|
1458
|
+
if (model.startsWith("gemini")) {
|
|
1406
1459
|
return resolveGeminiModelId(model);
|
|
1407
1460
|
}
|
|
1408
|
-
return isKnownModel(model) ? MODEL_CONFIGS[model].apiModel ?? model : model;
|
|
1461
|
+
return isKnownModel(model) ? (MODEL_CONFIGS[model].apiModel ?? model) : model;
|
|
1409
1462
|
}
|
|
1410
1463
|
program.action(async function () {
|
|
1411
1464
|
const options = this.optsWithGlobals();
|
|
@@ -1413,21 +1466,21 @@ program.action(async function () {
|
|
|
1413
1466
|
});
|
|
1414
1467
|
async function main() {
|
|
1415
1468
|
const parsePromise = program.parseAsync(normalizedArgv);
|
|
1416
|
-
const sigintPromise = once(process,
|
|
1417
|
-
const result = await Promise.race([parsePromise.then(() =>
|
|
1418
|
-
if (result ===
|
|
1419
|
-
console.log(chalk.yellow(
|
|
1469
|
+
const sigintPromise = once(process, "SIGINT").then(() => "sigint");
|
|
1470
|
+
const result = await Promise.race([parsePromise.then(() => "parsed"), sigintPromise]);
|
|
1471
|
+
if (result === "sigint") {
|
|
1472
|
+
console.log(chalk.yellow("\nCancelled."));
|
|
1420
1473
|
process.exitCode = 130;
|
|
1421
1474
|
}
|
|
1422
1475
|
}
|
|
1423
1476
|
void main().catch((error) => {
|
|
1424
1477
|
if (error instanceof Error) {
|
|
1425
1478
|
if (!isErrorLogged(error)) {
|
|
1426
|
-
console.error(chalk.red(
|
|
1479
|
+
console.error(chalk.red("✖"), error.message);
|
|
1427
1480
|
}
|
|
1428
1481
|
}
|
|
1429
1482
|
else {
|
|
1430
|
-
console.error(chalk.red(
|
|
1483
|
+
console.error(chalk.red("✖"), error);
|
|
1431
1484
|
}
|
|
1432
1485
|
process.exitCode = 1;
|
|
1433
1486
|
});
|