@steipete/oracle 0.9.0 → 0.11.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 +107 -49
- package/dist/bin/oracle-cli.js +551 -410
- 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/archiveConversation.js +224 -0
- package/dist/src/browser/actions/assistantResponse.js +175 -101
- package/dist/src/browser/actions/attachmentDataTransfer.js +49 -47
- package/dist/src/browser/actions/attachments.js +246 -150
- package/dist/src/browser/actions/deepResearch.js +662 -0
- package/dist/src/browser/actions/domEvents.js +2 -2
- package/dist/src/browser/actions/modelSelection.js +342 -119
- package/dist/src/browser/actions/navigation.js +183 -137
- package/dist/src/browser/actions/projectSources.js +491 -0
- package/dist/src/browser/actions/promptComposer.js +152 -91
- package/dist/src/browser/actions/remoteFileTransfer.js +10 -10
- package/dist/src/browser/actions/thinkingStatus.js +391 -0
- package/dist/src/browser/actions/thinkingTime.js +207 -110
- package/dist/src/browser/artifacts.js +150 -0
- package/dist/src/browser/attachRunning.js +31 -0
- package/dist/src/browser/chatgptImages.js +315 -0
- package/dist/src/browser/chromeLifecycle.js +276 -63
- package/dist/src/browser/config.js +59 -16
- package/dist/src/browser/constants.js +25 -12
- package/dist/src/browser/controlPlan.js +81 -0
- package/dist/src/browser/cookies.js +19 -19
- package/dist/src/browser/detect.js +250 -77
- package/dist/src/browser/domDebug.js +50 -1
- package/dist/src/browser/index.js +1559 -692
- package/dist/src/browser/liveTabs.js +434 -0
- 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 +127 -42
- package/dist/src/browser/projectSourcesRunner.js +366 -0
- package/dist/src/browser/prompt.js +72 -42
- package/dist/src/browser/promptSummary.js +5 -5
- package/dist/src/browser/providerDomFlow.js +1 -1
- package/dist/src/browser/providers/chatgptDomProvider.js +9 -9
- package/dist/src/browser/providers/geminiDeepThinkDomProvider.js +51 -42
- package/dist/src/browser/providers/index.js +2 -2
- package/dist/src/browser/reattach.js +178 -73
- package/dist/src/browser/reattachHelpers.js +32 -27
- package/dist/src/browser/sessionRunner.js +89 -25
- package/dist/src/browser/tabLeaseRegistry.js +182 -0
- package/dist/src/browser/utils.js +9 -9
- package/dist/src/browserMode.js +1 -1
- package/dist/src/cli/bridge/claudeConfig.js +24 -20
- 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 +102 -48
- package/dist/src/cli/browserDefaults.js +51 -26
- package/dist/src/cli/browserTabs.js +228 -0
- package/dist/src/cli/bundleWarnings.js +1 -1
- package/dist/src/cli/clipboard.js +11 -2
- package/dist/src/cli/detach.js +2 -2
- package/dist/src/cli/dryRun.js +62 -26
- package/dist/src/cli/duplicatePromptGuard.js +12 -4
- package/dist/src/cli/engine.js +9 -9
- package/dist/src/cli/errorUtils.js +1 -1
- package/dist/src/cli/fileSize.js +3 -3
- package/dist/src/cli/format.js +2 -2
- package/dist/src/cli/help.js +28 -28
- package/dist/src/cli/hiddenAliases.js +3 -3
- package/dist/src/cli/markdownBundle.js +7 -7
- package/dist/src/cli/markdownRenderer.js +15 -15
- package/dist/src/cli/notifier.js +77 -67
- package/dist/src/cli/options.js +131 -106
- package/dist/src/cli/oscUtils.js +1 -1
- package/dist/src/cli/projectSources.js +116 -0
- package/dist/src/cli/promptRequirement.js +2 -2
- package/dist/src/cli/renderOutput.js +1 -1
- package/dist/src/cli/rootAlias.js +1 -1
- package/dist/src/cli/runOptions.js +32 -28
- package/dist/src/cli/sessionCommand.js +82 -21
- package/dist/src/cli/sessionDisplay.js +213 -87
- package/dist/src/cli/sessionLineage.js +6 -2
- package/dist/src/cli/sessionRunner.js +149 -95
- package/dist/src/cli/sessionTable.js +26 -23
- package/dist/src/cli/stdin.js +22 -0
- package/dist/src/cli/tagline.js +121 -124
- package/dist/src/cli/tui/index.js +139 -128
- package/dist/src/cli/writeOutputPath.js +5 -5
- package/dist/src/config.js +7 -7
- package/dist/src/gemini-web/browserSessionManager.js +19 -15
- package/dist/src/gemini-web/client.js +76 -70
- package/dist/src/gemini-web/executionMode.js +6 -8
- package/dist/src/gemini-web/executor.js +98 -93
- package/dist/src/gemini-web/index.js +1 -1
- package/dist/src/mcp/consultPresets.js +19 -0
- package/dist/src/mcp/server.js +18 -12
- package/dist/src/mcp/tools/consult.js +246 -67
- package/dist/src/mcp/tools/projectSources.js +123 -0
- package/dist/src/mcp/tools/sessionResources.js +12 -12
- package/dist/src/mcp/tools/sessions.js +26 -17
- package/dist/src/mcp/types.js +12 -5
- package/dist/src/mcp/utils.js +21 -8
- package/dist/src/oracle/background.js +15 -15
- package/dist/src/oracle/claude.js +53 -25
- package/dist/src/oracle/client.js +50 -41
- package/dist/src/oracle/config.js +96 -66
- package/dist/src/oracle/errors.js +38 -38
- package/dist/src/oracle/files.js +55 -46
- package/dist/src/oracle/finishLine.js +10 -8
- package/dist/src/oracle/format.js +3 -3
- package/dist/src/oracle/gemini.js +37 -33
- package/dist/src/oracle/logging.js +7 -7
- package/dist/src/oracle/markdown.js +28 -28
- package/dist/src/oracle/modelResolver.js +16 -16
- package/dist/src/oracle/multiModelRunner.js +12 -12
- package/dist/src/oracle/oscProgress.js +8 -8
- package/dist/src/oracle/promptAssembly.js +6 -3
- package/dist/src/oracle/request.js +16 -13
- package/dist/src/oracle/run.js +160 -135
- 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/projectSources/plan.js +27 -0
- package/dist/src/projectSources/url.js +23 -0
- 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 +78 -75
- package/dist/src/sessionStore.js +3 -3
- package/dist/src/version.js +10 -10
- package/dist/vendor/oracle-notifier/README.md +2 -0
- package/package.json +67 -62
- package/vendor/oracle-notifier/README.md +2 -0
- package/dist/markdansi/types/index.js +0 -4
- package/dist/oracle/bin/oracle-cli.js +0 -472
- package/dist/oracle/src/browser/actions/assistantResponse.js +0 -471
- package/dist/oracle/src/browser/actions/attachments.js +0 -82
- package/dist/oracle/src/browser/actions/modelSelection.js +0 -190
- package/dist/oracle/src/browser/actions/navigation.js +0 -75
- package/dist/oracle/src/browser/actions/promptComposer.js +0 -167
- package/dist/oracle/src/browser/chromeLifecycle.js +0 -104
- package/dist/oracle/src/browser/config.js +0 -33
- package/dist/oracle/src/browser/constants.js +0 -40
- package/dist/oracle/src/browser/cookies.js +0 -210
- package/dist/oracle/src/browser/domDebug.js +0 -36
- package/dist/oracle/src/browser/index.js +0 -331
- package/dist/oracle/src/browser/pageActions.js +0 -5
- package/dist/oracle/src/browser/prompt.js +0 -88
- package/dist/oracle/src/browser/promptSummary.js +0 -20
- package/dist/oracle/src/browser/sessionRunner.js +0 -80
- package/dist/oracle/src/browser/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 → src/projectSources}/types.js +0 -0
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import chalk from
|
|
2
|
-
import { formatTokenCount } from
|
|
3
|
-
import { formatFinishLine } from
|
|
4
|
-
import { runBrowserMode } from
|
|
5
|
-
import { assembleBrowserPrompt } from
|
|
6
|
-
import { BrowserAutomationError } from
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { formatTokenCount } from "../oracle/runUtils.js";
|
|
3
|
+
import { formatFinishLine } from "../oracle/finishLine.js";
|
|
4
|
+
import { runBrowserMode } from "../browserMode.js";
|
|
5
|
+
import { assembleBrowserPrompt } from "./prompt.js";
|
|
6
|
+
import { BrowserAutomationError } from "../oracle/errors.js";
|
|
7
|
+
import { appendArtifacts, saveBrowserTranscriptArtifact, saveDeepResearchReportArtifact, } from "./artifacts.js";
|
|
7
8
|
export async function runBrowserSessionExecution({ runOptions, browserConfig, cwd, log }, deps = {}) {
|
|
8
9
|
const assemblePrompt = deps.assemblePrompt ?? assembleBrowserPrompt;
|
|
9
10
|
const executeBrowser = deps.executeBrowser ?? runBrowserMode;
|
|
@@ -14,14 +15,18 @@ export async function runBrowserSessionExecution({ runOptions, browserConfig, cw
|
|
|
14
15
|
})}`));
|
|
15
16
|
log(chalk.dim(`[verbose] Browser prompt length: ${promptArtifacts.composerText.length} chars`));
|
|
16
17
|
if (promptArtifacts.attachments.length > 0) {
|
|
17
|
-
const attachmentList = promptArtifacts.attachments
|
|
18
|
+
const attachmentList = promptArtifacts.attachments
|
|
19
|
+
.map((attachment) => attachment.displayPath)
|
|
20
|
+
.join(", ");
|
|
18
21
|
log(chalk.dim(`[verbose] Browser attachments: ${attachmentList}`));
|
|
19
22
|
if (promptArtifacts.bundled) {
|
|
20
23
|
log(chalk.yellow(`[browser] Bundled ${promptArtifacts.bundled.originalCount} files into ${promptArtifacts.bundled.bundlePath}.`));
|
|
21
24
|
}
|
|
22
25
|
}
|
|
23
|
-
else if (runOptions.file &&
|
|
24
|
-
|
|
26
|
+
else if (runOptions.file &&
|
|
27
|
+
runOptions.file.length > 0 &&
|
|
28
|
+
promptArtifacts.attachmentMode === "inline") {
|
|
29
|
+
log(chalk.dim("[verbose] Browser will paste file contents inline (no uploads)."));
|
|
25
30
|
}
|
|
26
31
|
}
|
|
27
32
|
if (promptArtifacts.bundled) {
|
|
@@ -29,19 +34,20 @@ export async function runBrowserSessionExecution({ runOptions, browserConfig, cw
|
|
|
29
34
|
}
|
|
30
35
|
const headerLine = `Launching browser mode (${runOptions.model}) with ~${promptArtifacts.estimatedInputTokens.toLocaleString()} tokens.`;
|
|
31
36
|
const automationLogger = ((message) => {
|
|
32
|
-
if (typeof message !==
|
|
37
|
+
if (typeof message !== "string")
|
|
33
38
|
return;
|
|
34
|
-
const shouldAlwaysPrint = message.startsWith(
|
|
39
|
+
const shouldAlwaysPrint = message.startsWith("[browser] ") &&
|
|
40
|
+
/archive|fallback|follow-up|retry|thinking|waiting for chatgpt|browser slot|browser control|browser guidance/i.test(message);
|
|
35
41
|
if (!runOptions.verbose && !shouldAlwaysPrint)
|
|
36
42
|
return;
|
|
37
43
|
log(message);
|
|
38
44
|
});
|
|
39
45
|
automationLogger.verbose = Boolean(runOptions.verbose);
|
|
40
|
-
automationLogger.sessionLog = runOptions.verbose ? log : (
|
|
46
|
+
automationLogger.sessionLog = runOptions.verbose ? log : () => { };
|
|
41
47
|
log(headerLine);
|
|
42
|
-
log(chalk.dim(
|
|
48
|
+
log(chalk.dim("This run can take up to an hour (usually ~10 minutes)."));
|
|
43
49
|
if (runOptions.verbose) {
|
|
44
|
-
log(chalk.dim(
|
|
50
|
+
log(chalk.dim("Chrome automation does not stream output; this may take a minute..."));
|
|
45
51
|
}
|
|
46
52
|
const persistRuntimeHint = deps.persistRuntimeHint ?? (() => { });
|
|
47
53
|
let browserResult;
|
|
@@ -50,14 +56,24 @@ export async function runBrowserSessionExecution({ runOptions, browserConfig, cw
|
|
|
50
56
|
prompt: promptArtifacts.composerText,
|
|
51
57
|
attachments: promptArtifacts.attachments,
|
|
52
58
|
fallbackSubmission: promptArtifacts.fallback
|
|
53
|
-
? {
|
|
59
|
+
? {
|
|
60
|
+
prompt: promptArtifacts.fallback.composerText,
|
|
61
|
+
attachments: promptArtifacts.fallback.attachments,
|
|
62
|
+
}
|
|
54
63
|
: undefined,
|
|
55
64
|
config: browserConfig,
|
|
56
65
|
log: automationLogger,
|
|
57
66
|
heartbeatIntervalMs: runOptions.heartbeatIntervalMs,
|
|
58
67
|
verbose: runOptions.verbose,
|
|
68
|
+
sessionId: runOptions.sessionId,
|
|
69
|
+
generateImagePath: runOptions.generateImage,
|
|
70
|
+
outputPath: runOptions.outputPath,
|
|
71
|
+
followUpPrompts: runOptions.browserFollowUps,
|
|
59
72
|
runtimeHintCb: async (runtime) => {
|
|
60
|
-
await persistRuntimeHint({
|
|
73
|
+
await persistRuntimeHint({
|
|
74
|
+
...runtime,
|
|
75
|
+
controllerPid: runtime.controllerPid ?? process.pid,
|
|
76
|
+
});
|
|
61
77
|
},
|
|
62
78
|
});
|
|
63
79
|
}
|
|
@@ -65,15 +81,24 @@ export async function runBrowserSessionExecution({ runOptions, browserConfig, cw
|
|
|
65
81
|
if (error instanceof BrowserAutomationError) {
|
|
66
82
|
throw error;
|
|
67
83
|
}
|
|
68
|
-
const message = error instanceof Error ? error.message :
|
|
69
|
-
throw new BrowserAutomationError(message, { stage:
|
|
84
|
+
const message = error instanceof Error ? error.message : "Browser automation failed.";
|
|
85
|
+
throw new BrowserAutomationError(message, { stage: "execute-browser" }, error);
|
|
70
86
|
}
|
|
71
87
|
if (!runOptions.silent) {
|
|
72
|
-
log(chalk.bold(
|
|
73
|
-
log(browserResult.answerMarkdown || browserResult.answerText || chalk.dim(
|
|
74
|
-
log(
|
|
88
|
+
log(chalk.bold("Answer:"));
|
|
89
|
+
log(browserResult.answerMarkdown || browserResult.answerText || chalk.dim("(no text output)"));
|
|
90
|
+
log("");
|
|
75
91
|
}
|
|
76
|
-
const answerText = browserResult.answerMarkdown || browserResult.answerText ||
|
|
92
|
+
const answerText = browserResult.answerMarkdown || browserResult.answerText || "";
|
|
93
|
+
const savedArtifacts = await ensureSessionArtifacts({
|
|
94
|
+
sessionId: runOptions.sessionId,
|
|
95
|
+
prompt: promptArtifacts.composerText,
|
|
96
|
+
answerMarkdown: answerText,
|
|
97
|
+
conversationUrl: browserResult.tabUrl,
|
|
98
|
+
browserConfig,
|
|
99
|
+
existingArtifacts: browserResult.artifacts,
|
|
100
|
+
logger: automationLogger,
|
|
101
|
+
});
|
|
77
102
|
const usage = {
|
|
78
103
|
inputTokens: promptArtifacts.estimatedInputTokens,
|
|
79
104
|
outputTokens: browserResult.answerTokens,
|
|
@@ -87,9 +112,9 @@ export async function runBrowserSessionExecution({ runOptions, browserConfig, cw
|
|
|
87
112
|
usage.totalTokens,
|
|
88
113
|
]
|
|
89
114
|
.map((value) => formatTokenCount(value))
|
|
90
|
-
.join(
|
|
115
|
+
.join("/");
|
|
91
116
|
const tokensPart = (() => {
|
|
92
|
-
const parts = tokensDisplay.split(
|
|
117
|
+
const parts = tokensDisplay.split("/");
|
|
93
118
|
if (parts.length !== 4)
|
|
94
119
|
return tokensDisplay;
|
|
95
120
|
return `↑${parts[0]} ↓${parts[1]} ↻${parts[2]} Δ${parts[3]}`;
|
|
@@ -98,7 +123,9 @@ export async function runBrowserSessionExecution({ runOptions, browserConfig, cw
|
|
|
98
123
|
elapsedMs: browserResult.tookMs,
|
|
99
124
|
model: `${runOptions.model}[browser]`,
|
|
100
125
|
tokensPart,
|
|
101
|
-
detailParts: [
|
|
126
|
+
detailParts: [
|
|
127
|
+
runOptions.file && runOptions.file.length > 0 ? `files=${runOptions.file.length}` : null,
|
|
128
|
+
],
|
|
102
129
|
});
|
|
103
130
|
log(chalk.blue(line1));
|
|
104
131
|
if (line2) {
|
|
@@ -108,12 +135,49 @@ export async function runBrowserSessionExecution({ runOptions, browserConfig, cw
|
|
|
108
135
|
usage,
|
|
109
136
|
elapsedMs: browserResult.tookMs,
|
|
110
137
|
runtime: {
|
|
138
|
+
browserTransport: browserResult.browserTransport,
|
|
111
139
|
chromePid: browserResult.chromePid,
|
|
112
140
|
chromePort: browserResult.chromePort,
|
|
113
141
|
chromeHost: browserResult.chromeHost,
|
|
142
|
+
chromeBrowserWSEndpoint: browserResult.chromeBrowserWSEndpoint,
|
|
143
|
+
chromeProfileRoot: browserResult.chromeProfileRoot,
|
|
114
144
|
userDataDir: browserResult.userDataDir,
|
|
145
|
+
chromeTargetId: browserResult.chromeTargetId,
|
|
146
|
+
tabUrl: browserResult.tabUrl,
|
|
147
|
+
conversationId: browserResult.conversationId,
|
|
115
148
|
controllerPid: browserResult.controllerPid ?? process.pid,
|
|
116
149
|
},
|
|
150
|
+
archive: browserResult.archive,
|
|
117
151
|
answerText,
|
|
152
|
+
artifacts: savedArtifacts,
|
|
118
153
|
};
|
|
119
154
|
}
|
|
155
|
+
export async function ensureSessionArtifacts(params) {
|
|
156
|
+
if (!params.sessionId || !params.answerMarkdown.trim()) {
|
|
157
|
+
return params.existingArtifacts;
|
|
158
|
+
}
|
|
159
|
+
let artifacts = params.existingArtifacts;
|
|
160
|
+
const hasReport = artifacts?.some((artifact) => artifact.kind === "deep-research-report");
|
|
161
|
+
if (params.browserConfig.researchMode === "deep" && !hasReport) {
|
|
162
|
+
const report = await saveDeepResearchReportArtifact({
|
|
163
|
+
sessionId: params.sessionId,
|
|
164
|
+
reportMarkdown: params.answerMarkdown,
|
|
165
|
+
conversationUrl: params.conversationUrl,
|
|
166
|
+
logger: params.logger,
|
|
167
|
+
}).catch(() => null);
|
|
168
|
+
artifacts = appendArtifacts(artifacts, [report]);
|
|
169
|
+
}
|
|
170
|
+
const hasTranscript = artifacts?.some((artifact) => artifact.kind === "transcript");
|
|
171
|
+
if (!hasTranscript) {
|
|
172
|
+
const transcript = await saveBrowserTranscriptArtifact({
|
|
173
|
+
sessionId: params.sessionId,
|
|
174
|
+
prompt: params.prompt,
|
|
175
|
+
answerMarkdown: params.answerMarkdown,
|
|
176
|
+
conversationUrl: params.conversationUrl,
|
|
177
|
+
artifacts,
|
|
178
|
+
logger: params.logger,
|
|
179
|
+
}).catch(() => null);
|
|
180
|
+
artifacts = appendArtifacts(artifacts, [transcript]);
|
|
181
|
+
}
|
|
182
|
+
return artifacts;
|
|
183
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
4
|
+
import { isProcessAlive } from "./profileState.js";
|
|
5
|
+
import { delay } from "./utils.js";
|
|
6
|
+
export const DEFAULT_MAX_CONCURRENT_CHATGPT_TABS = 3;
|
|
7
|
+
const REGISTRY_FILENAME = "oracle-tab-leases.json";
|
|
8
|
+
const REGISTRY_LOCK_DIRNAME = "oracle-tab-leases.lock";
|
|
9
|
+
const DEFAULT_POLL_MS = 1000;
|
|
10
|
+
const DEFAULT_STALE_MS = 6 * 60 * 60 * 1000;
|
|
11
|
+
const REGISTRY_LOCK_TIMEOUT_MS = 10_000;
|
|
12
|
+
export function normalizeMaxConcurrentTabs(value) {
|
|
13
|
+
if (value === undefined || value === null) {
|
|
14
|
+
return DEFAULT_MAX_CONCURRENT_CHATGPT_TABS;
|
|
15
|
+
}
|
|
16
|
+
const numeric = typeof value === "string" ? Number.parseInt(value, 10) : Number(value);
|
|
17
|
+
if (!Number.isFinite(numeric) || numeric <= 0) {
|
|
18
|
+
return DEFAULT_MAX_CONCURRENT_CHATGPT_TABS;
|
|
19
|
+
}
|
|
20
|
+
return Math.max(1, Math.trunc(numeric));
|
|
21
|
+
}
|
|
22
|
+
export async function acquireBrowserTabLease(profileDir, options, deps = {}) {
|
|
23
|
+
const maxConcurrentTabs = normalizeMaxConcurrentTabs(options.maxConcurrentTabs);
|
|
24
|
+
const pollMs = Math.max(50, options.pollMs ?? DEFAULT_POLL_MS);
|
|
25
|
+
const timeoutMs = Math.max(0, options.timeoutMs ?? 0);
|
|
26
|
+
const staleMs = Math.max(60_000, options.staleMs ?? DEFAULT_STALE_MS);
|
|
27
|
+
const now = deps.now ?? Date.now;
|
|
28
|
+
const pid = deps.pid ?? process.pid;
|
|
29
|
+
const leaseId = randomUUID();
|
|
30
|
+
const startedAt = now();
|
|
31
|
+
let warned = false;
|
|
32
|
+
let lastHeartbeatAt = 0;
|
|
33
|
+
for (;;) {
|
|
34
|
+
const acquired = await withRegistryLock(profileDir, async () => {
|
|
35
|
+
const registry = await readRegistry(profileDir);
|
|
36
|
+
const active = pruneStaleLeases(registry.leases, {
|
|
37
|
+
nowMs: now(),
|
|
38
|
+
staleMs,
|
|
39
|
+
isProcessAlive: deps.isProcessAlive ?? isProcessAlive,
|
|
40
|
+
});
|
|
41
|
+
if (active.length >= maxConcurrentTabs) {
|
|
42
|
+
if (active.length !== registry.leases.length) {
|
|
43
|
+
await writeRegistry(profileDir, { version: 1, leases: active });
|
|
44
|
+
}
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
const timestamp = new Date(now()).toISOString();
|
|
48
|
+
const lease = {
|
|
49
|
+
id: leaseId,
|
|
50
|
+
pid,
|
|
51
|
+
sessionId: options.sessionId,
|
|
52
|
+
chromeHost: options.chromeHost,
|
|
53
|
+
chromePort: options.chromePort,
|
|
54
|
+
createdAt: timestamp,
|
|
55
|
+
updatedAt: timestamp,
|
|
56
|
+
};
|
|
57
|
+
await writeRegistry(profileDir, { version: 1, leases: [...active, lease] });
|
|
58
|
+
return lease;
|
|
59
|
+
});
|
|
60
|
+
if (acquired) {
|
|
61
|
+
options.logger?.(`[browser] Acquired ChatGPT browser slot ${leaseId.slice(0, 8)} (${maxConcurrentTabs} max).`);
|
|
62
|
+
return {
|
|
63
|
+
id: leaseId,
|
|
64
|
+
release: async () => releaseBrowserTabLease(profileDir, leaseId, options.logger),
|
|
65
|
+
update: async (patch) => updateBrowserTabLease(profileDir, leaseId, patch),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
const elapsed = now() - startedAt;
|
|
69
|
+
if (!warned || now() - lastHeartbeatAt >= 30_000) {
|
|
70
|
+
options.logger?.(`[browser] Waiting for ChatGPT browser slot (${maxConcurrentTabs} max, ${Math.round(elapsed / 1000)}s elapsed).`);
|
|
71
|
+
warned = true;
|
|
72
|
+
lastHeartbeatAt = now();
|
|
73
|
+
}
|
|
74
|
+
if (timeoutMs > 0 && elapsed >= timeoutMs) {
|
|
75
|
+
throw new Error(`Timed out waiting for ChatGPT browser slot after ${Math.round(elapsed / 1000)}s (${maxConcurrentTabs} max).`);
|
|
76
|
+
}
|
|
77
|
+
await delay(timeoutMs > 0 ? Math.min(pollMs, timeoutMs - elapsed) : pollMs);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
export async function updateBrowserTabLease(profileDir, leaseId, patch) {
|
|
81
|
+
await withRegistryLock(profileDir, async () => {
|
|
82
|
+
const registry = await readRegistry(profileDir);
|
|
83
|
+
const leases = registry.leases.map((lease) => lease.id === leaseId
|
|
84
|
+
? { ...lease, ...patch, id: lease.id, updatedAt: new Date().toISOString() }
|
|
85
|
+
: lease);
|
|
86
|
+
await writeRegistry(profileDir, { version: 1, leases });
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
export async function releaseBrowserTabLease(profileDir, leaseId, logger) {
|
|
90
|
+
await withRegistryLock(profileDir, async () => {
|
|
91
|
+
const registry = await readRegistry(profileDir);
|
|
92
|
+
const leases = registry.leases.filter((lease) => lease.id !== leaseId);
|
|
93
|
+
await writeRegistry(profileDir, { version: 1, leases });
|
|
94
|
+
}).catch(() => undefined);
|
|
95
|
+
logger?.(`[browser] Released ChatGPT browser slot ${leaseId.slice(0, 8)}.`);
|
|
96
|
+
}
|
|
97
|
+
export async function hasOtherActiveBrowserTabLeases(profileDir, leaseId, options = {}) {
|
|
98
|
+
const now = options.now ?? Date.now;
|
|
99
|
+
const staleMs = Math.max(60_000, options.staleMs ?? DEFAULT_STALE_MS);
|
|
100
|
+
return withRegistryLock(profileDir, async () => {
|
|
101
|
+
const registry = await readRegistry(profileDir);
|
|
102
|
+
const active = pruneStaleLeases(registry.leases, {
|
|
103
|
+
nowMs: now(),
|
|
104
|
+
staleMs,
|
|
105
|
+
isProcessAlive: options.isProcessAlive ?? isProcessAlive,
|
|
106
|
+
});
|
|
107
|
+
if (active.length !== registry.leases.length) {
|
|
108
|
+
await writeRegistry(profileDir, { version: 1, leases: active });
|
|
109
|
+
}
|
|
110
|
+
return active.some((lease) => lease.id !== leaseId);
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
async function withRegistryLock(profileDir, callback) {
|
|
114
|
+
const lockDir = path.join(profileDir, REGISTRY_LOCK_DIRNAME);
|
|
115
|
+
const startedAt = Date.now();
|
|
116
|
+
for (;;) {
|
|
117
|
+
try {
|
|
118
|
+
await mkdir(lockDir, { recursive: false });
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
if (error.code !== "EEXIST") {
|
|
123
|
+
throw error;
|
|
124
|
+
}
|
|
125
|
+
if (Date.now() - startedAt > REGISTRY_LOCK_TIMEOUT_MS) {
|
|
126
|
+
await rm(lockDir, { recursive: true, force: true }).catch(() => undefined);
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
await delay(50);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
try {
|
|
133
|
+
return await callback();
|
|
134
|
+
}
|
|
135
|
+
finally {
|
|
136
|
+
await rm(lockDir, { recursive: true, force: true }).catch(() => undefined);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
async function readRegistry(profileDir) {
|
|
140
|
+
try {
|
|
141
|
+
const raw = await readFile(registryPath(profileDir), "utf8");
|
|
142
|
+
const parsed = JSON.parse(raw);
|
|
143
|
+
if (!Array.isArray(parsed.leases)) {
|
|
144
|
+
return { version: 1, leases: [] };
|
|
145
|
+
}
|
|
146
|
+
return {
|
|
147
|
+
version: 1,
|
|
148
|
+
leases: parsed.leases.filter(isLeaseRecord),
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
return { version: 1, leases: [] };
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
async function writeRegistry(profileDir, registry) {
|
|
156
|
+
await mkdir(profileDir, { recursive: true });
|
|
157
|
+
await writeFile(registryPath(profileDir), `${JSON.stringify(registry, null, 2)}\n`, "utf8");
|
|
158
|
+
}
|
|
159
|
+
function registryPath(profileDir) {
|
|
160
|
+
return path.join(profileDir, REGISTRY_FILENAME);
|
|
161
|
+
}
|
|
162
|
+
function pruneStaleLeases(leases, options) {
|
|
163
|
+
return leases.filter((lease) => {
|
|
164
|
+
if (!options.isProcessAlive(lease.pid)) {
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
const updatedAt = Date.parse(lease.updatedAt);
|
|
168
|
+
if (Number.isFinite(updatedAt) && options.nowMs - updatedAt > options.staleMs) {
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
return true;
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
function isLeaseRecord(value) {
|
|
175
|
+
if (!value || typeof value !== "object")
|
|
176
|
+
return false;
|
|
177
|
+
const record = value;
|
|
178
|
+
return (typeof record.id === "string" &&
|
|
179
|
+
typeof record.pid === "number" &&
|
|
180
|
+
typeof record.createdAt === "string" &&
|
|
181
|
+
typeof record.updatedAt === "string");
|
|
182
|
+
}
|
|
@@ -10,7 +10,7 @@ export function parseDuration(input, fallback) {
|
|
|
10
10
|
if (/^[0-9]+$/.test(lowercase)) {
|
|
11
11
|
return Number(lowercase);
|
|
12
12
|
}
|
|
13
|
-
const normalized = lowercase.replace(/\s+/g,
|
|
13
|
+
const normalized = lowercase.replace(/\s+/g, "");
|
|
14
14
|
const singleMatch = /^([0-9]+)(ms|s|m|h)$/i.exec(normalized);
|
|
15
15
|
if (singleMatch && singleMatch[0].length === normalized.length) {
|
|
16
16
|
const value = Number(singleMatch[1]);
|
|
@@ -33,13 +33,13 @@ export function parseDuration(input, fallback) {
|
|
|
33
33
|
function convertUnit(value, unitRaw) {
|
|
34
34
|
const unit = unitRaw?.toLowerCase();
|
|
35
35
|
switch (unit) {
|
|
36
|
-
case
|
|
36
|
+
case "ms":
|
|
37
37
|
return value;
|
|
38
|
-
case
|
|
38
|
+
case "s":
|
|
39
39
|
return value * 1000;
|
|
40
|
-
case
|
|
40
|
+
case "m":
|
|
41
41
|
return value * 60_000;
|
|
42
|
-
case
|
|
42
|
+
case "h":
|
|
43
43
|
return value * 3_600_000;
|
|
44
44
|
default:
|
|
45
45
|
return value;
|
|
@@ -72,11 +72,11 @@ export async function withRetries(task, options = {}) {
|
|
|
72
72
|
await delay(delayMs * attempt);
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
|
-
throw new Error(
|
|
75
|
+
throw new Error("withRetries exhausted without result");
|
|
76
76
|
}
|
|
77
77
|
export function formatBytes(size) {
|
|
78
78
|
if (!Number.isFinite(size) || size < 0) {
|
|
79
|
-
return
|
|
79
|
+
return "n/a";
|
|
80
80
|
}
|
|
81
81
|
if (size < 1024) {
|
|
82
82
|
return `${size} B`;
|
|
@@ -113,8 +113,8 @@ export function normalizeChatgptUrl(raw, fallback) {
|
|
|
113
113
|
export function isTemporaryChatUrl(url) {
|
|
114
114
|
try {
|
|
115
115
|
const parsed = new URL(url);
|
|
116
|
-
const value = (parsed.searchParams.get(
|
|
117
|
-
return value ===
|
|
116
|
+
const value = (parsed.searchParams.get("temporary-chat") ?? "").trim().toLowerCase();
|
|
117
|
+
return value === "true" || value === "1" || value === "yes";
|
|
118
118
|
}
|
|
119
119
|
catch {
|
|
120
120
|
return false;
|
package/dist/src/browserMode.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { runBrowserMode, CHATGPT_URL, DEFAULT_MODEL_STRATEGY, DEFAULT_MODEL_TARGET, parseDuration, normalizeChatgptUrl, isTemporaryChatUrl, } from
|
|
1
|
+
export { runBrowserMode, CHATGPT_URL, DEFAULT_MODEL_STRATEGY, DEFAULT_MODEL_TARGET, parseDuration, normalizeChatgptUrl, isTemporaryChatUrl, } from "./browser/index.js";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import chalk from
|
|
2
|
-
import os from
|
|
3
|
-
import path from
|
|
4
|
-
import { loadUserConfig } from
|
|
5
|
-
import { resolveRemoteServiceConfig } from
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { loadUserConfig } from "../../config.js";
|
|
5
|
+
import { resolveRemoteServiceConfig } from "../../remote/remoteServiceConfig.js";
|
|
6
6
|
export async function runBridgeClaudeConfig(options) {
|
|
7
7
|
const { config: userConfig } = await loadUserConfig();
|
|
8
8
|
const resolved = resolveRemoteServiceConfig({
|
|
@@ -12,40 +12,44 @@ export async function runBridgeClaudeConfig(options) {
|
|
|
12
12
|
env: process.env,
|
|
13
13
|
});
|
|
14
14
|
const snippet = formatClaudeMcpConfig({
|
|
15
|
-
oracleHomeDir:
|
|
16
|
-
|
|
17
|
-
path.join(os.homedir(),
|
|
15
|
+
oracleHomeDir: options.oracleHomeDir ??
|
|
16
|
+
process.env.ORACLE_HOME_DIR ??
|
|
17
|
+
path.join(os.homedir(), options.localBrowser ? ".oracle" : ".oracle-local"),
|
|
18
|
+
browserProfileDir: options.browserProfileDir ??
|
|
19
|
+
process.env.ORACLE_BROWSER_PROFILE_DIR ??
|
|
20
|
+
path.join(os.homedir(), options.localBrowser ? ".oracle" : ".oracle-local", "browser-profile"),
|
|
18
21
|
remoteHost: resolved.host,
|
|
19
22
|
remoteToken: resolved.token,
|
|
20
23
|
includeToken: Boolean(options.printToken),
|
|
24
|
+
localBrowser: Boolean(options.localBrowser),
|
|
21
25
|
});
|
|
22
26
|
console.log(snippet);
|
|
23
|
-
if (!options.printToken) {
|
|
24
|
-
console.
|
|
25
|
-
console.
|
|
27
|
+
if (!options.printToken && !options.localBrowser) {
|
|
28
|
+
console.error("");
|
|
29
|
+
console.error(chalk.dim("Tip: rerun with --print-token to include ORACLE_REMOTE_TOKEN in the snippet."));
|
|
26
30
|
}
|
|
27
31
|
}
|
|
28
|
-
export function formatClaudeMcpConfig({ oracleHomeDir, browserProfileDir, remoteHost, remoteToken, includeToken, }) {
|
|
32
|
+
export function formatClaudeMcpConfig({ oracleHomeDir, browserProfileDir, remoteHost, remoteToken, includeToken, localBrowser = false, }) {
|
|
29
33
|
const env = {};
|
|
30
34
|
// biome-ignore lint/complexity/useLiteralKeys: env vars are uppercase and include underscores.
|
|
31
|
-
env[
|
|
35
|
+
env["ORACLE_ENGINE"] = "browser";
|
|
32
36
|
// biome-ignore lint/complexity/useLiteralKeys: env vars are uppercase and include underscores.
|
|
33
|
-
env[
|
|
37
|
+
env["ORACLE_HOME_DIR"] = oracleHomeDir;
|
|
34
38
|
// biome-ignore lint/complexity/useLiteralKeys: env vars are uppercase and include underscores.
|
|
35
|
-
env[
|
|
36
|
-
if (remoteHost) {
|
|
39
|
+
env["ORACLE_BROWSER_PROFILE_DIR"] = browserProfileDir;
|
|
40
|
+
if (remoteHost && !localBrowser) {
|
|
37
41
|
// biome-ignore lint/complexity/useLiteralKeys: env vars are uppercase and include underscores.
|
|
38
|
-
env[
|
|
42
|
+
env["ORACLE_REMOTE_HOST"] = remoteHost;
|
|
39
43
|
// biome-ignore lint/complexity/useLiteralKeys: env vars are uppercase and include underscores.
|
|
40
|
-
env[
|
|
44
|
+
env["ORACLE_REMOTE_TOKEN"] = includeToken ? (remoteToken ?? "<YOUR_TOKEN>") : "<YOUR_TOKEN>";
|
|
41
45
|
}
|
|
42
46
|
// Claude Code supports project-scoped `.mcp.json` config files:
|
|
43
47
|
// https://docs.anthropic.com/en/docs/claude-code/mcp
|
|
44
48
|
return JSON.stringify({
|
|
45
49
|
mcpServers: {
|
|
46
50
|
oracle: {
|
|
47
|
-
type:
|
|
48
|
-
command:
|
|
51
|
+
type: "stdio",
|
|
52
|
+
command: "oracle-mcp",
|
|
49
53
|
args: [],
|
|
50
54
|
env,
|
|
51
55
|
},
|
|
@@ -1,29 +1,33 @@
|
|
|
1
|
-
import fs from
|
|
2
|
-
import path from
|
|
3
|
-
import chalk from
|
|
4
|
-
import { configPath as defaultConfigPath } from
|
|
5
|
-
import { parseBridgeConnectionString, readBridgeConnectionArtifact, looksLikePath, } from
|
|
6
|
-
import { readUserConfigFile, writeUserConfigFile } from
|
|
7
|
-
import { checkRemoteHealth } from
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import { configPath as defaultConfigPath } from "../../config.js";
|
|
5
|
+
import { parseBridgeConnectionString, readBridgeConnectionArtifact, looksLikePath, } from "../../bridge/connection.js";
|
|
6
|
+
import { readUserConfigFile, writeUserConfigFile } from "../../bridge/userConfigFile.js";
|
|
7
|
+
import { checkRemoteHealth } from "../../remote/health.js";
|
|
8
8
|
export async function runBridgeClient(options) {
|
|
9
9
|
const connectRaw = options.connect?.trim();
|
|
10
10
|
if (!connectRaw) {
|
|
11
|
-
throw new Error(
|
|
11
|
+
throw new Error("Missing --connect. Provide a connection string or a bridge-connection.json path.");
|
|
12
12
|
}
|
|
13
13
|
const { remoteHost, remoteToken, tunnel } = await resolveConnection(connectRaw);
|
|
14
14
|
if (options.test !== false) {
|
|
15
|
-
const health = await checkRemoteHealth({
|
|
15
|
+
const health = await checkRemoteHealth({
|
|
16
|
+
host: remoteHost,
|
|
17
|
+
token: remoteToken,
|
|
18
|
+
timeoutMs: 5000,
|
|
19
|
+
});
|
|
16
20
|
if (!health.ok) {
|
|
17
|
-
const suffix = health.statusCode ? ` (HTTP ${health.statusCode})` :
|
|
18
|
-
throw new Error(`Remote service health check failed: ${health.error ??
|
|
21
|
+
const suffix = health.statusCode ? ` (HTTP ${health.statusCode})` : "";
|
|
22
|
+
throw new Error(`Remote service health check failed: ${health.error ?? "unknown error"}${suffix}`);
|
|
19
23
|
}
|
|
20
|
-
console.log(chalk.green(`Remote service OK (${remoteHost})${health.version ? ` — oracle ${health.version}` :
|
|
24
|
+
console.log(chalk.green(`Remote service OK (${remoteHost})${health.version ? ` — oracle ${health.version}` : ""}`));
|
|
21
25
|
}
|
|
22
26
|
const configFilePath = options.config?.trim() || defaultConfigPath();
|
|
23
27
|
if (options.writeConfig !== false) {
|
|
24
28
|
const { config } = await readUserConfigFile(configFilePath);
|
|
25
|
-
const next = { ...config, browser: { ...
|
|
26
|
-
next.browser = { ...
|
|
29
|
+
const next = { ...config, browser: { ...config.browser } };
|
|
30
|
+
next.browser = { ...next.browser };
|
|
27
31
|
next.browser.remoteHost = remoteHost;
|
|
28
32
|
next.browser.remoteToken = remoteToken;
|
|
29
33
|
if (tunnel) {
|
|
@@ -38,19 +42,19 @@ export async function runBridgeClient(options) {
|
|
|
38
42
|
await writeUserConfigFile(configFilePath, next);
|
|
39
43
|
console.log(chalk.green(`Wrote remote config to ${configFilePath}`));
|
|
40
44
|
}
|
|
41
|
-
console.log(
|
|
42
|
-
console.log(
|
|
45
|
+
console.log("");
|
|
46
|
+
console.log("Next:");
|
|
43
47
|
console.log(chalk.dim(`- oracle --engine browser -p "hello" --file README.md`));
|
|
44
48
|
if (options.printEnv) {
|
|
45
|
-
console.log(
|
|
46
|
-
console.log(
|
|
49
|
+
console.log("");
|
|
50
|
+
console.log("# Optional env overrides (paste into your shell):");
|
|
47
51
|
console.log(`export ORACLE_ENGINE=browser`);
|
|
48
52
|
console.log(`export ORACLE_REMOTE_HOST=${shellQuote(remoteHost)}`);
|
|
49
53
|
console.log(`export ORACLE_REMOTE_TOKEN=${shellQuote(remoteToken)}`);
|
|
50
54
|
}
|
|
51
55
|
}
|
|
52
56
|
async function resolveConnection(input) {
|
|
53
|
-
if (input.includes(
|
|
57
|
+
if (input.includes("://")) {
|
|
54
58
|
return { ...parseBridgeConnectionString(input) };
|
|
55
59
|
}
|
|
56
60
|
const resolvedPath = looksLikePath(input) ? path.resolve(process.cwd(), input) : null;
|
|
@@ -58,7 +62,11 @@ async function resolveConnection(input) {
|
|
|
58
62
|
const stat = await fs.stat(resolvedPath).catch(() => null);
|
|
59
63
|
if (stat?.isFile()) {
|
|
60
64
|
const artifact = await readBridgeConnectionArtifact(resolvedPath);
|
|
61
|
-
return {
|
|
65
|
+
return {
|
|
66
|
+
remoteHost: artifact.remoteHost,
|
|
67
|
+
remoteToken: artifact.remoteToken,
|
|
68
|
+
tunnel: artifact.tunnel,
|
|
69
|
+
};
|
|
62
70
|
}
|
|
63
71
|
if (stat) {
|
|
64
72
|
throw new Error(`--connect points to ${resolvedPath}, but it is not a file.`);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import chalk from
|
|
2
|
-
import { loadUserConfig } from
|
|
3
|
-
import { resolveRemoteServiceConfig } from
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { loadUserConfig } from "../../config.js";
|
|
3
|
+
import { resolveRemoteServiceConfig } from "../../remote/remoteServiceConfig.js";
|
|
4
4
|
export async function runBridgeCodexConfig(options) {
|
|
5
5
|
const { config: userConfig } = await loadUserConfig();
|
|
6
6
|
const resolved = resolveRemoteServiceConfig({
|
|
@@ -16,28 +16,28 @@ export async function runBridgeCodexConfig(options) {
|
|
|
16
16
|
});
|
|
17
17
|
console.log(snippet);
|
|
18
18
|
if (!options.printToken) {
|
|
19
|
-
console.
|
|
20
|
-
console.
|
|
19
|
+
console.error("");
|
|
20
|
+
console.error(chalk.dim("Tip: rerun with --print-token to include ORACLE_REMOTE_TOKEN in the snippet."));
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
23
|
export function formatCodexMcpSnippet({ remoteHost, remoteToken, includeToken, }) {
|
|
24
|
-
const hostValue = remoteHost ??
|
|
25
|
-
const tokenValue = includeToken ? remoteToken ??
|
|
24
|
+
const hostValue = remoteHost ?? "127.0.0.1:9473";
|
|
25
|
+
const tokenValue = includeToken ? (remoteToken ?? "<YOUR_TOKEN>") : "<YOUR_TOKEN>";
|
|
26
26
|
return [
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
"# ~/.codex/config.toml",
|
|
28
|
+
"",
|
|
29
|
+
"[mcp.servers.oracle]",
|
|
30
30
|
'command = "oracle-mcp"',
|
|
31
|
-
|
|
31
|
+
"args = []",
|
|
32
32
|
`env = { ORACLE_ENGINE = "browser", ORACLE_REMOTE_HOST = "${escapeTomlString(hostValue)}", ORACLE_REMOTE_TOKEN = "${escapeTomlString(tokenValue)}" }`,
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
"",
|
|
34
|
+
"# If you prefer npx:",
|
|
35
|
+
"# [mcp.servers.oracle]",
|
|
36
36
|
'# command = "npx"',
|
|
37
37
|
'# args = ["-y", "@steipete/oracle", "oracle-mcp"]',
|
|
38
38
|
`# env = { ORACLE_ENGINE = "browser", ORACLE_REMOTE_HOST = "${escapeTomlString(hostValue)}", ORACLE_REMOTE_TOKEN = "${escapeTomlString(tokenValue)}" }`,
|
|
39
|
-
].join(
|
|
39
|
+
].join("\n");
|
|
40
40
|
}
|
|
41
41
|
function escapeTomlString(value) {
|
|
42
|
-
return value.replace(/\\/g,
|
|
42
|
+
return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
43
43
|
}
|