@indykish/oracle 0.9.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 (131) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +215 -0
  3. package/assets-oracle-icon.png +0 -0
  4. package/dist/bin/oracle-cli.js +1252 -0
  5. package/dist/bin/oracle-mcp.js +6 -0
  6. package/dist/scripts/agent-send.js +147 -0
  7. package/dist/scripts/browser-tools.js +536 -0
  8. package/dist/scripts/check.js +21 -0
  9. package/dist/scripts/debug/extract-chatgpt-response.js +53 -0
  10. package/dist/scripts/docs-list.js +110 -0
  11. package/dist/scripts/git-policy.js +125 -0
  12. package/dist/scripts/run-cli.js +14 -0
  13. package/dist/scripts/runner.js +1378 -0
  14. package/dist/scripts/test-browser.js +103 -0
  15. package/dist/scripts/test-remote-chrome.js +68 -0
  16. package/dist/src/bridge/connection.js +103 -0
  17. package/dist/src/bridge/userConfigFile.js +28 -0
  18. package/dist/src/browser/actions/assistantResponse.js +1067 -0
  19. package/dist/src/browser/actions/attachmentDataTransfer.js +138 -0
  20. package/dist/src/browser/actions/attachments.js +1910 -0
  21. package/dist/src/browser/actions/domEvents.js +19 -0
  22. package/dist/src/browser/actions/modelSelection.js +485 -0
  23. package/dist/src/browser/actions/navigation.js +445 -0
  24. package/dist/src/browser/actions/promptComposer.js +485 -0
  25. package/dist/src/browser/actions/remoteFileTransfer.js +37 -0
  26. package/dist/src/browser/actions/thinkingTime.js +206 -0
  27. package/dist/src/browser/chromeLifecycle.js +344 -0
  28. package/dist/src/browser/config.js +103 -0
  29. package/dist/src/browser/constants.js +71 -0
  30. package/dist/src/browser/cookies.js +191 -0
  31. package/dist/src/browser/detect.js +164 -0
  32. package/dist/src/browser/domDebug.js +36 -0
  33. package/dist/src/browser/index.js +1741 -0
  34. package/dist/src/browser/modelStrategy.js +13 -0
  35. package/dist/src/browser/pageActions.js +5 -0
  36. package/dist/src/browser/policies.js +43 -0
  37. package/dist/src/browser/profileState.js +280 -0
  38. package/dist/src/browser/prompt.js +152 -0
  39. package/dist/src/browser/promptSummary.js +20 -0
  40. package/dist/src/browser/reattach.js +186 -0
  41. package/dist/src/browser/reattachHelpers.js +382 -0
  42. package/dist/src/browser/sessionRunner.js +119 -0
  43. package/dist/src/browser/types.js +1 -0
  44. package/dist/src/browser/utils.js +122 -0
  45. package/dist/src/browserMode.js +1 -0
  46. package/dist/src/cli/bridge/claudeConfig.js +54 -0
  47. package/dist/src/cli/bridge/client.js +73 -0
  48. package/dist/src/cli/bridge/codexConfig.js +43 -0
  49. package/dist/src/cli/bridge/doctor.js +107 -0
  50. package/dist/src/cli/bridge/host.js +259 -0
  51. package/dist/src/cli/browserConfig.js +278 -0
  52. package/dist/src/cli/browserDefaults.js +81 -0
  53. package/dist/src/cli/bundleWarnings.js +9 -0
  54. package/dist/src/cli/clipboard.js +10 -0
  55. package/dist/src/cli/detach.js +11 -0
  56. package/dist/src/cli/dryRun.js +105 -0
  57. package/dist/src/cli/duplicatePromptGuard.js +14 -0
  58. package/dist/src/cli/engine.js +41 -0
  59. package/dist/src/cli/errorUtils.js +9 -0
  60. package/dist/src/cli/format.js +13 -0
  61. package/dist/src/cli/help.js +77 -0
  62. package/dist/src/cli/hiddenAliases.js +22 -0
  63. package/dist/src/cli/markdownBundle.js +17 -0
  64. package/dist/src/cli/markdownRenderer.js +97 -0
  65. package/dist/src/cli/notifier.js +306 -0
  66. package/dist/src/cli/options.js +281 -0
  67. package/dist/src/cli/oscUtils.js +2 -0
  68. package/dist/src/cli/promptRequirement.js +17 -0
  69. package/dist/src/cli/renderFlags.js +9 -0
  70. package/dist/src/cli/renderOutput.js +26 -0
  71. package/dist/src/cli/rootAlias.js +30 -0
  72. package/dist/src/cli/runOptions.js +78 -0
  73. package/dist/src/cli/sessionCommand.js +111 -0
  74. package/dist/src/cli/sessionDisplay.js +567 -0
  75. package/dist/src/cli/sessionRunner.js +602 -0
  76. package/dist/src/cli/sessionTable.js +92 -0
  77. package/dist/src/cli/tagline.js +258 -0
  78. package/dist/src/cli/tui/index.js +486 -0
  79. package/dist/src/cli/writeOutputPath.js +21 -0
  80. package/dist/src/config.js +26 -0
  81. package/dist/src/gemini-web/client.js +328 -0
  82. package/dist/src/gemini-web/executor.js +285 -0
  83. package/dist/src/gemini-web/index.js +1 -0
  84. package/dist/src/gemini-web/types.js +1 -0
  85. package/dist/src/heartbeat.js +43 -0
  86. package/dist/src/mcp/server.js +40 -0
  87. package/dist/src/mcp/tools/consult.js +290 -0
  88. package/dist/src/mcp/tools/sessionResources.js +75 -0
  89. package/dist/src/mcp/tools/sessions.js +105 -0
  90. package/dist/src/mcp/types.js +22 -0
  91. package/dist/src/mcp/utils.js +37 -0
  92. package/dist/src/oracle/background.js +141 -0
  93. package/dist/src/oracle/claude.js +101 -0
  94. package/dist/src/oracle/client.js +197 -0
  95. package/dist/src/oracle/config.js +227 -0
  96. package/dist/src/oracle/errors.js +132 -0
  97. package/dist/src/oracle/files.js +378 -0
  98. package/dist/src/oracle/finishLine.js +32 -0
  99. package/dist/src/oracle/format.js +30 -0
  100. package/dist/src/oracle/fsAdapter.js +10 -0
  101. package/dist/src/oracle/gemini.js +195 -0
  102. package/dist/src/oracle/logging.js +36 -0
  103. package/dist/src/oracle/markdown.js +46 -0
  104. package/dist/src/oracle/modelResolver.js +183 -0
  105. package/dist/src/oracle/multiModelRunner.js +153 -0
  106. package/dist/src/oracle/oscProgress.js +24 -0
  107. package/dist/src/oracle/promptAssembly.js +13 -0
  108. package/dist/src/oracle/request.js +50 -0
  109. package/dist/src/oracle/run.js +596 -0
  110. package/dist/src/oracle/runUtils.js +31 -0
  111. package/dist/src/oracle/tokenEstimate.js +37 -0
  112. package/dist/src/oracle/tokenStats.js +39 -0
  113. package/dist/src/oracle/tokenStringifier.js +24 -0
  114. package/dist/src/oracle/types.js +1 -0
  115. package/dist/src/oracle.js +12 -0
  116. package/dist/src/oracleHome.js +13 -0
  117. package/dist/src/remote/client.js +129 -0
  118. package/dist/src/remote/health.js +113 -0
  119. package/dist/src/remote/remoteServiceConfig.js +31 -0
  120. package/dist/src/remote/server.js +533 -0
  121. package/dist/src/remote/types.js +1 -0
  122. package/dist/src/sessionManager.js +637 -0
  123. package/dist/src/sessionStore.js +56 -0
  124. package/dist/src/version.js +39 -0
  125. package/dist/vendor/oracle-notifier/OracleNotifier.swift +45 -0
  126. package/dist/vendor/oracle-notifier/README.md +24 -0
  127. package/dist/vendor/oracle-notifier/build-notifier.sh +93 -0
  128. package/package.json +115 -0
  129. package/vendor/oracle-notifier/OracleNotifier.swift +45 -0
  130. package/vendor/oracle-notifier/README.md +24 -0
  131. package/vendor/oracle-notifier/build-notifier.sh +93 -0
