@steipete/oracle 0.9.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +61 -48
  3. package/dist/bin/oracle-cli.js +455 -402
  4. package/dist/bin/oracle-mcp.js +2 -2
  5. package/dist/bin/oracle.js +165 -279
  6. package/dist/scripts/agent-send.js +31 -31
  7. package/dist/scripts/check.js +6 -6
  8. package/dist/scripts/debug/extract-chatgpt-response.js +10 -10
  9. package/dist/scripts/docs-list.js +30 -30
  10. package/dist/scripts/git-policy.js +25 -23
  11. package/dist/scripts/run-cli.js +8 -8
  12. package/dist/scripts/runner.js +203 -195
  13. package/dist/scripts/test-browser.js +21 -18
  14. package/dist/scripts/test-remote-chrome.js +20 -20
  15. package/dist/src/bridge/connection.js +18 -18
  16. package/dist/src/bridge/userConfigFile.js +7 -7
  17. package/dist/src/browser/actions/assistantResponse.js +149 -101
  18. package/dist/src/browser/actions/attachmentDataTransfer.js +49 -47
  19. package/dist/src/browser/actions/attachments.js +246 -150
  20. package/dist/src/browser/actions/domEvents.js +2 -2
  21. package/dist/src/browser/actions/modelSelection.js +275 -117
  22. package/dist/src/browser/actions/navigation.js +161 -137
  23. package/dist/src/browser/actions/promptComposer.js +100 -64
  24. package/dist/src/browser/actions/remoteFileTransfer.js +10 -10
  25. package/dist/src/browser/actions/thinkingTime.js +207 -110
  26. package/dist/src/browser/chromeLifecycle.js +62 -60
  27. package/dist/src/browser/config.js +34 -15
  28. package/dist/src/browser/constants.js +17 -12
  29. package/dist/src/browser/cookies.js +19 -19
  30. package/dist/src/browser/detect.js +62 -62
  31. package/dist/src/browser/domDebug.js +1 -1
  32. package/dist/src/browser/index.js +390 -295
  33. package/dist/src/browser/modelStrategy.js +1 -1
  34. package/dist/src/browser/pageActions.js +5 -5
  35. package/dist/src/browser/policies.js +16 -13
  36. package/dist/src/browser/profileState.js +44 -39
  37. package/dist/src/browser/prompt.js +72 -42
  38. package/dist/src/browser/promptSummary.js +5 -5
  39. package/dist/src/browser/providerDomFlow.js +1 -1
  40. package/dist/src/browser/providers/chatgptDomProvider.js +9 -9
  41. package/dist/src/browser/providers/geminiDeepThinkDomProvider.js +51 -42
  42. package/dist/src/browser/providers/index.js +2 -2
  43. package/dist/src/browser/reattach.js +67 -34
  44. package/dist/src/browser/reattachHelpers.js +31 -26
  45. package/dist/src/browser/sessionRunner.js +37 -25
  46. package/dist/src/browser/utils.js +9 -9
  47. package/dist/src/browserMode.js +1 -1
  48. package/dist/src/cli/bridge/claudeConfig.js +16 -16
  49. package/dist/src/cli/bridge/client.js +28 -20
  50. package/dist/src/cli/bridge/codexConfig.js +16 -16
  51. package/dist/src/cli/bridge/doctor.js +47 -39
  52. package/dist/src/cli/bridge/host.js +58 -56
  53. package/dist/src/cli/browserConfig.js +62 -48
  54. package/dist/src/cli/browserDefaults.js +27 -26
  55. package/dist/src/cli/bundleWarnings.js +1 -1
  56. package/dist/src/cli/clipboard.js +11 -2
  57. package/dist/src/cli/detach.js +2 -2
  58. package/dist/src/cli/dryRun.js +29 -25
  59. package/dist/src/cli/duplicatePromptGuard.js +3 -3
  60. package/dist/src/cli/engine.js +9 -9
  61. package/dist/src/cli/errorUtils.js +1 -1
  62. package/dist/src/cli/fileSize.js +3 -3
  63. package/dist/src/cli/format.js +2 -2
  64. package/dist/src/cli/help.js +28 -28
  65. package/dist/src/cli/hiddenAliases.js +3 -3
  66. package/dist/src/cli/markdownBundle.js +7 -7
  67. package/dist/src/cli/markdownRenderer.js +15 -15
  68. package/dist/src/cli/notifier.js +77 -67
  69. package/dist/src/cli/options.js +127 -106
  70. package/dist/src/cli/oscUtils.js +1 -1
  71. package/dist/src/cli/promptRequirement.js +2 -2
  72. package/dist/src/cli/renderOutput.js +1 -1
  73. package/dist/src/cli/rootAlias.js +1 -1
  74. package/dist/src/cli/runOptions.js +32 -28
  75. package/dist/src/cli/sessionCommand.js +31 -21
  76. package/dist/src/cli/sessionDisplay.js +95 -81
  77. package/dist/src/cli/sessionLineage.js +6 -2
  78. package/dist/src/cli/sessionRunner.js +103 -93
  79. package/dist/src/cli/sessionTable.js +26 -23
  80. package/dist/src/cli/stdin.js +22 -0
  81. package/dist/src/cli/tagline.js +121 -124
  82. package/dist/src/cli/tui/index.js +139 -128
  83. package/dist/src/cli/writeOutputPath.js +5 -5
  84. package/dist/src/config.js +7 -7
  85. package/dist/src/gemini-web/browserSessionManager.js +19 -15
  86. package/dist/src/gemini-web/client.js +76 -70
  87. package/dist/src/gemini-web/executionMode.js +6 -8
  88. package/dist/src/gemini-web/executor.js +98 -93
  89. package/dist/src/gemini-web/index.js +1 -1
  90. package/dist/src/mcp/server.js +16 -12
  91. package/dist/src/mcp/tools/consult.js +51 -47
  92. package/dist/src/mcp/tools/sessionResources.js +12 -12
  93. package/dist/src/mcp/tools/sessions.js +26 -17
  94. package/dist/src/mcp/types.js +5 -5
  95. package/dist/src/mcp/utils.js +15 -7
  96. package/dist/src/oracle/background.js +15 -15
  97. package/dist/src/oracle/claude.js +53 -25
  98. package/dist/src/oracle/client.js +50 -41
  99. package/dist/src/oracle/config.js +96 -66
  100. package/dist/src/oracle/errors.js +38 -38
  101. package/dist/src/oracle/files.js +55 -46
  102. package/dist/src/oracle/finishLine.js +10 -8
  103. package/dist/src/oracle/format.js +3 -3
  104. package/dist/src/oracle/gemini.js +37 -33
  105. package/dist/src/oracle/logging.js +7 -7
  106. package/dist/src/oracle/markdown.js +28 -28
  107. package/dist/src/oracle/modelResolver.js +16 -16
  108. package/dist/src/oracle/multiModelRunner.js +12 -12
  109. package/dist/src/oracle/oscProgress.js +8 -8
  110. package/dist/src/oracle/promptAssembly.js +6 -3
  111. package/dist/src/oracle/request.js +16 -13
  112. package/dist/src/oracle/run.js +156 -134
  113. package/dist/src/oracle/runUtils.js +8 -5
  114. package/dist/src/oracle/tokenEstimate.js +6 -6
  115. package/dist/src/oracle/tokenStats.js +5 -5
  116. package/dist/src/oracle/tokenStringifier.js +5 -5
  117. package/dist/src/oracle.js +12 -12
  118. package/dist/src/oracleHome.js +3 -3
  119. package/dist/src/remote/client.js +25 -25
  120. package/dist/src/remote/health.js +20 -20
  121. package/dist/src/remote/remoteServiceConfig.js +9 -9
  122. package/dist/src/remote/server.js +129 -118
  123. package/dist/src/sessionManager.js +77 -75
  124. package/dist/src/sessionStore.js +3 -3
  125. package/dist/src/version.js +10 -10
  126. package/dist/vendor/oracle-notifier/README.md +2 -0
  127. package/package.json +66 -62
  128. package/vendor/oracle-notifier/README.md +2 -0
  129. package/dist/markdansi/types/index.js +0 -4
  130. package/dist/oracle/bin/oracle-cli.js +0 -472
  131. package/dist/oracle/src/browser/actions/assistantResponse.js +0 -471
  132. package/dist/oracle/src/browser/actions/attachments.js +0 -82
  133. package/dist/oracle/src/browser/actions/modelSelection.js +0 -190
  134. package/dist/oracle/src/browser/actions/navigation.js +0 -75
  135. package/dist/oracle/src/browser/actions/promptComposer.js +0 -167
  136. package/dist/oracle/src/browser/chromeLifecycle.js +0 -104
  137. package/dist/oracle/src/browser/config.js +0 -33
  138. package/dist/oracle/src/browser/constants.js +0 -40
  139. package/dist/oracle/src/browser/cookies.js +0 -210
  140. package/dist/oracle/src/browser/domDebug.js +0 -36
  141. package/dist/oracle/src/browser/index.js +0 -331
  142. package/dist/oracle/src/browser/pageActions.js +0 -5
  143. package/dist/oracle/src/browser/prompt.js +0 -88
  144. package/dist/oracle/src/browser/promptSummary.js +0 -20
  145. package/dist/oracle/src/browser/sessionRunner.js +0 -80
  146. package/dist/oracle/src/browser/types.js +0 -1
  147. package/dist/oracle/src/browser/utils.js +0 -62
  148. package/dist/oracle/src/browserMode.js +0 -1
  149. package/dist/oracle/src/cli/browserConfig.js +0 -44
  150. package/dist/oracle/src/cli/dryRun.js +0 -59
  151. package/dist/oracle/src/cli/engine.js +0 -17
  152. package/dist/oracle/src/cli/errorUtils.js +0 -9
  153. package/dist/oracle/src/cli/help.js +0 -70
  154. package/dist/oracle/src/cli/markdownRenderer.js +0 -15
  155. package/dist/oracle/src/cli/options.js +0 -103
  156. package/dist/oracle/src/cli/promptRequirement.js +0 -14
  157. package/dist/oracle/src/cli/rootAlias.js +0 -30
  158. package/dist/oracle/src/cli/sessionCommand.js +0 -77
  159. package/dist/oracle/src/cli/sessionDisplay.js +0 -270
  160. package/dist/oracle/src/cli/sessionRunner.js +0 -94
  161. package/dist/oracle/src/heartbeat.js +0 -43
  162. package/dist/oracle/src/oracle/client.js +0 -48
  163. package/dist/oracle/src/oracle/config.js +0 -29
  164. package/dist/oracle/src/oracle/errors.js +0 -101
  165. package/dist/oracle/src/oracle/files.js +0 -220
  166. package/dist/oracle/src/oracle/format.js +0 -33
  167. package/dist/oracle/src/oracle/fsAdapter.js +0 -7
  168. package/dist/oracle/src/oracle/oscProgress.js +0 -60
  169. package/dist/oracle/src/oracle/request.js +0 -48
  170. package/dist/oracle/src/oracle/run.js +0 -444
  171. package/dist/oracle/src/oracle/tokenStats.js +0 -39
  172. package/dist/oracle/src/oracle/types.js +0 -1
  173. package/dist/oracle/src/oracle.js +0 -9
  174. package/dist/oracle/src/sessionManager.js +0 -205
  175. package/dist/oracle/src/version.js +0 -39
  176. package/dist/scripts/chrome/browser-tools.js +0 -295
  177. package/dist/src/browser/profileSync.js +0 -141
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { startMcpServer } from '../src/mcp/server.js';
2
+ import { startMcpServer } from "../src/mcp/server.js";
3
3
  startMcpServer().catch((error) => {
4
- console.error('oracle-mcp exited with an error:', error);
4
+ console.error("oracle-mcp exited with an error:", error);
5
5
  process.exitCode = 1;
6
6
  });
