@steipete/oracle 0.8.6 → 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 +130 -45
- package/dist/bin/oracle-cli.js +613 -379
- 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 +314 -104
- package/dist/src/browser/actions/navigation.js +161 -136
- 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 +452 -303
- 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 +17 -0
- package/dist/src/browser/providers/chatgptDomProvider.js +49 -0
- package/dist/src/browser/providers/geminiDeepThinkDomProvider.js +254 -0
- package/dist/src/browser/providers/index.js +2 -0
- 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 +65 -45
- 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 +7 -4
- 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 +11 -0
- 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 +12 -8
- package/dist/src/cli/markdownRenderer.js +15 -15
- package/dist/src/cli/notifier.js +77 -67
- package/dist/src/cli/options.js +145 -87
- 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 +37 -25
- package/dist/src/cli/sessionCommand.js +31 -21
- package/dist/src/cli/sessionDisplay.js +182 -79
- package/dist/src/cli/sessionLineage.js +60 -0
- package/dist/src/cli/sessionRunner.js +118 -90
- package/dist/src/cli/sessionTable.js +28 -24
- package/dist/src/cli/stdin.js +22 -0
- package/dist/src/cli/tagline.js +121 -124
- package/dist/src/cli/tui/index.js +140 -127
- package/dist/src/cli/writeOutputPath.js +5 -5
- package/dist/src/config.js +7 -7
- package/dist/src/gemini-web/browserSessionManager.js +80 -0
- package/dist/src/gemini-web/client.js +81 -64
- package/dist/src/gemini-web/executionMode.js +16 -0
- package/dist/src/gemini-web/executor.js +327 -169
- package/dist/src/gemini-web/index.js +1 -1
- package/dist/src/mcp/server.js +16 -12
- package/dist/src/mcp/tools/consult.js +81 -64
- 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 +84 -46
- package/dist/src/oracle/config.js +124 -58
- package/dist/src/oracle/errors.js +38 -38
- package/dist/src/oracle/files.js +69 -45
- package/dist/src/oracle/finishLine.js +10 -8
- package/dist/src/oracle/format.js +3 -3
- package/dist/src/oracle/gemini.js +37 -30
- 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 +23 -15
- package/dist/src/oracle/run.js +172 -140
- 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 +81 -75
- package/dist/src/sessionStore.js +3 -3
- package/dist/src/version.js +10 -10
- package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
- package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
- package/dist/vendor/oracle-notifier/README.md +2 -0
- package/package.json +69 -65
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
- 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/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/{oracle/src/browser/types.js → src/gemini-web/executionClients.js} +0 -0
package/dist/bin/oracle-cli.js
CHANGED
|
@@ -1,58 +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 {
|
|
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";
|
|
50
52
|
const VERSION = getCliVersion();
|
|
51
53
|
const CLI_ENTRYPOINT = fileURLToPath(import.meta.url);
|
|
52
54
|
const LEGACY_FLAG_ALIASES = new Map([
|
|
53
|
-
[
|
|
54
|
-
[
|
|
55
|
-
[
|
|
55
|
+
["--[no-]notify", "--notify"],
|
|
56
|
+
["--[no-]notify-sound", "--notify-sound"],
|
|
57
|
+
["--[no-]background", "--background"],
|
|
56
58
|
]);
|
|
57
59
|
const normalizedArgv = process.argv.map((arg, index) => {
|
|
58
60
|
if (index < 2)
|
|
@@ -64,18 +66,18 @@ const userCliArgs = rawCliArgs[0] === CLI_ENTRYPOINT ? rawCliArgs.slice(1) : raw
|
|
|
64
66
|
const isTty = process.stdout.isTTY;
|
|
65
67
|
const program = new Command();
|
|
66
68
|
let introPrinted = false;
|
|
67
|
-
program.hook(
|
|
69
|
+
program.hook("preAction", () => {
|
|
68
70
|
if (introPrinted)
|
|
69
71
|
return;
|
|
70
72
|
console.log(formatIntroLine(VERSION, { env: process.env, richTty: isTty }));
|
|
71
73
|
introPrinted = true;
|
|
72
74
|
});
|
|
73
75
|
applyHelpStyling(program, VERSION, isTty);
|
|
74
|
-
program.hook(
|
|
76
|
+
program.hook("preAction", async (thisCommand) => {
|
|
75
77
|
if (thisCommand !== program) {
|
|
76
78
|
return;
|
|
77
79
|
}
|
|
78
|
-
if (userCliArgs.some((arg) => arg ===
|
|
80
|
+
if (userCliArgs.some((arg) => arg === "--help" || arg === "-h")) {
|
|
79
81
|
return;
|
|
80
82
|
}
|
|
81
83
|
if (userCliArgs.length === 0) {
|
|
@@ -87,7 +89,12 @@ program.hook('preAction', (thisCommand) => {
|
|
|
87
89
|
const positional = thisCommand.args?.[0];
|
|
88
90
|
if (!opts.prompt && positional) {
|
|
89
91
|
opts.prompt = positional;
|
|
90
|
-
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);
|
|
91
98
|
}
|
|
92
99
|
if (shouldRequirePrompt(userCliArgs, opts)) {
|
|
93
100
|
console.log(chalk.yellow('Prompt is required. Provide it via --prompt "<text>" or positional [prompt].'));
|
|
@@ -97,139 +104,146 @@ program.hook('preAction', (thisCommand) => {
|
|
|
97
104
|
}
|
|
98
105
|
});
|
|
99
106
|
program
|
|
100
|
-
.name(
|
|
101
|
-
.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.")
|
|
102
109
|
.version(VERSION)
|
|
103
|
-
.argument(
|
|
104
|
-
.option(
|
|
105
|
-
.addOption(new Option(
|
|
106
|
-
.option(
|
|
107
|
-
.
|
|
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.")
|
|
108
117
|
.argParser(collectPaths)
|
|
109
118
|
.default([])
|
|
110
119
|
.hideHelp())
|
|
111
|
-
.addOption(new Option(
|
|
120
|
+
.addOption(new Option("--files <paths...>", "Alias for --file.")
|
|
112
121
|
.argParser(collectPaths)
|
|
113
122
|
.default([])
|
|
114
123
|
.hideHelp())
|
|
115
|
-
.addOption(new Option(
|
|
124
|
+
.addOption(new Option("--path <paths...>", "Alias for --file.")
|
|
116
125
|
.argParser(collectPaths)
|
|
117
126
|
.default([])
|
|
118
127
|
.hideHelp())
|
|
119
|
-
.addOption(new Option(
|
|
128
|
+
.addOption(new Option("--paths <paths...>", "Alias for --file.")
|
|
120
129
|
.argParser(collectPaths)
|
|
121
130
|
.default([])
|
|
122
131
|
.hideHelp())
|
|
123
|
-
.addOption(new Option(
|
|
124
|
-
.addOption(new Option(
|
|
125
|
-
.option(
|
|
126
|
-
.option(
|
|
127
|
-
.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").')
|
|
128
137
|
.argParser(collectModelList)
|
|
129
138
|
.default([]))
|
|
130
|
-
.addOption(new Option(
|
|
131
|
-
.addOption(new Option(
|
|
132
|
-
.
|
|
133
|
-
.
|
|
134
|
-
.
|
|
135
|
-
.
|
|
136
|
-
.addOption(new Option(
|
|
137
|
-
.addOption(new Option(
|
|
138
|
-
.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).")
|
|
139
150
|
.argParser(parseTimeoutOption)
|
|
140
|
-
.default(
|
|
141
|
-
.addOption(new Option(
|
|
142
|
-
.addOption(new Option(
|
|
143
|
-
.addOption(new Option(
|
|
144
|
-
.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"))
|
|
145
156
|
.default(undefined))
|
|
146
|
-
.addOption(new Option(
|
|
147
|
-
.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"))
|
|
148
159
|
.default(undefined))
|
|
149
|
-
.option(
|
|
150
|
-
.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.")
|
|
151
162
|
.hideHelp()
|
|
152
|
-
.choices([
|
|
153
|
-
.preset(
|
|
154
|
-
.addOption(new Option(
|
|
155
|
-
.choices([
|
|
156
|
-
.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")
|
|
157
168
|
.default(false))
|
|
158
|
-
.addOption(new Option(
|
|
159
|
-
.addOption(new Option(
|
|
160
|
-
.addOption(new Option(
|
|
161
|
-
.
|
|
162
|
-
.
|
|
163
|
-
.option(
|
|
164
|
-
.option(
|
|
165
|
-
.option(
|
|
166
|
-
.
|
|
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).")
|
|
167
180
|
.argParser(parseSearchOption)
|
|
168
181
|
.hideHelp())
|
|
169
|
-
.addOption(new Option(
|
|
182
|
+
.addOption(new Option("--max-input <tokens>", "Override the input token budget for the selected model.")
|
|
170
183
|
.argParser(parseIntOption)
|
|
171
184
|
.hideHelp())
|
|
172
|
-
.addOption(new Option(
|
|
185
|
+
.addOption(new Option("--max-output <tokens>", "Override the max output tokens for the selected model.")
|
|
173
186
|
.argParser(parseIntOption)
|
|
174
187
|
.hideHelp())
|
|
175
|
-
.option(
|
|
176
|
-
.option(
|
|
177
|
-
.option(
|
|
178
|
-
.option(
|
|
179
|
-
.addOption(new Option(
|
|
180
|
-
.addOption(new Option(
|
|
181
|
-
.addOption(new 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
|
-
.
|
|
197
|
-
.
|
|
198
|
-
.
|
|
199
|
-
.addOption(new Option(
|
|
200
|
-
.addOption(new Option(
|
|
201
|
-
.addOption(new Option(
|
|
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
|
-
.
|
|
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"])
|
|
209
223
|
.hideHelp())
|
|
210
|
-
.addOption(new Option(
|
|
211
|
-
.addOption(new Option(
|
|
212
|
-
.choices([
|
|
213
|
-
.default(
|
|
214
|
-
.addOption(new Option(
|
|
215
|
-
.addOption(new Option(
|
|
216
|
-
.addOption(new Option(
|
|
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
|
-
.option(
|
|
226
|
-
.option(
|
|
227
|
-
.option(
|
|
228
|
-
.option(
|
|
229
|
-
.addOption(new Option(
|
|
230
|
-
.addOption(new Option(
|
|
231
|
-
.showHelpAfterError(
|
|
232
|
-
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", `
|
|
233
247
|
Examples:
|
|
234
248
|
# Quick API run with two files
|
|
235
249
|
oracle --prompt "Summarize the risk register" --file docs/risk-register.md docs/risk-matrix.md
|
|
@@ -242,15 +256,15 @@ Examples:
|
|
|
242
256
|
oracle --render --copy -p "Review the TS data layer" --file "src/**/*.ts" --file "!src/**/*.test.ts"
|
|
243
257
|
`);
|
|
244
258
|
program
|
|
245
|
-
.command(
|
|
246
|
-
.description(
|
|
247
|
-
.option(
|
|
248
|
-
.option(
|
|
249
|
-
.option(
|
|
250
|
-
.option(
|
|
251
|
-
.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).")
|
|
252
266
|
.action(async (commandOptions) => {
|
|
253
|
-
const { serveRemote } = await import(
|
|
267
|
+
const { serveRemote } = await import("../src/remote/server.js");
|
|
254
268
|
await serveRemote({
|
|
255
269
|
host: commandOptions.host,
|
|
256
270
|
port: commandOptions.port,
|
|
@@ -259,119 +273,122 @@ program
|
|
|
259
273
|
manualLoginProfileDir: commandOptions.manualLoginProfileDir,
|
|
260
274
|
});
|
|
261
275
|
});
|
|
262
|
-
const bridgeCommand = program
|
|
276
|
+
const bridgeCommand = program
|
|
277
|
+
.command("bridge")
|
|
278
|
+
.description("Bridge a Windows-hosted ChatGPT session to Linux clients.");
|
|
263
279
|
bridgeCommand
|
|
264
|
-
.command(
|
|
265
|
-
.description(
|
|
266
|
-
.option(
|
|
267
|
-
.option(
|
|
268
|
-
.option(
|
|
269
|
-
.option(
|
|
270
|
-
.option(
|
|
271
|
-
.option(
|
|
272
|
-
.option(
|
|
273
|
-
.option(
|
|
274
|
-
.option(
|
|
275
|
-
.option(
|
|
276
|
-
.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)
|
|
277
293
|
.action(async (commandOptions) => {
|
|
278
|
-
const { runBridgeHost } = await import(
|
|
294
|
+
const { runBridgeHost } = await import("../src/cli/bridge/host.js");
|
|
279
295
|
await runBridgeHost(commandOptions);
|
|
280
296
|
});
|
|
281
297
|
bridgeCommand
|
|
282
|
-
.command(
|
|
283
|
-
.description(
|
|
284
|
-
.requiredOption(
|
|
285
|
-
.option(
|
|
286
|
-
.option(
|
|
287
|
-
.option(
|
|
288
|
-
.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)
|
|
289
305
|
.action(async (commandOptions) => {
|
|
290
|
-
const { runBridgeClient } = await import(
|
|
306
|
+
const { runBridgeClient } = await import("../src/cli/bridge/client.js");
|
|
291
307
|
await runBridgeClient(commandOptions);
|
|
292
308
|
});
|
|
293
309
|
bridgeCommand
|
|
294
|
-
.command(
|
|
295
|
-
.description(
|
|
296
|
-
.option(
|
|
310
|
+
.command("doctor")
|
|
311
|
+
.description("Diagnose bridge connectivity and browser engine prerequisites.")
|
|
312
|
+
.option("--verbose", "Show extra diagnostics.", false)
|
|
297
313
|
.action(async (commandOptions) => {
|
|
298
|
-
const { runBridgeDoctor } = await import(
|
|
314
|
+
const { runBridgeDoctor } = await import("../src/cli/bridge/doctor.js");
|
|
299
315
|
await runBridgeDoctor(commandOptions);
|
|
300
316
|
});
|
|
301
317
|
bridgeCommand
|
|
302
|
-
.command(
|
|
303
|
-
.description(
|
|
304
|
-
.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)
|
|
305
321
|
.action(async (commandOptions) => {
|
|
306
|
-
const { runBridgeCodexConfig } = await import(
|
|
322
|
+
const { runBridgeCodexConfig } = await import("../src/cli/bridge/codexConfig.js");
|
|
307
323
|
await runBridgeCodexConfig(commandOptions);
|
|
308
324
|
});
|
|
309
325
|
bridgeCommand
|
|
310
|
-
.command(
|
|
311
|
-
.description(
|
|
312
|
-
.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)
|
|
313
329
|
.action(async (commandOptions) => {
|
|
314
|
-
const { runBridgeClaudeConfig } = await import(
|
|
330
|
+
const { runBridgeClaudeConfig } = await import("../src/cli/bridge/claudeConfig.js");
|
|
315
331
|
await runBridgeClaudeConfig(commandOptions);
|
|
316
332
|
});
|
|
317
333
|
program
|
|
318
|
-
.command(
|
|
319
|
-
.description(
|
|
334
|
+
.command("tui")
|
|
335
|
+
.description("Launch the interactive terminal UI for humans (no automation).")
|
|
320
336
|
.action(async () => {
|
|
321
337
|
await sessionStore.ensureStorage();
|
|
322
338
|
await launchTui({ version: VERSION, printIntro: false });
|
|
323
339
|
});
|
|
324
|
-
|
|
325
|
-
.command(
|
|
326
|
-
.description(
|
|
327
|
-
.option(
|
|
328
|
-
.option(
|
|
329
|
-
.option(
|
|
330
|
-
.option(
|
|
331
|
-
.option(
|
|
332
|
-
.option(
|
|
333
|
-
.option(
|
|
334
|
-
.option(
|
|
335
|
-
.option(
|
|
336
|
-
.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())
|
|
337
353
|
.action(async (sessionId, _options, cmd) => {
|
|
338
354
|
await handleSessionCommand(sessionId, cmd);
|
|
339
355
|
});
|
|
340
|
-
|
|
341
|
-
.command(
|
|
342
|
-
.description(
|
|
343
|
-
.option(
|
|
344
|
-
.option(
|
|
345
|
-
.option(
|
|
346
|
-
.option(
|
|
347
|
-
.option(
|
|
348
|
-
.option(
|
|
349
|
-
.option(
|
|
350
|
-
.option(
|
|
351
|
-
.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())
|
|
352
368
|
.action(async (sessionId, _options, command) => {
|
|
353
369
|
const statusOptions = command.opts();
|
|
354
370
|
const clearRequested = Boolean(statusOptions.clear || statusOptions.clean);
|
|
355
371
|
if (clearRequested) {
|
|
356
372
|
if (sessionId) {
|
|
357
|
-
console.error(
|
|
373
|
+
console.error("Cannot combine a session ID with --clear. Remove the ID to delete cached sessions.");
|
|
358
374
|
process.exitCode = 1;
|
|
359
375
|
return;
|
|
360
376
|
}
|
|
361
377
|
const hours = statusOptions.hours;
|
|
362
378
|
const includeAll = statusOptions.all;
|
|
363
379
|
const result = await sessionStore.deleteOlderThan({ hours, includeAll });
|
|
364
|
-
const scope = includeAll ?
|
|
380
|
+
const scope = includeAll ? "all stored sessions" : `sessions older than ${hours}h`;
|
|
365
381
|
console.log(formatSessionCleanupMessage(result, scope));
|
|
366
382
|
return;
|
|
367
383
|
}
|
|
368
|
-
if (sessionId ===
|
|
384
|
+
if (sessionId === "clear" || sessionId === "clean") {
|
|
369
385
|
console.error('Session cleanup now uses --clear. Run "oracle status --clear --hours <n>" instead.');
|
|
370
386
|
process.exitCode = 1;
|
|
371
387
|
return;
|
|
372
388
|
}
|
|
373
389
|
if (sessionId) {
|
|
374
|
-
const autoRender = !command.getOptionValueSource?.(
|
|
390
|
+
const autoRender = !command.getOptionValueSource?.("render") &&
|
|
391
|
+
!command.getOptionValueSource?.("renderMarkdown")
|
|
375
392
|
? process.stdout.isTTY
|
|
376
393
|
: false;
|
|
377
394
|
const renderMarkdown = Boolean(statusOptions.render || statusOptions.renderMarkdown || autoRender);
|
|
@@ -387,19 +404,19 @@ const statusCommand = program
|
|
|
387
404
|
});
|
|
388
405
|
});
|
|
389
406
|
program
|
|
390
|
-
.command(
|
|
391
|
-
.description(
|
|
392
|
-
.addOption(new Option(
|
|
393
|
-
.addOption(new Option(
|
|
394
|
-
.option(
|
|
395
|
-
.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.")
|
|
396
413
|
.action(async (sessionId, _options, cmd) => {
|
|
397
414
|
const restartOptions = cmd.opts();
|
|
398
415
|
await restartSession(sessionId, restartOptions);
|
|
399
416
|
});
|
|
400
417
|
function buildRunOptions(options, overrides = {}) {
|
|
401
418
|
if (!options.prompt) {
|
|
402
|
-
throw new Error(
|
|
419
|
+
throw new Error("Prompt is required.");
|
|
403
420
|
}
|
|
404
421
|
const normalizedBaseUrl = normalizeBaseUrl(overrides.baseUrl ?? options.baseUrl);
|
|
405
422
|
const azure = options.azureEndpoint || overrides.azure?.endpoint
|
|
@@ -413,8 +430,10 @@ function buildRunOptions(options, overrides = {}) {
|
|
|
413
430
|
prompt: options.prompt,
|
|
414
431
|
model: options.model,
|
|
415
432
|
models: overrides.models ?? options.models,
|
|
433
|
+
previousResponseId: overrides.previousResponseId ?? options.previousResponseId,
|
|
416
434
|
effectiveModelId: overrides.effectiveModelId ?? options.effectiveModelId ?? options.model,
|
|
417
435
|
file: overrides.file ?? options.file ?? [],
|
|
436
|
+
maxFileSizeBytes: overrides.maxFileSizeBytes ?? options.maxFileSizeBytes,
|
|
418
437
|
slug: overrides.slug ?? options.slug,
|
|
419
438
|
filesReport: overrides.filesReport ?? options.filesReport,
|
|
420
439
|
maxInput: overrides.maxInput ?? options.maxInput,
|
|
@@ -434,7 +453,9 @@ function buildRunOptions(options, overrides = {}) {
|
|
|
434
453
|
sessionId: overrides.sessionId ?? options.sessionId,
|
|
435
454
|
verbose: overrides.verbose ?? options.verbose,
|
|
436
455
|
heartbeatIntervalMs: overrides.heartbeatIntervalMs ?? resolveHeartbeatIntervalMs(options.heartbeat),
|
|
437
|
-
browserAttachments: overrides.browserAttachments ??
|
|
456
|
+
browserAttachments: overrides.browserAttachments ??
|
|
457
|
+
options.browserAttachments ??
|
|
458
|
+
"auto",
|
|
438
459
|
browserInlineFiles: overrides.browserInlineFiles ?? options.browserInlineFiles ?? false,
|
|
439
460
|
browserBundleFiles: overrides.browserBundleFiles ?? options.browserBundleFiles ?? false,
|
|
440
461
|
background: overrides.background ?? undefined,
|
|
@@ -443,25 +464,154 @@ function buildRunOptions(options, overrides = {}) {
|
|
|
443
464
|
};
|
|
444
465
|
}
|
|
445
466
|
export function enforceBrowserSearchFlag(runOptions, sessionMode, logFn = console.log) {
|
|
446
|
-
if (sessionMode ===
|
|
447
|
-
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."));
|
|
448
469
|
runOptions.search = undefined;
|
|
449
470
|
}
|
|
450
471
|
}
|
|
451
472
|
function resolveHeartbeatIntervalMs(seconds) {
|
|
452
|
-
if (typeof seconds !==
|
|
473
|
+
if (typeof seconds !== "number" || seconds <= 0) {
|
|
453
474
|
return undefined;
|
|
454
475
|
}
|
|
455
476
|
return Math.round(seconds * 1000);
|
|
456
477
|
}
|
|
478
|
+
function assertFollowupSupported({ engine, model, baseUrl, azureEndpoint, }) {
|
|
479
|
+
if (engine !== "api") {
|
|
480
|
+
throw new Error("--followup requires --engine api.");
|
|
481
|
+
}
|
|
482
|
+
if (model.startsWith("gemini") || model.startsWith("claude")) {
|
|
483
|
+
throw new Error(`--followup is only supported for OpenAI Responses API runs. Model ${model} uses a provider client without previous_response_id support.`);
|
|
484
|
+
}
|
|
485
|
+
if (baseUrl && !azureEndpoint) {
|
|
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.");
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
function levenshteinDistance(a, b) {
|
|
490
|
+
if (a === b)
|
|
491
|
+
return 0;
|
|
492
|
+
if (a.length === 0)
|
|
493
|
+
return b.length;
|
|
494
|
+
if (b.length === 0)
|
|
495
|
+
return a.length;
|
|
496
|
+
const previous = Array.from({ length: b.length + 1 });
|
|
497
|
+
const current = Array.from({ length: b.length + 1 });
|
|
498
|
+
for (let j = 0; j <= b.length; j += 1) {
|
|
499
|
+
previous[j] = j;
|
|
500
|
+
}
|
|
501
|
+
for (let i = 1; i <= a.length; i += 1) {
|
|
502
|
+
current[0] = i;
|
|
503
|
+
for (let j = 1; j <= b.length; j += 1) {
|
|
504
|
+
const substitutionCost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
505
|
+
current[j] = Math.min(previous[j] + 1, current[j - 1] + 1, previous[j - 1] + substitutionCost);
|
|
506
|
+
}
|
|
507
|
+
for (let j = 0; j <= b.length; j += 1) {
|
|
508
|
+
previous[j] = current[j];
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
return previous[b.length];
|
|
512
|
+
}
|
|
513
|
+
function scoreSessionSimilarity(input, candidate) {
|
|
514
|
+
if (input === candidate)
|
|
515
|
+
return 1;
|
|
516
|
+
if (candidate.startsWith(input) || input.startsWith(candidate))
|
|
517
|
+
return 0.95;
|
|
518
|
+
if (candidate.includes(input) || input.includes(candidate))
|
|
519
|
+
return 0.8;
|
|
520
|
+
const distance = levenshteinDistance(input, candidate);
|
|
521
|
+
const maxLength = Math.max(input.length, candidate.length);
|
|
522
|
+
if (maxLength === 0)
|
|
523
|
+
return 0;
|
|
524
|
+
return Math.max(0, 1 - distance / maxLength);
|
|
525
|
+
}
|
|
526
|
+
async function suggestFollowupSessionIds(input, limit = 3) {
|
|
527
|
+
const normalizedInput = input.trim().toLowerCase();
|
|
528
|
+
if (!normalizedInput)
|
|
529
|
+
return [];
|
|
530
|
+
const sessions = await sessionStore.listSessions().catch(() => []);
|
|
531
|
+
const seen = new Set();
|
|
532
|
+
const ranked = sessions
|
|
533
|
+
.map((meta) => meta.id)
|
|
534
|
+
.filter((id) => typeof id === "string" && id.length > 0)
|
|
535
|
+
.filter((id) => {
|
|
536
|
+
if (seen.has(id))
|
|
537
|
+
return false;
|
|
538
|
+
seen.add(id);
|
|
539
|
+
return true;
|
|
540
|
+
})
|
|
541
|
+
.map((id) => ({ id, score: scoreSessionSimilarity(normalizedInput, id.toLowerCase()) }))
|
|
542
|
+
.filter((entry) => entry.score >= 0.45)
|
|
543
|
+
.sort((a, b) => b.score - a.score)
|
|
544
|
+
.slice(0, limit);
|
|
545
|
+
return ranked.map((entry) => entry.id);
|
|
546
|
+
}
|
|
547
|
+
async function resolveFollowupReference(value, followupModel) {
|
|
548
|
+
const trimmed = value.trim();
|
|
549
|
+
if (trimmed.length === 0) {
|
|
550
|
+
throw new Error("--followup requires a session id or response id.");
|
|
551
|
+
}
|
|
552
|
+
if (trimmed.startsWith("resp_")) {
|
|
553
|
+
return { responseId: trimmed };
|
|
554
|
+
}
|
|
555
|
+
// Treat as oracle session id (slug).
|
|
556
|
+
const meta = await sessionStore.readSession(trimmed);
|
|
557
|
+
if (!meta) {
|
|
558
|
+
const suggestions = await suggestFollowupSessionIds(trimmed);
|
|
559
|
+
const suggestionText = suggestions.length > 0
|
|
560
|
+
? ` Did you mean: ${suggestions.map((id) => `"${id}"`).join(", ")}?`
|
|
561
|
+
: "";
|
|
562
|
+
throw new Error(`No session found with ID ${trimmed}.${suggestionText} Run "oracle status --hours 72 --limit 20" to list recent sessions.`);
|
|
563
|
+
}
|
|
564
|
+
const fromMetadata = extractResponseIdFromSession(meta, followupModel);
|
|
565
|
+
if (fromMetadata) {
|
|
566
|
+
return { responseId: fromMetadata, sessionId: meta.id };
|
|
567
|
+
}
|
|
568
|
+
// Fallback: scrape the log for a response id (covers older sessions / edge cases).
|
|
569
|
+
const logText = await sessionStore.readLog(trimmed).catch(() => "");
|
|
570
|
+
const matches = logText.match(/resp_[A-Za-z0-9]+/g) ?? [];
|
|
571
|
+
const last = matches.length > 0 ? matches[matches.length - 1] : null;
|
|
572
|
+
if (last) {
|
|
573
|
+
return { responseId: last, sessionId: meta.id };
|
|
574
|
+
}
|
|
575
|
+
throw new Error(`Session ${trimmed} does not contain a stored response id. Ensure the original run produced a Responses API response id (background/store helps).`);
|
|
576
|
+
}
|
|
577
|
+
function extractResponseIdFromSession(meta, followupModel) {
|
|
578
|
+
// Single-model sessions store response metadata at the session root.
|
|
579
|
+
const rootResponse = meta.response ?? null;
|
|
580
|
+
const rootResponseId = rootResponse?.responseId ?? rootResponse?.id;
|
|
581
|
+
if (rootResponseId && rootResponseId.startsWith("resp_")) {
|
|
582
|
+
return rootResponseId;
|
|
583
|
+
}
|
|
584
|
+
const runs = Array.isArray(meta.models) ? meta.models : [];
|
|
585
|
+
if (runs.length === 0) {
|
|
586
|
+
return null;
|
|
587
|
+
}
|
|
588
|
+
const pickRun = () => {
|
|
589
|
+
if (followupModel) {
|
|
590
|
+
return runs.find((r) => r.model === followupModel) ?? null;
|
|
591
|
+
}
|
|
592
|
+
return runs.length === 1 ? runs[0] : null;
|
|
593
|
+
};
|
|
594
|
+
const chosen = pickRun();
|
|
595
|
+
if (!chosen) {
|
|
596
|
+
const models = runs.map((r) => r.model).join(", ");
|
|
597
|
+
throw new Error(followupModel
|
|
598
|
+
? `Session ${meta.id} has no model named ${followupModel}. Available: ${models}`
|
|
599
|
+
: `Session ${meta.id} has multiple model runs. Re-run with --followup-model. Available: ${models}`);
|
|
600
|
+
}
|
|
601
|
+
const runResponse = chosen.response ?? null;
|
|
602
|
+
const runResponseId = runResponse?.responseId ?? runResponse?.id;
|
|
603
|
+
return runResponseId && runResponseId.startsWith("resp_") ? runResponseId : null;
|
|
604
|
+
}
|
|
457
605
|
function buildRunOptionsFromMetadata(metadata) {
|
|
458
606
|
const stored = metadata.options ?? {};
|
|
459
607
|
return {
|
|
460
|
-
prompt: stored.prompt ??
|
|
608
|
+
prompt: stored.prompt ?? "",
|
|
461
609
|
model: stored.model ?? DEFAULT_MODEL,
|
|
462
610
|
models: stored.models,
|
|
611
|
+
previousResponseId: stored.previousResponseId,
|
|
463
612
|
effectiveModelId: stored.effectiveModelId ?? stored.model,
|
|
464
613
|
file: stored.file ?? [],
|
|
614
|
+
maxFileSizeBytes: stored.maxFileSizeBytes,
|
|
465
615
|
slug: stored.slug,
|
|
466
616
|
filesReport: stored.filesReport,
|
|
467
617
|
maxInput: stored.maxInput,
|
|
@@ -490,36 +640,36 @@ function buildRunOptionsFromMetadata(metadata) {
|
|
|
490
640
|
};
|
|
491
641
|
}
|
|
492
642
|
function getSessionMode(metadata) {
|
|
493
|
-
return metadata.mode ?? metadata.options?.mode ??
|
|
643
|
+
return metadata.mode ?? metadata.options?.mode ?? "api";
|
|
494
644
|
}
|
|
495
645
|
function getBrowserConfigFromMetadata(metadata) {
|
|
496
646
|
return metadata.options?.browserConfig ?? metadata.browser?.config;
|
|
497
647
|
}
|
|
498
648
|
async function runRootCommand(options) {
|
|
499
|
-
if (process.env.ORACLE_FORCE_TUI ===
|
|
649
|
+
if (process.env.ORACLE_FORCE_TUI === "1") {
|
|
500
650
|
await sessionStore.ensureStorage();
|
|
501
651
|
await launchTui({ version: VERSION, printIntro: false });
|
|
502
652
|
return;
|
|
503
653
|
}
|
|
504
654
|
const userConfig = (await loadUserConfig()).config;
|
|
505
|
-
const helpRequested = rawCliArgs.some((arg) => arg ===
|
|
655
|
+
const helpRequested = rawCliArgs.some((arg) => arg === "--help" || arg === "-h");
|
|
506
656
|
const multiModelProvided = Array.isArray(options.models) && options.models.length > 0;
|
|
507
657
|
if (multiModelProvided) {
|
|
508
|
-
const modelFromConfigOrCli = normalizeModelOption(options.model ?? userConfig.model ??
|
|
658
|
+
const modelFromConfigOrCli = normalizeModelOption(options.model ?? userConfig.model ?? "");
|
|
509
659
|
if (modelFromConfigOrCli) {
|
|
510
|
-
throw new Error(
|
|
660
|
+
throw new Error("--models cannot be combined with --model.");
|
|
511
661
|
}
|
|
512
662
|
}
|
|
513
663
|
const optionUsesDefault = (name) => {
|
|
514
664
|
// Commander reports undefined for untouched options, so treat undefined/default the same
|
|
515
665
|
const source = program.getOptionValueSource?.(name);
|
|
516
|
-
return source == null || source ===
|
|
666
|
+
return source == null || source === "default";
|
|
517
667
|
};
|
|
518
668
|
if (helpRequested) {
|
|
519
669
|
if (options.verbose) {
|
|
520
|
-
console.log(
|
|
670
|
+
console.log("");
|
|
521
671
|
printDebugHelp(program.name());
|
|
522
|
-
console.log(
|
|
672
|
+
console.log("");
|
|
523
673
|
}
|
|
524
674
|
program.help({ error: false });
|
|
525
675
|
return;
|
|
@@ -529,8 +679,8 @@ async function runRootCommand(options) {
|
|
|
529
679
|
if (mergedFileInputs.length > 0) {
|
|
530
680
|
const { deduped, duplicates } = dedupePathInputs(mergedFileInputs, { cwd: process.cwd() });
|
|
531
681
|
if (duplicates.length > 0) {
|
|
532
|
-
const preview = duplicates.slice(0, 8).join(
|
|
533
|
-
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)` : "";
|
|
534
684
|
console.log(chalk.dim(`Ignoring duplicate --file inputs: ${preview}${suffix}`));
|
|
535
685
|
}
|
|
536
686
|
options.file = deduped;
|
|
@@ -539,11 +689,11 @@ async function runRootCommand(options) {
|
|
|
539
689
|
const renderMarkdown = resolveRenderFlag(options.render, options.renderMarkdown);
|
|
540
690
|
const renderPlain = resolveRenderPlain(options.renderPlain, options.render, options.renderMarkdown);
|
|
541
691
|
const applyRetentionOption = () => {
|
|
542
|
-
if (optionUsesDefault(
|
|
692
|
+
if (optionUsesDefault("retainHours") && typeof userConfig.sessionRetentionHours === "number") {
|
|
543
693
|
options.retainHours = userConfig.sessionRetentionHours;
|
|
544
694
|
}
|
|
545
695
|
const envRetention = process.env.ORACLE_RETAIN_HOURS;
|
|
546
|
-
if (optionUsesDefault(
|
|
696
|
+
if (optionUsesDefault("retainHours") && envRetention) {
|
|
547
697
|
const parsed = Number.parseFloat(envRetention);
|
|
548
698
|
if (!Number.isNaN(parsed)) {
|
|
549
699
|
options.retainHours = parsed;
|
|
@@ -563,11 +713,11 @@ async function runRootCommand(options) {
|
|
|
563
713
|
console.log(chalk.dim(`Remote browser host detected: ${remoteHost}`));
|
|
564
714
|
}
|
|
565
715
|
if (userCliArgs.length === 0) {
|
|
566
|
-
console.log(chalk.yellow(
|
|
716
|
+
console.log(chalk.yellow("No prompt or subcommand supplied. Run `oracle --help` or `oracle tui` for the TUI."));
|
|
567
717
|
program.outputHelp();
|
|
568
718
|
return;
|
|
569
719
|
}
|
|
570
|
-
const retentionHours = typeof options.retainHours ===
|
|
720
|
+
const retentionHours = typeof options.retainHours === "number" ? options.retainHours : undefined;
|
|
571
721
|
await sessionStore.ensureStorage();
|
|
572
722
|
await pruneOldSessions(retentionHours, (message) => console.log(chalk.dim(message)));
|
|
573
723
|
if (options.debugHelp) {
|
|
@@ -575,35 +725,39 @@ async function runRootCommand(options) {
|
|
|
575
725
|
return;
|
|
576
726
|
}
|
|
577
727
|
if (options.dryRun && options.renderMarkdown) {
|
|
578
|
-
throw new Error(
|
|
728
|
+
throw new Error("--dry-run cannot be combined with --render-markdown.");
|
|
579
729
|
}
|
|
580
730
|
const preferredEngine = options.engine ?? userConfig.engine;
|
|
581
|
-
let engine = resolveEngine({
|
|
731
|
+
let engine = resolveEngine({
|
|
732
|
+
engine: preferredEngine,
|
|
733
|
+
browserFlag: options.browser,
|
|
734
|
+
env: process.env,
|
|
735
|
+
});
|
|
582
736
|
if (options.browser) {
|
|
583
|
-
console.log(chalk.yellow(
|
|
737
|
+
console.log(chalk.yellow("`--browser` is deprecated; use `--engine browser` instead."));
|
|
584
738
|
}
|
|
585
|
-
if (optionUsesDefault(
|
|
739
|
+
if (optionUsesDefault("model") && userConfig.model) {
|
|
586
740
|
options.model = userConfig.model;
|
|
587
741
|
}
|
|
588
|
-
if (optionUsesDefault(
|
|
589
|
-
options.search = userConfig.search ===
|
|
742
|
+
if (optionUsesDefault("search") && userConfig.search) {
|
|
743
|
+
options.search = userConfig.search === "on";
|
|
590
744
|
}
|
|
591
|
-
if (optionUsesDefault(
|
|
745
|
+
if (optionUsesDefault("filesReport") && userConfig.filesReport != null) {
|
|
592
746
|
options.filesReport = Boolean(userConfig.filesReport);
|
|
593
747
|
}
|
|
594
|
-
if (optionUsesDefault(
|
|
748
|
+
if (optionUsesDefault("heartbeat") && typeof userConfig.heartbeatSeconds === "number") {
|
|
595
749
|
options.heartbeat = userConfig.heartbeatSeconds;
|
|
596
750
|
}
|
|
597
|
-
if (optionUsesDefault(
|
|
751
|
+
if (optionUsesDefault("baseUrl") && userConfig.apiBaseUrl) {
|
|
598
752
|
options.baseUrl = userConfig.apiBaseUrl;
|
|
599
753
|
}
|
|
600
|
-
if (remoteHost && engine !==
|
|
601
|
-
throw new Error(
|
|
754
|
+
if (remoteHost && engine !== "browser") {
|
|
755
|
+
throw new Error("--remote-host requires --engine browser.");
|
|
602
756
|
}
|
|
603
757
|
if (remoteHost && options.remoteChrome) {
|
|
604
|
-
throw new Error(
|
|
758
|
+
throw new Error("--remote-host cannot be combined with --remote-chrome.");
|
|
605
759
|
}
|
|
606
|
-
if (optionUsesDefault(
|
|
760
|
+
if (optionUsesDefault("azureEndpoint")) {
|
|
607
761
|
if (process.env.AZURE_OPENAI_ENDPOINT) {
|
|
608
762
|
options.azureEndpoint = process.env.AZURE_OPENAI_ENDPOINT;
|
|
609
763
|
}
|
|
@@ -611,7 +765,7 @@ async function runRootCommand(options) {
|
|
|
611
765
|
options.azureEndpoint = userConfig.azure.endpoint;
|
|
612
766
|
}
|
|
613
767
|
}
|
|
614
|
-
if (optionUsesDefault(
|
|
768
|
+
if (optionUsesDefault("azureDeployment")) {
|
|
615
769
|
if (process.env.AZURE_OPENAI_DEPLOYMENT) {
|
|
616
770
|
options.azureDeployment = process.env.AZURE_OPENAI_DEPLOYMENT;
|
|
617
771
|
}
|
|
@@ -619,7 +773,7 @@ async function runRootCommand(options) {
|
|
|
619
773
|
options.azureDeployment = userConfig.azure.deployment;
|
|
620
774
|
}
|
|
621
775
|
}
|
|
622
|
-
if (optionUsesDefault(
|
|
776
|
+
if (optionUsesDefault("azureApiVersion")) {
|
|
623
777
|
if (process.env.AZURE_OPENAI_API_VERSION) {
|
|
624
778
|
options.azureApiVersion = process.env.AZURE_OPENAI_API_VERSION;
|
|
625
779
|
}
|
|
@@ -630,48 +784,57 @@ async function runRootCommand(options) {
|
|
|
630
784
|
const normalizedMultiModels = multiModelProvided
|
|
631
785
|
? Array.from(new Set(options.models.map((entry) => resolveApiModel(entry))))
|
|
632
786
|
: [];
|
|
633
|
-
const cliModelArg = normalizeModelOption(options.model) || (multiModelProvided ?
|
|
787
|
+
const cliModelArg = normalizeModelOption(options.model) || (multiModelProvided ? "" : DEFAULT_MODEL);
|
|
634
788
|
const resolvedModelCandidate = multiModelProvided
|
|
635
789
|
? normalizedMultiModels[0]
|
|
636
|
-
: engine ===
|
|
790
|
+
: engine === "browser"
|
|
637
791
|
? inferModelFromLabel(cliModelArg || DEFAULT_MODEL)
|
|
638
792
|
: resolveApiModel(cliModelArg || DEFAULT_MODEL);
|
|
639
793
|
const primaryModelCandidate = normalizedMultiModels[0] ?? resolvedModelCandidate;
|
|
640
|
-
const isGemini = primaryModelCandidate.startsWith(
|
|
641
|
-
const isCodex = primaryModelCandidate.startsWith(
|
|
642
|
-
const isClaude = primaryModelCandidate.startsWith(
|
|
643
|
-
const userForcedBrowser = options.browser || options.engine ===
|
|
644
|
-
const isBrowserCompatible = (model) => model.startsWith(
|
|
645
|
-
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) &&
|
|
646
800
|
(normalizedMultiModels.length > 0
|
|
647
801
|
? normalizedMultiModels.some((model) => !isBrowserCompatible(model))
|
|
648
802
|
: !isBrowserCompatible(resolvedModelCandidate));
|
|
649
803
|
if (hasNonBrowserCompatibleTarget) {
|
|
650
|
-
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.");
|
|
651
805
|
}
|
|
652
|
-
if (isClaude && engine ===
|
|
653
|
-
console.log(chalk.dim(
|
|
654
|
-
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";
|
|
655
809
|
}
|
|
656
|
-
if (isCodex && engine ===
|
|
657
|
-
console.log(chalk.dim(
|
|
658
|
-
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";
|
|
659
813
|
}
|
|
660
814
|
if (normalizedMultiModels.length > 0) {
|
|
661
|
-
engine =
|
|
815
|
+
engine = "api";
|
|
662
816
|
}
|
|
663
817
|
if (remoteHost && normalizedMultiModels.length > 0) {
|
|
664
|
-
throw new Error(
|
|
818
|
+
throw new Error("--remote-host does not support --models yet. Use API engine locally instead.");
|
|
665
819
|
}
|
|
666
820
|
const resolvedModel = normalizedMultiModels[0] ?? (isGemini ? resolveApiModel(cliModelArg) : resolvedModelCandidate);
|
|
667
|
-
const
|
|
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.");
|
|
824
|
+
}
|
|
825
|
+
if (engine === "browser" && includesGeminiApiOnly) {
|
|
826
|
+
console.log(chalk.dim("gemini-3.1-pro is API-only today; switching to API."));
|
|
827
|
+
engine = "api";
|
|
828
|
+
}
|
|
829
|
+
const effectiveModelId = resolvedModel.startsWith("gemini")
|
|
668
830
|
? resolveGeminiModelId(resolvedModel)
|
|
669
831
|
: isKnownModel(resolvedModel)
|
|
670
|
-
? MODEL_CONFIGS[resolvedModel].apiModel ?? resolvedModel
|
|
832
|
+
? (MODEL_CONFIGS[resolvedModel].apiModel ?? resolvedModel)
|
|
671
833
|
: resolvedModel;
|
|
672
834
|
const resolvedBaseUrl = normalizeBaseUrl(options.baseUrl ?? (isClaude ? process.env.ANTHROPIC_BASE_URL : process.env.OPENAI_BASE_URL));
|
|
673
835
|
const { models: _rawModels, ...optionsWithoutModels } = options;
|
|
674
836
|
const resolvedOptions = { ...optionsWithoutModels, model: resolvedModel };
|
|
837
|
+
resolvedOptions.maxFileSizeBytes = resolveConfiguredMaxFileSizeBytes(userConfig, process.env);
|
|
675
838
|
if (normalizedMultiModels.length > 0) {
|
|
676
839
|
resolvedOptions.models = normalizedMultiModels;
|
|
677
840
|
}
|
|
@@ -687,7 +850,7 @@ async function runRootCommand(options) {
|
|
|
687
850
|
engine,
|
|
688
851
|
});
|
|
689
852
|
if (remoteHost && waitPreference === false) {
|
|
690
|
-
console.log(chalk.dim(
|
|
853
|
+
console.log(chalk.dim("Remote browser runs require --wait; ignoring --no-wait."));
|
|
691
854
|
waitPreference = true;
|
|
692
855
|
}
|
|
693
856
|
if (await handleStatusFlag(options, { attachSession, showStatus })) {
|
|
@@ -702,10 +865,12 @@ async function runRootCommand(options) {
|
|
|
702
865
|
}
|
|
703
866
|
if (renderMarkdown || copyMarkdown) {
|
|
704
867
|
if (!options.prompt) {
|
|
705
|
-
throw new Error(
|
|
868
|
+
throw new Error("Prompt is required when using --render-markdown or --copy-markdown.");
|
|
706
869
|
}
|
|
707
870
|
const bundle = await buildMarkdownBundle({ prompt: options.prompt, file: options.file, system: options.system }, { cwd: process.cwd() });
|
|
708
|
-
const modelConfig = isKnownModel(resolvedModel)
|
|
871
|
+
const modelConfig = isKnownModel(resolvedModel)
|
|
872
|
+
? MODEL_CONFIGS[resolvedModel]
|
|
873
|
+
: MODEL_CONFIGS["gpt-5.1"];
|
|
709
874
|
const requestBody = buildRequestBody({
|
|
710
875
|
modelConfig,
|
|
711
876
|
systemPrompt: bundle.systemPrompt,
|
|
@@ -722,17 +887,19 @@ async function runRootCommand(options) {
|
|
|
722
887
|
? bundle.markdown
|
|
723
888
|
: await formatRenderedMarkdown(bundle.markdown, { richTty: isTty });
|
|
724
889
|
// Trim trailing newlines from the rendered bundle so we print exactly one blank before the summary line.
|
|
725
|
-
console.log(output.replace(/\n+$/u,
|
|
890
|
+
console.log(output.replace(/\n+$/u, ""));
|
|
726
891
|
}
|
|
727
892
|
if (copyMarkdown) {
|
|
728
893
|
const result = await copyToClipboard(bundle.markdown);
|
|
729
894
|
if (result.success) {
|
|
730
|
-
const filesPart = bundle.files.length > 0 ? `; ${bundle.files.length} files` :
|
|
895
|
+
const filesPart = bundle.files.length > 0 ? `; ${bundle.files.length} files` : "";
|
|
731
896
|
const summary = `Copied markdown to clipboard (~${formatCompactNumber(estimatedTokens)} tokens${filesPart}).`;
|
|
732
897
|
console.log(chalk.green(summary));
|
|
733
898
|
}
|
|
734
899
|
else {
|
|
735
|
-
const reason = result.error instanceof Error
|
|
900
|
+
const reason = result.error instanceof Error
|
|
901
|
+
? result.error.message
|
|
902
|
+
: String(result.error ?? "unknown error");
|
|
736
903
|
console.log(chalk.dim(`Copy failed (${reason}); markdown not printed. Re-run with --render-markdown if you need the content.`));
|
|
737
904
|
}
|
|
738
905
|
}
|
|
@@ -740,14 +907,33 @@ async function runRootCommand(options) {
|
|
|
740
907
|
}
|
|
741
908
|
if (previewMode) {
|
|
742
909
|
if (!options.prompt) {
|
|
743
|
-
throw new Error(
|
|
910
|
+
throw new Error("Prompt is required when using --dry-run/preview.");
|
|
744
911
|
}
|
|
745
912
|
if (userConfig.promptSuffix) {
|
|
746
913
|
options.prompt = `${options.prompt.trim()}\n${userConfig.promptSuffix}`;
|
|
747
914
|
}
|
|
748
915
|
resolvedOptions.prompt = options.prompt;
|
|
749
|
-
|
|
750
|
-
|
|
916
|
+
if (options.followup) {
|
|
917
|
+
assertFollowupSupported({
|
|
918
|
+
engine,
|
|
919
|
+
model: resolvedModel,
|
|
920
|
+
baseUrl: resolvedBaseUrl,
|
|
921
|
+
azureEndpoint: resolvedOptions.azure?.endpoint,
|
|
922
|
+
});
|
|
923
|
+
if (normalizedMultiModels.length > 0) {
|
|
924
|
+
throw new Error("--followup cannot be combined with --models.");
|
|
925
|
+
}
|
|
926
|
+
const followup = await resolveFollowupReference(options.followup, options.followupModel);
|
|
927
|
+
resolvedOptions.previousResponseId = followup.responseId;
|
|
928
|
+
resolvedOptions.followupSessionId = followup.sessionId;
|
|
929
|
+
resolvedOptions.followupModel = options.followupModel;
|
|
930
|
+
}
|
|
931
|
+
const runOptions = buildRunOptions(resolvedOptions, {
|
|
932
|
+
preview: true,
|
|
933
|
+
previewMode,
|
|
934
|
+
baseUrl: resolvedBaseUrl,
|
|
935
|
+
});
|
|
936
|
+
if (engine === "browser") {
|
|
751
937
|
await runBrowserPreview({
|
|
752
938
|
runOptions,
|
|
753
939
|
cwd: process.cwd(),
|
|
@@ -758,7 +944,7 @@ async function runRootCommand(options) {
|
|
|
758
944
|
return;
|
|
759
945
|
}
|
|
760
946
|
// API dry-run/preview path
|
|
761
|
-
if (previewMode ===
|
|
947
|
+
if (previewMode === "summary") {
|
|
762
948
|
await runDryRunSummary({
|
|
763
949
|
engine,
|
|
764
950
|
runOptions,
|
|
@@ -778,12 +964,27 @@ async function runRootCommand(options) {
|
|
|
778
964
|
return;
|
|
779
965
|
}
|
|
780
966
|
if (!options.prompt) {
|
|
781
|
-
throw new Error(
|
|
967
|
+
throw new Error("Prompt is required when starting a new session.");
|
|
782
968
|
}
|
|
783
969
|
if (userConfig.promptSuffix) {
|
|
784
970
|
options.prompt = `${options.prompt.trim()}\n${userConfig.promptSuffix}`;
|
|
785
971
|
}
|
|
786
972
|
resolvedOptions.prompt = options.prompt;
|
|
973
|
+
if (options.followup) {
|
|
974
|
+
assertFollowupSupported({
|
|
975
|
+
engine,
|
|
976
|
+
model: resolvedModel,
|
|
977
|
+
baseUrl: resolvedBaseUrl,
|
|
978
|
+
azureEndpoint: resolvedOptions.azure?.endpoint,
|
|
979
|
+
});
|
|
980
|
+
if (normalizedMultiModels.length > 0) {
|
|
981
|
+
throw new Error("--followup cannot be combined with --models.");
|
|
982
|
+
}
|
|
983
|
+
const followup = await resolveFollowupReference(options.followup, options.followupModel);
|
|
984
|
+
resolvedOptions.previousResponseId = followup.responseId;
|
|
985
|
+
resolvedOptions.followupSessionId = followup.sessionId;
|
|
986
|
+
resolvedOptions.followupModel = options.followupModel;
|
|
987
|
+
}
|
|
787
988
|
const duplicateBlocked = await shouldBlockDuplicatePrompt({
|
|
788
989
|
prompt: resolvedOptions.prompt,
|
|
789
990
|
force: options.force,
|
|
@@ -795,10 +996,15 @@ async function runRootCommand(options) {
|
|
|
795
996
|
return;
|
|
796
997
|
}
|
|
797
998
|
if (options.file && options.file.length > 0) {
|
|
798
|
-
const isBrowserMode = engine ===
|
|
799
|
-
const filesToValidate = isBrowserMode
|
|
999
|
+
const isBrowserMode = engine === "browser" || userForcedBrowser;
|
|
1000
|
+
const filesToValidate = isBrowserMode
|
|
1001
|
+
? options.file.filter((f) => !isMediaFile(f))
|
|
1002
|
+
: options.file;
|
|
800
1003
|
if (filesToValidate.length > 0) {
|
|
801
|
-
await readFiles(filesToValidate, {
|
|
1004
|
+
await readFiles(filesToValidate, {
|
|
1005
|
+
cwd: process.cwd(),
|
|
1006
|
+
maxFileSizeBytes: resolvedOptions.maxFileSizeBytes,
|
|
1007
|
+
});
|
|
802
1008
|
}
|
|
803
1009
|
}
|
|
804
1010
|
const getSource = (key) => program.getOptionValueSource?.(key) ?? undefined;
|
|
@@ -809,9 +1015,9 @@ async function runRootCommand(options) {
|
|
|
809
1015
|
env: process.env,
|
|
810
1016
|
config: userConfig.notify,
|
|
811
1017
|
});
|
|
812
|
-
const sessionMode = engine ===
|
|
813
|
-
const browserModelLabelOverride = sessionMode ===
|
|
814
|
-
const browserConfig = sessionMode ===
|
|
1018
|
+
const sessionMode = engine === "browser" ? "browser" : "api";
|
|
1019
|
+
const browserModelLabelOverride = sessionMode === "browser" ? resolveBrowserModelLabel(cliModelArg, resolvedModel) : undefined;
|
|
1020
|
+
const browserConfig = sessionMode === "browser"
|
|
815
1021
|
? await buildBrowserConfig({
|
|
816
1022
|
...options,
|
|
817
1023
|
model: resolvedModel,
|
|
@@ -825,7 +1031,7 @@ async function runRootCommand(options) {
|
|
|
825
1031
|
};
|
|
826
1032
|
console.log(chalk.dim(`Routing browser automation to remote host ${remoteHost}`));
|
|
827
1033
|
}
|
|
828
|
-
else if (browserConfig && resolvedModel.startsWith(
|
|
1034
|
+
else if (browserConfig && resolvedModel.startsWith("gemini")) {
|
|
829
1035
|
browserDeps = {
|
|
830
1036
|
executeBrowser: createGeminiWebExecutor({
|
|
831
1037
|
youtube: options.youtube,
|
|
@@ -836,9 +1042,9 @@ async function runRootCommand(options) {
|
|
|
836
1042
|
showThoughts: options.geminiShowThoughts,
|
|
837
1043
|
}),
|
|
838
1044
|
};
|
|
839
|
-
console.log(chalk.dim(
|
|
840
|
-
if (browserConfig.modelStrategy && browserConfig.modelStrategy !==
|
|
841
|
-
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."));
|
|
842
1048
|
}
|
|
843
1049
|
}
|
|
844
1050
|
const remoteExecutionActive = Boolean(browserDeps);
|
|
@@ -866,14 +1072,16 @@ async function runRootCommand(options) {
|
|
|
866
1072
|
baseUrl: resolvedBaseUrl,
|
|
867
1073
|
});
|
|
868
1074
|
enforceBrowserSearchFlag(baseRunOptions, sessionMode, console.log);
|
|
869
|
-
if (sessionMode ===
|
|
870
|
-
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."));
|
|
871
1077
|
baseRunOptions.search = undefined;
|
|
872
1078
|
}
|
|
873
1079
|
const sessionMeta = await sessionStore.createSession({
|
|
874
1080
|
...baseRunOptions,
|
|
875
1081
|
mode: sessionMode,
|
|
876
1082
|
browserConfig,
|
|
1083
|
+
followupSessionId: resolvedOptions.followupSessionId,
|
|
1084
|
+
followupModel: resolvedOptions.followupModel,
|
|
877
1085
|
waitPreference,
|
|
878
1086
|
youtube: options.youtube,
|
|
879
1087
|
generateImage: options.generateImage,
|
|
@@ -887,7 +1095,7 @@ async function runRootCommand(options) {
|
|
|
887
1095
|
sessionId: sessionMeta.id,
|
|
888
1096
|
effectiveModelId,
|
|
889
1097
|
};
|
|
890
|
-
const disableDetachEnv = process.env.ORACLE_NO_DETACH ===
|
|
1098
|
+
const disableDetachEnv = process.env.ORACLE_NO_DETACH === "1";
|
|
891
1099
|
const detachAllowed = remoteExecutionActive
|
|
892
1100
|
? false
|
|
893
1101
|
: shouldDetachSession({
|
|
@@ -905,12 +1113,12 @@ async function runRootCommand(options) {
|
|
|
905
1113
|
});
|
|
906
1114
|
if (!waitPreference) {
|
|
907
1115
|
if (!detached) {
|
|
908
|
-
console.log(chalk.red(
|
|
1116
|
+
console.log(chalk.red("Unable to start in background; use --wait to run inline."));
|
|
909
1117
|
process.exitCode = 1;
|
|
910
1118
|
return;
|
|
911
1119
|
}
|
|
912
1120
|
console.log(chalk.blue(`Session running in background. Reattach via: oracle session ${sessionMeta.id}`));
|
|
913
|
-
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."));
|
|
914
1122
|
return;
|
|
915
1123
|
}
|
|
916
1124
|
if (detached === false) {
|
|
@@ -925,8 +1133,8 @@ async function runRootCommand(options) {
|
|
|
925
1133
|
async function runInteractiveSession(sessionMeta, runOptions, mode, browserConfig, showReattachHint = true, notifications, userConfig, suppressSummary = false, browserDeps, cwd = process.cwd()) {
|
|
926
1134
|
const { logLine, writeChunk, stream } = sessionStore.createLogWriter(sessionMeta.id);
|
|
927
1135
|
let headerAugmented = false;
|
|
928
|
-
const combinedLog = (message =
|
|
929
|
-
if (!headerAugmented && message.startsWith(
|
|
1136
|
+
const combinedLog = (message = "") => {
|
|
1137
|
+
if (!headerAugmented && message.startsWith("oracle (")) {
|
|
930
1138
|
headerAugmented = true;
|
|
931
1139
|
if (showReattachHint) {
|
|
932
1140
|
console.log(`${message}\n${chalk.blue(`Reattach via: oracle session ${sessionMeta.id}`)}`);
|
|
@@ -955,21 +1163,19 @@ async function runInteractiveSession(sessionMeta, runOptions, mode, browserConfi
|
|
|
955
1163
|
log: combinedLog,
|
|
956
1164
|
write: combinedWrite,
|
|
957
1165
|
version: VERSION,
|
|
958
|
-
notifications: notifications ??
|
|
1166
|
+
notifications: notifications ??
|
|
1167
|
+
deriveNotificationSettingsFromMetadata(sessionMeta, process.env, userConfig?.notify),
|
|
959
1168
|
browserDeps,
|
|
960
1169
|
});
|
|
961
1170
|
const latest = await sessionStore.readSession(sessionMeta.id);
|
|
962
1171
|
if (!suppressSummary) {
|
|
963
1172
|
const summary = latest ? formatCompletionSummary(latest, { includeSlug: true }) : null;
|
|
964
1173
|
if (summary) {
|
|
965
|
-
console.log(
|
|
1174
|
+
console.log("\n" + chalk.green.bold(summary));
|
|
966
1175
|
logLine(summary); // plain text in log, colored on stdout
|
|
967
1176
|
}
|
|
968
1177
|
}
|
|
969
1178
|
}
|
|
970
|
-
catch (error) {
|
|
971
|
-
throw error;
|
|
972
|
-
}
|
|
973
1179
|
finally {
|
|
974
1180
|
stream.end();
|
|
975
1181
|
}
|
|
@@ -977,14 +1183,14 @@ async function runInteractiveSession(sessionMeta, runOptions, mode, browserConfi
|
|
|
977
1183
|
async function launchDetachedSession(sessionId) {
|
|
978
1184
|
return new Promise((resolve, reject) => {
|
|
979
1185
|
try {
|
|
980
|
-
const args = [
|
|
1186
|
+
const args = ["--", CLI_ENTRYPOINT, "--exec-session", sessionId];
|
|
981
1187
|
const child = spawn(process.execPath, args, {
|
|
982
1188
|
detached: true,
|
|
983
|
-
stdio:
|
|
1189
|
+
stdio: "ignore",
|
|
984
1190
|
env: process.env,
|
|
985
1191
|
});
|
|
986
|
-
child.once(
|
|
987
|
-
child.once(
|
|
1192
|
+
child.once("error", reject);
|
|
1193
|
+
child.once("spawn", () => {
|
|
988
1194
|
child.unref();
|
|
989
1195
|
resolve(true);
|
|
990
1196
|
});
|
|
@@ -1008,9 +1214,9 @@ async function restartSession(sessionId, options) {
|
|
|
1008
1214
|
return;
|
|
1009
1215
|
}
|
|
1010
1216
|
const sessionMode = getSessionMode(metadata);
|
|
1011
|
-
const engine = sessionMode ===
|
|
1217
|
+
const engine = sessionMode === "browser" ? "browser" : "api";
|
|
1012
1218
|
const browserConfig = getBrowserConfigFromMetadata(metadata);
|
|
1013
|
-
if (sessionMode ===
|
|
1219
|
+
if (sessionMode === "browser" && !browserConfig) {
|
|
1014
1220
|
console.error(chalk.red(`Session ${sessionId} is missing browser config; cannot restart.`));
|
|
1015
1221
|
process.exitCode = 1;
|
|
1016
1222
|
return;
|
|
@@ -1019,10 +1225,15 @@ async function restartSession(sessionId, options) {
|
|
|
1019
1225
|
const cwd = metadata.cwd ?? process.cwd();
|
|
1020
1226
|
const storedOptions = metadata.options ?? {};
|
|
1021
1227
|
if (runOptions.file && runOptions.file.length > 0) {
|
|
1022
|
-
const isBrowserMode = engine ===
|
|
1023
|
-
const filesToValidate = isBrowserMode
|
|
1228
|
+
const isBrowserMode = engine === "browser";
|
|
1229
|
+
const filesToValidate = isBrowserMode
|
|
1230
|
+
? runOptions.file.filter((f) => !isMediaFile(f))
|
|
1231
|
+
: runOptions.file;
|
|
1024
1232
|
if (filesToValidate.length > 0) {
|
|
1025
|
-
await readFiles(filesToValidate, {
|
|
1233
|
+
await readFiles(filesToValidate, {
|
|
1234
|
+
cwd,
|
|
1235
|
+
maxFileSizeBytes: runOptions.maxFileSizeBytes,
|
|
1236
|
+
});
|
|
1026
1237
|
}
|
|
1027
1238
|
}
|
|
1028
1239
|
enforceBrowserSearchFlag(runOptions, sessionMode, console.log);
|
|
@@ -1040,14 +1251,14 @@ async function restartSession(sessionId, options) {
|
|
|
1040
1251
|
});
|
|
1041
1252
|
const remoteHost = remoteConfig.host;
|
|
1042
1253
|
const remoteToken = remoteConfig.token;
|
|
1043
|
-
if (remoteHost && engine !==
|
|
1044
|
-
throw new Error(
|
|
1254
|
+
if (remoteHost && engine !== "browser") {
|
|
1255
|
+
throw new Error("--remote-host requires a browser session.");
|
|
1045
1256
|
}
|
|
1046
1257
|
if (remoteHost) {
|
|
1047
1258
|
console.log(chalk.dim(`Remote browser host detected: ${remoteHost}`));
|
|
1048
1259
|
}
|
|
1049
1260
|
if (remoteHost && waitPreference === false) {
|
|
1050
|
-
console.log(chalk.dim(
|
|
1261
|
+
console.log(chalk.dim("Remote browser runs require --wait; ignoring --no-wait."));
|
|
1051
1262
|
waitPreference = true;
|
|
1052
1263
|
}
|
|
1053
1264
|
let browserDeps;
|
|
@@ -1057,7 +1268,7 @@ async function restartSession(sessionId, options) {
|
|
|
1057
1268
|
};
|
|
1058
1269
|
console.log(chalk.dim(`Routing browser automation to remote host ${remoteHost}`));
|
|
1059
1270
|
}
|
|
1060
|
-
else if (browserConfig && runOptions.model.startsWith(
|
|
1271
|
+
else if (browserConfig && runOptions.model.startsWith("gemini")) {
|
|
1061
1272
|
browserDeps = {
|
|
1062
1273
|
executeBrowser: createGeminiWebExecutor({
|
|
1063
1274
|
youtube: storedOptions.youtube,
|
|
@@ -1068,9 +1279,9 @@ async function restartSession(sessionId, options) {
|
|
|
1068
1279
|
showThoughts: storedOptions.geminiShowThoughts,
|
|
1069
1280
|
}),
|
|
1070
1281
|
};
|
|
1071
|
-
console.log(chalk.dim(
|
|
1072
|
-
if (browserConfig.modelStrategy && browserConfig.modelStrategy !==
|
|
1073
|
-
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."));
|
|
1074
1285
|
}
|
|
1075
1286
|
}
|
|
1076
1287
|
const remoteExecutionActive = Boolean(browserDeps);
|
|
@@ -1080,6 +1291,8 @@ async function restartSession(sessionId, options) {
|
|
|
1080
1291
|
...runOptions,
|
|
1081
1292
|
mode: sessionMode,
|
|
1082
1293
|
browserConfig,
|
|
1294
|
+
followupSessionId: storedOptions.followupSessionId,
|
|
1295
|
+
followupModel: storedOptions.followupModel,
|
|
1083
1296
|
waitPreference,
|
|
1084
1297
|
youtube: storedOptions.youtube,
|
|
1085
1298
|
generateImage: storedOptions.generateImage,
|
|
@@ -1093,7 +1306,7 @@ async function restartSession(sessionId, options) {
|
|
|
1093
1306
|
sessionId: sessionMeta.id,
|
|
1094
1307
|
effectiveModelId: resolveEffectiveModelIdForRun(runOptions.model, runOptions.effectiveModelId),
|
|
1095
1308
|
};
|
|
1096
|
-
const disableDetachEnv = process.env.ORACLE_NO_DETACH ===
|
|
1309
|
+
const disableDetachEnv = process.env.ORACLE_NO_DETACH === "1";
|
|
1097
1310
|
const detachAllowed = remoteExecutionActive
|
|
1098
1311
|
? false
|
|
1099
1312
|
: shouldDetachSession({
|
|
@@ -1111,12 +1324,12 @@ async function restartSession(sessionId, options) {
|
|
|
1111
1324
|
});
|
|
1112
1325
|
if (!waitPreference) {
|
|
1113
1326
|
if (!detached) {
|
|
1114
|
-
console.log(chalk.red(
|
|
1327
|
+
console.log(chalk.red("Unable to start in background; use --wait to run inline."));
|
|
1115
1328
|
process.exitCode = 1;
|
|
1116
1329
|
return;
|
|
1117
1330
|
}
|
|
1118
1331
|
console.log(chalk.blue(`Session running in background. Reattach via: oracle session ${sessionMeta.id}`));
|
|
1119
|
-
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."));
|
|
1120
1333
|
return;
|
|
1121
1334
|
}
|
|
1122
1335
|
if (detached === false) {
|
|
@@ -1162,37 +1375,58 @@ async function executeSession(sessionId) {
|
|
|
1162
1375
|
}
|
|
1163
1376
|
}
|
|
1164
1377
|
function printDebugHelp(cliName) {
|
|
1165
|
-
console.log(chalk.bold(
|
|
1378
|
+
console.log(chalk.bold("Advanced Options"));
|
|
1166
1379
|
printDebugOptionGroup([
|
|
1167
|
-
[
|
|
1168
|
-
[
|
|
1169
|
-
[
|
|
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)."],
|
|
1170
1383
|
]);
|
|
1171
|
-
console.log(
|
|
1172
|
-
console.log(chalk.bold(
|
|
1384
|
+
console.log("");
|
|
1385
|
+
console.log(chalk.bold("Browser Options"));
|
|
1173
1386
|
printDebugOptionGroup([
|
|
1174
|
-
[
|
|
1175
|
-
[
|
|
1176
|
-
[
|
|
1177
|
-
[
|
|
1178
|
-
[
|
|
1179
|
-
[
|
|
1180
|
-
[
|
|
1181
|
-
[
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
[
|
|
1186
|
-
[
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
[
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
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."],
|
|
1194
1428
|
]);
|
|
1195
|
-
console.log(
|
|
1429
|
+
console.log("");
|
|
1196
1430
|
console.log(chalk.dim(`Tip: run \`${cliName} --help\` to see the primary option set.`));
|
|
1197
1431
|
}
|
|
1198
1432
|
function printDebugOptionGroup(entries) {
|
|
@@ -1214,17 +1448,17 @@ function resolveRestartWaitPreference({ waitFlag, storedPreference, model, engin
|
|
|
1214
1448
|
return true;
|
|
1215
1449
|
if (waitFlag === false)
|
|
1216
1450
|
return false;
|
|
1217
|
-
if (typeof storedPreference ===
|
|
1451
|
+
if (typeof storedPreference === "boolean")
|
|
1218
1452
|
return storedPreference;
|
|
1219
1453
|
return defaultWaitPreference(model, engine);
|
|
1220
1454
|
}
|
|
1221
1455
|
function resolveEffectiveModelIdForRun(model, stored) {
|
|
1222
1456
|
if (stored)
|
|
1223
1457
|
return stored;
|
|
1224
|
-
if (model.startsWith(
|
|
1458
|
+
if (model.startsWith("gemini")) {
|
|
1225
1459
|
return resolveGeminiModelId(model);
|
|
1226
1460
|
}
|
|
1227
|
-
return isKnownModel(model) ? MODEL_CONFIGS[model].apiModel ?? model : model;
|
|
1461
|
+
return isKnownModel(model) ? (MODEL_CONFIGS[model].apiModel ?? model) : model;
|
|
1228
1462
|
}
|
|
1229
1463
|
program.action(async function () {
|
|
1230
1464
|
const options = this.optsWithGlobals();
|
|
@@ -1232,21 +1466,21 @@ program.action(async function () {
|
|
|
1232
1466
|
});
|
|
1233
1467
|
async function main() {
|
|
1234
1468
|
const parsePromise = program.parseAsync(normalizedArgv);
|
|
1235
|
-
const sigintPromise = once(process,
|
|
1236
|
-
const result = await Promise.race([parsePromise.then(() =>
|
|
1237
|
-
if (result ===
|
|
1238
|
-
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."));
|
|
1239
1473
|
process.exitCode = 130;
|
|
1240
1474
|
}
|
|
1241
1475
|
}
|
|
1242
1476
|
void main().catch((error) => {
|
|
1243
1477
|
if (error instanceof Error) {
|
|
1244
1478
|
if (!isErrorLogged(error)) {
|
|
1245
|
-
console.error(chalk.red(
|
|
1479
|
+
console.error(chalk.red("✖"), error.message);
|
|
1246
1480
|
}
|
|
1247
1481
|
}
|
|
1248
1482
|
else {
|
|
1249
|
-
console.error(chalk.red(
|
|
1483
|
+
console.error(chalk.red("✖"), error);
|
|
1250
1484
|
}
|
|
1251
1485
|
process.exitCode = 1;
|
|
1252
1486
|
});
|