@steipete/oracle 0.15.2 → 0.16.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 (67) hide show
  1. package/dist/bin/oracle-cli.js +1 -1
  2. package/dist/src/browser/actions/assistantResponse.js +218 -126
  3. package/dist/src/browser/actions/modelSelection.js +133 -22
  4. package/dist/src/browser/actions/navigation.js +235 -14
  5. package/dist/src/browser/actions/thinkingStatus.js +228 -0
  6. package/dist/src/browser/actions/thinkingTime.js +68 -11
  7. package/dist/src/browser/chromeLifecycle.js +29 -28
  8. package/dist/src/browser/config.js +14 -1
  9. package/dist/src/browser/constants.js +5 -1
  10. package/dist/src/browser/controlPlan.js +2 -2
  11. package/dist/src/browser/index.js +56 -16
  12. package/dist/src/browser/liveTabs.js +113 -31
  13. package/dist/src/browser/pageActions.js +1 -1
  14. package/dist/src/browser/projectSourcesRunner.js +4 -4
  15. package/dist/src/browser/reattach.js +2 -2
  16. package/dist/src/browser/recoverConversation.js +90 -29
  17. package/dist/src/cli/browserConfig.js +5 -1
  18. package/dist/src/cli/browserTabs.js +54 -33
  19. package/dist/src/cli/options.js +24 -0
  20. package/dist/src/cli/runOptions.js +6 -1
  21. package/dist/src/cli/sessionDisplay.js +38 -14
  22. package/dist/src/oracle/config.js +25 -0
  23. package/dist/src/oracle/geminiModels.js +2 -0
  24. package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
  25. package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
  26. package/package.json +9 -10
  27. package/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
  28. package/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
  29. package/dist/bin/oracle.js +0 -569
  30. package/dist/docs-site/.nojekyll +0 -0
  31. package/dist/docs-site/CNAME +0 -1
  32. package/dist/docs-site/RELEASING.html +0 -410
  33. package/dist/docs-site/agents.html +0 -374
  34. package/dist/docs-site/anthropic.html +0 -368
  35. package/dist/docs-site/bridge.html +0 -416
  36. package/dist/docs-site/browser-mode.html +0 -594
  37. package/dist/docs-site/chromium-forks.html +0 -347
  38. package/dist/docs-site/cli-reference.html +0 -346
  39. package/dist/docs-site/configuration.html +0 -462
  40. package/dist/docs-site/favicon.svg +0 -14
  41. package/dist/docs-site/followup.html +0 -375
  42. package/dist/docs-site/gemini.html +0 -383
  43. package/dist/docs-site/grok.html +0 -325
  44. package/dist/docs-site/index.html +0 -360
  45. package/dist/docs-site/install.html +0 -335
  46. package/dist/docs-site/linux.html +0 -321
  47. package/dist/docs-site/llms.txt +0 -43
  48. package/dist/docs-site/manual-tests.html +0 -596
  49. package/dist/docs-site/mcp.html +0 -391
  50. package/dist/docs-site/multimodel.html +0 -364
  51. package/dist/docs-site/mythical-pro-agents.html +0 -360
  52. package/dist/docs-site/notifier.html +0 -338
  53. package/dist/docs-site/openai-endpoints.html +0 -399
  54. package/dist/docs-site/openrouter.html +0 -344
  55. package/dist/docs-site/quickstart.html +0 -369
  56. package/dist/docs-site/refactor/ux.html +0 -532
  57. package/dist/docs-site/sessions.html +0 -388
  58. package/dist/docs-site/social-card.png +0 -0
  59. package/dist/docs-site/social-card.svg +0 -79
  60. package/dist/docs-site/spec.html +0 -363
  61. package/dist/docs-site/testing.html +0 -320
  62. package/dist/docs-site/tui-debug.html +0 -326
  63. package/dist/docs-site/windows-work.html +0 -323
  64. package/dist/docs-site/windows.html +0 -320
  65. package/dist/src/browser/chromeCookies.js +0 -312
  66. package/dist/src/browser/keytarShim.js +0 -56
  67. package/dist/src/browser/windowsCookies.js +0 -219
