@steipete/oracle 1.0.6 → 1.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # oracle — Whispering your tokens to the silicon sage
1
+ # oracle 🧿 — Whispering your tokens to the silicon sage
2
2
 
3
3
  <p align="center">
4
4
  <img src="./README-header.png" alt="Oracle CLI header banner" width="1100">
@@ -200,6 +200,19 @@ function isWebSocketClosureError(error) {
200
200
  message.includes('websocket error') ||
201
201
  message.includes('target closed'));
202
202
  }
203
+ export function formatThinkingLog(startedAt, now, message, locatorSuffix) {
204
+ const elapsedMs = now - startedAt;
205
+ const elapsedText = formatElapsed(elapsedMs);
206
+ const progress = Math.min(1, elapsedMs / 600_000); // soft target: 10 minutes
207
+ const barSegments = 10;
208
+ const filled = Math.round(progress * barSegments);
209
+ const bar = `${'█'.repeat(filled).padEnd(barSegments, '░')}`;
210
+ const pct = Math.round(progress * 100)
211
+ .toString()
212
+ .padStart(3, ' ');
213
+ const statusLabel = message ? ` — ${message}` : '';
214
+ return `[${elapsedText} / ~10m] ${bar} ${pct}%${statusLabel}${locatorSuffix}`;
215
+ }
203
216
  function startThinkingStatusMonitor(Runtime, logger, includeDiagnostics = false) {
204
217
  let stopped = false;
205
218
  let pending = false;
@@ -215,7 +228,6 @@ function startThinkingStatusMonitor(Runtime, logger, includeDiagnostics = false)
215
228
  const nextMessage = await readThinkingStatus(Runtime);
216
229
  if (nextMessage && nextMessage !== lastMessage) {
217
230
  lastMessage = nextMessage;
218
- const elapsedText = formatElapsed(Date.now() - startedAt);
219
231
  let locatorSuffix = '';
220
232
  if (includeDiagnostics) {
221
233
  try {
@@ -226,7 +238,7 @@ function startThinkingStatusMonitor(Runtime, logger, includeDiagnostics = false)
226
238
  locatorSuffix = ' | assistant-turn=error';
227
239
  }
228
240
  }
229
- logger(`[${elapsedText} / ~10m] Pro thinking: ${nextMessage}${locatorSuffix}`);
241
+ logger(formatThinkingLog(startedAt, Date.now(), nextMessage, locatorSuffix));
230
242
  }
231
243
  }
232
244
  catch {
@@ -1,3 +1,6 @@
1
+ import fs from 'node:fs/promises';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
1
4
  import { readFiles, createFileSections, DEFAULT_SYSTEM_PROMPT, MODEL_CONFIGS, TOKENIZER_OPTIONS } from '../oracle.js';
2
5
  export async function assembleBrowserPrompt(runOptions, deps = {}) {
3
6
  const cwd = deps.cwd ?? process.cwd();
@@ -39,6 +42,25 @@ export async function assembleBrowserPrompt(runOptions, deps = {}) {
39
42
  displayPath: section.displayPath,
40
43
  sizeBytes: Buffer.byteLength(section.content, 'utf8'),
41
44
  }));
45
+ const MAX_BROWSER_ATTACHMENTS = 10;
46
+ if (!inlineFiles && attachments.length > MAX_BROWSER_ATTACHMENTS) {
47
+ const bundleDir = await fs.mkdtemp(path.join(os.tmpdir(), 'oracle-browser-bundle-'));
48
+ const bundlePath = path.join(bundleDir, 'attachments-bundle.txt');
49
+ const bundleLines = [];
50
+ sections.forEach((section) => {
51
+ bundleLines.push(`### File: ${section.displayPath}`);
52
+ bundleLines.push(section.content.trimEnd());
53
+ bundleLines.push('');
54
+ });
55
+ const bundleText = `${bundleLines.join('\n').trimEnd()}\n`;
56
+ await fs.writeFile(bundlePath, bundleText, 'utf8');
57
+ attachments.length = 0;
58
+ attachments.push({
59
+ path: bundlePath,
60
+ displayPath: 'attachments-bundle.txt',
61
+ sizeBytes: Buffer.byteLength(bundleText, 'utf8'),
62
+ });
63
+ }
42
64
  const inlineFileCount = inlineFiles ? sections.length : 0;
43
65
  const tokenizer = MODEL_CONFIGS[runOptions.model].tokenizer;
44
66
  const tokenizerUserContent = inlineFileCount > 0 && inlineBlock
@@ -52,5 +74,15 @@ export async function assembleBrowserPrompt(runOptions, deps = {}) {
52
74
  ? tokenizerMessages
53
75
  : [{ role: 'user', content: '' }], TOKENIZER_OPTIONS);
54
76
  const tokenEstimateIncludesInlineFiles = inlineFileCount > 0 && Boolean(inlineBlock);
