@steipete/oracle 0.17.2 → 0.17.3
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/dist/bin/oracle-cli.js +0 -0
- package/dist/bin/oracle-mcp.js +0 -0
- package/dist/bin/oracle.js +683 -0
- package/dist/docs-site/bridge.html +1 -17
- package/dist/docs-site/browser-mode.html +18 -26
- package/dist/docs-site/cli-reference.html +6 -6
- package/dist/docs-site/configuration.html +7 -33
- package/dist/docs-site/followup.html +9 -16
- package/dist/docs-site/gemini.html +6 -10
- package/dist/docs-site/index.html +7 -7
- package/dist/docs-site/install.html +1 -1
- package/dist/docs-site/manual-tests.html +2 -4
- package/dist/docs-site/mcp.html +5 -28
- package/dist/docs-site/mythical-pro-agents.html +10 -10
- package/dist/docs-site/openai-endpoints.html +1 -24
- package/dist/docs-site/quickstart.html +1 -1
- package/dist/docs-site/sessions.html +5 -6
- package/dist/docs-site/spec.html +1 -1
- package/dist/docs-site/windows-work.html +1 -2
- package/dist/docs-site/windows.html +1 -1
- package/dist/markdansi/types/index.js +4 -0
- package/dist/oracle/bin/oracle-cli.js +472 -0
- package/dist/oracle/src/browser/actions/assistantResponse.js +471 -0
- package/dist/oracle/src/browser/actions/attachments.js +82 -0
- package/dist/oracle/src/browser/actions/modelSelection.js +190 -0
- package/dist/oracle/src/browser/actions/navigation.js +75 -0
- package/dist/oracle/src/browser/actions/promptComposer.js +167 -0
- package/dist/oracle/src/browser/chromeLifecycle.js +104 -0
- package/dist/oracle/src/browser/config.js +33 -0
- package/dist/oracle/src/browser/constants.js +40 -0
- package/dist/oracle/src/browser/cookies.js +210 -0
- package/dist/oracle/src/browser/domDebug.js +36 -0
- package/dist/oracle/src/browser/index.js +331 -0
- package/dist/oracle/src/browser/pageActions.js +5 -0
- package/dist/oracle/src/browser/prompt.js +88 -0
- package/dist/oracle/src/browser/promptSummary.js +20 -0
- package/dist/oracle/src/browser/sessionRunner.js +80 -0
- package/dist/oracle/src/browser/types.js +1 -0
- package/dist/oracle/src/browser/utils.js +62 -0
- package/dist/oracle/src/browserMode.js +1 -0
- package/dist/oracle/src/cli/browserConfig.js +44 -0
- package/dist/oracle/src/cli/dryRun.js +59 -0
- package/dist/oracle/src/cli/engine.js +17 -0
- package/dist/oracle/src/cli/errorUtils.js +9 -0
- package/dist/oracle/src/cli/help.js +70 -0
- package/dist/oracle/src/cli/markdownRenderer.js +15 -0
- package/dist/oracle/src/cli/options.js +103 -0
- package/dist/oracle/src/cli/promptRequirement.js +14 -0
- package/dist/oracle/src/cli/rootAlias.js +30 -0
- package/dist/oracle/src/cli/sessionCommand.js +77 -0
- package/dist/oracle/src/cli/sessionDisplay.js +270 -0
- package/dist/oracle/src/cli/sessionRunner.js +94 -0
- package/dist/oracle/src/heartbeat.js +43 -0
- package/dist/oracle/src/oracle/client.js +48 -0
- package/dist/oracle/src/oracle/config.js +29 -0
- package/dist/oracle/src/oracle/errors.js +101 -0
- package/dist/oracle/src/oracle/files.js +220 -0
- package/dist/oracle/src/oracle/format.js +33 -0
- package/dist/oracle/src/oracle/fsAdapter.js +7 -0
- package/dist/oracle/src/oracle/oscProgress.js +60 -0
- package/dist/oracle/src/oracle/request.js +48 -0
- package/dist/oracle/src/oracle/run.js +444 -0
- package/dist/oracle/src/oracle/tokenStats.js +39 -0
- package/dist/oracle/src/oracle/types.js +1 -0
- package/dist/oracle/src/oracle.js +9 -0
- package/dist/oracle/src/sessionManager.js +205 -0
- package/dist/oracle/src/version.js +39 -0
- package/dist/scripts/chrome/browser-tools.js +295 -0
- package/dist/src/browser/actions/assistantResponse.js +71 -23
- package/dist/src/browser/actions/thinkingTime.js +16 -9
- package/dist/src/browser/chromeCookies.js +312 -0
- package/dist/src/browser/index.js +5 -1
- package/dist/src/browser/keytarShim.js +56 -0
- package/dist/src/browser/policies.js +4 -0
- package/dist/src/browser/profileSync.js +141 -0
- package/dist/src/browser/projectSourcesRunner.js +2 -2
- package/dist/src/browser/reattach.js +47 -35
- package/dist/src/browser/windowsCookies.js +219 -0
- package/dist/src/cli/browserConfig.js +8 -2
- package/dist/src/cli/browserDefaults.js +6 -1
- package/dist/src/cli/projectSources.js +8 -1
- package/dist/src/mcp/tools/consult.js +1 -1
- package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
- package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/Info.plist +20 -0
- package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
- package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/Resources/OracleIcon.icns +0 -0
- package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/_CodeSignature/CodeResources +128 -0
- package/package.json +8 -8
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/Info.plist +20 -0
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/Resources/OracleIcon.icns +0 -0
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/_CodeSignature/CodeResources +128 -0
|
@@ -270,7 +270,7 @@ body:not(.home) .doc>h1:first-child{display:none}
|
|
|
270
270
|
<p><code>--browser-manual-login</code> skips Keychain cookie copy (no permission popups) and reuses a persistent automation profile under <code>~/.oracle/browser/</code>.</p>
|
|
271
271
|
<h3 id="render-and-copy"><a class="anchor" href="#render-and-copy" aria-label="Anchor link">#</a>Render and copy</h3>
|
|
272
272
|
<pre class="shiki github-dark-dimmed" style="background-color:var(--code-bg);color:var(--code-fg)" tabindex="0"><code class="language-bash"><span class="line"><span style="color:#F69D50">oracle</span><span style="color:#6CB6FF"> --render</span><span style="color:#6CB6FF"> --copy</span><span style="color:#6CB6FF"> -p</span><span style="color:#96D0FF"> "Architecture review"</span><span style="color:#6CB6FF"> --file</span><span style="color:#96D0FF"> "src/**/*.ts"</span></span></code></pre>
|
|
273
|
-
<p>The bundle is on your clipboard. Paste it into ChatGPT, Claude, Gemini, AI Studio, or wherever you want the answer
|
|
273
|
+
<p>The bundle is on your clipboard. Paste it into ChatGPT, Claude, Gemini, AI Studio, or wherever you want the answer.</p>
|
|
274
274
|
<h2 id="3-preview-before-you-spend"><a class="anchor" href="#3-preview-before-you-spend" aria-label="Anchor link">#</a>3. Preview before you spend</h2>
|
|
275
275
|
<pre class="shiki github-dark-dimmed" style="background-color:var(--code-bg);color:var(--code-fg)" tabindex="0"><code class="language-bash"><span class="line"><span style="color:#F69D50">oracle</span><span style="color:#6CB6FF"> --dry-run</span><span style="color:#96D0FF"> summary</span><span style="color:#6CB6FF"> --files-report</span><span style="color:#F47067"> \</span></span>
|
|
276
276
|
<span class="line"><span style="color:#6CB6FF"> -p</span><span style="color:#96D0FF"> "Audit the storage layer for race conditions"</span><span style="color:#F47067"> \</span></span>
|
|
@@ -251,7 +251,7 @@ body:not(.home) .doc>h1:first-child{display:none}
|
|
|
251
251
|
<span class="line"><span>├── prompt.md # assembled bundle (what was sent)</span></span>
|
|
252
252
|
<span class="line"><span>├── response.md # the model's answer (when complete)</span></span>
|
|
253
253
|
<span class="line"><span>├── log.jsonl # per-event log</span></span>
|
|
254
|
-
<span class="line"><span>└── artifacts/ # browser-only: transcript, generated images
|
|
254
|
+
<span class="line"><span>└── artifacts/ # browser-only: transcript, generated images, deep-research-report.md</span></span></code></pre>
|
|
255
255
|
<p>Override the root with <code>ORACLE_HOME_DIR=/some/path</code>.</p>
|
|
256
256
|
<h2 id="listing"><a class="anchor" href="#listing" aria-label="Anchor link">#</a>Listing</h2>
|
|
257
257
|
<pre class="shiki github-dark-dimmed" style="background-color:var(--code-bg);color:var(--code-fg)" tabindex="0"><code class="language-bash"><span class="line"><span style="color:#F69D50">oracle</span><span style="color:#96D0FF"> status</span><span style="color:#768390"> # last 20 sessions</span></span>
|
|
@@ -277,10 +277,9 @@ body:not(.home) .doc>h1:first-child{display:none}
|
|
|
277
277
|
<span class="line"><span>Models: 3 parallel</span></span>
|
|
278
278
|
<span class="line"><span>Detach: yes, polling</span></span>
|
|
279
279
|
<span class="line"><span>Reattach: oracle session 20260515-name-panel</span></span></code></pre>
|
|
280
|
-
<p><code>oracle status</code> uses compact mode labels such as <code>api/fg</code>, <code>api/bg</code>,
|
|
281
|
-
<p>To
|
|
280
|
+
<p><code>oracle status</code> uses compact mode labels such as <code>api/fg</code>, <code>api/bg</code>, and <code>br/fg</code>; <code>oracle session <id></code> shows the persisted execution state.</p>
|
|
281
|
+
<p>To block in the original command, pass <code>--wait</code>:</p>
|
|
282
282
|
<pre class="shiki github-dark-dimmed" style="background-color:var(--code-bg);color:var(--code-fg)" tabindex="0"><code class="language-bash"><span class="line"><span style="color:#F69D50">oracle</span><span style="color:#6CB6FF"> --wait</span><span style="color:#6CB6FF"> --model</span><span style="color:#96D0FF"> gpt-5.5-pro</span><span style="color:#6CB6FF"> -p</span><span style="color:#96D0FF"> "Long architecture review"</span><span style="color:#6CB6FF"> --file</span><span style="color:#96D0FF"> "src/**"</span></span></code></pre>
|
|
283
|
-
<p>For API runs, <code>--wait</code> executes the request in the foreground. Local Pro browser runs use a detached worker even with <code>--wait</code>, while the original CLI stays attached to the session log. This lets the browser worker capture and save the answer if the foreground CLI exits unexpectedly. Pressing Ctrl-C still cancels the worker and exits with code 130.</p>
|
|
284
283
|
<p>For browser runs, ChatGPT sometimes redirects mid-page-load. The auto-reattach flags poll the existing tab without manual intervention:</p>
|
|
285
284
|
<pre class="shiki github-dark-dimmed" style="background-color:var(--code-bg);color:var(--code-fg)" tabindex="0"><code class="language-bash"><span class="line"><span style="color:#F69D50">oracle</span><span style="color:#6CB6FF"> --engine</span><span style="color:#96D0FF"> browser</span><span style="color:#F47067"> \</span></span>
|
|
286
285
|
<span class="line"><span style="color:#6CB6FF"> --browser-timeout</span><span style="color:#96D0FF"> 6m</span><span style="color:#F47067"> \</span></span>
|
|
@@ -293,10 +292,10 @@ body:not(.home) .doc>h1:first-child{display:none}
|
|
|
293
292
|
<pre class="shiki github-dark-dimmed" style="background-color:var(--code-bg);color:var(--code-fg)" tabindex="0"><code class="language-bash"><span class="line"><span style="color:#F69D50">oracle</span><span style="color:#96D0FF"> restart</span><span style="color:#F47067"> <</span><span style="color:#96D0FF">i</span><span style="color:#ADBAC7">d</span><span style="color:#F47067">></span><span style="color:#768390"> # re-run with the same prompt + files</span></span></code></pre>
|
|
294
293
|
<p>Useful when a transient browser/API error truncated the answer. Restart copies the bundle, opens a new session, and links lineage back.</p>
|
|
295
294
|
<h2 id="follow-up"><a class="anchor" href="#follow-up" aria-label="Anchor link">#</a>Follow up</h2>
|
|
296
|
-
<p>Continue
|
|
295
|
+
<p>Continue an OpenAI / Azure Responses API session with new context:</p>
|
|
297
296
|
<pre class="shiki github-dark-dimmed" style="background-color:var(--code-bg);color:var(--code-fg)" tabindex="0"><code class="language-bash"><span class="line"><span style="color:#F69D50">oracle</span><span style="color:#6CB6FF"> --followup</span><span style="color:#F47067"> <</span><span style="color:#96D0FF">i</span><span style="color:#ADBAC7">d</span><span style="color:#F47067">></span><span style="color:#6CB6FF"> -p</span><span style="color:#96D0FF"> "Re-evaluate with these files"</span><span style="color:#F47067"> \</span></span>
|
|
298
297
|
<span class="line"><span style="color:#6CB6FF"> --file</span><span style="color:#96D0FF"> "src/migrations/**"</span></span></code></pre>
|
|
299
|
-
<p>
|
|
298
|
+
<p>For multi-model parents, pick the lineage with <code>--followup-model</code>. See <a href="followup.html">Followup</a> for the full flow and the formats <code>--followup</code> accepts (session ids, slugs, or <code>resp_…</code> response ids).</p>
|
|
300
299
|
<h2 id="background-mode"><a class="anchor" href="#background-mode" aria-label="Anchor link">#</a>Background mode</h2>
|
|
301
300
|
<p>Force a Responses API run into background mode (create + retrieve) regardless of model defaults:</p>
|
|
302
301
|
<pre class="shiki github-dark-dimmed" style="background-color:var(--code-bg);color:var(--code-fg)" tabindex="0"><code class="language-bash"><span class="line"><span style="color:#F69D50">oracle</span><span style="color:#6CB6FF"> --background</span><span style="color:#6CB6FF"> --model</span><span style="color:#96D0FF"> gpt-5.5-pro</span><span style="color:#6CB6FF"> -p</span><span style="color:#96D0FF"> "..."</span><span style="color:#6CB6FF"> --file</span><span style="color:#96D0FF"> "src/**"</span></span>
|
package/dist/docs-site/spec.html
CHANGED
|
@@ -275,7 +275,7 @@ body:not(.home) .doc>h1:first-child{display:none}
|
|
|
275
275
|
<p>Auto-pick rules:</p>
|
|
276
276
|
<ol>
|
|
277
277
|
<li>If <code>--engine</code> is set, use it.</li>
|
|
278
|
-
<li>Else if <code>engine</code> is set in
|
|
278
|
+
<li>Else if <code>engine</code> is set in <code>~/.oracle/config.json</code>, use it.</li>
|
|
279
279
|
<li>Else if <code>OPENAI_API_KEY</code> (or another supported API key) is set, use API.</li>
|
|
280
280
|
<li>Else use browser.</li>
|
|
281
281
|
</ol>
|
|
@@ -249,10 +249,9 @@ body:not(.home) .doc>h1:first-child{display:none}
|
|
|
249
249
|
<ul>
|
|
250
250
|
<li>Browser engine now allowed on Windows; expect more flakiness. If automation fails, rerun with <code>--engine api --wait</code> or point <code>--remote-chrome</code> to a running Chrome with remote debugging.</li>
|
|
251
251
|
<li>Chrome DevTools via mcporter: <code>chrome-devtools</code> server needs <code>CHROME_DEVTOOLS_URL</code> from a live session; without it <code>mcporter call chrome-devtools.*</code> fails. Expect this to be unset on Windows unless you bring your own Chrome session/URL.</li>
|
|
252
|
-
<li>
|
|
252
|
+
<li>agent-scripts bash helpers: <code>runner</code>/<code>scripts/committer</code> can fail under PowerShell/CMD because of CRLF and bash expectations. If they explode, run commands directly (<code>pnpm ...</code>, <code>git add/commit</code>) instead.</li>
|
|
253
253
|
<li>browser-tools binary: not built in <code>agent-scripts/bin</code> on Windows; <code>pnpm tsx scripts/browser-tools.ts</code> also fails there (no package manifest). Use a macOS-built binary or run from macOS if you need it.</li>
|
|
254
254
|
<li>Prefer PowerShell + pnpm directly; watch for CRLF warnings when touching tracked files.</li>
|
|
255
|
-
<li>WSL browser launch host detection: a systemd-resolved stub such as <code>nameserver 127.0.0.53</code> is guest loopback, not the Windows host. Keep resolver-derived non-loopback hosts for Windows Chrome compatibility, but route resolver-derived <code>127/8</code> values to the standard local Chrome launcher.</li>
|
|
256
255
|
</ul>
|
|
257
256
|
<p>Future Windows gotchas belong here. Update this doc when you learn something new.</p>
|
|
258
257
|
<ul>
|
|
@@ -252,7 +252,7 @@ body:not(.home) .doc>h1:first-child{display:none}
|
|
|
252
252
|
<li>Manual login flow: run with <code>--browser-manual-login</code> and sign into chatgpt.com in the opened Chrome; Oracle waits until the session is active. For initial login/setup or debugging, add <code>--browser-keep-browser</code> to keep the window open after the run; otherwise Oracle closes Chrome but preserves the profile at <code>~/.oracle/browser-profile</code> (override with <code>ORACLE_BROWSER_PROFILE_DIR</code> or <code>browser.manualLoginProfileDir</code> in <code>~/.oracle/config.json</code>). If that automation Chrome is already running with remote debugging enabled (DevToolsActivePort present), reuse it instead of relaunching by pointing Oracle at it via <code>--remote-chrome <host:port></code>.</li>
|
|
253
253
|
<li>Cookie paths: preferred path is <code>%LOCALAPPDATA%\\Google\\Chrome\\User Data\\<Profile>\\Network\\Cookies</code>. If that errors, try the top-level <code>Cookies</code> file or supply the exact path via <code>--browser-cookie-path</code>.</li>
|
|
254
254
|
<li>mcporter chrome-devtools: requires a valid <code>CHROME_DEVTOOLS_URL</code> from a live session; otherwise calls will fail.</li>
|
|
255
|
-
<li>
|
|
255
|
+
<li>agent-scripts helpers (<code>runner</code>, <code>scripts/committer</code>) are bash-based and may fail under PowerShell/CMD; run commands directly if they misbehave.</li>
|
|
256
256
|
</ul><nav class="page-nav" aria-label="Pager"><a class="page-nav-prev" href="linux.html"><small>Previous</small><span>Linux Notes</span></a><a class="page-nav-next" href="windows-work.html"><small>Next</small><span>Windows Work</span></a></nav></article>
|
|
257
257
|
|
|
258
258
|
</div>
|
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import 'dotenv/config';
|
|
3
|
+
import { spawn } from 'node:child_process';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { Command, Option } from 'commander';
|
|
6
|
+
import { resolveEngine } from '../src/cli/engine.js';
|
|
7
|
+
import { shouldRequirePrompt } from '../src/cli/promptRequirement.js';
|
|
8
|
+
import chalk from 'chalk';
|
|
9
|
+
import { ensureSessionStorage, initializeSession, readSessionMetadata, createSessionLogWriter, deleteSessionsOlderThan, } from '../src/sessionManager.js';
|
|
10
|
+
import { runOracle, renderPromptMarkdown, readFiles } from '../src/oracle.js';
|
|
11
|
+
import { CHATGPT_URL } from '../src/browserMode.js';
|
|
12
|
+
import { applyHelpStyling } from '../src/cli/help.js';
|
|
13
|
+
import { collectPaths, parseFloatOption, parseIntOption, parseSearchOption, usesDefaultStatusFilters, resolvePreviewMode, normalizeModelOption, resolveApiModel, inferModelFromLabel, parseHeartbeatOption, } from '../src/cli/options.js';
|
|
14
|
+
import { buildBrowserConfig, resolveBrowserModelLabel } from '../src/cli/browserConfig.js';
|
|
15
|
+
import { performSessionRun } from '../src/cli/sessionRunner.js';
|
|
16
|
+
import { attachSession, showStatus } from '../src/cli/sessionDisplay.js';
|
|
17
|
+
import { handleSessionCommand, formatSessionCleanupMessage } from '../src/cli/sessionCommand.js';
|
|
18
|
+
import { isErrorLogged } from '../src/cli/errorUtils.js';
|
|
19
|
+
import { handleSessionAlias, handleStatusFlag } from '../src/cli/rootAlias.js';
|
|
20
|
+
import { getCliVersion } from '../src/version.js';
|
|
21
|
+
import { runDryRunSummary } from '../src/cli/dryRun.js';
|
|
22
|
+
const VERSION = getCliVersion();
|
|
23
|
+
const CLI_ENTRYPOINT = fileURLToPath(import.meta.url);
|
|
24
|
+
const rawCliArgs = process.argv.slice(2);
|
|
25
|
+
const isTty = process.stdout.isTTY;
|
|
26
|
+
const program = new Command();
|
|
27
|
+
applyHelpStyling(program, VERSION, isTty);
|
|
28
|
+
program.hook('preAction', (thisCommand) => {
|
|
29
|
+
if (thisCommand !== program) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (rawCliArgs.some((arg) => arg === '--help' || arg === '-h')) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const opts = thisCommand.optsWithGlobals();
|
|
36
|
+
const positional = thisCommand.args?.[0];
|
|
37
|
+
if (!opts.prompt && positional) {
|
|
38
|
+
opts.prompt = positional;
|
|
39
|
+
thisCommand.setOptionValue('prompt', positional);
|
|
40
|
+
}
|
|
41
|
+
if (shouldRequirePrompt(rawCliArgs, opts)) {
|
|
42
|
+
console.log(chalk.yellow('Prompt is required. Provide it via --prompt "<text>" or positional [prompt].'));
|
|
43
|
+
thisCommand.help({ error: false });
|
|
44
|
+
process.exitCode = 1;
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
program
|
|
49
|
+
.name('oracle')
|
|
50
|
+
.description('One-shot GPT-5 Pro / GPT-5.1 tool for hard questions that benefit from large file context and server-side search.')
|
|
51
|
+
.version(VERSION)
|
|
52
|
+
.argument('[prompt]', 'Prompt text (shorthand for --prompt).')
|
|
53
|
+
.option('-p, --prompt <text>', 'User prompt to send to the model.')
|
|
54
|
+
.option('-f, --file <paths...>', 'Files/directories or glob patterns to attach (prefix with !pattern to exclude). Files larger than 1 MB are rejected automatically.', collectPaths, [])
|
|
55
|
+
.option('-s, --slug <words>', 'Custom session slug (3-5 words).')
|
|
56
|
+
.option('-m, --model <model>', 'Model to target (gpt-5-pro | gpt-5.1, or ChatGPT labels like "5.1 Instant" for browser runs).', normalizeModelOption, 'gpt-5-pro')
|
|
57
|
+
.addOption(new Option('-e, --engine <mode>', 'Execution engine (api | browser). If omitted, Oracle picks api when OPENAI_API_KEY is set, otherwise browser.').choices(['api', 'browser']))
|
|
58
|
+
.option('--files-report', 'Show token usage per attached file (also prints automatically when files exceed the token budget).', false)
|
|
59
|
+
.option('-v, --verbose', 'Enable verbose logging for all operations.', false)
|
|
60
|
+
.option('--dry-run', 'Validate inputs and show token estimates without calling the model.', false)
|
|
61
|
+
.addOption(new Option('--preview [mode]', 'Preview the request without calling the API (summary | json | full).')
|
|
62
|
+
.choices(['summary', 'json', 'full'])
|
|
63
|
+
.preset('summary'))
|
|
64
|
+
.addOption(new Option('--exec-session <id>').hideHelp())
|
|
65
|
+
.addOption(new Option('--session <id>').hideHelp())
|
|
66
|
+
.addOption(new Option('--status', 'Show stored sessions (alias for `oracle status`).').default(false).hideHelp())
|
|
67
|
+
.option('--render-markdown', 'Emit the assembled markdown bundle for prompt + files and exit.', false)
|
|
68
|
+
.option('--verbose-render', 'Show render/TTY diagnostics when replaying sessions.', false)
|
|
69
|
+
.addOption(new Option('--search <mode>', 'Set server-side search behavior (on/off).')
|
|
70
|
+
.argParser(parseSearchOption)
|
|
71
|
+
.hideHelp())
|
|
72
|
+
.addOption(new Option('--max-input <tokens>', 'Override the input token budget for the selected model.')
|
|
73
|
+
.argParser(parseIntOption)
|
|
74
|
+
.hideHelp())
|
|
75
|
+
.addOption(new Option('--max-output <tokens>', 'Override the max output tokens for the selected model.')
|
|
76
|
+
.argParser(parseIntOption)
|
|
77
|
+
.hideHelp())
|
|
78
|
+
.addOption(new Option('--browser', '(deprecated) Use --engine browser instead.').default(false).hideHelp())
|
|
79
|
+
.addOption(new Option('--browser-chrome-profile <name>', 'Chrome profile name/path for cookie reuse.').hideHelp())
|
|
80
|
+
.addOption(new Option('--browser-chrome-path <path>', 'Explicit Chrome or Chromium executable path.').hideHelp())
|
|
81
|
+
.addOption(new Option('--browser-url <url>', `Override the ChatGPT URL (default ${CHATGPT_URL}).`).hideHelp())
|
|
82
|
+
.addOption(new Option('--browser-timeout <ms|s|m>', 'Maximum time to wait for an answer (default 900s).').hideHelp())
|
|
83
|
+
.addOption(new Option('--browser-input-timeout <ms|s|m>', 'Maximum time to wait for the prompt textarea (default 30s).').hideHelp())
|
|
84
|
+
.addOption(new Option('--browser-no-cookie-sync', 'Skip copying cookies from Chrome.').hideHelp())
|
|
85
|
+
.addOption(new Option('--browser-headless', 'Launch Chrome in headless mode.').hideHelp())
|
|
86
|
+
.addOption(new Option('--browser-hide-window', 'Hide the Chrome window after launch (macOS headful only).').hideHelp())
|
|
87
|
+
.addOption(new Option('--browser-keep-browser', 'Keep Chrome running after completion.').hideHelp())
|
|
88
|
+
.addOption(new Option('--browser-allow-cookie-errors', 'Continue even if Chrome cookies cannot be copied.').hideHelp())
|
|
89
|
+
.addOption(new Option('--browser-inline-files', 'Paste files directly into the ChatGPT composer instead of uploading attachments.').default(false))
|
|
90
|
+
.option('--debug-help', 'Show the advanced/debug option set and exit.', false)
|
|
91
|
+
.option('--heartbeat <seconds>', 'Emit periodic in-progress updates (0 to disable).', parseHeartbeatOption, 30)
|
|
92
|
+
.showHelpAfterError('(use --help for usage)');
|
|
93
|
+
program.addHelpText('after', `
|
|
94
|
+
Examples:
|
|
95
|
+
# Quick API run with two files
|
|
96
|
+
oracle --prompt "Summarize the risk register" --file docs/risk-register.md docs/risk-matrix.md
|
|
97
|
+
|
|
98
|
+
# Browser run (no API key) + globbed TypeScript sources, excluding tests
|
|
99
|
+
oracle --engine browser --prompt "Review the TS data layer" \\
|
|
100
|
+
--file "src/**/*.ts" --file "!src/**/*.test.ts"
|
|
101
|
+
`);
|
|
102
|
+
const sessionCommand = program
|
|
103
|
+
.command('session [id]')
|
|
104
|
+
.description('Attach to a stored session or list recent sessions when no ID is provided.')
|
|
105
|
+
.option('--hours <hours>', 'Look back this many hours when listing sessions (default 24).', parseFloatOption, 24)
|
|
106
|
+
.option('--limit <count>', 'Maximum sessions to show when listing (max 1000).', parseIntOption, 100)
|
|
107
|
+
.option('--all', 'Include all stored sessions regardless of age.', false)
|
|
108
|
+
.option('--clear', 'Delete stored sessions older than the provided window (24h default).', false)
|
|
109
|
+
.option('--render', 'Render completed session output as markdown (rich TTY only).', false)
|
|
110
|
+
.option('--render-markdown', 'Alias for --render.', false)
|
|
111
|
+
.addOption(new Option('--clean', 'Deprecated alias for --clear.').default(false).hideHelp())
|
|
112
|
+
.action(async (sessionId, _options, cmd) => {
|
|
113
|
+
await handleSessionCommand(sessionId, cmd);
|
|
114
|
+
});
|
|
115
|
+
const statusCommand = program
|
|
116
|
+
.command('status [id]')
|
|
117
|
+
.description('List recent sessions (24h window by default) or attach to a session when an ID is provided.')
|
|
118
|
+
.option('--hours <hours>', 'Look back this many hours (default 24).', parseFloatOption, 24)
|
|
119
|
+
.option('--limit <count>', 'Maximum sessions to show (max 1000).', parseIntOption, 100)
|
|
120
|
+
.option('--all', 'Include all stored sessions regardless of age.', false)
|
|
121
|
+
.option('--clear', 'Delete stored sessions older than the provided window (24h default).', false)
|
|
122
|
+
.option('--render', 'Render completed session output as markdown (rich TTY only).', false)
|
|
123
|
+
.option('--render-markdown', 'Alias for --render.', false)
|
|
124
|
+
.addOption(new Option('--clean', 'Deprecated alias for --clear.').default(false).hideHelp())
|
|
125
|
+
.action(async (sessionId, _options, command) => {
|
|
126
|
+
const statusOptions = command.opts();
|
|
127
|
+
const clearRequested = Boolean(statusOptions.clear || statusOptions.clean);
|
|
128
|
+
if (clearRequested) {
|
|
129
|
+
if (sessionId) {
|
|
130
|
+
console.error('Cannot combine a session ID with --clear. Remove the ID to delete cached sessions.');
|
|
131
|
+
process.exitCode = 1;
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const hours = statusOptions.hours;
|
|
135
|
+
const includeAll = statusOptions.all;
|
|
136
|
+
const result = await deleteSessionsOlderThan({ hours, includeAll });
|
|
137
|
+
const scope = includeAll ? 'all stored sessions' : `sessions older than ${hours}h`;
|
|
138
|
+
console.log(formatSessionCleanupMessage(result, scope));
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
if (sessionId === 'clear' || sessionId === 'clean') {
|
|
142
|
+
console.error('Session cleanup now uses --clear. Run "oracle status --clear --hours <n>" instead.');
|
|
143
|
+
process.exitCode = 1;
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
if (sessionId) {
|
|
147
|
+
await attachSession(sessionId);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
const showExamples = usesDefaultStatusFilters(command);
|
|
151
|
+
await showStatus({
|
|
152
|
+
hours: statusOptions.all ? Infinity : statusOptions.hours,
|
|
153
|
+
includeAll: statusOptions.all,
|
|
154
|
+
limit: statusOptions.limit,
|
|
155
|
+
showExamples,
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
function buildRunOptions(options, overrides = {}) {
|
|
159
|
+
if (!options.prompt) {
|
|
160
|
+
throw new Error('Prompt is required.');
|
|
161
|
+
}
|
|
162
|
+
return {
|
|
163
|
+
prompt: options.prompt,
|
|
164
|
+
model: options.model,
|
|
165
|
+
file: overrides.file ?? options.file ?? [],
|
|
166
|
+
slug: overrides.slug ?? options.slug,
|
|
167
|
+
filesReport: overrides.filesReport ?? options.filesReport,
|
|
168
|
+
maxInput: overrides.maxInput ?? options.maxInput,
|
|
169
|
+
maxOutput: overrides.maxOutput ?? options.maxOutput,
|
|
170
|
+
system: overrides.system ?? options.system,
|
|
171
|
+
silent: overrides.silent ?? options.silent,
|
|
172
|
+
search: overrides.search ?? options.search,
|
|
173
|
+
preview: overrides.preview ?? undefined,
|
|
174
|
+
previewMode: overrides.previewMode ?? options.previewMode,
|
|
175
|
+
apiKey: overrides.apiKey ?? options.apiKey,
|
|
176
|
+
sessionId: overrides.sessionId ?? options.sessionId,
|
|
177
|
+
verbose: overrides.verbose ?? options.verbose,
|
|
178
|
+
heartbeatIntervalMs: overrides.heartbeatIntervalMs ?? resolveHeartbeatIntervalMs(options.heartbeat),
|
|
179
|
+
browserInlineFiles: overrides.browserInlineFiles ?? options.browserInlineFiles ?? false,
|
|
180
|
+
background: overrides.background ?? undefined,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
function resolveHeartbeatIntervalMs(seconds) {
|
|
184
|
+
if (typeof seconds !== 'number' || seconds <= 0) {
|
|
185
|
+
return undefined;
|
|
186
|
+
}
|
|
187
|
+
return Math.round(seconds * 1000);
|
|
188
|
+
}
|
|
189
|
+
function buildRunOptionsFromMetadata(metadata) {
|
|
190
|
+
const stored = metadata.options ?? {};
|
|
191
|
+
return {
|
|
192
|
+
prompt: stored.prompt ?? '',
|
|
193
|
+
model: stored.model ?? 'gpt-5-pro',
|
|
194
|
+
file: stored.file ?? [],
|
|
195
|
+
slug: stored.slug,
|
|
196
|
+
filesReport: stored.filesReport,
|
|
197
|
+
maxInput: stored.maxInput,
|
|
198
|
+
maxOutput: stored.maxOutput,
|
|
199
|
+
system: stored.system,
|
|
200
|
+
silent: stored.silent,
|
|
201
|
+
search: undefined,
|
|
202
|
+
preview: false,
|
|
203
|
+
previewMode: undefined,
|
|
204
|
+
apiKey: undefined,
|
|
205
|
+
sessionId: metadata.id,
|
|
206
|
+
verbose: stored.verbose,
|
|
207
|
+
heartbeatIntervalMs: stored.heartbeatIntervalMs,
|
|
208
|
+
browserInlineFiles: stored.browserInlineFiles,
|
|
209
|
+
background: stored.background,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
function getSessionMode(metadata) {
|
|
213
|
+
return metadata.mode ?? metadata.options?.mode ?? 'api';
|
|
214
|
+
}
|
|
215
|
+
function getBrowserConfigFromMetadata(metadata) {
|
|
216
|
+
return metadata.options?.browserConfig ?? metadata.browser?.config;
|
|
217
|
+
}
|
|
218
|
+
async function runRootCommand(options) {
|
|
219
|
+
const helpRequested = rawCliArgs.some((arg) => arg === '--help' || arg === '-h');
|
|
220
|
+
if (helpRequested) {
|
|
221
|
+
if (options.verbose) {
|
|
222
|
+
console.log('');
|
|
223
|
+
printDebugHelp(program.name());
|
|
224
|
+
console.log('');
|
|
225
|
+
}
|
|
226
|
+
program.help({ error: false });
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
const previewMode = resolvePreviewMode(options.preview);
|
|
230
|
+
if (rawCliArgs.length === 0) {
|
|
231
|
+
console.log(chalk.yellow('No prompt or subcommand supplied. See `oracle --help` for usage.'));
|
|
232
|
+
program.help({ error: false });
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
if (options.debugHelp) {
|
|
236
|
+
printDebugHelp(program.name());
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
if (options.dryRun && previewMode) {
|
|
240
|
+
throw new Error('--dry-run cannot be combined with --preview.');
|
|
241
|
+
}
|
|
242
|
+
if (options.dryRun && options.renderMarkdown) {
|
|
243
|
+
throw new Error('--dry-run cannot be combined with --render-markdown.');
|
|
244
|
+
}
|
|
245
|
+
const engine = resolveEngine({ engine: options.engine, browserFlag: options.browser, env: process.env });
|
|
246
|
+
if (options.browser) {
|
|
247
|
+
console.log(chalk.yellow('`--browser` is deprecated; use `--engine browser` instead.'));
|
|
248
|
+
}
|
|
249
|
+
const cliModelArg = normalizeModelOption(options.model) || 'gpt-5-pro';
|
|
250
|
+
const resolvedModel = engine === 'browser' ? inferModelFromLabel(cliModelArg) : resolveApiModel(cliModelArg);
|
|
251
|
+
const resolvedOptions = { ...options, model: resolvedModel };
|
|
252
|
+
if (await handleStatusFlag(options, { attachSession, showStatus })) {
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
if (await handleSessionAlias(options, { attachSession })) {
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
if (options.execSession) {
|
|
259
|
+
await executeSession(options.execSession);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
if (options.renderMarkdown) {
|
|
263
|
+
if (!options.prompt) {
|
|
264
|
+
throw new Error('Prompt is required when using --render-markdown.');
|
|
265
|
+
}
|
|
266
|
+
const markdown = await renderPromptMarkdown({ prompt: options.prompt, file: options.file, system: options.system }, { cwd: process.cwd() });
|
|
267
|
+
console.log(markdown);
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
if (previewMode) {
|
|
271
|
+
if (engine === 'browser') {
|
|
272
|
+
throw new Error('--engine browser cannot be combined with --preview.');
|
|
273
|
+
}
|
|
274
|
+
if (!options.prompt) {
|
|
275
|
+
throw new Error('Prompt is required when using --preview.');
|
|
276
|
+
}
|
|
277
|
+
const runOptions = buildRunOptions(resolvedOptions, { preview: true, previewMode });
|
|
278
|
+
await runOracle(runOptions, { log: console.log, write: (chunk) => process.stdout.write(chunk) });
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
if (!options.prompt) {
|
|
282
|
+
throw new Error('Prompt is required when starting a new session.');
|
|
283
|
+
}
|
|
284
|
+
if (options.dryRun) {
|
|
285
|
+
const baseRunOptions = buildRunOptions(resolvedOptions, { preview: false, previewMode: undefined });
|
|
286
|
+
await runDryRunSummary({
|
|
287
|
+
engine,
|
|
288
|
+
runOptions: baseRunOptions,
|
|
289
|
+
cwd: process.cwd(),
|
|
290
|
+
version: VERSION,
|
|
291
|
+
log: console.log,
|
|
292
|
+
}, {});
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
if (options.file && options.file.length > 0) {
|
|
296
|
+
await readFiles(options.file, { cwd: process.cwd() });
|
|
297
|
+
}
|
|
298
|
+
const sessionMode = engine === 'browser' ? 'browser' : 'api';
|
|
299
|
+
const browserModelLabelOverride = sessionMode === 'browser' ? resolveBrowserModelLabel(cliModelArg, resolvedModel) : undefined;
|
|
300
|
+
const browserConfig = sessionMode === 'browser'
|
|
301
|
+
? buildBrowserConfig({
|
|
302
|
+
...options,
|
|
303
|
+
model: resolvedModel,
|
|
304
|
+
browserModelLabel: browserModelLabelOverride,
|
|
305
|
+
})
|
|
306
|
+
: undefined;
|
|
307
|
+
await ensureSessionStorage();
|
|
308
|
+
const baseRunOptions = buildRunOptions(resolvedOptions, { preview: false, previewMode: undefined });
|
|
309
|
+
const sessionMeta = await initializeSession({
|
|
310
|
+
...baseRunOptions,
|
|
311
|
+
mode: sessionMode,
|
|
312
|
+
browserConfig,
|
|
313
|
+
}, process.cwd());
|
|
314
|
+
const reattachCommand = `pnpm oracle session ${sessionMeta.id}`;
|
|
315
|
+
console.log(chalk.bold(`Reattach later with: ${chalk.cyan(reattachCommand)}`));
|
|
316
|
+
console.log('');
|
|
317
|
+
const liveRunOptions = { ...baseRunOptions, sessionId: sessionMeta.id };
|
|
318
|
+
const disableDetach = process.env.ORACLE_NO_DETACH === '1';
|
|
319
|
+
const detached = disableDetach
|
|
320
|
+
? false
|
|
321
|
+
: await launchDetachedSession(sessionMeta.id).catch((error) => {
|
|
322
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
323
|
+
console.log(chalk.yellow(`Unable to detach session runner (${message}). Running inline...`));
|
|
324
|
+
return false;
|
|
325
|
+
});
|
|
326
|
+
if (detached === false) {
|
|
327
|
+
await runInteractiveSession(sessionMeta, liveRunOptions, sessionMode, browserConfig, true);
|
|
328
|
+
console.log(chalk.bold(`Session ${sessionMeta.id} completed`));
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
if (detached) {
|
|
332
|
+
console.log(chalk.blue(`Reattach via: oracle session ${sessionMeta.id}`));
|
|
333
|
+
await attachSession(sessionMeta.id, { suppressMetadata: true });
|
|
334
|
+
console.log(chalk.bold(`Session ${sessionMeta.id} completed`));
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
async function runInteractiveSession(sessionMeta, runOptions, mode, browserConfig, showReattachHint = true) {
|
|
338
|
+
const { logLine, writeChunk, stream } = createSessionLogWriter(sessionMeta.id);
|
|
339
|
+
let headerAugmented = false;
|
|
340
|
+
const combinedLog = (message = '') => {
|
|
341
|
+
if (!headerAugmented && message.startsWith('Oracle (')) {
|
|
342
|
+
headerAugmented = true;
|
|
343
|
+
if (showReattachHint) {
|
|
344
|
+
console.log(`${message}\n${chalk.blue(`Reattach via: oracle session ${sessionMeta.id}`)}`);
|
|
345
|
+
}
|
|
346
|
+
else {
|
|
347
|
+
console.log(message);
|
|
348
|
+
}
|
|
349
|
+
logLine(message);
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
console.log(message);
|
|
353
|
+
logLine(message);
|
|
354
|
+
};
|
|
355
|
+
const combinedWrite = (chunk) => {
|
|
356
|
+
writeChunk(chunk);
|
|
357
|
+
return process.stdout.write(chunk);
|
|
358
|
+
};
|
|
359
|
+
try {
|
|
360
|
+
await performSessionRun({
|
|
361
|
+
sessionMeta,
|
|
362
|
+
runOptions,
|
|
363
|
+
mode,
|
|
364
|
+
browserConfig,
|
|
365
|
+
cwd: process.cwd(),
|
|
366
|
+
log: combinedLog,
|
|
367
|
+
write: combinedWrite,
|
|
368
|
+
version: VERSION,
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
catch (error) {
|
|
372
|
+
throw error;
|
|
373
|
+
}
|
|
374
|
+
finally {
|
|
375
|
+
stream.end();
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
async function launchDetachedSession(sessionId) {
|
|
379
|
+
return new Promise((resolve, reject) => {
|
|
380
|
+
try {
|
|
381
|
+
const args = ['--', CLI_ENTRYPOINT, '--exec-session', sessionId];
|
|
382
|
+
const child = spawn(process.execPath, args, {
|
|
383
|
+
detached: true,
|
|
384
|
+
stdio: 'ignore',
|
|
385
|
+
env: process.env,
|
|
386
|
+
});
|
|
387
|
+
child.once('error', reject);
|
|
388
|
+
child.once('spawn', () => {
|
|
389
|
+
child.unref();
|
|
390
|
+
resolve(true);
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
catch (error) {
|
|
394
|
+
reject(error);
|
|
395
|
+
}
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
async function executeSession(sessionId) {
|
|
399
|
+
const metadata = await readSessionMetadata(sessionId);
|
|
400
|
+
if (!metadata) {
|
|
401
|
+
console.error(chalk.red(`No session found with ID ${sessionId}`));
|
|
402
|
+
process.exitCode = 1;
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
const runOptions = buildRunOptionsFromMetadata(metadata);
|
|
406
|
+
const sessionMode = getSessionMode(metadata);
|
|
407
|
+
const browserConfig = getBrowserConfigFromMetadata(metadata);
|
|
408
|
+
const { logLine, writeChunk, stream } = createSessionLogWriter(sessionId);
|
|
409
|
+
try {
|
|
410
|
+
await performSessionRun({
|
|
411
|
+
sessionMeta: metadata,
|
|
412
|
+
runOptions,
|
|
413
|
+
mode: sessionMode,
|
|
414
|
+
browserConfig,
|
|
415
|
+
cwd: metadata.cwd ?? process.cwd(),
|
|
416
|
+
log: logLine,
|
|
417
|
+
write: writeChunk,
|
|
418
|
+
version: VERSION,
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
catch {
|
|
422
|
+
// Errors are already logged to the session log; keep quiet to mirror stored-session behavior.
|
|
423
|
+
}
|
|
424
|
+
finally {
|
|
425
|
+
stream.end();
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
function printDebugHelp(cliName) {
|
|
429
|
+
console.log(chalk.bold('Advanced Options'));
|
|
430
|
+
printDebugOptionGroup([
|
|
431
|
+
['--search <on|off>', 'Enable or disable the server-side search tool (default on).'],
|
|
432
|
+
['--max-input <tokens>', 'Override the input token budget.'],
|
|
433
|
+
['--max-output <tokens>', 'Override the max output tokens (model default otherwise).'],
|
|
434
|
+
]);
|
|
435
|
+
console.log('');
|
|
436
|
+
console.log(chalk.bold('Browser Options'));
|
|
437
|
+
printDebugOptionGroup([
|
|
438
|
+
['--browser-chrome-profile <name>', 'Reuse cookies from a specific Chrome profile.'],
|
|
439
|
+
['--browser-chrome-path <path>', 'Point to a custom Chrome/Chromium binary.'],
|
|
440
|
+
['--browser-url <url>', 'Hit an alternate ChatGPT host.'],
|
|
441
|
+
['--browser-timeout <ms|s|m>', 'Cap total wait time for the assistant response.'],
|
|
442
|
+
['--browser-input-timeout <ms|s|m>', 'Cap how long we wait for the composer textarea.'],
|
|
443
|
+
['--browser-no-cookie-sync', 'Skip copying cookies from your main profile.'],
|
|
444
|
+
['--browser-headless', 'Launch Chrome in headless mode.'],
|
|
445
|
+
['--browser-hide-window', 'Hide the Chrome window (macOS headful only).'],
|
|
446
|
+
['--browser-keep-browser', 'Leave Chrome running after completion.'],
|
|
447
|
+
]);
|
|
448
|
+
console.log('');
|
|
449
|
+
console.log(chalk.dim(`Tip: run \`${cliName} --help\` to see the primary option set.`));
|
|
450
|
+
}
|
|
451
|
+
function printDebugOptionGroup(entries) {
|
|
452
|
+
const flagWidth = Math.max(...entries.map(([flag]) => flag.length));
|
|
453
|
+
entries.forEach(([flag, description]) => {
|
|
454
|
+
const label = chalk.cyan(flag.padEnd(flagWidth + 2));
|
|
455
|
+
console.log(` ${label}${description}`);
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
program.action(async function () {
|
|
459
|
+
const options = this.optsWithGlobals();
|
|
460
|
+
await runRootCommand(options);
|
|
461
|
+
});
|
|
462
|
+
await program.parseAsync(process.argv).catch((error) => {
|
|
463
|
+
if (error instanceof Error) {
|
|
464
|
+
if (!isErrorLogged(error)) {
|
|
465
|
+
console.error(chalk.red('✖'), error.message);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
else {
|
|
469
|
+
console.error(chalk.red('✖'), error);
|
|
470
|
+
}
|
|
471
|
+
process.exitCode = 1;
|
|
472
|
+
});
|