@@ -4,70 +4,107 @@ import { Command, InvalidArgumentError, Option } from 'commander';
4
4
  import chalk from 'chalk';
5
5
  import kleur from 'kleur';
6
6
  import { ensureSessionStorage, initializeSession, updateSessionMetadata, readSessionMetadata, listSessionsMetadata, filterSessionsByRange, createSessionLogWriter, readSessionLog, wait, SESSIONS_DIR, deleteSessionsOlderThan, } from '../src/sessionManager.js';
7
- import { runOracle, MODEL_CONFIGS, parseIntOption, renderPromptMarkdown, readFiles, buildPrompt, createFileSections, DEFAULT_SYSTEM_PROMPT, formatElapsed, TOKENIZER_OPTIONS, OracleResponseError, extractResponseMetadata, } from '../src/oracle.js';
8
- import { runBrowserMode, CHATGPT_URL, DEFAULT_MODEL_TARGET, parseDuration } from '../src/browserMode.js';
7
+ import { runOracle, MODEL_CONFIGS, parseIntOption, renderPromptMarkdown, readFiles } from '../src/oracle.js';
9
8
  const VERSION = '1.0.0';
10
9
  const rawCliArgs = process.argv.slice(2);
11
10
  const isTty = process.stdout.isTTY;
12
- const colorIfTty = (styler) => (text) => (isTty ? styler(text) : text);
13
- const helpColors = {
14
- banner: colorIfTty((text) => kleur.bold().blue(text)),
15
- subtitle: colorIfTty((text) => kleur.dim(text)),
16
- section: colorIfTty((text) => kleur.bold().white(text)),
17
- bullet: colorIfTty((text) => kleur.blue(text)),
18
- command: colorIfTty((text) => kleur.bold().blue(text)),
19
- option: colorIfTty((text) => kleur.cyan(text)),
20
- argument: colorIfTty((text) => kleur.magenta(text)),
21
- description: colorIfTty((text) => kleur.white(text)),
22
- muted: colorIfTty((text) => kleur.gray(text)),
23
- accent: colorIfTty((text) => kleur.cyan(text)),
11
+ const HELP_THEME_NAMES = ['aurora', 'ember', 'slate'];
12
+ const passthrough = (text) => text;
13
+ const wrapForTty = (styler) => (text) => (isTty ? styler(text) : text);
14
+ const createTheme = (name, config) => ({
15
+ name,
16
+ banner: wrapForTty(config.banner),
17
+ subtitle: wrapForTty(config.subtitle),
18
+ heading: wrapForTty(config.heading),
19
+ bullet: wrapForTty(config.bullet),
20
+ body: wrapForTty(config.body),
21
+ muted: wrapForTty(config.muted),
22
+ command: wrapForTty(config.command),
23
+ accent: wrapForTty(config.accent),
24
+ });
25
+ const HELP_THEMES = {
26
+ aurora: createTheme('aurora', {
27
+ banner: (text) => kleur.bold().cyan(text),
28
+ subtitle: (text) => kleur.dim(text),
29
+ heading: (text) => kleur.bold().magenta(text),
30
+ bullet: (text) => kleur.cyan(text),
31
+ body: passthrough,
32
+ muted: (text) => kleur.gray(text),
33
+ command: (text) => kleur.bold().white(text),
34
+ accent: (text) => kleur.magenta(text),
35
+ }),
36
+ ember: createTheme('ember', {
37
+ banner: (text) => kleur.bold().yellow(text),
38
+ subtitle: (text) => kleur.dim(text),
39
+ heading: (text) => kleur.bold().red(text),
40
+ bullet: (text) => kleur.yellow(text),
41
+ body: passthrough,
42
+ muted: (text) => kleur.dim(text),
43
+ command: (text) => kleur.bold().yellow(text),
44
+ accent: (text) => kleur.red(text),
45
+ }),
46
+ slate: createTheme('slate', {
47
+ banner: (text) => kleur.bold().blue(text),
48
+ subtitle: (text) => kleur.dim(text),
49
+ heading: (text) => kleur.bold().white(text),
50
+ bullet: (text) => kleur.blue(text),
51
+ body: passthrough,
52
+ muted: (text) => kleur.gray(text),
53
+ command: (text) => kleur.bold().blue(text),
54
+ accent: (text) => kleur.cyan(text),
55
+ }),
24
56
  };
57
+ const helpThemeName = resolveHelpThemeName(rawCliArgs, process.env.ORACLE_HELP_THEME);
58
+ const helpTheme = HELP_THEMES[helpThemeName];
59
+ function resolveHelpThemeName(args, envValue) {
60
+ const fromArgs = extractHelpThemeFromArgs(args);
61
+ if (isHelpThemeName(fromArgs)) {
62
+ return fromArgs;
63
+ }
64
+ const fromEnv = envValue?.toLowerCase();
65
+ if (isHelpThemeName(fromEnv)) {
66
+ return fromEnv;
67
+ }
68
+ return 'aurora';
69
+ }
70
+ function extractHelpThemeFromArgs(args) {
71
+ for (let index = 0; index < args.length; index += 1) {
72
+ const current = args[index];
73
+ if (current === '--help-theme') {
74
+ const maybeTheme = args[index + 1];
75
+ if (maybeTheme) {
76
+ return maybeTheme.toLowerCase();
77
+ }
78
+ }
79
+ else if (current.startsWith('--help-theme=')) {
80
+ return current.split('=')[1]?.toLowerCase();
81
+ }
82
+ }
83
+ return undefined;
84
+ }
85
+ function isHelpThemeName(value) {
86
+ if (!value) {
87
+ return false;
88
+ }
89
+ return HELP_THEME_NAMES.includes(value);
90
+ }
25
91
  const program = new Command();
26
- program.configureHelp({
27
- styleTitle(title) {
28
- return helpColors.section(title);
29
- },
30
- styleDescriptionText(text) {
31
- return helpColors.description(text);
32
- },
33
- styleCommandText(text) {
34
- return helpColors.command(text);
35
- },
36
- styleSubcommandText(text) {
37
- return helpColors.command(text);
38
- },
39
- styleOptionText(text) {
40
- return helpColors.option(text);
41
- },
42
- styleArgumentText(text) {
43
- return helpColors.argument(text);
44
- },
45
- });
46
92
  program
47
93
  .name('oracle')
48
94
  .description('One-shot GPT-5 Pro / GPT-5.1 tool for hard questions that benefit from large file context and server-side search.')
49
95
  .version(VERSION)
50
96
  .option('-p, --prompt <text>', 'User prompt to send to the model.')
51
97
  .option('-f, --file <paths...>', 'Paths to files or directories to append to the prompt; repeat, comma-separate, or supply a space-separated list.', collectPaths, [])
52
- .option('-s, --slug <words>', 'Custom session slug (3-5 words).')
53
98
  .option('-m, --model <model>', 'Model to target (gpt-5-pro | gpt-5.1).', validateModel, 'gpt-5-pro')
54
99
  .option('--files-report', 'Show token usage per attached file (also prints automatically when files exceed the token budget).', false)
55
- .option('-v, --verbose', 'Enable verbose logging for all operations.', false)
56
100
  .addOption(new Option('--preview [mode]', 'Preview the request without calling the API (summary | json | full).')
57
101
  .choices(['summary', 'json', 'full'])
58
102
  .preset('summary'))
103
+ .addOption(new Option('--help-theme <theme>', 'Choose a color palette for --help output.')
104
+ .choices(Array.from(HELP_THEME_NAMES))
105
+ .env('ORACLE_HELP_THEME'))
59
106
  .addOption(new Option('--exec-session <id>').hideHelp())
60
107
  .option('--render-markdown', 'Emit the assembled markdown bundle for prompt + files and exit.', false)
61
- .option('--browser', 'Run the prompt via the ChatGPT web UI (Chrome automation).', false)
62
- .option('--browser-chrome-profile <name>', 'Chrome profile name/path for cookie reuse.')
63
- .option('--browser-chrome-path <path>', 'Explicit Chrome or Chromium executable path.')
64
- .option('--browser-url <url>', `Override the ChatGPT URL (default ${CHATGPT_URL}).`)
65
- .option('--browser-timeout <ms|s|m>', 'Maximum time to wait for an answer (default 900s).')
66
- .option('--browser-input-timeout <ms|s|m>', 'Maximum time to wait for the prompt textarea (default 30s).')
67
- .option('--browser-no-cookie-sync', 'Skip copying cookies from Chrome.', false)
68
- .option('--browser-headless', 'Launch Chrome in headless mode.', false)
69
- .option('--browser-hide-window', 'Hide the Chrome window after launch (macOS headful only).', false)
70
- .option('--browser-keep-browser', 'Keep Chrome running after completion.', false)
71
108
  .showHelpAfterError('(use --help for usage)');
72
109
  program
73
110
  .command('session [id]')
@@ -75,10 +112,10 @@ program
75
112
  .option('--hours <hours>', 'Look back this many hours when listing sessions (default 24).', parseFloatOption, 24)
76
113
  .option('--limit <count>', 'Maximum sessions to show when listing (max 1000).', parseIntOption, 100)
77
114
  .option('--all', 'Include all stored sessions regardless of age.', false)
78
- .action(async (sessionId, cmd) => {
79
- const sessionOptions = cmd.opts();
115
+ .action(async function (sessionId) {
116
+ const sessionOptions = this.opts();
80
117
  if (!sessionId) {
81
- const showExamples = usesDefaultStatusFilters(cmd);
118
+ const showExamples = usesDefaultStatusFilters(this);
82
119
  await showStatus({
83
120
  hours: sessionOptions.all ? Infinity : sessionOptions.hours,
84
121
  includeAll: sessionOptions.all,
@@ -95,9 +132,9 @@ const statusCommand = program
95
132
  .option('--hours <hours>', 'Look back this many hours (default 24).', parseFloatOption, 24)
96
133
  .option('--limit <count>', 'Maximum sessions to show (max 1000).', parseIntOption, 100)
97
134
  .option('--all', 'Include all stored sessions regardless of age.', false)
98
- .action(async (_options, command) => {
99
- const statusOptions = command.opts();
100
- const showExamples = usesDefaultStatusFilters(command);
135
+ .action(async function () {
136
+ const statusOptions = this.opts();
137
+ const showExamples = usesDefaultStatusFilters(this);
101
138
  await showStatus({
102
139
  hours: statusOptions.all ? Infinity : statusOptions.hours,
103
140
  includeAll: statusOptions.all,
@@ -110,42 +147,63 @@ statusCommand
110
147
  .description('Delete stored sessions older than the provided window (24h default).')
111
148
  .option('--hours <hours>', 'Delete sessions older than this many hours (default 24).', parseFloatOption, 24)
112
149
  .option('--all', 'Delete all stored sessions.', false)
113
- .action(async (_options, command) => {
114
- const clearOptions = command.opts();
150
+ .action(async function () {
151
+ const clearOptions = this.opts();
115
152
  const result = await deleteSessionsOlderThan({ hours: clearOptions.hours, includeAll: clearOptions.all });
116
153
  const scope = clearOptions.all ? 'all stored sessions' : `sessions older than ${clearOptions.hours}h`;
117
154
  console.log(`Deleted ${result.deleted} ${result.deleted === 1 ? 'session' : 'sessions'} (${scope}).`);
118
155
  });
119
156
  const bold = (text) => (isTty ? kleur.bold(text) : text);
120
157
  const dim = (text) => (isTty ? kleur.dim(text) : text);
121
- program.addHelpText('beforeAll', renderHelpBanner);
122
- program.addHelpText('after', renderHelpFooter);
123
- function renderHelpBanner() {
158
+ program.addHelpText('beforeAll', () => renderHelpBanner(helpTheme));
159
+ program.addHelpText('after', () => renderHelpFooter(helpTheme));
160
+ function renderHelpBanner(theme) {
124
161
  const subtitle = 'GPT-5 Pro/GPT-5.1 for tough questions with code/file context.';
125
- return `${helpColors.banner(`Oracle CLI v${VERSION}`)} ${helpColors.subtitle(`— ${subtitle}`)}\n`;
162
+ return `${theme.banner(`Oracle CLI v${VERSION}`)} ${theme.subtitle(`— ${subtitle}`)}\n`;
126
163
  }
127
- function renderHelpFooter() {
128
- const tips = [
129
- `${helpColors.bullet('•')} Attach source files for best results, but keep total input under ~196k tokens.`,
130
- `${helpColors.bullet('•')} The model has no built-in knowledge of your project—open with the architecture, key components, and why you’re asking.`,
131
- `${helpColors.bullet('•')} Run ${helpColors.accent('--files-report')} to inspect token spend before hitting the API.`,
132
- `${helpColors.bullet('•')} Non-preview runs spawn detached sessions so they keep streaming even if your terminal closes.`,
133
- `${helpColors.bullet('')} Ask the model for a memorable 3–5 word slug and pass it via ${helpColors.accent('--slug "<words>"')} to keep session IDs tidy.`,
164
+ function renderHelpFooter(theme) {
165
+ const tipLines = [
166
+ `${theme.bullet(' •')} Attach source files for best results, but keep total input under ~196k tokens.`,
167
+ `${theme.bullet(' •')} The model has no built-in knowledge of your project—open with the architecture, key components, and why you’re asking.`,
168
+ `${theme.bullet(' •')} Run ${theme.accent('--files-report')} to see per-file token impact before spending money.`,
169
+ `${theme.bullet(' •')} Non-preview runs spawn detached sessions so requests keep running even if your terminal closes.`,
170
+ ].join('\n');
171
+ const exampleEntries = [
172
+ {
173
+ command: `${program.name()} --prompt "Summarize risks" --file docs/risk.md --files-report --preview`,
174
+ description: 'Inspect tokens + files without calling the API.',
175
+ },
176
+ {
177
+ command: `${program.name()} --prompt "Explain bug" --file src/,docs/crash.log --files-report`,
178
+ description: 'Attach src/ plus docs/crash.log, launch a background session, and capture the Session ID.',
179
+ },
180
+ {
181
+ command: `${program.name()} status --hours 72 --limit 50`,
182
+ description: 'Show sessions from the last 72h (capped at 50 entries).',
183
+ },
184
+ {
185
+ command: `${program.name()} session <sessionId>`,
186
+ description: 'Attach to a running/completed session and stream the saved transcript.',
187
+ },
188
+ ];
189
+ const exampleLines = exampleEntries
190
+ .map((entry) => `${theme.command(` ${entry.command}`)}\n${theme.muted(` ${entry.description}`)}`)
191
+ .join('\n\n');
192
+ const paletteList = HELP_THEME_NAMES.map((name) => name === theme.name ? theme.command(name) : theme.accent(name)).join(theme.muted(', '));
193
+ const themeLines = [
194
+ `${theme.bullet(' •')} Current: ${theme.command(theme.name)}`,
195
+ `${theme.bullet(' •')} Try: ${paletteList}`,
196
+ `${theme.bullet(' •')} Switch via ${theme.accent('--help-theme <name>')} or ${theme.accent('ORACLE_HELP_THEME=<name>')}.`,
134
197
  ].join('\n');
135
- const formatExample = (command, description) => `${helpColors.command(` ${command}`)}\n${helpColors.muted(` ${description}`)}`;
136
- const examples = [
137
- formatExample(`${program.name()} --prompt "Summarize risks" --file docs/risk.md --files-report --preview`, 'Inspect tokens + files without calling the API.'),
138
- formatExample(`${program.name()} --prompt "Explain bug" --file src/,docs/crash.log --files-report`, 'Attach src/ plus docs/crash.log, launch a background session, and capture the Session ID.'),
139
- formatExample(`${program.name()} status --hours 72 --limit 50`, 'Show sessions from the last 72h (capped at 50 entries).'),
140
- formatExample(`${program.name()} session <sessionId>`, 'Attach to a running/completed session and stream the saved transcript.'),
141
- formatExample(`${program.name()} --prompt "Ship review" --slug "release-readiness-audit"`, 'Encourage the model to hand you a 3–5 word slug and pass it along with --slug.'),
142
- ].join('\n\n');
143
198
  return `
144
- ${helpColors.section('Tips')}
145
- ${tips}
199
+ ${theme.heading('Tips')}
200
+ ${tipLines}
201
+
202
+ ${theme.heading('Examples')}
203
+ ${exampleLines}
146
204
 
147
- ${helpColors.section('Examples')}
148
- ${examples}
205
+ ${theme.heading('Color Themes')}
206
+ ${themeLines}
149
207
  `;
150
208
  }
151
209
  function collectPaths(value, previous = []) {
@@ -162,32 +220,6 @@ function parseFloatOption(value) {
162
220
  }
163
221
  return parsed;
164
222
  }
165
- const DEFAULT_BROWSER_TIMEOUT_MS = 900_000;
166
- const DEFAULT_BROWSER_INPUT_TIMEOUT_MS = 30_000;
167
- const BROWSER_MODEL_LABELS = {
168
- 'gpt-5-pro': 'GPT-5 Pro',
169
- 'gpt-5.1': 'ChatGPT 5.1',
170
- };
171
- function buildBrowserConfig(options) {
172
- return {
173
- chromeProfile: options.browserChromeProfile ?? null,
174
- chromePath: options.browserChromePath ?? null,
175
- url: options.browserUrl,
176
- timeoutMs: options.browserTimeout ? parseDuration(options.browserTimeout, DEFAULT_BROWSER_TIMEOUT_MS) : undefined,
177
- inputTimeoutMs: options.browserInputTimeout
178
- ? parseDuration(options.browserInputTimeout, DEFAULT_BROWSER_INPUT_TIMEOUT_MS)
179
- : undefined,
180
- cookieSync: options.browserNoCookieSync ? false : undefined,
181
- headless: options.browserHeadless ? true : undefined,
182
- keepBrowser: options.browserKeepBrowser ? true : undefined,
183
- hideWindow: options.browserHideWindow ? true : undefined,
184
- desiredModel: mapModelToBrowserLabel(options.model),
185
- debug: options.verbose ? true : undefined,
186
- };
187
- }
188
- function mapModelToBrowserLabel(model) {
189
- return BROWSER_MODEL_LABELS[model] ?? DEFAULT_MODEL_TARGET;
190
- }
191
223
  function validateModel(value) {
192
224
  if (!(value in MODEL_CONFIGS)) {
193
225
  throw new InvalidArgumentError(`Unsupported model "${value}". Choose one of: ${Object.keys(MODEL_CONFIGS).join(', ')}`);
@@ -217,7 +249,6 @@ function buildRunOptions(options, overrides = {}) {
217
249
  prompt: options.prompt,
218
250
  model: options.model,
219
251
  file: overrides.file ?? options.file ?? [],
220
- slug: overrides.slug ?? options.slug,
221
252
  filesReport: overrides.filesReport ?? options.filesReport,
222
253
  maxInput: overrides.maxInput ?? options.maxInput,
223
254
  maxOutput: overrides.maxOutput ?? options.maxOutput,
@@ -228,7 +259,6 @@ function buildRunOptions(options, overrides = {}) {
228
259
  previewMode: overrides.previewMode ?? options.previewMode,
229
260
  apiKey: overrides.apiKey ?? options.apiKey,
230
261
  sessionId: overrides.sessionId ?? options.sessionId,
231
- verbose: overrides.verbose ?? options.verbose,
232
262
  };
233
263
  }
234
264
  function buildRunOptionsFromMetadata(metadata) {
@@ -237,7 +267,6 @@ function buildRunOptionsFromMetadata(metadata) {
237
267
  prompt: stored.prompt ?? '',
238
268
  model: stored.model ?? 'gpt-5-pro',
239
269
  file: stored.file ?? [],
240
- slug: stored.slug,
241
270
  filesReport: stored.filesReport,
242
271
  maxInput: stored.maxInput,
243
272
  maxOutput: stored.maxOutput,
@@ -248,15 +277,8 @@ function buildRunOptionsFromMetadata(metadata) {
248
277
  previewMode: undefined,
249
278
  apiKey: undefined,
250
279
  sessionId: metadata.id,
251
- verbose: stored.verbose,
252
280
  };
253
281
  }
254
- function getSessionMode(metadata) {
255
- return metadata.mode ?? metadata.options?.mode ?? 'api';
256
- }
257
- function getBrowserConfigFromMetadata(metadata) {
258
- return metadata.options?.browserConfig ?? metadata.browser?.config;
259
- }
260
282
  async function runRootCommand(options) {
261
283
  const helpRequested = rawCliArgs.some((arg) => arg === '--help' || arg === '-h');
262
284
  if (helpRequested) {
@@ -286,9 +308,6 @@ async function runRootCommand(options) {
286
308
  return;
287
309
  }
288
310
  if (previewMode) {
289
- if (options.browser) {
290
- throw new Error('--browser cannot be combined with --preview.');
291
- }
292
311
  if (!options.prompt) {
293
312
  throw new Error('Prompt is required when using --preview.');
294
313
  }
@@ -302,20 +321,14 @@ async function runRootCommand(options) {
302
321
  if (options.file && options.file.length > 0) {
303
322
  await readFiles(options.file, { cwd: process.cwd() });
304
323
  }
305
- const sessionMode = options.browser ? 'browser' : 'api';
306
- const browserConfig = sessionMode === 'browser' ? buildBrowserConfig(options) : undefined;
307
324
  await ensureSessionStorage();
308
325
  const baseRunOptions = buildRunOptions(options, { preview: false, previewMode: undefined });
309
- const sessionMeta = await initializeSession({
310
- ...baseRunOptions,
311
- mode: sessionMode,
312
- browserConfig,
313
- }, process.cwd());
326
+ const sessionMeta = await initializeSession(baseRunOptions, process.cwd());
314
327
  const liveRunOptions = { ...baseRunOptions, sessionId: sessionMeta.id };
315
- await runInteractiveSession(sessionMeta, liveRunOptions, sessionMode, browserConfig);
328
+ await runInteractiveSession(sessionMeta, liveRunOptions);
316
329
  console.log(chalk.bold(`Session ${sessionMeta.id} completed`));
317
330
  }
318
- async function runInteractiveSession(sessionMeta, runOptions, mode, browserConfig) {
331
+ async function runInteractiveSession(sessionMeta, runOptions) {
319
332
  const { logLine, writeChunk, stream } = createSessionLogWriter(sessionMeta.id);
320
333
  let headerAugmented = false;
321
334
  const combinedLog = (message = '') => {
@@ -333,17 +346,29 @@ async function runInteractiveSession(sessionMeta, runOptions, mode, browserConfi
333
346
  return process.stdout.write(chunk);
334
347
  };
335
348
  try {
336
- await performSessionRun({
337
- sessionMeta,
338
- runOptions,
339
- mode,
340
- browserConfig,
341
- cwd: process.cwd(),
349
+ await updateSessionMetadata(sessionMeta.id, { status: 'running', startedAt: new Date().toISOString() });
350
+ const result = await runOracle(runOptions, {
342
351
  log: combinedLog,
343
352
  write: combinedWrite,
344
353
  });
354
+ if (result.mode !== 'live') {
355
+ throw new Error('Unexpected preview result while running an interactive session.');
356
+ }
357
+ await updateSessionMetadata(sessionMeta.id, {
358
+ status: 'completed',
359
+ completedAt: new Date().toISOString(),
360
+ usage: result.usage,
361
+ elapsedMs: result.elapsedMs,
362
+ });
345
363
  }
346
364
  catch (error) {
365
+ const message = formatError(error);
366
+ combinedLog(`ERROR: ${message}`);
367
+ await updateSessionMetadata(sessionMeta.id, {
368
+ status: 'error',
369
+ completedAt: new Date().toISOString(),
370
+ errorMessage: message,
371
+ });
347
372
  throw error;
348
373
  }
349
374
  finally {
@@ -358,152 +383,36 @@ async function executeSession(sessionId) {
358
383
  return;
359
384
  }
360
385
  const runOptions = buildRunOptionsFromMetadata(metadata);
361
- const sessionMode = getSessionMode(metadata);
362
- const browserConfig = getBrowserConfigFromMetadata(metadata);
363
386
  const { logLine, writeChunk, stream } = createSessionLogWriter(sessionId);
364
387
  try {
365
- await performSessionRun({
366
- sessionMeta: metadata,
367
- runOptions,
368
- mode: sessionMode,
369
- browserConfig,
370
- cwd: metadata.cwd ?? process.cwd(),
388
+ await updateSessionMetadata(sessionId, { status: 'running', startedAt: new Date().toISOString() });
389
+ const result = await runOracle(runOptions, {
390
+ cwd: metadata.cwd,
371
391
  log: logLine,
372
392
  write: writeChunk,
373
393
  });
374
- }
375
- catch {
376
- // Errors are already logged to the session log; keep quiet to mirror stored-session behavior.
377
- }
378
- finally {
379
- stream.end();
380
- }
381
- }
382
- async function performSessionRun({ sessionMeta, runOptions, mode, browserConfig, cwd, log, write, }) {
383
- await updateSessionMetadata(sessionMeta.id, {
384
- status: 'running',
385
- startedAt: new Date().toISOString(),
386
- mode,
387
- ...(browserConfig ? { browser: { config: browserConfig } } : {}),
388
- });
389
- try {
390
- if (mode === 'browser') {
391
- if (!browserConfig) {
392
- throw new Error('Missing browser configuration for session.');
393
- }
394
- const result = await runBrowserSessionExecution({
395
- runOptions,
396
- browserConfig,
397
- cwd,
398
- log,
399
- });
400
- await updateSessionMetadata(sessionMeta.id, {
401
- status: 'completed',
402
- completedAt: new Date().toISOString(),
403
- usage: result.usage,
404
- elapsedMs: result.elapsedMs,
405
- browser: {
406
- config: browserConfig,
407
- runtime: result.runtime,
408
- },
409
- response: undefined,
410
- });
411
- return;
412
- }
413
- const result = await runOracle(runOptions, {
414
- cwd,
415
- log,
416
- write,
417
- });
418
394
  if (result.mode !== 'live') {
419
- throw new Error('Unexpected preview result while running a session.');
395
+ throw new Error('Unexpected preview result while executing a stored session.');
420
396
  }
421
- await updateSessionMetadata(sessionMeta.id, {
397
+ await updateSessionMetadata(sessionId, {
422
398
  status: 'completed',
423
399
  completedAt: new Date().toISOString(),
424
400
  usage: result.usage,
425
401
  elapsedMs: result.elapsedMs,
426
- response: extractResponseMetadata(result.response),
427
402
  });
428
403
  }
429
404
  catch (error) {
430
405
  const message = formatError(error);
431
- log(`ERROR: ${message}`);
432
- const responseMetadata = error instanceof OracleResponseError ? error.metadata : undefined;
433
- const metadataLine = formatResponseMetadata(responseMetadata);
434
- if (metadataLine) {
435
- log(dim(`Response metadata: ${metadataLine}`));
436
- }
437
- await updateSessionMetadata(sessionMeta.id, {
406
+ logLine(`ERROR: ${message}`);
407
+ await updateSessionMetadata(sessionId, {
438
408
  status: 'error',
439
409
  completedAt: new Date().toISOString(),
440
410
  errorMessage: message,
441
- mode,
442
- browser: browserConfig ? { config: browserConfig } : undefined,
443
- response: responseMetadata,
444
411
  });
445
- throw error;
446
412
  }
447
- }
448
- async function runBrowserSessionExecution({ runOptions, browserConfig, cwd, log, }) {
449
- const promptArtifacts = await assembleBrowserPrompt(runOptions, cwd);
450
- if (runOptions.verbose) {
451
- log(dim(`[verbose] Browser config: ${JSON.stringify({
452
- ...browserConfig,
453
- })}`));
454
- log(dim(`[verbose] Browser prompt length: ${promptArtifacts.markdown.length} chars`));
455
- }
456
- const headerLine = `Oracle (${VERSION}) launching browser mode (${runOptions.model}) with ~${promptArtifacts.estimatedInputTokens.toLocaleString()} tokens`;
457
- log(headerLine);
458
- log(dim('Chrome automation does not stream output; this may take a minute...'));
459
- const browserResult = await runBrowserMode({
460
- prompt: promptArtifacts.markdown,
461
- config: browserConfig,
462
- log,
463
- });
464
- if (!runOptions.silent) {
465
- log(chalk.bold('Answer:'));
466
- log(browserResult.answerMarkdown || browserResult.answerText || chalk.dim('(no text output)'));
467
- log('');
468
- }
469
- const usage = {
470
- inputTokens: promptArtifacts.estimatedInputTokens,
471
- outputTokens: browserResult.answerTokens,
472
- reasoningTokens: 0,
473
- totalTokens: promptArtifacts.estimatedInputTokens + browserResult.answerTokens,
474
- };
475
- const tokensDisplay = `${usage.inputTokens}/${usage.outputTokens}/${usage.reasoningTokens}/${usage.totalTokens}`;
476
- const statsParts = [`${runOptions.model}[browser]`, `tok(i/o/r/t)=${tokensDisplay}`];
477
- if (runOptions.file && runOptions.file.length > 0) {
478
- statsParts.push(`files=${runOptions.file.length}`);
413
+ finally {
414
+ stream.end();
479
415
  }
480
- log(chalk.blue(`Finished in ${formatElapsed(browserResult.tookMs)} (${statsParts.join(' | ')})`));
481
- return {
482
- usage,
483
- elapsedMs: browserResult.tookMs,
484
- runtime: {
485
- chromePid: browserResult.chromePid,
486
- chromePort: browserResult.chromePort,
487
- userDataDir: browserResult.userDataDir,
488
- },
489
- };
490
- }
491
- async function assembleBrowserPrompt(runOptions, cwd) {
492
- const files = await readFiles(runOptions.file ?? [], { cwd });
493
- const userPrompt = buildPrompt(runOptions.prompt, files, cwd);
494
- const systemPrompt = runOptions.system?.trim() || DEFAULT_SYSTEM_PROMPT;
495
- const sections = createFileSections(files, cwd);
496
- const lines = ['[SYSTEM]', systemPrompt, '', '[USER]', userPrompt, ''];
497
- sections.forEach((section) => {
498
- lines.push(`[FILE: ${section.displayPath}]`, section.content.trimEnd(), '');
499
- });
500
- const markdown = lines.join('\n').trimEnd();
501
- const tokenizer = MODEL_CONFIGS[runOptions.model].tokenizer;
502
- const estimatedInputTokens = tokenizer([
503
- { role: 'system', content: systemPrompt },
504
- { role: 'user', content: userPrompt },
505
- ], TOKENIZER_OPTIONS);
506
- return { markdown, estimatedInputTokens };
507
416
  }
508
417
  async function showStatus({ hours, includeAll, limit, showExamples = false }) {
509
418
  const metas = await listSessionsMetadata();
@@ -556,10 +465,6 @@ async function attachSession(sessionId) {
556
465
  console.log(`Created: ${metadata.createdAt}`);
557
466
  console.log(`Status: ${metadata.status}`);
558
467
  console.log(`Model: ${metadata.model}`);
559
- const responseSummary = formatResponseMetadata(metadata.response);
560
- if (responseSummary) {
561
- console.log(dim(`Response: ${responseSummary}`));
562
- }
563
468
  let lastLength = 0;
564
469
  const printNew = async () => {
565
470
  const text = await readSessionLog(sessionId);
@@ -594,25 +499,6 @@ async function attachSession(sessionId) {
594
499
  function formatError(error) {
595
500
  return error instanceof Error ? error.message : String(error);
596
501
  }
597
- function formatResponseMetadata(metadata) {
598
- if (!metadata) {
599
- return null;
600
- }
601
- const parts = [];
602
- if (metadata.responseId) {
603
- parts.push(`response=${metadata.responseId}`);
604
- }
605
- if (metadata.requestId) {
606
- parts.push(`request=${metadata.requestId}`);
607
- }
608
- if (metadata.status) {
609
- parts.push(`status=${metadata.status}`);
610
- }
611
- if (metadata.incompleteReason) {
612
- parts.push(`incomplete=${metadata.incompleteReason}`);
613
- }
614
- return parts.length > 0 ? parts.join(' | ') : null;
615
- }
616
502
  function buildReattachLine(metadata) {
617
503
  if (!metadata.id) {
618
504
  return null;