@@ -0,0 +1,281 @@
1
+ import { InvalidArgumentError } from 'commander';
2
+ import { parseDuration } from '../browserMode.js';
3
+ import path from 'node:path';
4
+ import fg from 'fast-glob';
5
+ import { DEFAULT_MODEL, MODEL_CONFIGS } from '../oracle.js';
6
+ export function collectPaths(value, previous = []) {
7
+ if (!value) {
8
+ return previous;
9
+ }
10
+ const nextValues = Array.isArray(value) ? value : [value];
11
+ return previous.concat(nextValues.flatMap((entry) => entry.split(',')).map((entry) => entry.trim()).filter(Boolean));
12
+ }
13
+ /**
14
+ * Merge all path-like CLI inputs (file/include aliases) into a single list, preserving order.
15
+ */
16
+ export function mergePathLikeOptions(file, include, filesAlias, pathAlias, pathsAlias) {
17
+ const withFile = collectPaths(file, []);
18
+ const withInclude = collectPaths(include, withFile);
19
+ const withFilesAlias = collectPaths(filesAlias, withInclude);
20
+ const withPathAlias = collectPaths(pathAlias, withFilesAlias);
21
+ return collectPaths(pathsAlias, withPathAlias);
22
+ }
23
+ export function dedupePathInputs(inputs, { cwd = process.cwd() } = {}) {
24
+ const deduped = [];
25
+ const duplicates = [];
26
+ const seen = new Set();
27
+ for (const entry of inputs ?? []) {
28
+ const raw = entry?.trim();
29
+ if (!raw)
30
+ continue;
31
+ let key = raw;
32
+ if (!raw.startsWith('!') && !fg.isDynamicPattern(raw)) {
33
+ const absolute = path.isAbsolute(raw) ? raw : path.resolve(cwd, raw);
34
+ key = `path:${path.normalize(absolute)}`;
35
+ }
36
+ else {
37
+ key = `pattern:${raw}`;
38
+ }
39
+ if (seen.has(key)) {
40
+ duplicates.push(raw);
41
+ continue;
42
+ }
43
+ seen.add(key);
44
+ deduped.push(raw);
45
+ }
46
+ return { deduped, duplicates };
47
+ }
48
+ export function collectModelList(value, previous = []) {
49
+ if (!value) {
50
+ return previous;
51
+ }
52
+ const entries = value
53
+ .split(',')
54
+ .map((entry) => entry.trim())
55
+ .filter((entry) => entry.length > 0);
56
+ return previous.concat(entries);
57
+ }
58
+ export function parseFloatOption(value) {
59
+ const parsed = Number.parseFloat(value);
60
+ if (Number.isNaN(parsed)) {
61
+ throw new InvalidArgumentError('Value must be a number.');
62
+ }
63
+ return parsed;
64
+ }
65
+ export function parseIntOption(value) {
66
+ if (value == null) {
67
+ return undefined;
68
+ }
69
+ const parsed = Number.parseInt(value, 10);
70
+ if (Number.isNaN(parsed)) {
71
+ throw new InvalidArgumentError('Value must be an integer.');
72
+ }
73
+ return parsed;
74
+ }
75
+ export function parseHeartbeatOption(value) {
76
+ if (value == null) {
77
+ return 30;
78
+ }
79
+ if (typeof value === 'number') {
80
+ if (Number.isNaN(value) || value < 0) {
81
+ throw new InvalidArgumentError('Heartbeat interval must be zero or a positive number.');
82
+ }
83
+ return value;
84
+ }
85
+ const normalized = value.trim().toLowerCase();
86
+ if (normalized.length === 0) {
87
+ return 30;
88
+ }
89
+ if (normalized === 'false' || normalized === 'off') {
90
+ return 0;
91
+ }
92
+ const parsed = Number.parseFloat(normalized);
93
+ if (Number.isNaN(parsed) || parsed < 0) {
94
+ throw new InvalidArgumentError('Heartbeat interval must be zero or a positive number.');
95
+ }
96
+ return parsed;
97
+ }
98
+ export function usesDefaultStatusFilters(cmd) {
99
+ const hoursSource = cmd.getOptionValueSource?.('hours') ?? 'default';
100
+ const limitSource = cmd.getOptionValueSource?.('limit') ?? 'default';
101
+ const allSource = cmd.getOptionValueSource?.('all') ?? 'default';
102
+ return hoursSource === 'default' && limitSource === 'default' && allSource === 'default';
103
+ }
104
+ export function resolvePreviewMode(value) {
105
+ if (typeof value === 'string' && value.length > 0) {
106
+ return value;
107
+ }
108
+ if (value === true) {
109
+ return 'summary';
110
+ }
111
+ return undefined;
112
+ }
113
+ export function parseSearchOption(value) {
114
+ const normalized = value.trim().toLowerCase();
115
+ if (['on', 'true', '1', 'yes'].includes(normalized)) {
116
+ return true;
117
+ }
118
+ if (['off', 'false', '0', 'no'].includes(normalized)) {
119
+ return false;
120
+ }
121
+ throw new InvalidArgumentError('Search mode must be "on" or "off".');
122
+ }
123
+ export function normalizeModelOption(value) {
124
+ return (value ?? '').trim();
125
+ }
126
+ export function normalizeBaseUrl(value) {
127
+ const trimmed = value?.trim();
128
+ return trimmed?.length ? trimmed : undefined;
129
+ }
130
+ export function parseTimeoutOption(value) {
131
+ if (value == null)
132
+ return undefined;
133
+ const normalized = value.trim().toLowerCase();
134
+ if (normalized === 'auto')
135
+ return 'auto';
136
+ const parsed = Number.parseFloat(normalized);
137
+ if (Number.isNaN(parsed) || parsed <= 0) {
138
+ throw new InvalidArgumentError('Timeout must be a positive number of seconds or "auto".');
139
+ }
140
+ return parsed;
141
+ }
142
+ export function parseDurationOption(value, label) {
143
+ if (value == null)
144
+ return undefined;
145
+ const trimmed = value.trim();
146
+ if (!trimmed) {
147
+ throw new InvalidArgumentError(`${label} must be a duration like 30m, 10s, 500ms, or 2h.`);
148
+ }
149
+ const parsed = parseDuration(trimmed, Number.NaN);
150
+ if (!Number.isFinite(parsed) || parsed <= 0) {
151
+ throw new InvalidArgumentError(`${label} must be a positive duration like 30m, 10s, 500ms, or 2h.`);
152
+ }
153
+ return parsed;
154
+ }
155
+ export function resolveApiModel(modelValue) {
156
+ const normalized = normalizeModelOption(modelValue).toLowerCase();
157
+ if (normalized in MODEL_CONFIGS) {
158
+ return normalized;
159
+ }
160
+ if (normalized.includes('grok')) {
161
+ return normalized.includes('4.2') || normalized.includes('4-2') ? 'grok-4.2' : 'grok-4.1';
162
+ }
163
+ if (normalized.includes('claude') && normalized.includes('sonnet')) {
164
+ if (normalized.includes('4.6') || normalized.includes('4-6')) {
165
+ return 'claude-4.6-sonnet';
166
+ }
167
+ return 'claude-4.5-sonnet';
168
+ }
169
+ if (normalized.includes('claude') && normalized.includes('opus')) {
170
+ if (normalized.includes('4.6') || normalized.includes('4-6')) {
171
+ return 'claude-4.6-opus';
172
+ }
173
+ return 'claude-4.1-opus';
174
+ }
175
+ if (normalized === 'claude' || normalized === 'sonnet' || /(^|\b)sonnet(\b|$)/.test(normalized)) {
176
+ return 'claude-4.6-sonnet';
177
+ }
178
+ if (normalized === 'opus' || normalized === 'claude-4.1') {
179
+ return 'claude-4.1-opus';
180
+ }
181
+ if (normalized.includes('gemini')) {
182
+ return normalized.includes('3.5') || normalized.includes('3-5') ? 'gemini-3.5-pro' : 'gemini-3-pro';
183
+ }
184
+ if (normalized.includes('5.0') || normalized === 'gpt-5-pro' || normalized === 'gpt-5') {
185
+ return 'gpt-5-pro';
186
+ }
187
+ if (normalized.includes('5.3') && normalized.includes('pro')) {
188
+ return 'gpt-5.3-pro';
189
+ }
190
+ if (normalized.includes('5.3')) {
191
+ return 'gpt-5.3';
192
+ }
193
+ if (normalized.includes('5-pro') && !normalized.includes('5.1')) {
194
+ return 'gpt-5-pro';
195
+ }
196
+ if (normalized.includes('5.2') && normalized.includes('pro')) {
197
+ return 'gpt-5.2-pro';
198
+ }
199
+ if (normalized.includes('5.1') && normalized.includes('pro')) {
200
+ return 'gpt-5.1-pro';
201
+ }
202
+ if (normalized.includes('codex')) {
203
+ if (normalized.includes('max')) {
204
+ throw new InvalidArgumentError('gpt-5.1-codex-max is not available yet. OpenAI has not released the API.');
205
+ }
206
+ return 'gpt-5.1-codex';
207
+ }
208
+ if (normalized.includes('pro')) {
209
+ return 'gpt-5.3-pro';
210
+ }
211
+ // Passthrough for custom/OpenRouter model IDs.
212
+ return normalized;
213
+ }
214
+ export function inferModelFromLabel(modelValue) {
215
+ const normalized = normalizeModelOption(modelValue).toLowerCase();
216
+ if (!normalized) {
217
+ return DEFAULT_MODEL;
218
+ }
219
+ if (normalized in MODEL_CONFIGS) {
220
+ return normalized;
221
+ }
222
+ if (normalized.includes('grok')) {
223
+ return normalized.includes('4.2') || normalized.includes('4-2') ? 'grok-4.2' : 'grok-4.1';
224
+ }
225
+ if (normalized.includes('claude') && normalized.includes('sonnet')) {
226
+ return normalized.includes('4.6') || normalized.includes('4-6') ? 'claude-4.6-sonnet' : 'claude-4.5-sonnet';
227
+ }
228
+ if (normalized.includes('claude') && normalized.includes('opus')) {
229
+ return normalized.includes('4.6') || normalized.includes('4-6') ? 'claude-4.6-opus' : 'claude-4.1-opus';
230
+ }
231
+ if (normalized.includes('codex')) {
232
+ return 'gpt-5.1-codex';
233
+ }
234
+ if (normalized.includes('gemini')) {
235
+ return normalized.includes('3.5') || normalized.includes('3-5') ? 'gemini-3.5-pro' : 'gemini-3-pro';
236
+ }
237
+ if (normalized.includes('classic')) {
238
+ return 'gpt-5-pro';
239
+ }
240
+ if ((normalized.includes('5.3') || normalized.includes('5_3')) && normalized.includes('pro')) {
241
+ return 'gpt-5.3-pro';
242
+ }
243
+ if (normalized.includes('5.3') || normalized.includes('5_3')) {
244
+ return 'gpt-5.3';
245
+ }
246
+ if ((normalized.includes('5.2') || normalized.includes('5_2')) && normalized.includes('pro')) {
247
+ return 'gpt-5.2-pro';
248
+ }
249
+ // Browser-only: pass through 5.2 thinking/instant variants for browser label mapping
250
+ if ((normalized.includes('5.2') || normalized.includes('5_2')) && normalized.includes('thinking')) {
251
+ return 'gpt-5.2-thinking';
252
+ }
253
+ if ((normalized.includes('5.2') || normalized.includes('5_2')) && normalized.includes('instant')) {
254
+ return 'gpt-5.2-instant';
255
+ }
256
+ if (normalized.includes('5.0') || normalized.includes('5-pro')) {
257
+ return 'gpt-5-pro';
258
+ }
259
+ if (normalized.includes('gpt-5') &&
260
+ normalized.includes('pro') &&
261
+ !normalized.includes('5.1') &&
262
+ !normalized.includes('5.2')) {
263
+ return 'gpt-5-pro';
264
+ }
265
+ if ((normalized.includes('5.1') || normalized.includes('5_1')) && normalized.includes('pro')) {
266
+ return 'gpt-5.1-pro';
267
+ }
268
+ if (normalized.includes('pro')) {
269
+ return 'gpt-5.3-pro';
270
+ }
271
+ if (normalized.includes('5.1') || normalized.includes('5_1')) {
272
+ return 'gpt-5.1';
273
+ }
274
+ if (normalized.includes('thinking')) {
275
+ return 'gpt-5.2-thinking';
276
+ }
277
+ if (normalized.includes('instant') || normalized.includes('fast')) {
278
+ return 'gpt-5.2-instant';
279
+ }
280
+ return 'gpt-5.3';
281
+ }
@@ -0,0 +1,2 @@
1
+ // Utilities for handling OSC progress codes embedded in stored logs.
2
+ export { sanitizeOscProgress } from 'osc-progress';
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Determine whether the CLI should enforce a prompt requirement based on raw args and options.
3
+ */
4
+ export function shouldRequirePrompt(rawArgs, options) {
5
+ if (rawArgs.length === 0) {
6
+ return !options.prompt;
7
+ }
8
+ const firstArg = rawArgs[0];
9
+ const bypassPrompt = Boolean(options.session ||
10
+ options.execSession ||
11
+ options.status ||
12
+ options.debugHelp ||
13
+ firstArg === 'status' ||
14
+ firstArg === 'session');
15
+ const requiresPrompt = options.renderMarkdown || Boolean(options.preview) || Boolean(options.dryRun) || !bypassPrompt;
16
+ return requiresPrompt && !options.prompt;
17
+ }
@@ -0,0 +1,9 @@
1
+ export function resolveRenderFlag(render, renderMarkdown) {
2
+ return Boolean(renderMarkdown || render);
3
+ }
4
+ export function resolveRenderPlain(renderPlain, render, renderMarkdown) {
5
+ // Explicit plain render wins when any render flag is set; otherwise false.
6
+ if (!renderPlain)
7
+ return false;
8
+ return Boolean(renderMarkdown || render || renderPlain);
9
+ }
@@ -0,0 +1,26 @@
1
+ import { ensureShikiReady, renderMarkdownAnsi } from './markdownRenderer.js';
2
+ export function shouldRenderRich(options = {}) {
3
+ return options.richTty ?? Boolean(process.stdout.isTTY);
4
+ }
5
+ /**
6
+ * Format markdown for CLI output. Uses our ANSI renderer + syntax highlighting
7
+ * when running in a rich TTY; otherwise returns the raw markdown to avoid
8
+ * escape codes in redirected output.
9
+ */
10
+ export async function formatRenderedMarkdown(markdown, options = {}) {
11
+ const richTty = shouldRenderRich(options);
12
+ if (!richTty)
13
+ return markdown;
14
+ try {
15
+ await ensureShikiReady();
16
+ }
17
+ catch {
18
+ // If Shiki fails to init (missing terminals/themes), fall back to plain output gracefully.
19
+ }
20
+ try {
21
+ return renderMarkdownAnsi(markdown);
22
+ }
23
+ catch {
24
+ return markdown;
25
+ }
26
+ }
@@ -0,0 +1,30 @@
1
+ import { attachSession, showStatus } from './sessionDisplay.js';
2
+ const defaultDeps = {
3
+ attachSession,
4
+ showStatus,
5
+ };
6
+ export async function handleStatusFlag(options, deps = defaultDeps) {
7
+ if (!options.status) {
8
+ return false;
9
+ }
10
+ if (options.session) {
11
+ await deps.attachSession(options.session);
12
+ return true;
13
+ }
14
+ await deps.showStatus({ hours: 24, includeAll: false, limit: 100, showExamples: true });
15
+ return true;
16
+ }
17
+ const defaultSessionDeps = {
18
+ attachSession,
19
+ };
20
+ /**
21
+ * Hidden root-level alias to attach to a stored session (`--session <id>`).
22
+ * Returns true when the alias was handled so callers can short-circuit.
23
+ */
24
+ export async function handleSessionAlias(options, deps = defaultSessionDeps) {
25
+ if (!options.session) {
26
+ return false;
27
+ }
28
+ await deps.attachSession(options.session);
29
+ return true;
30
+ }
@@ -0,0 +1,78 @@
1
+ import { DEFAULT_MODEL, MODEL_CONFIGS } from '../oracle.js';
2
+ import { resolveEngine } from './engine.js';
3
+ import { normalizeModelOption, inferModelFromLabel, resolveApiModel, normalizeBaseUrl } from './options.js';
4
+ import { resolveGeminiModelId } from '../oracle/gemini.js';
5
+ import { PromptValidationError } from '../oracle/errors.js';
6
+ import { normalizeChatGptModelForBrowser } from './browserConfig.js';
7
+ export function resolveRunOptionsFromConfig({ prompt, files = [], model, models, engine, userConfig, env = process.env, }) {
8
+ const resolvedEngine = resolveEngineWithConfig({ engine, configEngine: userConfig?.engine, env });
9
+ const browserRequested = engine === 'browser';
10
+ const browserConfigured = userConfig?.engine === 'browser';
11
+ const requestedModelList = Array.isArray(models) ? models : [];
12
+ const normalizedRequestedModels = requestedModelList.map((entry) => normalizeModelOption(entry)).filter(Boolean);
13
+ const cliModelArg = normalizeModelOption(model ?? userConfig?.model) || DEFAULT_MODEL;
14
+ const inferredModel = resolvedEngine === 'browser' && normalizedRequestedModels.length === 0
15
+ ? inferModelFromLabel(cliModelArg)
16
+ : resolveApiModel(cliModelArg);
17
+ // Browser engine maps Pro/legacy aliases to the latest ChatGPT picker targets (GPT-5.2 / GPT-5.2 Pro).
18
+ const resolvedModel = resolvedEngine === 'browser' ? normalizeChatGptModelForBrowser(inferredModel) : inferredModel;
19
+ const isCodex = resolvedModel.startsWith('gpt-5.1-codex');
20
+ const isClaude = resolvedModel.startsWith('claude');
21
+ const isGrok = resolvedModel.startsWith('grok');
22
+ const engineWasBrowser = resolvedEngine === 'browser';
23
+ const allModels = normalizedRequestedModels.length > 0
24
+ ? Array.from(new Set(normalizedRequestedModels.map((entry) => resolveApiModel(entry))))
25
+ : [resolvedModel];
26
+ const isBrowserCompatible = (m) => m.startsWith('gpt-') || m.startsWith('gemini');
27
+ const hasNonBrowserCompatibleTarget = (browserRequested || browserConfigured) && allModels.some((m) => !isBrowserCompatible(m));
28
+ if (hasNonBrowserCompatibleTarget) {
29
+ throw new PromptValidationError('Browser engine only supports GPT and Gemini models. Re-run with --engine api for Grok, Claude, or other models.', { engine: 'browser', models: allModels });
30
+ }
31
+ const engineCoercedToApi = engineWasBrowser && (isCodex || isClaude || isGrok);
32
+ const fixedEngine = isCodex || isClaude || isGrok || normalizedRequestedModels.length > 0 ? 'api' : resolvedEngine;
33
+ const promptWithSuffix = userConfig?.promptSuffix && userConfig.promptSuffix.trim().length > 0
34
+ ? `${prompt.trim()}\n${userConfig.promptSuffix}`
35
+ : prompt;
36
+ const search = userConfig?.search !== 'off';
37
+ const heartbeatIntervalMs = userConfig?.heartbeatSeconds !== undefined ? userConfig.heartbeatSeconds * 1000 : 30_000;
38
+ const baseUrl = normalizeBaseUrl(userConfig?.apiBaseUrl ??
39
+ (isClaude ? env.ANTHROPIC_BASE_URL : isGrok ? env.XAI_BASE_URL : env.OPENAI_BASE_URL));
40
+ const uniqueMultiModels = normalizedRequestedModels.length > 0 ? allModels : [];
41
+ const includesCodexMultiModel = uniqueMultiModels.some((entry) => entry.startsWith('gpt-5.1-codex'));
42
+ if (includesCodexMultiModel && browserRequested) {
43
+ // Silent coerce; multi-model still forces API.
44
+ }
45
+ const chosenModel = uniqueMultiModels[0] ?? resolvedModel;
46
+ const effectiveModelId = resolveEffectiveModelId(chosenModel);
47
+ const runOptions = {
48
+ prompt: promptWithSuffix,
49
+ model: chosenModel,
50
+ models: uniqueMultiModels.length > 0 ? uniqueMultiModels : undefined,
51
+ file: files ?? [],
52
+ search,
53
+ heartbeatIntervalMs,
54
+ filesReport: userConfig?.filesReport,
55
+ background: userConfig?.background,
56
+ baseUrl,
57
+ effectiveModelId,
58
+ };
59
+ return { runOptions, resolvedEngine: fixedEngine, engineCoercedToApi };
60
+ }
61
+ function resolveEngineWithConfig({ engine, configEngine, env, }) {
62
+ if (engine)
63
+ return engine;
64
+ const envOverride = (env.ORACLE_ENGINE ?? '').trim().toLowerCase();
65
+ if (envOverride === 'api' || envOverride === 'browser') {
66
+ return envOverride;
67
+ }
68
+ if (configEngine)
69
+ return configEngine;
70
+ return resolveEngine({ engine: undefined, env });
71
+ }
72
+ function resolveEffectiveModelId(model) {
73
+ if (typeof model === 'string' && model.startsWith('gemini')) {
74
+ return resolveGeminiModelId(model);
75
+ }
76
+ const config = MODEL_CONFIGS[model];
77
+ return config?.apiModel ?? model;
78
+ }
@@ -0,0 +1,111 @@
1
+ import chalk from 'chalk';
2
+ import { usesDefaultStatusFilters } from './options.js';
3
+ import { attachSession, showStatus } from './sessionDisplay.js';
4
+ import { sessionStore } from '../sessionStore.js';
5
+ const defaultDependencies = {
6
+ showStatus,
7
+ attachSession,
8
+ usesDefaultStatusFilters,
9
+ deleteSessionsOlderThan: (options) => sessionStore.deleteOlderThan(options),
10
+ getSessionPaths: (sessionId) => sessionStore.getPaths(sessionId),
11
+ };
12
+ const SESSION_OPTION_KEYS = new Set(['hours', 'limit', 'all', 'clear', 'clean', 'render', 'renderMarkdown', 'path', 'model']);
13
+ export async function handleSessionCommand(sessionId, command, deps = defaultDependencies) {
14
+ const sessionOptions = command.opts();
15
+ if (sessionOptions.verboseRender) {
16
+ process.env.ORACLE_VERBOSE_RENDER = '1';
17
+ }
18
+ const renderSource = command.getOptionValueSource?.('render');
19
+ const renderMarkdownSource = command.getOptionValueSource?.('renderMarkdown');
20
+ const renderExplicit = renderSource === 'cli' || renderMarkdownSource === 'cli';
21
+ const autoRender = !renderExplicit && process.stdout.isTTY;
22
+ const pathRequested = Boolean(sessionOptions.path);
23
+ const clearRequested = Boolean(sessionOptions.clear || sessionOptions.clean);
24
+ if (clearRequested) {
25
+ if (sessionId) {
26
+ console.error('Cannot combine a session ID with --clear. Remove the ID to delete cached sessions.');
27
+ process.exitCode = 1;
28
+ return;
29
+ }
30
+ const hours = sessionOptions.hours;
31
+ const includeAll = sessionOptions.all;
32
+ const result = await deps.deleteSessionsOlderThan({ hours, includeAll });
33
+ const scope = includeAll ? 'all stored sessions' : `sessions older than ${hours}h`;
34
+ console.log(formatSessionCleanupMessage(result, scope));
35
+ return;
36
+ }
37
+ if (sessionId === 'clear' || sessionId === 'clean') {
38
+ console.error('Session cleanup now uses --clear. Run "oracle session --clear --hours <n>" instead.');
39
+ process.exitCode = 1;
40
+ return;
41
+ }
42
+ if (pathRequested) {
43
+ if (!sessionId) {
44
+ console.error('The --path flag requires a session ID.');
45
+ process.exitCode = 1;
46
+ return;
47
+ }
48
+ try {
49
+ const paths = await deps.getSessionPaths(sessionId);
50
+ const richTty = Boolean(process.stdout.isTTY && chalk.level > 0);
51
+ const label = (text) => (richTty ? chalk.cyan(text) : text);
52
+ const value = (text) => (richTty ? chalk.dim(text) : text);
53
+ console.log(`${label('Session dir:')} ${value(paths.dir)}`);
54
+ console.log(`${label('Metadata:')} ${value(paths.metadata)}`);
55
+ console.log(`${label('Request:')} ${value(paths.request)}`);
56
+ console.log(`${label('Log:')} ${value(paths.log)}`);
57
+ }
58
+ catch (error) {
59
+ console.error(error instanceof Error ? error.message : String(error));
60
+ process.exitCode = 1;
61
+ }
62
+ return;
63
+ }
64
+ if (!sessionId) {
65
+ const showExamples = deps.usesDefaultStatusFilters(command);
66
+ await deps.showStatus({
67
+ hours: sessionOptions.all ? Infinity : sessionOptions.hours,
68
+ includeAll: sessionOptions.all,
69
+ limit: sessionOptions.limit,
70
+ showExamples,
71
+ modelFilter: sessionOptions.model,
72
+ });
73
+ return;
74
+ }
75
+ // Surface any root-level flags that were provided but are ignored when attaching to a session.
76
+ const ignoredFlags = listIgnoredFlags(command);
77
+ if (ignoredFlags.length > 0) {
78
+ console.log(`Ignoring flags on session attach: ${ignoredFlags.join(', ')}`);
79
+ }
80
+ const renderMarkdown = Boolean(sessionOptions.render || sessionOptions.renderMarkdown || autoRender);
81
+ await deps.attachSession(sessionId, {
82
+ renderMarkdown,
83
+ renderPrompt: !sessionOptions.hidePrompt,
84
+ model: sessionOptions.model,
85
+ });
86
+ }
87
+ export function formatSessionCleanupMessage(result, scope) {
88
+ const deletedLabel = `${result.deleted} ${result.deleted === 1 ? 'session' : 'sessions'}`;
89
+ const remainingLabel = `${result.remaining} ${result.remaining === 1 ? 'session' : 'sessions'} remain`;
90
+ const hint = 'Run "oracle session --clear --all" to delete everything.';
91
+ return `Deleted ${deletedLabel} (${scope}). ${remainingLabel}.\n${hint}`;
92
+ }
93
+ function listIgnoredFlags(command) {
94
+ const opts = command.optsWithGlobals();
95
+ const ignored = [];
96
+ for (const key of Object.keys(opts)) {
97
+ if (SESSION_OPTION_KEYS.has(key)) {
98
+ continue;
99
+ }
100
+ const source = command.getOptionValueSource?.(key);
101
+ if (source !== 'cli' && source !== 'env') {
102
+ continue;
103
+ }
104
+ const value = opts[key];
105
+ if (value === undefined || value === false || value === null) {
106
+ continue;
107
+ }
108
+ ignored.push(key);
109
+ }
110
+ return ignored;
111
+ }