@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,37 @@
1
+ import { TOKENIZER_OPTIONS } from './config.js';
2
+ /**
3
+ * Estimate input tokens from the full request body instead of just system/user text.
4
+ * This is a conservative approximation: we tokenize the key textual fields and add a fixed buffer
5
+ * to cover structural JSON overhead and server-side wrappers (tools/reasoning/background/store).
6
+ */
7
+ export function estimateRequestTokens(requestBody, modelConfig, bufferTokens = 200) {
8
+ const SEARCH_RESULT_BUFFER_TOKENS = 4000;
9
+ const parts = [];
10
+ if (requestBody.instructions) {
11
+ parts.push(requestBody.instructions);
12
+ }
13
+ for (const turn of requestBody.input ?? []) {
14
+ for (const content of turn.content ?? []) {
15
+ if (typeof content.text === 'string') {
16
+ parts.push(content.text);
17
+ }
18
+ }
19
+ }
20
+ if (requestBody.tools && requestBody.tools.length > 0) {
21
+ parts.push(JSON.stringify(requestBody.tools));
22
+ }
23
+ if (requestBody.reasoning) {
24
+ parts.push(JSON.stringify(requestBody.reasoning));
25
+ }
26
+ if (requestBody.background) {
27
+ parts.push('background:true');
28
+ }
29
+ if (requestBody.store) {
30
+ parts.push('store:true');
31
+ }
32
+ const concatenated = parts.join('\n');
33
+ const baseEstimate = modelConfig.tokenizer(concatenated, TOKENIZER_OPTIONS);
34
+ const hasWebSearch = requestBody.tools?.some((tool) => tool?.type === 'web_search_preview');
35
+ const searchBuffer = hasWebSearch ? SEARCH_RESULT_BUFFER_TOKENS : 0;
36
+ return baseEstimate + bufferTokens + searchBuffer;
37
+ }
@@ -0,0 +1,39 @@
1
+ import chalk from 'chalk';
2
+ import { createFileSections } from './files.js';
3
+ export function getFileTokenStats(files, { cwd = process.cwd(), tokenizer, tokenizerOptions, inputTokenBudget, }) {
4
+ if (!files.length) {
5
+ return { stats: [], totalTokens: 0 };
6
+ }
7
+ const sections = createFileSections(files, cwd);
8
+ const stats = sections
9
+ .map((section) => {
10
+ const tokens = tokenizer(section.sectionText, tokenizerOptions);
11
+ const percent = inputTokenBudget ? (tokens / inputTokenBudget) * 100 : undefined;
12
+ return {
13
+ path: section.absolutePath,
14
+ displayPath: section.displayPath,
15
+ tokens,
16
+ percent,
17
+ };
18
+ })
19
+ .sort((a, b) => b.tokens - a.tokens);
20
+ const totalTokens = stats.reduce((sum, entry) => sum + entry.tokens, 0);
21
+ return { stats, totalTokens };
22
+ }
23
+ export function printFileTokenStats({ stats, totalTokens }, { inputTokenBudget, log = console.log }) {
24
+ if (!stats.length) {
25
+ return;
26
+ }
27
+ log(chalk.bold('File Token Usage'));
28
+ for (const entry of stats) {
29
+ const percentLabel = inputTokenBudget && entry.percent != null ? `${entry.percent.toFixed(2)}%` : 'n/a';
30
+ log(`${entry.tokens.toLocaleString().padStart(10)} ${percentLabel.padStart(8)} ${entry.displayPath}`);
31
+ }
32
+ if (inputTokenBudget) {
33
+ const totalPercent = (totalTokens / inputTokenBudget) * 100;
34
+ log(`Total: ${totalTokens.toLocaleString()} tokens (${totalPercent.toFixed(2)}% of ${inputTokenBudget.toLocaleString()})`);
35
+ }
36
+ else {
37
+ log(`Total: ${totalTokens.toLocaleString()} tokens`);
38
+ }
39
+ }
@@ -0,0 +1,24 @@
1
+ // Minimal helper to stringify arbitrary input for tokenizer consumption.
2
+ // Anthropic's tokenizer expects a string; we accept unknown and coerce safely.
3
+ export function stringifyTokenizerInput(input) {
4
+ if (typeof input === 'string')
5
+ return input;
6
+ if (input === null || input === undefined)
7
+ return '';
8
+ if (typeof input === 'number' || typeof input === 'boolean' || typeof input === 'bigint') {
9
+ return String(input);
10
+ }
11
+ if (typeof input === 'object') {
12
+ try {
13
+ return JSON.stringify(input);
14
+ }
15
+ catch {
16
+ // fall through to generic stringification
17
+ }
18
+ }
19
+ if (typeof input === 'function') {
20
+ return input.toString();
21
+ }
22
+ return String(input);
23
+ }
24
+ export default stringifyTokenizerInput;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,12 @@
1
+ export * from './oracle/types.js';
2
+ export { MODEL_CONFIGS, DEFAULT_MODEL, PRO_MODELS, DEFAULT_SYSTEM_PROMPT, TOKENIZER_OPTIONS, } from './oracle/config.js';
3
+ export { readFiles, createFileSections } from './oracle/files.js';
4
+ export { buildPrompt, buildRequestBody, renderPromptMarkdown } from './oracle/request.js';
5
+ export { estimateRequestTokens } from './oracle/tokenEstimate.js';
6
+ export { formatUSD, formatNumber, formatElapsed } from './oracle/format.js';
7
+ export { formatFileSection } from './oracle/markdown.js';
8
+ export { getFileTokenStats, printFileTokenStats } from './oracle/tokenStats.js';
9
+ export { OracleResponseError, OracleTransportError, OracleUserError, FileValidationError, BrowserAutomationError, PromptValidationError, describeTransportError, extractResponseMetadata, asOracleUserError, toTransportError, } from './oracle/errors.js';
10
+ export { createDefaultClientFactory } from './oracle/client.js';
11
+ export { runOracle, extractTextOutput } from './oracle/run.js';
12
+ export { resolveGeminiModelId } from './oracle/gemini.js';
@@ -0,0 +1,13 @@
1
+ import os from 'node:os';
2
+ import path from 'node:path';
3
+ let oracleHomeDirOverride = null;
4
+ /**
5
+ * Test-only hook: avoid mutating process.env (shared across Vitest worker threads).
6
+ * This override is scoped to the current Node worker.
7
+ */
8
+ export function setOracleHomeDirOverrideForTest(dir) {
9
+ oracleHomeDirOverride = dir;
10
+ }
11
+ export function getOracleHomeDir() {
12
+ return oracleHomeDirOverride ?? process.env.ORACLE_HOME_DIR ?? path.join(os.homedir(), '.oracle');
13
+ }
@@ -0,0 +1,129 @@
1
+ import http from 'node:http';
2
+ import path from 'node:path';
3
+ import { readFile } from 'node:fs/promises';
4
+ import { parseHostPort } from '../bridge/connection.js';
5
+ export function createRemoteBrowserExecutor({ host, token }) {
6
+ // Return a drop-in replacement for runBrowserMode so the browser session runner can stay unchanged.
7
+ return async function remoteBrowserExecutor(options) {
8
+ const payload = {
9
+ prompt: options.prompt,
10
+ attachments: await serializeAttachments(options.attachments ?? []),
11
+ browserConfig: options.config ?? {},
12
+ options: {
13
+ heartbeatIntervalMs: options.heartbeatIntervalMs,
14
+ verbose: options.verbose,
15
+ },
16
+ };
17
+ const body = Buffer.from(JSON.stringify(payload));
18
+ const { hostname, port } = parseHost(host);
19
+ return new Promise((resolve, reject) => {
20
+ const req = http.request({
21
+ hostname,
22
+ port,
23
+ path: '/runs',
24
+ method: 'POST',
25
+ headers: {
26
+ 'Content-Type': 'application/json',
27
+ 'Content-Length': body.length,
28
+ ...(token ? { authorization: `Bearer ${token}` } : {}),
29
+ },
30
+ }, (res) => {
31
+ if (res.statusCode !== 200) {
32
+ collectError(res)
33
+ .then((message) => reject(new Error(message)))
34
+ .catch(reject);
35
+ return;
36
+ }
37
+ res.setEncoding('utf8');
38
+ let buffer = '';
39
+ let resolved = null;
40
+ res.on('data', (chunk) => {
41
+ buffer += chunk;
42
+ let newlineIndex = buffer.indexOf('\n');
43
+ while (newlineIndex !== -1) {
44
+ const line = buffer.slice(0, newlineIndex).trim();
45
+ buffer = buffer.slice(newlineIndex + 1);
46
+ if (line.length > 0) {
47
+ handleEvent(line, options, (result) => {
48
+ resolved = result;
49
+ }, reject);
50
+ }
51
+ newlineIndex = buffer.indexOf('\n');
52
+ }
53
+ });
54
+ res.on('end', () => {
55
+ if (resolved) {
56
+ resolve(resolved);
57
+ return;
58
+ }
59
+ reject(new Error('Remote browser run completed without a result.'));
60
+ });
61
+ });
62
+ req.on('error', reject);
63
+ req.write(body);
64
+ req.end();
65
+ });
66
+ };
67
+ }
68
+ async function serializeAttachments(attachments) {
69
+ const serialized = [];
70
+ for (const attachment of attachments) {
71
+ // Read the local file upfront so the remote host never touches the caller's filesystem.
72
+ const content = await readFile(attachment.path);
73
+ serialized.push({
74
+ fileName: path.basename(attachment.path),
75
+ displayPath: attachment.displayPath,
76
+ sizeBytes: attachment.sizeBytes,
77
+ contentBase64: content.toString('base64'),
78
+ });
79
+ }
80
+ return serialized;
81
+ }
82
+ function parseHost(input) {
83
+ try {
84
+ return parseHostPort(input);
85
+ }
86
+ catch (error) {
87
+ throw new Error(`Invalid remote host: ${input} (${error instanceof Error ? error.message : String(error)})`);
88
+ }
89
+ }
90
+ function handleEvent(line, options, onResult, onError) {
91
+ let event;
92
+ try {
93
+ event = JSON.parse(line);
94
+ }
95
+ catch (error) {
96
+ onError(new Error(`Failed to parse remote event: ${error instanceof Error ? error.message : String(error)}`));
97
+ return;
98
+ }
99
+ if (event.type === 'log') {
100
+ options.log?.(event.message);
101
+ return;
102
+ }
103
+ if (event.type === 'error') {
104
+ onError(new Error(event.message));
105
+ return;
106
+ }
107
+ if (event.type === 'result') {
108
+ onResult(event.result);
109
+ }
110
+ }
111
+ function collectError(res) {
112
+ return new Promise((resolve, reject) => {
113
+ const chunks = [];
114
+ res.on('data', (chunk) => {
115
+ chunks.push(typeof chunk === 'string' ? Buffer.from(chunk) : chunk);
116
+ });
117
+ res.on('end', () => {
118
+ const raw = Buffer.concat(chunks).toString('utf8');
119
+ try {
120
+ const parsed = JSON.parse(raw);
121
+ resolve(parsed.error ?? `Remote host responded with status ${res.statusCode}`);
122
+ }
123
+ catch {
124
+ resolve(raw || `Remote host responded with status ${res.statusCode}`);
125
+ }
126
+ });
127
+ res.on('error', reject);
128
+ });
129
+ }
@@ -0,0 +1,113 @@
1
+ import http from 'node:http';
2
+ import net from 'node:net';
3
+ import { parseHostPort } from '../bridge/connection.js';
4
+ export async function checkTcpConnection(host, timeoutMs = 2000) {
5
+ const { hostname, port } = parseHostPort(host);
6
+ return await new Promise((resolve) => {
7
+ const socket = net.createConnection({ host: hostname, port });
8
+ const onError = (err) => {
9
+ cleanup();
10
+ resolve({ ok: false, error: err.message });
11
+ };
12
+ const onConnect = () => {
13
+ cleanup();
14
+ resolve({ ok: true });
15
+ };
16
+ const onTimeout = () => {
17
+ cleanup();
18
+ resolve({ ok: false, error: `timeout after ${timeoutMs}ms` });
19
+ };
20
+ const cleanup = () => {
21
+ socket.removeAllListeners();
22
+ socket.end();
23
+ socket.destroy();
24
+ socket.unref();
25
+ };
26
+ socket.setTimeout(timeoutMs);
27
+ socket.once('error', onError);
28
+ socket.once('connect', onConnect);
29
+ socket.once('timeout', onTimeout);
30
+ });
31
+ }
32
+ export async function checkRemoteHealth({ host, token, timeoutMs = 5000, }) {
33
+ const { hostname, port } = parseHostPort(host);
34
+ const headers = { accept: 'application/json' };
35
+ if (token) {
36
+ headers.authorization = `Bearer ${token}`;
37
+ }
38
+ try {
39
+ const response = await requestJson({
40
+ hostname,
41
+ port,
42
+ path: '/health',
43
+ headers,
44
+ timeoutMs,
45
+ });
46
+ if (response.statusCode === 200 && typeof response.json === 'object' && response.json) {
47
+ const ok = response.json.ok === true;
48
+ const version = response.json.version;
49
+ const uptimeSeconds = response.json.uptimeSeconds;
50
+ return {
51
+ ok,
52
+ statusCode: response.statusCode,
53
+ version: typeof version === 'string' ? version : undefined,
54
+ uptimeSeconds: typeof uptimeSeconds === 'number' ? uptimeSeconds : undefined,
55
+ };
56
+ }
57
+ if (response.statusCode === 404) {
58
+ return {
59
+ ok: false,
60
+ statusCode: response.statusCode,
61
+ error: 'remote host does not expose /health (upgrade oracle on the host and retry)',
62
+ };
63
+ }
64
+ const error = extractErrorMessage(response.json, response.bodyText) ?? `HTTP ${response.statusCode}`;
65
+ return { ok: false, statusCode: response.statusCode, error };
66
+ }
67
+ catch (error) {
68
+ return { ok: false, error: error instanceof Error ? error.message : String(error) };
69
+ }
70
+ }
71
+ function extractErrorMessage(json, bodyText) {
72
+ if (json && typeof json === 'object') {
73
+ const err = json.error;
74
+ if (typeof err === 'string' && err.trim().length > 0) {
75
+ return err.trim();
76
+ }
77
+ }
78
+ const trimmed = bodyText.trim();
79
+ return trimmed.length ? trimmed : null;
80
+ }
81
+ async function requestJson({ hostname, port, path, headers, timeoutMs, }) {
82
+ return await new Promise((resolve, reject) => {
83
+ const req = http.request({
84
+ hostname,
85
+ port,
86
+ path,
87
+ method: 'GET',
88
+ headers,
89
+ }, (res) => {
90
+ res.setEncoding('utf8');
91
+ let body = '';
92
+ res.on('data', (chunk) => {
93
+ body += chunk;
94
+ });
95
+ res.on('end', () => {
96
+ const statusCode = res.statusCode ?? 0;
97
+ let json = null;
98
+ try {
99
+ json = body.length ? JSON.parse(body) : null;
100
+ }
101
+ catch {
102
+ json = null;
103
+ }
104
+ resolve({ statusCode, json, bodyText: body });
105
+ });
106
+ });
107
+ req.setTimeout(timeoutMs, () => {
108
+ req.destroy(new Error(`timeout after ${timeoutMs}ms`));
109
+ });
110
+ req.on('error', reject);
111
+ req.end();
112
+ });
113
+ }
@@ -0,0 +1,31 @@
1
+ function normalizeString(value) {
2
+ if (typeof value !== 'string')
3
+ return undefined;
4
+ const trimmed = value.trim();
5
+ return trimmed.length ? trimmed : undefined;
6
+ }
7
+ export function resolveRemoteServiceConfig({ cliHost, cliToken, userConfig, env = process.env, }) {
8
+ const configBrowserHost = normalizeString(userConfig?.browser?.remoteHost);
9
+ const configBrowserToken = normalizeString(userConfig?.browser?.remoteToken);
10
+ const envHost = normalizeString(env.ORACLE_REMOTE_HOST);
11
+ const envToken = normalizeString(env.ORACLE_REMOTE_TOKEN);
12
+ const cliHostValue = normalizeString(cliHost);
13
+ const cliTokenValue = normalizeString(cliToken);
14
+ const host = cliHostValue ?? configBrowserHost ?? envHost;
15
+ const token = cliTokenValue ?? configBrowserToken ?? envToken;
16
+ const hostSource = cliHostValue
17
+ ? 'cli'
18
+ : configBrowserHost
19
+ ? 'config.browser'
20
+ : envHost
21
+ ? 'env'
22
+ : 'unset';
23
+ const tokenSource = cliTokenValue
24
+ ? 'cli'
25
+ : configBrowserToken
26
+ ? 'config.browser'
27
+ : envToken
28
+ ? 'env'
29
+ : 'unset';
30
+ return { host, token, sources: { host: hostSource, token: tokenSource } };
31
+ }