@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,92 @@
1
+ import chalk from 'chalk';
2
+ import kleur from 'kleur';
3
+ import { MODEL_CONFIGS } from '../oracle.js';
4
+ import { estimateUsdCost } from 'tokentally';
5
+ const isRich = (rich) => rich ?? Boolean(process.stdout.isTTY && chalk.level > 0);
6
+ const dim = (text, rich) => (rich ? kleur.dim(text) : text);
7
+ export const STATUS_PAD = 9;
8
+ export const MODEL_PAD = 13;
9
+ export const MODE_PAD = 7;
10
+ export const TIMESTAMP_PAD = 19;
11
+ export const CHARS_PAD = 5;
12
+ export const COST_PAD = 7;
13
+ export function formatSessionTableHeader(rich) {
14
+ const header = `${'Status'.padEnd(STATUS_PAD)} ${'Model'.padEnd(MODEL_PAD)} ${'Mode'.padEnd(MODE_PAD)} ${'Timestamp'.padEnd(TIMESTAMP_PAD)} ${'Chars'.padStart(CHARS_PAD)} ${'Cost'.padStart(COST_PAD)} Slug`;
15
+ return dim(header, isRich(rich));
16
+ }
17
+ export function formatSessionTableRow(meta, options) {
18
+ const rich = isRich(options?.rich);
19
+ const status = colorStatus(meta.status ?? 'unknown', rich);
20
+ const modelLabel = (meta.model ?? 'n/a').padEnd(MODEL_PAD);
21
+ const model = rich ? chalk.white(modelLabel) : modelLabel;
22
+ const modeLabel = (meta.mode ?? meta.options?.mode ?? 'api').padEnd(MODE_PAD);
23
+ const mode = rich ? chalk.gray(modeLabel) : modeLabel;
24
+ const timestampLabel = formatTimestampAligned(meta.createdAt).padEnd(TIMESTAMP_PAD);
25
+ const timestamp = rich ? chalk.gray(timestampLabel) : timestampLabel;
26
+ const charsValue = meta.options?.prompt?.length ?? meta.promptPreview?.length ?? 0;
27
+ const charsRaw = charsValue > 0 ? String(charsValue).padStart(CHARS_PAD) : `${''.padStart(CHARS_PAD - 1)}-`;
28
+ const chars = rich ? chalk.gray(charsRaw) : charsRaw;
29
+ const costValue = resolveSessionCost(meta);
30
+ const costRaw = costValue != null ? formatCostTable(costValue) : `${''.padStart(COST_PAD - 1)}-`;
31
+ const cost = rich ? chalk.gray(costRaw) : costRaw;
32
+ const slug = rich ? chalk.cyan(meta.id) : meta.id;
33
+ return `${status} ${model} ${mode} ${timestamp} ${chars} ${cost} ${slug}`;
34
+ }
35
+ export function resolveSessionCost(meta) {
36
+ const mode = meta.mode ?? meta.options?.mode;
37
+ if (mode === 'browser') {
38
+ return null;
39
+ }
40
+ if (meta.usage?.cost != null) {
41
+ return meta.usage.cost;
42
+ }
43
+ if (!meta.model || !meta.usage) {
44
+ return null;
45
+ }
46
+ const pricing = MODEL_CONFIGS[meta.model]?.pricing;
47
+ if (!pricing) {
48
+ return null;
49
+ }
50
+ const input = meta.usage.inputTokens ?? 0;
51
+ const output = meta.usage.outputTokens ?? 0;
52
+ const cost = estimateUsdCost({
53
+ usage: { inputTokens: input, outputTokens: output },
54
+ pricing: { inputUsdPerToken: pricing.inputPerToken, outputUsdPerToken: pricing.outputPerToken },
55
+ })?.totalUsd ?? 0;
56
+ return cost > 0 ? cost : null;
57
+ }
58
+ export function formatTimestampAligned(iso) {
59
+ const date = new Date(iso);
60
+ const locale = 'en-US';
61
+ const opts = {
62
+ year: 'numeric',
63
+ month: '2-digit',
64
+ day: '2-digit',
65
+ hour: 'numeric',
66
+ minute: '2-digit',
67
+ second: undefined,
68
+ hour12: true,
69
+ };
70
+ let formatted = date.toLocaleString(locale, opts);
71
+ formatted = formatted.replace(', ', ' ');
72
+ return formatted.replace(/(\s)(\d:)/, '$1 $2');
73
+ }
74
+ function formatCostTable(cost) {
75
+ return `$${cost.toFixed(3)}`.padStart(COST_PAD);
76
+ }
77
+ function colorStatus(status, rich) {
78
+ const padded = status.padEnd(STATUS_PAD);
79
+ if (!rich) {
80
+ return padded;
81
+ }
82
+ switch (status) {
83
+ case 'completed':
84
+ return chalk.green(padded);
85
+ case 'error':
86
+ return chalk.red(padded);
87
+ case 'running':
88
+ return chalk.yellow(padded);
89
+ default:
90
+ return padded;
91
+ }
92
+ }
@@ -0,0 +1,258 @@
1
+ import chalk from 'chalk';
2
+ const TAGLINES = [
3
+ 'Whispering your tokens to the silicon sage.',
4
+ 'Turning scattered files into one sharp question.',
5
+ 'One slug to gather them all.',
6
+ 'Token thrift, oracle lift.',
7
+ 'Globs to gospel, minus the incense.',
8
+ 'Your repo, neatly bottled, gently shaken.',
9
+ 'Clarity, with a hint of smoke.',
10
+ 'Questions in, clarity out.',
11
+ 'Globs become guidance.',
12
+ 'Token-aware, omen-ready.',
13
+ 'Globs go in; citations and costs come out.',
14
+ 'Keeps 196k tokens feeling roomy, not risky.',
15
+ 'Remembers your paths, forgets your past runs.',
16
+ 'A TUI when you want it, a one-liner when you do not.',
17
+ 'Less ceremony, more certainty.',
18
+ 'Guidance without the guesswork.',
19
+ 'One prompt fanned out, no echoes wasted.',
20
+ 'Detached runs, tethered results.',
21
+ 'Calm CLI, loud answers.',
22
+ 'Single scroll, many seers.',
23
+ 'Background magic with foreground receipts.',
24
+ 'Paths aligned, models attuned.',
25
+ 'Light spell, heavy insight.',
26
+ 'Signal first, sorcery second.',
27
+ 'One command, several seers; results stay grounded.',
28
+ 'Context braided, answers sharpened.',
29
+ 'Short incantation, long provenance.',
30
+ 'Attach, cast, reattach later.',
31
+ 'Spell once, cite always.',
32
+ 'Edge cases foretold, receipts attached.',
33
+ 'Silent run, loud receipts.',
34
+ 'Detours gone; clarity walks in.',
35
+ 'Tokens tallied, omens tallied.',
36
+ 'Calm prompt, converged truths.',
37
+ 'Single spell, multiple verdicts.',
38
+ 'Prompt once, harvest many omens.',
39
+ 'Light on ceremony, heavy on receipts.',
40
+ 'From globs to guidance in one breath.',
41
+ 'Quiet prompt, thunderous answers.',
42
+ 'Balanced mystique, measurable results.',
43
+ 'Debugger by day, oracle by night.',
44
+ "Your code's confessional booth.",
45
+ 'Edge cases fear this inbox.',
46
+ 'Slop in, sharp answers out.',
47
+ "Your AI coworker's quality control.",
48
+ "Because vibes aren't a deliverable.",
49
+ 'When the other agents shrug, the oracle ships.',
50
+ 'Hallucinations checked at the door.',
51
+ 'Context police for overeager LLMs.',
52
+ 'Turns prompt spaghetti into ship-ready sauce.',
53
+ 'Lint for large language models.',
54
+ "Slaps wrists before they hit 'ship'.",
55
+ "Because 'let the model figure it out' is not QA.",
56
+ "Fine, I'll write the test for the AI too.",
57
+ 'We bring receipts; they bring excuses.',
58
+ 'Less swagger, more citations.',
59
+ 'LLM babysitter with a shipping agenda.',
60
+ 'Ships facts, not vibes.',
61
+ 'Context sanitizer for reckless prompts.',
62
+ 'AI babysitter with merge rights.',
63
+ 'Stops the hallucination before it hits prod.',
64
+ 'Slop filter set to aggressive.',
65
+ 'We debug the debugger.',
66
+ 'Model said maybe; oracle says ship/no.',
67
+ 'Less lorem, more logic.',
68
+ "Your prompt's adult supervision.",
69
+ 'Cleanup crew for AI messes.',
70
+ 'AI wrote it? Oracle babysits it.',
71
+ 'Turning maybe into mergeable.',
72
+ 'The AI said vibes; we said tests.',
73
+ 'Cleanup crew for model-made messes—now with citations.',
74
+ 'Less hallucination, more escalation.',
75
+ "Your AI's ghostwriter, but with citations.",
76
+ 'Where prompt soup becomes production code.',
77
+ 'From shruggy agents to shippable PRs.',
78
+ 'Token mop for agent spillover.',
79
+ 'We QA the AI so you can ship the code.',
80
+ 'Less improv, more implementation.',
81
+ 'Ships facts faster than agents make excuses.',
82
+ 'From prompt chaos to PR-ready prose.',
83
+ "Your AI's hot take, fact-checked.",
84
+ 'Cleanup crew for LLM loose ends.',
85
+ 'We babysit the bot; you ship the build.',
86
+ 'Prompt drama in; release notes out.',
87
+ 'AI confidence filtered through reality.',
88
+ "From 'it told me so' to 'tests say so'.",
89
+ "We refactor the model's hubris before it hits prod.",
90
+ 'Prompt chaos triaged, answers discharged.',
91
+ 'Oracle babysits; you merge.',
92
+ 'Vibes quarantined; facts admitted.',
93
+ 'The cleanup crew for speculative stack traces.',
94
+ 'Ship-ready answers, minus the AI improv.',
95
+ "We pre-empt the hallucination so you don't triage it at 2am.",
96
+ 'AI confidence monitored, citations required.',
97
+ 'Ship logs, not lore.',
98
+ 'Hallucinations flagged, reality shipped.',
99
+ 'We lint the lore so you can ship the code.',
100
+ 'Hallucination hotline: we answer, not the pager.',
101
+ 'Less mystique, more mergeability.',
102
+ 'Slop filter set past 11.',
103
+ 'Bottled prompt chaos, filtered answers.',
104
+ "Your AI's swagger, audited.",
105
+ 'New year, same oracle: resolutions shipped, not wished.',
106
+ 'Lunar New Year sweep: clear caches, invite good deploys.',
107
+ 'Eid Mubarak: feast on clarity, fast from hallucinations.',
108
+ 'Diwali: lights on, incident lights off.',
109
+ 'Holi colors on dashboards, not in logs.',
110
+ "Workers' Day: let oracle haul the heavy context.",
111
+ 'Earth Day: trim carbon, trim token waste.',
112
+ 'Halloween: ship treats, not trick exceptions.',
113
+ 'Independence Day: sparkles in the sky, not in the error console.',
114
+ 'Christmas: all is calm, all is shipped.',
115
+ 'Nowruz reset: sweep caches, welcome clean deploys.',
116
+ 'Hanukkah lights, zero prod fires.',
117
+ 'Ramadan focus: fast from scope creep, feast on clarity.',
118
+ 'Pride Month: more color on the streets, less red in CI.',
119
+ 'Thanksgiving: grateful for green builds, no turkey outages.',
120
+ 'Solstice deploy: longest day, shortest incident list.',
121
+ ];
122
+ const DAY_MS = 24 * 60 * 60 * 1000;
123
+ function utcParts(date) {
124
+ return {
125
+ year: date.getUTCFullYear(),
126
+ month: date.getUTCMonth(),
127
+ day: date.getUTCDate(),
128
+ };
129
+ }
130
+ const onMonthDay = (month, day) => (date) => {
131
+ const parts = utcParts(date);
132
+ return parts.month === month && parts.day === day;
133
+ };
134
+ const onSpecificDates = (dates, durationDays = 1) => (date) => {
135
+ const parts = utcParts(date);
136
+ return dates.some(([year, month, day]) => {
137
+ if (parts.year !== year)
138
+ return false;
139
+ const start = Date.UTC(year, month, day);
140
+ const current = Date.UTC(parts.year, parts.month, parts.day);
141
+ return current >= start && current < start + durationDays * DAY_MS;
142
+ });
143
+ };
144
+ const inYearWindow = (windows) => (date) => {
145
+ const parts = utcParts(date);
146
+ const window = windows.find((entry) => entry.year === parts.year);
147
+ if (!window)
148
+ return false;
149
+ const start = Date.UTC(window.year, window.month, window.day);
150
+ const current = Date.UTC(parts.year, parts.month, parts.day);
151
+ return current >= start && current < start + window.duration * DAY_MS;
152
+ };
153
+ const isFourthThursdayOfNovember = (date) => {
154
+ const parts = utcParts(date);
155
+ if (parts.month !== 10)
156
+ return false; // November
157
+ const firstDay = new Date(Date.UTC(parts.year, 10, 1)).getUTCDay();
158
+ const offsetToThursday = (4 - firstDay + 7) % 7; // 4 = Thursday
159
+ const fourthThursday = 1 + offsetToThursday + 21; // 1st + offset + 3 weeks
160
+ return parts.day === fourthThursday;
161
+ };
162
+ const HOLIDAY_RULES = new Map([
163
+ ['New year, same oracle: resolutions shipped, not wished.', onMonthDay(0, 1)],
164
+ [
165
+ 'Lunar New Year sweep: clear caches, invite good deploys.',
166
+ onSpecificDates([
167
+ [2025, 0, 29],
168
+ [2026, 1, 17],
169
+ [2027, 1, 6],
170
+ ], 1),
171
+ ],
172
+ [
173
+ 'Eid Mubarak: feast on clarity, fast from hallucinations.',
174
+ onSpecificDates([
175
+ [2025, 2, 31],
176
+ [2026, 2, 20],
177
+ [2027, 2, 10],
178
+ ], 1),
179
+ ],
180
+ [
181
+ 'Diwali: lights on, incident lights off.',
182
+ onSpecificDates([
183
+ [2025, 9, 20],
184
+ [2026, 10, 8],
185
+ [2027, 9, 29],
186
+ ], 1),
187
+ ],
188
+ [
189
+ 'Holi colors on dashboards, not in logs.',
190
+ onSpecificDates([
191
+ [2025, 2, 14],
192
+ [2026, 2, 3],
193
+ [2027, 2, 23],
194
+ ], 1),
195
+ ],
196
+ ["Workers' Day: let oracle haul the heavy context.", onMonthDay(4, 1)],
197
+ ['Earth Day: trim carbon, trim token waste.', onMonthDay(3, 22)],
198
+ ['Halloween: ship treats, not trick exceptions.', onMonthDay(9, 31)],
199
+ [
200
+ 'Independence Day: sparkles in the sky, not in the error console.',
201
+ onMonthDay(6, 4),
202
+ ],
203
+ ['Christmas: all is calm, all is shipped.', onMonthDay(11, 25)],
204
+ ['Nowruz reset: sweep caches, welcome clean deploys.', onMonthDay(2, 20)],
205
+ [
206
+ 'Hanukkah lights, zero prod fires.',
207
+ inYearWindow([
208
+ { year: 2025, month: 11, day: 14, duration: 8 },
209
+ { year: 2026, month: 11, day: 4, duration: 8 },
210
+ { year: 2027, month: 10, day: 24, duration: 8 },
211
+ ]),
212
+ ],
213
+ [
214
+ 'Ramadan focus: fast from scope creep, feast on clarity.',
215
+ inYearWindow([
216
+ { year: 2025, month: 1, day: 28, duration: 30 },
217
+ { year: 2026, month: 1, day: 17, duration: 30 },
218
+ { year: 2027, month: 1, day: 7, duration: 30 },
219
+ ]),
220
+ ],
221
+ ['Pride Month: more color on the streets, less red in CI.', (date) => utcParts(date).month === 5],
222
+ ['Thanksgiving: grateful for green builds, no turkey outages.', isFourthThursdayOfNovember],
223
+ ['Solstice deploy: longest day, shortest incident list.', onMonthDay(5, 21)],
224
+ ]);
225
+ function isTaglineActive(tagline, date) {
226
+ const rule = HOLIDAY_RULES.get(tagline);
227
+ if (!rule)
228
+ return true;
229
+ return rule(date);
230
+ }
231
+ export function activeTaglines(options = {}) {
232
+ const today = options.now ? options.now() : new Date();
233
+ const filtered = TAGLINES.filter((tagline) => isTaglineActive(tagline, today));
234
+ return filtered.length > 0 ? filtered : TAGLINES;
235
+ }
236
+ export function pickTagline(options = {}) {
237
+ const env = options.env ?? process.env;
238
+ const override = env?.ORACLE_TAGLINE_INDEX;
239
+ if (override !== undefined) {
240
+ const parsed = Number.parseInt(override, 10);
241
+ if (!Number.isNaN(parsed) && parsed >= 0) {
242
+ return TAGLINES[parsed % TAGLINES.length];
243
+ }
244
+ }
245
+ const pool = activeTaglines(options);
246
+ const rand = options.random ?? Math.random;
247
+ const index = Math.floor(rand() * pool.length) % pool.length;
248
+ return pool[index];
249
+ }
250
+ export function formatIntroLine(version, options = {}) {
251
+ const tagline = pickTagline(options);
252
+ const rich = options.richTty ?? true;
253
+ if (rich && chalk.level > 0) {
254
+ return `${chalk.bold('🧿 oracle')} ${version} — ${tagline}`;
255
+ }
256
+ return `🧿 oracle ${version} — ${tagline}`;
257
+ }
258
+ export { TAGLINES };