@@ -1,569 +0,0 @@
1
- #!/usr/bin/env node
2
- import 'dotenv/config';
3
- import { Command, InvalidArgumentError, Option } from 'commander';
4
- import chalk from 'chalk';
5
- import kleur from 'kleur';
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 } from '../src/oracle.js';
8
- const VERSION = '1.0.0';
9
- const rawCliArgs = process.argv.slice(2);
10
- const isTty = process.stdout.isTTY;
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
- }),
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
- }
91
- const program = new Command();
92
- program
93
- .name('oracle')
94
- .description('One-shot GPT-5 Pro / GPT-5.1 tool for hard questions that benefit from large file context and server-side search.')
95
- .version(VERSION)
96
- .option('-p, --prompt <text>', 'User prompt to send to the model.')
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, [])
98
- .option('-m, --model <model>', 'Model to target (gpt-5-pro | gpt-5.1).', validateModel, 'gpt-5-pro')
99
- .option('--files-report', 'Show token usage per attached file (also prints automatically when files exceed the token budget).', false)
100
- .addOption(new Option('--preview [mode]', 'Preview the request without calling the API (summary | json | full).')
101
- .choices(['summary', 'json', 'full'])
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'))
106
- .addOption(new Option('--exec-session <id>').hideHelp())
107
- .option('--render-markdown', 'Emit the assembled markdown bundle for prompt + files and exit.', false)
108
- .showHelpAfterError('(use --help for usage)');
109
- program
110
- .command('session [id]')
111
- .description('Attach to a stored session or list recent sessions when no ID is provided.')
112
- .option('--hours <hours>', 'Look back this many hours when listing sessions (default 24).', parseFloatOption, 24)
113
- .option('--limit <count>', 'Maximum sessions to show when listing (max 1000).', parseIntOption, 100)
114
- .option('--all', 'Include all stored sessions regardless of age.', false)
115
- .action(async function (sessionId) {
116
- const sessionOptions = this.opts();
117
- if (!sessionId) {
118
- const showExamples = usesDefaultStatusFilters(this);
119
- await showStatus({
120
- hours: sessionOptions.all ? Infinity : sessionOptions.hours,
121
- includeAll: sessionOptions.all,
122
- limit: sessionOptions.limit,
123
- showExamples,
124
- });
125
- return;
126
- }
127
- await attachSession(sessionId);
128
- });
129
- const statusCommand = program
130
- .command('status')
131
- .description('List recent sessions (24h window by default).')
132
- .option('--hours <hours>', 'Look back this many hours (default 24).', parseFloatOption, 24)
133
- .option('--limit <count>', 'Maximum sessions to show (max 1000).', parseIntOption, 100)
134
- .option('--all', 'Include all stored sessions regardless of age.', false)
135
- .action(async function () {
136
- const statusOptions = this.opts();
137
- const showExamples = usesDefaultStatusFilters(this);
138
- await showStatus({
139
- hours: statusOptions.all ? Infinity : statusOptions.hours,
140
- includeAll: statusOptions.all,
141
- limit: statusOptions.limit,
142
- showExamples,
143
- });
144
- });
145
- statusCommand
146
- .command('clear')
147
- .description('Delete stored sessions older than the provided window (24h default).')
148
- .option('--hours <hours>', 'Delete sessions older than this many hours (default 24).', parseFloatOption, 24)
149
- .option('--all', 'Delete all stored sessions.', false)
150
- .action(async function () {
151
- const clearOptions = this.opts();
152
- const result = await deleteSessionsOlderThan({ hours: clearOptions.hours, includeAll: clearOptions.all });
153
- const scope = clearOptions.all ? 'all stored sessions' : `sessions older than ${clearOptions.hours}h`;
154
- console.log(`Deleted ${result.deleted} ${result.deleted === 1 ? 'session' : 'sessions'} (${scope}).`);
155
- });
156
- const bold = (text) => (isTty ? kleur.bold(text) : text);
157
- const dim = (text) => (isTty ? kleur.dim(text) : text);
158
- program.addHelpText('beforeAll', () => renderHelpBanner(helpTheme));
159
- program.addHelpText('after', () => renderHelpFooter(helpTheme));
160
- function renderHelpBanner(theme) {
161
- const subtitle = 'GPT-5 Pro/GPT-5.1 for tough questions with code/file context.';
162
- return `${theme.banner(`Oracle CLI v${VERSION}`)} ${theme.subtitle(`— ${subtitle}`)}\n`;
163
- }
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>')}.`,
197
- ].join('\n');
198
- return `
199
- ${theme.heading('Tips')}
200
- ${tipLines}
201
-
202
- ${theme.heading('Examples')}
203
- ${exampleLines}
204
-
205
- ${theme.heading('Color Themes')}
206
- ${themeLines}
207
- `;
208
- }
209
- function collectPaths(value, previous = []) {
210
- if (!value) {
211
- return previous;
212
- }
213
- const nextValues = Array.isArray(value) ? value : [value];
214
- return previous.concat(nextValues.flatMap((entry) => entry.split(',')).map((entry) => entry.trim()).filter(Boolean));
215
- }
216
- function parseFloatOption(value) {
217
- const parsed = Number.parseFloat(value);
218
- if (Number.isNaN(parsed)) {
219
- throw new InvalidArgumentError('Value must be a number.');
220
- }
221
- return parsed;
222
- }
223
- function validateModel(value) {
224
- if (!(value in MODEL_CONFIGS)) {
225
- throw new InvalidArgumentError(`Unsupported model "${value}". Choose one of: ${Object.keys(MODEL_CONFIGS).join(', ')}`);
226
- }
227
- return value;
228
- }
229
- function usesDefaultStatusFilters(cmd) {
230
- const hoursSource = cmd.getOptionValueSource?.('hours') ?? 'default';
231
- const limitSource = cmd.getOptionValueSource?.('limit') ?? 'default';
232
- const allSource = cmd.getOptionValueSource?.('all') ?? 'default';
233
- return hoursSource === 'default' && limitSource === 'default' && allSource === 'default';
234
- }
235
- function resolvePreviewMode(value) {
236
- if (typeof value === 'string' && value.length > 0) {
237
- return value;
238
- }
239
- if (value === true) {
240
- return 'summary';
241
- }
242
- return undefined;
243
- }
244
- function buildRunOptions(options, overrides = {}) {
245
- if (!options.prompt) {
246
- throw new Error('Prompt is required.');
247
- }
248
- return {
249
- prompt: options.prompt,
250
- model: options.model,
251
- file: overrides.file ?? options.file ?? [],
252
- filesReport: overrides.filesReport ?? options.filesReport,
253
- maxInput: overrides.maxInput ?? options.maxInput,
254
- maxOutput: overrides.maxOutput ?? options.maxOutput,
255
- system: overrides.system ?? options.system,
256
- silent: overrides.silent ?? options.silent,
257
- search: overrides.search ?? options.search,
258
- preview: overrides.preview ?? undefined,
259
- previewMode: overrides.previewMode ?? options.previewMode,
260
- apiKey: overrides.apiKey ?? options.apiKey,
261
- sessionId: overrides.sessionId ?? options.sessionId,
262
- };
263
- }
264
- function buildRunOptionsFromMetadata(metadata) {
265
- const stored = metadata.options ?? {};
266
- return {
267
- prompt: stored.prompt ?? '',
268
- model: stored.model ?? 'gpt-5-pro',
269
- file: stored.file ?? [],
270
- filesReport: stored.filesReport,
271
- maxInput: stored.maxInput,
272
- maxOutput: stored.maxOutput,
273
- system: stored.system,
274
- silent: stored.silent,
275
- search: undefined,
276
- preview: false,
277
- previewMode: undefined,
278
- apiKey: undefined,
279
- sessionId: metadata.id,
280
- };
281
- }
282
- async function runRootCommand(options) {
283
- const helpRequested = rawCliArgs.some((arg) => arg === '--help' || arg === '-h');
284
- if (helpRequested) {
285
- program.help({ error: false });
286
- return;
287
- }
288
- const previewMode = resolvePreviewMode(options.preview);
289
- if (rawCliArgs.length === 0) {
290
- console.log(chalk.yellow('No prompt or subcommand supplied. See `oracle --help` for usage.'));
291
- program.help({ error: false });
292
- return;
293
- }
294
- if (options.session) {
295
- await attachSession(options.session);
296
- return;
297
- }
298
- if (options.execSession) {
299
- await executeSession(options.execSession);
300
- return;
301
- }
302
- if (options.renderMarkdown) {
303
- if (!options.prompt) {
304
- throw new Error('Prompt is required when using --render-markdown.');
305
- }
306
- const markdown = await renderPromptMarkdown({ prompt: options.prompt, file: options.file, system: options.system }, { cwd: process.cwd() });
307
- console.log(markdown);
308
- return;
309
- }
310
- if (previewMode) {
311
- if (!options.prompt) {
312
- throw new Error('Prompt is required when using --preview.');
313
- }
314
- const runOptions = buildRunOptions(options, { preview: true, previewMode });
315
- await runOracle(runOptions, { log: console.log, write: (chunk) => process.stdout.write(chunk) });
316
- return;
317
- }
318
- if (!options.prompt) {
319
- throw new Error('Prompt is required when starting a new session.');
320
- }
321
- if (options.file && options.file.length > 0) {
322
- await readFiles(options.file, { cwd: process.cwd() });
323
- }
324
- await ensureSessionStorage();
325
- const baseRunOptions = buildRunOptions(options, { preview: false, previewMode: undefined });
326
- const sessionMeta = await initializeSession(baseRunOptions, process.cwd());
327
- const liveRunOptions = { ...baseRunOptions, sessionId: sessionMeta.id };
328
- await runInteractiveSession(sessionMeta, liveRunOptions);
329
- console.log(chalk.bold(`Session ${sessionMeta.id} completed`));
330
- }
331
- async function runInteractiveSession(sessionMeta, runOptions) {
332
- const { logLine, writeChunk, stream } = createSessionLogWriter(sessionMeta.id);
333
- let headerAugmented = false;
334
- const combinedLog = (message = '') => {
335
- if (!headerAugmented && message.startsWith('Oracle (')) {
336
- headerAugmented = true;
337
- console.log(`${message}\n${chalk.blue(`Reattach via: oracle session ${sessionMeta.id}`)}`);
338
- logLine(message);
339
- return;
340
- }
341
- console.log(message);
342
- logLine(message);
343
- };
344
- const combinedWrite = (chunk) => {
345
- writeChunk(chunk);
346
- return process.stdout.write(chunk);
347
- };
348
- try {
349
- await updateSessionMetadata(sessionMeta.id, { status: 'running', startedAt: new Date().toISOString() });
350
- const result = await runOracle(runOptions, {
351
- log: combinedLog,
352
- write: combinedWrite,
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
- });
363
- }
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
- });
372
- throw error;
373
- }
374
- finally {
375
- stream.end();
376
- }
377
- }
378
- async function executeSession(sessionId) {
379
- const metadata = await readSessionMetadata(sessionId);
380
- if (!metadata) {
381
- console.error(chalk.red(`No session found with ID ${sessionId}`));
382
- process.exitCode = 1;
383
- return;
384
- }
385
- const runOptions = buildRunOptionsFromMetadata(metadata);
386
- const { logLine, writeChunk, stream } = createSessionLogWriter(sessionId);
387
- try {
388
- await updateSessionMetadata(sessionId, { status: 'running', startedAt: new Date().toISOString() });
389
- const result = await runOracle(runOptions, {
390
- cwd: metadata.cwd,
391
- log: logLine,
392
- write: writeChunk,
393
- });
394
- if (result.mode !== 'live') {
395
- throw new Error('Unexpected preview result while executing a stored session.');
396
- }
397
- await updateSessionMetadata(sessionId, {
398
- status: 'completed',
399
- completedAt: new Date().toISOString(),
400
- usage: result.usage,
401
- elapsedMs: result.elapsedMs,
402
- });
403
- }
404
- catch (error) {
405
- const message = formatError(error);
406
- logLine(`ERROR: ${message}`);
407
- await updateSessionMetadata(sessionId, {
408
- status: 'error',
409
- completedAt: new Date().toISOString(),
410
- errorMessage: message,
411
- });
412
- }
413
- finally {
414
- stream.end();
415
- }
416
- }
417
- async function showStatus({ hours, includeAll, limit, showExamples = false }) {
418
- const metas = await listSessionsMetadata();
419
- const { entries, truncated, total } = filterSessionsByRange(metas, { hours, includeAll, limit });
420
- if (!entries.length) {
421
- console.log('No sessions found for the requested range.');
422
- if (showExamples) {
423
- printStatusExamples();
424
- }
425
- return;
426
- }
427
- console.log(chalk.bold('Recent Sessions'));
428
- for (const entry of entries) {
429
- const status = (entry.status || 'unknown').padEnd(9);
430
- const model = (entry.model || 'n/a').padEnd(10);
431
- const created = entry.createdAt.replace('T', ' ').replace('Z', '');
432
- console.log(`${created} | ${status} | ${model} | ${entry.id}`);
433
- }
434
- if (truncated) {
435
- console.log(chalk.yellow(`Showing ${entries.length} of ${total} sessions from the requested range. Run "oracle status clear" or delete entries in ${SESSIONS_DIR} to free space, or rerun with --status-limit/--status-all.`));
436
- }
437
- if (showExamples) {
438
- printStatusExamples();
439
- }
440
- }
441
- function printStatusExamples() {
442
- console.log('');
443
- console.log(chalk.bold('Usage Examples'));
444
- console.log(`${chalk.bold(' oracle status --hours 72 --limit 50')}`);
445
- console.log(dim(' Show 72h of history capped at 50 entries.'));
446
- console.log(`${chalk.bold(' oracle status clear --hours 168')}`);
447
- console.log(dim(' Delete sessions older than 7 days (use --all to wipe everything).'));
448
- console.log(`${chalk.bold(' oracle session <session-id>')}`);
449
- console.log(dim(' Attach to a specific running/completed session to stream its output.'));
450
- }
451
- async function attachSession(sessionId) {
452
- const metadata = await readSessionMetadata(sessionId);
453
- if (!metadata) {
454
- console.error(chalk.red(`No session found with ID ${sessionId}`));
455
- process.exitCode = 1;
456
- return;
457
- }
458
- const reattachLine = buildReattachLine(metadata);
459
- if (reattachLine) {
460
- console.log(chalk.blue(reattachLine));
461
- }
462
- else {
463
- console.log(chalk.bold(`Session ${sessionId}`));
464
- }
465
- console.log(`Created: ${metadata.createdAt}`);
466
- console.log(`Status: ${metadata.status}`);
467
- console.log(`Model: ${metadata.model}`);
468
- let lastLength = 0;
469
- const printNew = async () => {
470
- const text = await readSessionLog(sessionId);
471
- const nextChunk = text.slice(lastLength);
472
- if (nextChunk.length > 0) {
473
- process.stdout.write(nextChunk);
474
- lastLength = text.length;
475
- }
476
- };
477
- await printNew();
478
- // biome-ignore lint/nursery/noUnnecessaryConditions: deliberate infinite poll
479
- while (true) {
480
- const latest = await readSessionMetadata(sessionId);
481
- if (!latest) {
482
- break;
483
- }
484
- if (latest.status === 'completed' || latest.status === 'error') {
485
- await printNew();
486
- if (latest.status === 'error' && latest.errorMessage) {
487
- console.log(`\nSession failed: ${latest.errorMessage}`);
488
- }
489
- if (latest.usage) {
490
- const usage = latest.usage;
491
- console.log(`\nFinished (tok i/o/r/t: ${usage.inputTokens}/${usage.outputTokens}/${usage.reasoningTokens}/${usage.totalTokens})`);
492
- }
493
- break;
494
- }
495
- await wait(1000);
496
- await printNew();
497
- }
498
- }
499
- function formatError(error) {
500
- return error instanceof Error ? error.message : String(error);
501
- }
502
- function buildReattachLine(metadata) {
503
- if (!metadata.id) {
504
- return null;
505
- }
506
- const referenceTime = metadata.startedAt ?? metadata.createdAt;
507
- if (!referenceTime) {
508
- return null;
509
- }
510
- const elapsedLabel = formatRelativeDuration(referenceTime);
511
- if (!elapsedLabel) {
512
- return null;
513
- }
514
- if (metadata.status === 'running') {
515
- return `Session ${metadata.id} reattached, request started ${elapsedLabel} ago.`;
516
- }
517
- return null;
518
- }
519
- function formatRelativeDuration(referenceIso) {
520
- const timestamp = Date.parse(referenceIso);
521
- if (Number.isNaN(timestamp)) {
522
- return null;
523
- }
524
- const diffMs = Date.now() - timestamp;
525
- if (diffMs < 0) {
526
- return null;
527
- }
528
- const seconds = Math.max(1, Math.round(diffMs / 1000));
529
- if (seconds < 60) {
530
- return `${seconds}s`;
531
- }
532
- const minutes = Math.floor(seconds / 60);
533
- const remainingSeconds = seconds % 60;
534
- if (minutes < 60) {
535
- return remainingSeconds > 0 ? `${minutes}m ${remainingSeconds}s` : `${minutes}m`;
536
- }
537
- const hours = Math.floor(minutes / 60);
538
- const remainingMinutes = minutes % 60;
539
- if (hours < 24) {
540
- const parts = [`${hours}h`];
541
- if (remainingMinutes > 0) {
542
- parts.push(`${remainingMinutes}m`);
543
- }
544
- return parts.join(' ');
545
- }
546
- const days = Math.floor(hours / 24);
547
- const remainingHours = hours % 24;
548
- const parts = [`${days}d`];
549
- if (remainingHours > 0) {
550
- parts.push(`${remainingHours}h`);
551
- }
552
- if (remainingMinutes > 0 && days === 0) {
553
- parts.push(`${remainingMinutes}m`);
554
- }
555
- return parts.join(' ');
556
- }
557
- program.action(async function () {
558
- const options = this.optsWithGlobals();
559
- await runRootCommand(options);
560
- });
561
- await program.parseAsync(process.argv).catch((error) => {
562
- if (error instanceof Error) {
563
- console.error(chalk.red('✖'), error.message);
564
- }
565
- else {
566
- console.error(chalk.red('✖'), error);
567
- }
568
- process.exitCode = 1;
569
- });
File without changes
@@ -1 +0,0 @@
1
- askoracle.sh