55
- return { markdown, composerText, estimatedInputTokens, attachments, inlineFileCount, tokenEstimateIncludesInlineFiles };
77
+ return {
78
+ markdown,
79
+ composerText,
80
+ estimatedInputTokens,
81
+ attachments,
82
+ inlineFileCount,
83
+ tokenEstimateIncludesInlineFiles,
84
+ bundled: !inlineFiles && attachments.length === 1 && sections.length > MAX_BROWSER_ATTACHMENTS && attachments[0]?.displayPath
85
+ ? { originalCount: sections.length, bundlePath: attachments[0].displayPath }
86
+ : null,
87
+ };
56
88
  }
@@ -15,6 +15,9 @@ export async function runBrowserSessionExecution({ runOptions, browserConfig, cw
15
15
  if (promptArtifacts.attachments.length > 0) {
16
16
  const attachmentList = promptArtifacts.attachments.map((attachment) => attachment.displayPath).join(', ');
17
17
  log(chalk.dim(`[verbose] Browser attachments: ${attachmentList}`));
18
+ if (promptArtifacts.bundled) {
19
+ log(chalk.yellow(`[browser] More than 10 files provided; bundled ${promptArtifacts.bundled.originalCount} files into ${promptArtifacts.bundled.bundlePath} to satisfy ChatGPT upload limits.`));
20
+ }
18
21
  }
19
22
  else if (runOptions.file && runOptions.file.length > 0 && runOptions.browserInlineFiles) {
20
23
  log(chalk.dim('[verbose] Browser inline file fallback enabled (pasting file contents).'));
@@ -43,11 +43,13 @@ function renderHelpBanner(version, colors) {
43
43
  }
44
44
  function renderHelpFooter(program, colors) {
45
45
  const tips = [
46
- `${colors.bullet('•')} Attach source files for best results, but keep total input under ~196k tokens.`,
47
- `${colors.bullet('•')} The model has no built-in knowledge of your project—open with the architecture, key components, and why you’re asking.`,
46
+ `${colors.bullet('•')} Attach lots of source (whole directories beat single files) and keep total input under ~196k tokens.`,
47
+ `${colors.bullet('•')} Oracle starts empty—open with a short project briefing (stack, services, build steps), spell out the question and prior attempts, and why it matters; the more explanation and context you provide, the better the response will be.`,
48
+ `${colors.bullet('•')} Oracle is one-shot: it does not remember prior runs, so start fresh each time with full context.`,
48
49
  `${colors.bullet('•')} Run ${colors.accent('--files-report')} to inspect token spend before hitting the API.`,
49
50
  `${colors.bullet('•')} Non-preview runs spawn detached sessions so they keep streaming even if your terminal closes — reattach anytime via ${colors.accent('pnpm oracle session <slug>')}.`,
50
51
  `${colors.bullet('•')} Set a memorable 3–5 word slug via ${colors.accent('--slug "<words>"')} to keep session IDs tidy.`,
52
+ `${colors.bullet('•')} Finished sessions auto-hide preamble logs when reattached; raw timestamps remain in the saved log file.`,
51
53
  `${colors.bullet('•')} Need hidden flags? Run ${colors.accent(`${program.name()} --help --verbose`)} to list search/token/browser overrides.`,
52
54
  ].join('\n');
53
55
  const formatExample = (command, description) => `${colors.command(` ${command}`)}\n${colors.muted(` ${description}`)}`;
@@ -71,6 +71,13 @@ export async function attachSession(sessionId, options) {
71
71
  console.log(dim(`User error: ${userErrorSummary}`));
72
72
  }
73
73
  }
74
+ const shouldTrimIntro = initialStatus === 'completed' || initialStatus === 'error';
75
+ if (shouldTrimIntro) {
76
+ const fullLog = await readSessionLog(sessionId);
77
+ const trimmed = trimBeforeFirstAnswer(fullLog);
78
+ process.stdout.write(trimmed);
79
+ return;
80
+ }
74
81
  let lastLength = 0;
75
82
  const printNew = async () => {
76
83
  const text = await readSessionLog(sessionId);
@@ -170,6 +177,14 @@ export function buildReattachLine(metadata) {
170
177
  }
171
178
  return null;
172
179
  }
180
+ export function trimBeforeFirstAnswer(logText) {
181
+ const marker = 'Answer:';
182
+ const index = logText.indexOf(marker);
183
+ if (index === -1) {
184
+ return logText;
185
+ }
186
+ return logText.slice(index);
187
+ }
173
188
  function formatRelativeDuration(referenceIso) {
174
189
  const timestamp = Date.parse(referenceIso);
175
190
  if (Number.isNaN(timestamp)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@steipete/oracle",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "description": "CLI wrapper around OpenAI Responses API with GPT-5 Pro and GPT-5.1 high reasoning modes.",
5
5
  "type": "module",
6
6
  "main": "dist/bin/oracle-cli.js",