@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.
- package/LICENSE +21 -0
- package/README.md +215 -0
- package/assets-oracle-icon.png +0 -0
- package/dist/bin/oracle-cli.js +1252 -0
- package/dist/bin/oracle-mcp.js +6 -0
- package/dist/scripts/agent-send.js +147 -0
- package/dist/scripts/browser-tools.js +536 -0
- package/dist/scripts/check.js +21 -0
- package/dist/scripts/debug/extract-chatgpt-response.js +53 -0
- package/dist/scripts/docs-list.js +110 -0
- package/dist/scripts/git-policy.js +125 -0
- package/dist/scripts/run-cli.js +14 -0
- package/dist/scripts/runner.js +1378 -0
- package/dist/scripts/test-browser.js +103 -0
- package/dist/scripts/test-remote-chrome.js +68 -0
- package/dist/src/bridge/connection.js +103 -0
- package/dist/src/bridge/userConfigFile.js +28 -0
- package/dist/src/browser/actions/assistantResponse.js +1067 -0
- package/dist/src/browser/actions/attachmentDataTransfer.js +138 -0
- package/dist/src/browser/actions/attachments.js +1910 -0
- package/dist/src/browser/actions/domEvents.js +19 -0
- package/dist/src/browser/actions/modelSelection.js +485 -0
- package/dist/src/browser/actions/navigation.js +445 -0
- package/dist/src/browser/actions/promptComposer.js +485 -0
- package/dist/src/browser/actions/remoteFileTransfer.js +37 -0
- package/dist/src/browser/actions/thinkingTime.js +206 -0
- package/dist/src/browser/chromeLifecycle.js +344 -0
- package/dist/src/browser/config.js +103 -0
- package/dist/src/browser/constants.js +71 -0
- package/dist/src/browser/cookies.js +191 -0
- package/dist/src/browser/detect.js +164 -0
- package/dist/src/browser/domDebug.js +36 -0
- package/dist/src/browser/index.js +1741 -0
- package/dist/src/browser/modelStrategy.js +13 -0
- package/dist/src/browser/pageActions.js +5 -0
- package/dist/src/browser/policies.js +43 -0
- package/dist/src/browser/profileState.js +280 -0
- package/dist/src/browser/prompt.js +152 -0
- package/dist/src/browser/promptSummary.js +20 -0
- package/dist/src/browser/reattach.js +186 -0
- package/dist/src/browser/reattachHelpers.js +382 -0
- package/dist/src/browser/sessionRunner.js +119 -0
- package/dist/src/browser/types.js +1 -0
- package/dist/src/browser/utils.js +122 -0
- package/dist/src/browserMode.js +1 -0
- package/dist/src/cli/bridge/claudeConfig.js +54 -0
- package/dist/src/cli/bridge/client.js +73 -0
- package/dist/src/cli/bridge/codexConfig.js +43 -0
- package/dist/src/cli/bridge/doctor.js +107 -0
- package/dist/src/cli/bridge/host.js +259 -0
- package/dist/src/cli/browserConfig.js +278 -0
- package/dist/src/cli/browserDefaults.js +81 -0
- package/dist/src/cli/bundleWarnings.js +9 -0
- package/dist/src/cli/clipboard.js +10 -0
- package/dist/src/cli/detach.js +11 -0
- package/dist/src/cli/dryRun.js +105 -0
- package/dist/src/cli/duplicatePromptGuard.js +14 -0
- package/dist/src/cli/engine.js +41 -0
- package/dist/src/cli/errorUtils.js +9 -0
- package/dist/src/cli/format.js +13 -0
- package/dist/src/cli/help.js +77 -0
- package/dist/src/cli/hiddenAliases.js +22 -0
- package/dist/src/cli/markdownBundle.js +17 -0
- package/dist/src/cli/markdownRenderer.js +97 -0
- package/dist/src/cli/notifier.js +306 -0
- package/dist/src/cli/options.js +281 -0
- package/dist/src/cli/oscUtils.js +2 -0
- package/dist/src/cli/promptRequirement.js +17 -0
- package/dist/src/cli/renderFlags.js +9 -0
- package/dist/src/cli/renderOutput.js +26 -0
- package/dist/src/cli/rootAlias.js +30 -0
- package/dist/src/cli/runOptions.js +78 -0
- package/dist/src/cli/sessionCommand.js +111 -0
- package/dist/src/cli/sessionDisplay.js +567 -0
- package/dist/src/cli/sessionRunner.js +602 -0
- package/dist/src/cli/sessionTable.js +92 -0
- package/dist/src/cli/tagline.js +258 -0
- package/dist/src/cli/tui/index.js +486 -0
- package/dist/src/cli/writeOutputPath.js +21 -0
- package/dist/src/config.js +26 -0
- package/dist/src/gemini-web/client.js +328 -0
- package/dist/src/gemini-web/executor.js +285 -0
- package/dist/src/gemini-web/index.js +1 -0
- package/dist/src/gemini-web/types.js +1 -0
- package/dist/src/heartbeat.js +43 -0
- package/dist/src/mcp/server.js +40 -0
- package/dist/src/mcp/tools/consult.js +290 -0
- package/dist/src/mcp/tools/sessionResources.js +75 -0
- package/dist/src/mcp/tools/sessions.js +105 -0
- package/dist/src/mcp/types.js +22 -0
- package/dist/src/mcp/utils.js +37 -0
- package/dist/src/oracle/background.js +141 -0
- package/dist/src/oracle/claude.js +101 -0
- package/dist/src/oracle/client.js +197 -0
- package/dist/src/oracle/config.js +227 -0
- package/dist/src/oracle/errors.js +132 -0
- package/dist/src/oracle/files.js +378 -0
- package/dist/src/oracle/finishLine.js +32 -0
- package/dist/src/oracle/format.js +30 -0
- package/dist/src/oracle/fsAdapter.js +10 -0
- package/dist/src/oracle/gemini.js +195 -0
- package/dist/src/oracle/logging.js +36 -0
- package/dist/src/oracle/markdown.js +46 -0
- package/dist/src/oracle/modelResolver.js +183 -0
- package/dist/src/oracle/multiModelRunner.js +153 -0
- package/dist/src/oracle/oscProgress.js +24 -0
- package/dist/src/oracle/promptAssembly.js +13 -0
- package/dist/src/oracle/request.js +50 -0
- package/dist/src/oracle/run.js +596 -0
- package/dist/src/oracle/runUtils.js +31 -0
- package/dist/src/oracle/tokenEstimate.js +37 -0
- package/dist/src/oracle/tokenStats.js +39 -0
- package/dist/src/oracle/tokenStringifier.js +24 -0
- package/dist/src/oracle/types.js +1 -0
- package/dist/src/oracle.js +12 -0
- package/dist/src/oracleHome.js +13 -0
- package/dist/src/remote/client.js +129 -0
- package/dist/src/remote/health.js +113 -0
- package/dist/src/remote/remoteServiceConfig.js +31 -0
- package/dist/src/remote/server.js +533 -0
- package/dist/src/remote/types.js +1 -0
- package/dist/src/sessionManager.js +637 -0
- package/dist/src/sessionStore.js +56 -0
- package/dist/src/version.js +39 -0
- package/dist/vendor/oracle-notifier/OracleNotifier.swift +45 -0
- package/dist/vendor/oracle-notifier/README.md +24 -0
- package/dist/vendor/oracle-notifier/build-notifier.sh +93 -0
- package/package.json +115 -0
- package/vendor/oracle-notifier/OracleNotifier.swift +45 -0
- package/vendor/oracle-notifier/README.md +24 -0
- package/vendor/oracle-notifier/build-notifier.sh +93 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalize hidden alias flags so they behave like their primary counterparts.
|
|
3
|
+
*
|
|
4
|
+
* - `--message` maps to `--prompt` when no prompt is provided.
|
|
5
|
+
* - `--include` extends the `--file` list.
|
|
6
|
+
* - `--mode` maps to `--engine` for backward compatibility with older docs/UX.
|
|
7
|
+
*/
|
|
8
|
+
export function applyHiddenAliases(options, setOptionValue) {
|
|
9
|
+
if (options.include && options.include.length > 0) {
|
|
10
|
+
const mergedFiles = [...(options.file ?? []), ...options.include];
|
|
11
|
+
options.file = mergedFiles;
|
|
12
|
+
setOptionValue?.('file', mergedFiles);
|
|
13
|
+
}
|
|
14
|
+
if (!options.prompt && options.message) {
|
|
15
|
+
options.prompt = options.message;
|
|
16
|
+
setOptionValue?.('prompt', options.message);
|
|
17
|
+
}
|
|
18
|
+
if (!options.engine && options.mode) {
|
|
19
|
+
options.engine = options.mode;
|
|
20
|
+
setOptionValue?.('engine', options.mode);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import { DEFAULT_SYSTEM_PROMPT } from '../oracle/config.js';
|
|
3
|
+
import { buildPrompt } from '../oracle/request.js';
|
|
4
|
+
import { createFileSections, readFiles } from '../oracle/files.js';
|
|
5
|
+
import { createFsAdapter } from '../oracle/fsAdapter.js';
|
|
6
|
+
import { buildPromptMarkdown } from '../oracle/promptAssembly.js';
|
|
7
|
+
export async function buildMarkdownBundle(options, deps = {}) {
|
|
8
|
+
const cwd = deps.cwd ?? process.cwd();
|
|
9
|
+
const fsModule = deps.fs ?? createFsAdapter(fs);
|
|
10
|
+
const files = await readFiles(options.file ?? [], { cwd, fsModule });
|
|
11
|
+
const sections = createFileSections(files, cwd);
|
|
12
|
+
const systemPrompt = options.system?.trim() || DEFAULT_SYSTEM_PROMPT;
|
|
13
|
+
const userPrompt = (options.prompt ?? '').trim();
|
|
14
|
+
const markdown = buildPromptMarkdown(systemPrompt, userPrompt, sections);
|
|
15
|
+
const promptWithFiles = buildPrompt(userPrompt, files, cwd);
|
|
16
|
+
return { markdown, promptWithFiles, systemPrompt, files };
|
|
17
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { render as renderMarkdown } from 'markdansi';
|
|
3
|
+
import { bundledLanguages, bundledThemes, createHighlighter, } from 'shiki';
|
|
4
|
+
const DEFAULT_THEME = 'github-dark';
|
|
5
|
+
const HIGHLIGHT_LANGS = ['ts', 'tsx', 'js', 'jsx', 'json', 'swift'];
|
|
6
|
+
const SUPPORTED_LANG_ALIASES = {
|
|
7
|
+
ts: 'ts',
|
|
8
|
+
typescript: 'ts',
|
|
9
|
+
tsx: 'tsx',
|
|
10
|
+
js: 'js',
|
|
11
|
+
javascript: 'js',
|
|
12
|
+
jsx: 'jsx',
|
|
13
|
+
json: 'json',
|
|
14
|
+
swift: 'swift',
|
|
15
|
+
};
|
|
16
|
+
const shikiPromise = createHighlighter({
|
|
17
|
+
themes: [bundledThemes[DEFAULT_THEME]],
|
|
18
|
+
langs: HIGHLIGHT_LANGS.map((lang) => bundledLanguages[lang]),
|
|
19
|
+
});
|
|
20
|
+
let shiki = null;
|
|
21
|
+
void shikiPromise
|
|
22
|
+
.then((instance) => {
|
|
23
|
+
shiki = instance;
|
|
24
|
+
})
|
|
25
|
+
.catch(() => {
|
|
26
|
+
shiki = null;
|
|
27
|
+
});
|
|
28
|
+
export async function ensureShikiReady() {
|
|
29
|
+
if (shiki)
|
|
30
|
+
return;
|
|
31
|
+
try {
|
|
32
|
+
shiki = await shikiPromise;
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
shiki = null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function normalizeLanguage(lang) {
|
|
39
|
+
if (!lang)
|
|
40
|
+
return null;
|
|
41
|
+
const key = lang.toLowerCase();
|
|
42
|
+
return SUPPORTED_LANG_ALIASES[key] ?? null;
|
|
43
|
+
}
|
|
44
|
+
function styleToken(text, fontStyle = 0) {
|
|
45
|
+
let styled = text;
|
|
46
|
+
if (fontStyle & 1)
|
|
47
|
+
styled = chalk.italic(styled);
|
|
48
|
+
if (fontStyle & 2)
|
|
49
|
+
styled = chalk.bold(styled);
|
|
50
|
+
if (fontStyle & 4)
|
|
51
|
+
styled = chalk.underline(styled);
|
|
52
|
+
if (fontStyle & 8)
|
|
53
|
+
styled = chalk.strikethrough(styled);
|
|
54
|
+
return styled;
|
|
55
|
+
}
|
|
56
|
+
function shikiHighlighter(code, lang) {
|
|
57
|
+
if (!process.stdout.isTTY || !shiki)
|
|
58
|
+
return code;
|
|
59
|
+
const normalizedLang = normalizeLanguage(lang);
|
|
60
|
+
if (!normalizedLang)
|
|
61
|
+
return code;
|
|
62
|
+
try {
|
|
63
|
+
if (!shiki.getLoadedLanguages().includes(normalizedLang)) {
|
|
64
|
+
return code;
|
|
65
|
+
}
|
|
66
|
+
const { tokens } = shiki.codeToTokens(code, { lang: normalizedLang, theme: DEFAULT_THEME });
|
|
67
|
+
return tokens
|
|
68
|
+
.map((line) => line
|
|
69
|
+
.map((token) => {
|
|
70
|
+
const colored = token.color ? chalk.hex(token.color)(token.content) : token.content;
|
|
71
|
+
return styleToken(colored, token.fontStyle);
|
|
72
|
+
})
|
|
73
|
+
.join(''))
|
|
74
|
+
.join('\n');
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return code;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
export function renderMarkdownAnsi(markdown) {
|
|
81
|
+
try {
|
|
82
|
+
const color = Boolean(process.stdout.isTTY);
|
|
83
|
+
const width = process.stdout.columns;
|
|
84
|
+
const hyperlinks = color; // enable OSC 8 only when we have color/TTY
|
|
85
|
+
return renderMarkdown(markdown, {
|
|
86
|
+
color,
|
|
87
|
+
width,
|
|
88
|
+
wrap: true,
|
|
89
|
+
hyperlinks,
|
|
90
|
+
highlighter: color ? shikiHighlighter : undefined,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
// Last-resort fallback: return the raw markdown so we never crash.
|
|
95
|
+
return markdown;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
import notifier from 'toasted-notifier';
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
import { formatUSD, formatNumber } from '../oracle/format.js';
|
|
4
|
+
import { MODEL_CONFIGS } from '../oracle/config.js';
|
|
5
|
+
import { estimateUsdCost } from 'tokentally';
|
|
6
|
+
import fs from 'node:fs/promises';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import { createRequire } from 'node:module';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
10
|
+
const ORACLE_EMOJI = '🧿';
|
|
11
|
+
export function resolveNotificationSettings({ cliNotify, cliNotifySound, env, config, }) {
|
|
12
|
+
const defaultEnabled = !(bool(env.CI) || bool(env.SSH_CONNECTION) || muteByConfig(env, config));
|
|
13
|
+
const envNotify = parseToggle(env.ORACLE_NOTIFY);
|
|
14
|
+
const envSound = parseToggle(env.ORACLE_NOTIFY_SOUND);
|
|
15
|
+
const enabled = cliNotify ?? envNotify ?? config?.enabled ?? defaultEnabled;
|
|
16
|
+
const sound = cliNotifySound ?? envSound ?? config?.sound ?? false;
|
|
17
|
+
return { enabled, sound };
|
|
18
|
+
}
|
|
19
|
+
export function deriveNotificationSettingsFromMetadata(metadata, env, config) {
|
|
20
|
+
if (metadata?.notifications) {
|
|
21
|
+
return metadata.notifications;
|
|
22
|
+
}
|
|
23
|
+
return resolveNotificationSettings({ cliNotify: undefined, cliNotifySound: undefined, env, config });
|
|
24
|
+
}
|
|
25
|
+
export async function sendSessionNotification(payload, settings, log, answerPreview) {
|
|
26
|
+
if (!settings.enabled || isTestEnv(process.env)) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const title = `Oracle${ORACLE_EMOJI} finished`;
|
|
30
|
+
const message = buildMessage(payload, sanitizePreview(answerPreview));
|
|
31
|
+
try {
|
|
32
|
+
if (await tryMacNativeNotifier(title, message, settings)) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (!(await shouldSkipToastedNotifier())) {
|
|
36
|
+
// Fallback to toasted-notifier (cross-platform). macAppIconOption() is only honored on macOS.
|
|
37
|
+
await notifier.notify({
|
|
38
|
+
title,
|
|
39
|
+
message,
|
|
40
|
+
sound: settings.sound,
|
|
41
|
+
});
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
if (isMacExecError(error)) {
|
|
47
|
+
const repaired = await repairMacNotifier(log);
|
|
48
|
+
if (repaired) {
|
|
49
|
+
try {
|
|
50
|
+
await notifier.notify({ title, message, sound: settings.sound, ...(macAppIconOption()) });
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
catch (retryError) {
|
|
54
|
+
const reason = describeNotifierError(retryError);
|
|
55
|
+
log(`(notify skipped after retry: ${reason})`);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (isMacBadCpuError(error)) {
|
|
61
|
+
const reason = describeNotifierError(error);
|
|
62
|
+
log(`(notify skipped: ${reason})`);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const reason = describeNotifierError(error);
|
|
66
|
+
log(`(notify skipped: ${reason})`);
|
|
67
|
+
}
|
|
68
|
+
// Last-resort macOS fallback: AppleScript alert (simple, noisy, but works when helpers are blocked).
|
|
69
|
+
if (process.platform === 'darwin') {
|
|
70
|
+
try {
|
|
71
|
+
await sendOsascriptAlert(title, message, log);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
catch (scriptError) {
|
|
75
|
+
const reason = describeNotifierError(scriptError);
|
|
76
|
+
log(`(notify skipped: osascript fallback failed: ${reason})`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function buildMessage(payload, answerPreview) {
|
|
81
|
+
const parts = [];
|
|
82
|
+
const sessionLabel = payload.sessionName || payload.sessionId;
|
|
83
|
+
parts.push(sessionLabel);
|
|
84
|
+
// Show cost only for API runs.
|
|
85
|
+
if (payload.mode === 'api') {
|
|
86
|
+
const cost = payload.costUsd ?? inferCost(payload);
|
|
87
|
+
if (cost !== undefined) {
|
|
88
|
+
// Round to $0.00 for a concise toast.
|
|
89
|
+
parts.push(formatUSD(Number(cost.toFixed(2))));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (payload.characters != null) {
|
|
93
|
+
parts.push(`${formatNumber(payload.characters)} chars`);
|
|
94
|
+
}
|
|
95
|
+
if (answerPreview) {
|
|
96
|
+
parts.push(answerPreview);
|
|
97
|
+
}
|
|
98
|
+
return parts.join(' · ');
|
|
99
|
+
}
|
|
100
|
+
function sanitizePreview(preview) {
|
|
101
|
+
if (!preview)
|
|
102
|
+
return undefined;
|
|
103
|
+
let text = preview;
|
|
104
|
+
// Strip code fences and inline code markers.
|
|
105
|
+
text = text.replace(/```[\s\S]*?```/g, ' ');
|
|
106
|
+
text = text.replace(/`([^`]+)`/g, '$1');
|
|
107
|
+
// Convert markdown links and images to their visible text.
|
|
108
|
+
text = text.replace(/!\[([^\]]*)\]\([^)]+\)/g, '$1');
|
|
109
|
+
text = text.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1');
|
|
110
|
+
// Drop bold/italic markers.
|
|
111
|
+
text = text.replace(/(\*\*|__|\*|_)/g, '');
|
|
112
|
+
// Remove headings / list markers / blockquotes.
|
|
113
|
+
text = text.replace(/^\s*#+\s*/gm, '');
|
|
114
|
+
text = text.replace(/^\s*[-*+]\s+/gm, '');
|
|
115
|
+
text = text.replace(/^\s*>\s+/gm, '');
|
|
116
|
+
// Collapse whitespace and trim.
|
|
117
|
+
text = text.replace(/\s+/g, ' ').trim();
|
|
118
|
+
// Limit length to keep notifications short.
|
|
119
|
+
const max = 200;
|
|
120
|
+
if (text.length > max) {
|
|
121
|
+
text = `${text.slice(0, max - 1)}…`;
|
|
122
|
+
}
|
|
123
|
+
return text;
|
|
124
|
+
}
|
|
125
|
+
// Exposed for unit tests only.
|
|
126
|
+
export const testHelpers = { sanitizePreview };
|
|
127
|
+
function inferCost(payload) {
|
|
128
|
+
const model = payload.model;
|
|
129
|
+
const usage = payload.usage;
|
|
130
|
+
if (!model || !usage)
|
|
131
|
+
return undefined;
|
|
132
|
+
const config = MODEL_CONFIGS[model];
|
|
133
|
+
if (!config?.pricing)
|
|
134
|
+
return undefined;
|
|
135
|
+
return (estimateUsdCost({
|
|
136
|
+
usage: { inputTokens: usage.inputTokens, outputTokens: usage.outputTokens },
|
|
137
|
+
pricing: {
|
|
138
|
+
inputUsdPerToken: config.pricing.inputPerToken,
|
|
139
|
+
outputUsdPerToken: config.pricing.outputPerToken,
|
|
140
|
+
},
|
|
141
|
+
})?.totalUsd ?? undefined);
|
|
142
|
+
}
|
|
143
|
+
function parseToggle(value) {
|
|
144
|
+
if (value == null)
|
|
145
|
+
return undefined;
|
|
146
|
+
const normalized = value.trim().toLowerCase();
|
|
147
|
+
if (['1', 'true', 'yes', 'on'].includes(normalized))
|
|
148
|
+
return true;
|
|
149
|
+
if (['0', 'false', 'no', 'off'].includes(normalized))
|
|
150
|
+
return false;
|
|
151
|
+
return undefined;
|
|
152
|
+
}
|
|
153
|
+
function bool(value) {
|
|
154
|
+
return Boolean(value && String(value).length > 0);
|
|
155
|
+
}
|
|
156
|
+
function isMacExecError(error) {
|
|
157
|
+
return Boolean(process.platform === 'darwin' &&
|
|
158
|
+
error &&
|
|
159
|
+
typeof error === 'object' &&
|
|
160
|
+
'code' in error &&
|
|
161
|
+
error.code === 'EACCES');
|
|
162
|
+
}
|
|
163
|
+
function isMacBadCpuError(error) {
|
|
164
|
+
return Boolean(process.platform === 'darwin' &&
|
|
165
|
+
error &&
|
|
166
|
+
typeof error === 'object' &&
|
|
167
|
+
'errno' in error &&
|
|
168
|
+
error.errno === -86);
|
|
169
|
+
}
|
|
170
|
+
async function repairMacNotifier(log) {
|
|
171
|
+
const binPath = macNotifierPath();
|
|
172
|
+
if (!binPath)
|
|
173
|
+
return false;
|
|
174
|
+
try {
|
|
175
|
+
await fs.chmod(binPath, 0o755);
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
catch (chmodError) {
|
|
179
|
+
const reason = chmodError instanceof Error ? chmodError.message : String(chmodError);
|
|
180
|
+
log(`(notify repair failed: ${reason} — try: xattr -dr com.apple.quarantine "${path.dirname(binPath)}")`);
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
function macNotifierPath() {
|
|
185
|
+
if (process.platform !== 'darwin')
|
|
186
|
+
return null;
|
|
187
|
+
try {
|
|
188
|
+
const req = createRequire(import.meta.url);
|
|
189
|
+
const modPath = req.resolve('toasted-notifier');
|
|
190
|
+
const base = path.dirname(modPath);
|
|
191
|
+
return path.join(base, 'vendor', 'mac.noindex', 'terminal-notifier.app', 'Contents', 'MacOS', 'terminal-notifier');
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
async function shouldSkipToastedNotifier() {
|
|
198
|
+
if (process.platform !== 'darwin')
|
|
199
|
+
return false;
|
|
200
|
+
// On Apple Silicon without Rosetta, prefer the native helper and skip x86-only fallback.
|
|
201
|
+
const arch = process.arch;
|
|
202
|
+
if (arch !== 'arm64')
|
|
203
|
+
return false;
|
|
204
|
+
return !(await hasRosetta());
|
|
205
|
+
}
|
|
206
|
+
async function hasRosetta() {
|
|
207
|
+
return new Promise((resolve) => {
|
|
208
|
+
const child = spawn('pkgutil', ['--files', 'com.apple.pkg.RosettaUpdateAuto'], { stdio: 'ignore' });
|
|
209
|
+
child.on('exit', (code) => resolve(code === 0));
|
|
210
|
+
child.on('error', () => resolve(false));
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
async function sendOsascriptAlert(title, message, _log) {
|
|
214
|
+
return new Promise((resolve, reject) => {
|
|
215
|
+
const child = spawn('osascript', ['-e', `display notification "${escapeAppleScript(message)}" with title "${escapeAppleScript(title)}"`], {
|
|
216
|
+
stdio: 'ignore',
|
|
217
|
+
});
|
|
218
|
+
child.on('exit', (code) => {
|
|
219
|
+
if (code === 0) {
|
|
220
|
+
resolve();
|
|
221
|
+
}
|
|
222
|
+
else {
|
|
223
|
+
reject(new Error(`osascript exited with code ${code ?? -1}`));
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
child.on('error', reject);
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
function escapeAppleScript(value) {
|
|
230
|
+
return value.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
231
|
+
}
|
|
232
|
+
function macAppIconOption() {
|
|
233
|
+
if (process.platform !== 'darwin')
|
|
234
|
+
return {};
|
|
235
|
+
const iconPaths = [
|
|
236
|
+
path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../assets-oracle-icon.png'),
|
|
237
|
+
path.resolve(process.cwd(), 'assets-oracle-icon.png'),
|
|
238
|
+
];
|
|
239
|
+
for (const candidate of iconPaths) {
|
|
240
|
+
if (candidate && fsExistsSync(candidate)) {
|
|
241
|
+
return { appIcon: candidate };
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return {};
|
|
245
|
+
}
|
|
246
|
+
function fsExistsSync(target) {
|
|
247
|
+
try {
|
|
248
|
+
return Boolean(require('node:fs').statSync(target));
|
|
249
|
+
}
|
|
250
|
+
catch {
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
async function tryMacNativeNotifier(title, message, settings) {
|
|
255
|
+
const binary = macNativeNotifierPath();
|
|
256
|
+
if (!binary)
|
|
257
|
+
return false;
|
|
258
|
+
return new Promise((resolve) => {
|
|
259
|
+
const child = spawn(binary, [title, message, settings.sound ? 'Glass' : ''], {
|
|
260
|
+
stdio: 'ignore',
|
|
261
|
+
});
|
|
262
|
+
child.on('error', () => resolve(false));
|
|
263
|
+
child.on('exit', (code) => resolve(code === 0));
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
function macNativeNotifierPath() {
|
|
267
|
+
if (process.platform !== 'darwin')
|
|
268
|
+
return null;
|
|
269
|
+
const candidates = [
|
|
270
|
+
path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier'),
|
|
271
|
+
path.resolve(process.cwd(), 'vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier'),
|
|
272
|
+
];
|
|
273
|
+
for (const candidate of candidates) {
|
|
274
|
+
if (fsExistsSync(candidate)) {
|
|
275
|
+
return candidate;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
function muteByConfig(env, config) {
|
|
281
|
+
if (!config?.muteIn)
|
|
282
|
+
return false;
|
|
283
|
+
return ((config.muteIn.includes('CI') && bool(env.CI)) ||
|
|
284
|
+
(config.muteIn.includes('SSH') && bool(env.SSH_CONNECTION)));
|
|
285
|
+
}
|
|
286
|
+
function isTestEnv(env) {
|
|
287
|
+
return (env.ORACLE_DISABLE_NOTIFICATIONS === '1' ||
|
|
288
|
+
env.NODE_ENV === 'test' ||
|
|
289
|
+
Boolean(env.VITEST || env.VITEST_WORKER_ID || env.JEST_WORKER_ID));
|
|
290
|
+
}
|
|
291
|
+
function describeNotifierError(error) {
|
|
292
|
+
if (error && typeof error === 'object') {
|
|
293
|
+
const err = error;
|
|
294
|
+
if (typeof err.errno === 'number' || typeof err.code === 'string') {
|
|
295
|
+
const errno = typeof err.errno === 'number' ? err.errno : undefined;
|
|
296
|
+
// macOS returns errno -86 for “Bad CPU type in executable” (e.g., wrong arch or quarantined binary).
|
|
297
|
+
if (errno === -86) {
|
|
298
|
+
return 'notifier binary failed to launch (Bad CPU type/quarantine); try xattr -dr com.apple.quarantine vendor/oracle-notifier && ./vendor/oracle-notifier/build-notifier.sh';
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
if (typeof err.message === 'string') {
|
|
302
|
+
return err.message;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return typeof error === 'string' ? error : String(error);
|
|
306
|
+
}
|