@steipete/oracle 0.4.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 +129 -0
- package/assets-oracle-icon.png +0 -0
- package/dist/bin/oracle-cli.js +954 -0
- package/dist/bin/oracle-mcp.js +6 -0
- package/dist/bin/oracle.js +683 -0
- package/dist/markdansi/types/index.js +4 -0
- package/dist/oracle/bin/oracle-cli.js +472 -0
- package/dist/oracle/src/browser/actions/assistantResponse.js +471 -0
- package/dist/oracle/src/browser/actions/attachments.js +82 -0
- package/dist/oracle/src/browser/actions/modelSelection.js +190 -0
- package/dist/oracle/src/browser/actions/navigation.js +75 -0
- package/dist/oracle/src/browser/actions/promptComposer.js +167 -0
- package/dist/oracle/src/browser/chromeLifecycle.js +104 -0
- package/dist/oracle/src/browser/config.js +33 -0
- package/dist/oracle/src/browser/constants.js +40 -0
- package/dist/oracle/src/browser/cookies.js +210 -0
- package/dist/oracle/src/browser/domDebug.js +36 -0
- package/dist/oracle/src/browser/index.js +331 -0
- package/dist/oracle/src/browser/pageActions.js +5 -0
- package/dist/oracle/src/browser/prompt.js +88 -0
- package/dist/oracle/src/browser/promptSummary.js +20 -0
- package/dist/oracle/src/browser/sessionRunner.js +80 -0
- package/dist/oracle/src/browser/types.js +1 -0
- package/dist/oracle/src/browser/utils.js +62 -0
- package/dist/oracle/src/browserMode.js +1 -0
- package/dist/oracle/src/cli/browserConfig.js +44 -0
- package/dist/oracle/src/cli/dryRun.js +59 -0
- package/dist/oracle/src/cli/engine.js +17 -0
- package/dist/oracle/src/cli/errorUtils.js +9 -0
- package/dist/oracle/src/cli/help.js +70 -0
- package/dist/oracle/src/cli/markdownRenderer.js +15 -0
- package/dist/oracle/src/cli/options.js +103 -0
- package/dist/oracle/src/cli/promptRequirement.js +14 -0
- package/dist/oracle/src/cli/rootAlias.js +30 -0
- package/dist/oracle/src/cli/sessionCommand.js +77 -0
- package/dist/oracle/src/cli/sessionDisplay.js +270 -0
- package/dist/oracle/src/cli/sessionRunner.js +94 -0
- package/dist/oracle/src/heartbeat.js +43 -0
- package/dist/oracle/src/oracle/client.js +48 -0
- package/dist/oracle/src/oracle/config.js +29 -0
- package/dist/oracle/src/oracle/errors.js +101 -0
- package/dist/oracle/src/oracle/files.js +220 -0
- package/dist/oracle/src/oracle/format.js +33 -0
- package/dist/oracle/src/oracle/fsAdapter.js +7 -0
- package/dist/oracle/src/oracle/oscProgress.js +60 -0
- package/dist/oracle/src/oracle/request.js +48 -0
- package/dist/oracle/src/oracle/run.js +444 -0
- package/dist/oracle/src/oracle/tokenStats.js +39 -0
- package/dist/oracle/src/oracle/types.js +1 -0
- package/dist/oracle/src/oracle.js +9 -0
- package/dist/oracle/src/sessionManager.js +205 -0
- package/dist/oracle/src/version.js +39 -0
- package/dist/scripts/browser-tools.js +536 -0
- package/dist/scripts/check.js +21 -0
- package/dist/scripts/chrome/browser-tools.js +295 -0
- package/dist/scripts/run-cli.js +14 -0
- package/dist/src/browser/actions/assistantResponse.js +555 -0
- package/dist/src/browser/actions/attachments.js +82 -0
- package/dist/src/browser/actions/modelSelection.js +300 -0
- package/dist/src/browser/actions/navigation.js +175 -0
- package/dist/src/browser/actions/promptComposer.js +167 -0
- package/dist/src/browser/actions/remoteFileTransfer.js +154 -0
- package/dist/src/browser/chromeCookies.js +274 -0
- package/dist/src/browser/chromeLifecycle.js +107 -0
- package/dist/src/browser/config.js +49 -0
- package/dist/src/browser/constants.js +42 -0
- package/dist/src/browser/cookies.js +130 -0
- package/dist/src/browser/domDebug.js +36 -0
- package/dist/src/browser/index.js +541 -0
- package/dist/src/browser/keytarShim.js +56 -0
- package/dist/src/browser/pageActions.js +5 -0
- package/dist/src/browser/policies.js +43 -0
- package/dist/src/browser/prompt.js +82 -0
- package/dist/src/browser/promptSummary.js +20 -0
- package/dist/src/browser/sessionRunner.js +96 -0
- package/dist/src/browser/types.js +1 -0
- package/dist/src/browser/utils.js +112 -0
- package/dist/src/browser/windowsCookies.js +218 -0
- package/dist/src/browserMode.js +1 -0
- package/dist/src/cli/browserConfig.js +193 -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 +103 -0
- package/dist/src/cli/duplicatePromptGuard.js +14 -0
- package/dist/src/cli/engine.js +25 -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 +300 -0
- package/dist/src/cli/options.js +193 -0
- package/dist/src/cli/oscUtils.js +20 -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 +62 -0
- package/dist/src/cli/sessionCommand.js +111 -0
- package/dist/src/cli/sessionDisplay.js +540 -0
- package/dist/src/cli/sessionRunner.js +419 -0
- package/dist/src/cli/tagline.js +258 -0
- package/dist/src/cli/tui/index.js +520 -0
- package/dist/src/cli/writeOutputPath.js +21 -0
- package/dist/src/config.js +27 -0
- package/dist/src/heartbeat.js +43 -0
- package/dist/src/mcp/server.js +36 -0
- package/dist/src/mcp/tools/consult.js +221 -0
- package/dist/src/mcp/tools/sessionResources.js +75 -0
- package/dist/src/mcp/tools/sessions.js +96 -0
- package/dist/src/mcp/types.js +18 -0
- package/dist/src/mcp/utils.js +27 -0
- package/dist/src/oracle/background.js +134 -0
- package/dist/src/oracle/claude.js +95 -0
- package/dist/src/oracle/client.js +87 -0
- package/dist/src/oracle/config.js +92 -0
- package/dist/src/oracle/errors.js +104 -0
- package/dist/src/oracle/files.js +371 -0
- package/dist/src/oracle/format.js +30 -0
- package/dist/src/oracle/fsAdapter.js +10 -0
- package/dist/src/oracle/gemini.js +185 -0
- package/dist/src/oracle/logging.js +36 -0
- package/dist/src/oracle/markdown.js +46 -0
- package/dist/src/oracle/multiModelRunner.js +164 -0
- package/dist/src/oracle/oscProgress.js +66 -0
- package/dist/src/oracle/promptAssembly.js +13 -0
- package/dist/src/oracle/request.js +49 -0
- package/dist/src/oracle/run.js +492 -0
- package/dist/src/oracle/runUtils.js +27 -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/remote/client.js +128 -0
- package/dist/src/remote/server.js +294 -0
- package/dist/src/remote/types.js +1 -0
- package/dist/src/sessionManager.js +462 -0
- package/dist/src/sessionStore.js +56 -0
- package/dist/src/version.js +39 -0
- package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
- package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/Info.plist +20 -0
- package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
- package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/Resources/OracleIcon.icns +0 -0
- package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/_CodeSignature/CodeResources +128 -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/dist/vendor/oracle-notifier/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
- package/dist/vendor/oracle-notifier/oracle-notifier/OracleNotifier.app/Contents/Info.plist +20 -0
- package/dist/vendor/oracle-notifier/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
- package/dist/vendor/oracle-notifier/oracle-notifier/OracleNotifier.app/Contents/Resources/OracleIcon.icns +0 -0
- package/dist/vendor/oracle-notifier/oracle-notifier/OracleNotifier.app/Contents/_CodeSignature/CodeResources +128 -0
- package/dist/vendor/oracle-notifier/oracle-notifier/OracleNotifier.swift +45 -0
- package/dist/vendor/oracle-notifier/oracle-notifier/README.md +24 -0
- package/dist/vendor/oracle-notifier/oracle-notifier/build-notifier.sh +93 -0
- package/package.json +102 -0
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/Info.plist +20 -0
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/Resources/OracleIcon.icns +0 -0
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/_CodeSignature/CodeResources +128 -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,205 @@
|
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import fs from 'node:fs/promises';
|
|
4
|
+
import { createWriteStream } from 'node:fs';
|
|
5
|
+
const ORACLE_HOME = process.env.ORACLE_HOME_DIR ?? path.join(os.homedir(), '.oracle');
|
|
6
|
+
const SESSIONS_DIR = path.join(ORACLE_HOME, 'sessions');
|
|
7
|
+
const MAX_STATUS_LIMIT = 1000;
|
|
8
|
+
const DEFAULT_SLUG = 'session';
|
|
9
|
+
const MAX_SLUG_WORDS = 5;
|
|
10
|
+
const MIN_CUSTOM_SLUG_WORDS = 3;
|
|
11
|
+
async function ensureDir(dirPath) {
|
|
12
|
+
await fs.mkdir(dirPath, { recursive: true });
|
|
13
|
+
}
|
|
14
|
+
export async function ensureSessionStorage() {
|
|
15
|
+
await ensureDir(SESSIONS_DIR);
|
|
16
|
+
}
|
|
17
|
+
function slugify(text, maxWords = MAX_SLUG_WORDS) {
|
|
18
|
+
const normalized = text?.toLowerCase() ?? '';
|
|
19
|
+
const words = normalized.match(/[a-z0-9]+/g) ?? [];
|
|
20
|
+
const trimmed = words.slice(0, maxWords);
|
|
21
|
+
return trimmed.length > 0 ? trimmed.join('-') : DEFAULT_SLUG;
|
|
22
|
+
}
|
|
23
|
+
function countSlugWords(slug) {
|
|
24
|
+
return slug.split('-').filter(Boolean).length;
|
|
25
|
+
}
|
|
26
|
+
function normalizeCustomSlug(candidate) {
|
|
27
|
+
const slug = slugify(candidate, MAX_SLUG_WORDS);
|
|
28
|
+
const wordCount = countSlugWords(slug);
|
|
29
|
+
if (wordCount < MIN_CUSTOM_SLUG_WORDS || wordCount > MAX_SLUG_WORDS) {
|
|
30
|
+
throw new Error(`Custom slug must include between ${MIN_CUSTOM_SLUG_WORDS} and ${MAX_SLUG_WORDS} words.`);
|
|
31
|
+
}
|
|
32
|
+
return slug;
|
|
33
|
+
}
|
|
34
|
+
export function createSessionId(prompt, customSlug) {
|
|
35
|
+
if (customSlug) {
|
|
36
|
+
return normalizeCustomSlug(customSlug);
|
|
37
|
+
}
|
|
38
|
+
return slugify(prompt);
|
|
39
|
+
}
|
|
40
|
+
function sessionDir(id) {
|
|
41
|
+
return path.join(SESSIONS_DIR, id);
|
|
42
|
+
}
|
|
43
|
+
function metaPath(id) {
|
|
44
|
+
return path.join(sessionDir(id), 'session.json');
|
|
45
|
+
}
|
|
46
|
+
function logPath(id) {
|
|
47
|
+
return path.join(sessionDir(id), 'output.log');
|
|
48
|
+
}
|
|
49
|
+
function requestPath(id) {
|
|
50
|
+
return path.join(sessionDir(id), 'request.json');
|
|
51
|
+
}
|
|
52
|
+
async function fileExists(targetPath) {
|
|
53
|
+
try {
|
|
54
|
+
await fs.access(targetPath);
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
async function ensureUniqueSessionId(baseSlug) {
|
|
62
|
+
let candidate = baseSlug;
|
|
63
|
+
let suffix = 2;
|
|
64
|
+
while (await fileExists(sessionDir(candidate))) {
|
|
65
|
+
candidate = `${baseSlug}-${suffix}`;
|
|
66
|
+
suffix += 1;
|
|
67
|
+
}
|
|
68
|
+
return candidate;
|
|
69
|
+
}
|
|
70
|
+
export async function initializeSession(options, cwd) {
|
|
71
|
+
await ensureSessionStorage();
|
|
72
|
+
const baseSlug = createSessionId(options.prompt || DEFAULT_SLUG, options.slug);
|
|
73
|
+
const sessionId = await ensureUniqueSessionId(baseSlug);
|
|
74
|
+
const dir = sessionDir(sessionId);
|
|
75
|
+
await ensureDir(dir);
|
|
76
|
+
const mode = options.mode ?? 'api';
|
|
77
|
+
const browserConfig = options.browserConfig;
|
|
78
|
+
const metadata = {
|
|
79
|
+
id: sessionId,
|
|
80
|
+
createdAt: new Date().toISOString(),
|
|
81
|
+
status: 'pending',
|
|
82
|
+
promptPreview: (options.prompt || '').slice(0, 160),
|
|
83
|
+
model: options.model,
|
|
84
|
+
cwd,
|
|
85
|
+
mode,
|
|
86
|
+
browser: browserConfig ? { config: browserConfig } : undefined,
|
|
87
|
+
options: {
|
|
88
|
+
prompt: options.prompt,
|
|
89
|
+
file: options.file ?? [],
|
|
90
|
+
model: options.model,
|
|
91
|
+
maxInput: options.maxInput,
|
|
92
|
+
system: options.system,
|
|
93
|
+
maxOutput: options.maxOutput,
|
|
94
|
+
silent: options.silent,
|
|
95
|
+
filesReport: options.filesReport,
|
|
96
|
+
slug: sessionId,
|
|
97
|
+
mode,
|
|
98
|
+
browserConfig,
|
|
99
|
+
verbose: options.verbose,
|
|
100
|
+
heartbeatIntervalMs: options.heartbeatIntervalMs,
|
|
101
|
+
browserInlineFiles: options.browserInlineFiles,
|
|
102
|
+
background: options.background,
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
await fs.writeFile(metaPath(sessionId), JSON.stringify(metadata, null, 2), 'utf8');
|
|
106
|
+
await fs.writeFile(requestPath(sessionId), JSON.stringify(metadata.options, null, 2), 'utf8');
|
|
107
|
+
await fs.writeFile(logPath(sessionId), '', 'utf8');
|
|
108
|
+
return metadata;
|
|
109
|
+
}
|
|
110
|
+
export async function readSessionMetadata(sessionId) {
|
|
111
|
+
try {
|
|
112
|
+
const raw = await fs.readFile(metaPath(sessionId), 'utf8');
|
|
113
|
+
return JSON.parse(raw);
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
export async function updateSessionMetadata(sessionId, updates) {
|
|
120
|
+
const existing = (await readSessionMetadata(sessionId)) ?? { id: sessionId };
|
|
121
|
+
const next = { ...existing, ...updates };
|
|
122
|
+
await fs.writeFile(metaPath(sessionId), JSON.stringify(next, null, 2), 'utf8');
|
|
123
|
+
return next;
|
|
124
|
+
}
|
|
125
|
+
export function createSessionLogWriter(sessionId) {
|
|
126
|
+
const stream = createWriteStream(logPath(sessionId), { flags: 'a' });
|
|
127
|
+
const logLine = (line = '') => {
|
|
128
|
+
stream.write(`${line}\n`);
|
|
129
|
+
};
|
|
130
|
+
const writeChunk = (chunk) => {
|
|
131
|
+
stream.write(chunk);
|
|
132
|
+
return true;
|
|
133
|
+
};
|
|
134
|
+
return { stream, logLine, writeChunk, logPath: logPath(sessionId) };
|
|
135
|
+
}
|
|
136
|
+
export async function listSessionsMetadata() {
|
|
137
|
+
await ensureSessionStorage();
|
|
138
|
+
const entries = await fs.readdir(SESSIONS_DIR).catch(() => []);
|
|
139
|
+
const metas = [];
|
|
140
|
+
for (const entry of entries) {
|
|
141
|
+
const meta = await readSessionMetadata(entry);
|
|
142
|
+
if (meta) {
|
|
143
|
+
metas.push(meta);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return metas.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
|
|
147
|
+
}
|
|
148
|
+
export function filterSessionsByRange(metas, { hours = 24, includeAll = false, limit = 100 }) {
|
|
149
|
+
const maxLimit = Math.min(limit, MAX_STATUS_LIMIT);
|
|
150
|
+
let filtered = metas;
|
|
151
|
+
if (!includeAll) {
|
|
152
|
+
const cutoff = Date.now() - hours * 60 * 60 * 1000;
|
|
153
|
+
filtered = metas.filter((meta) => new Date(meta.createdAt).getTime() >= cutoff);
|
|
154
|
+
}
|
|
155
|
+
const limited = filtered.slice(0, maxLimit);
|
|
156
|
+
const truncated = filtered.length > maxLimit;
|
|
157
|
+
return { entries: limited, truncated, total: filtered.length };
|
|
158
|
+
}
|
|
159
|
+
export async function readSessionLog(sessionId) {
|
|
160
|
+
try {
|
|
161
|
+
return await fs.readFile(logPath(sessionId), 'utf8');
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
return '';
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
export async function deleteSessionsOlderThan({ hours = 24, includeAll = false, } = {}) {
|
|
168
|
+
await ensureSessionStorage();
|
|
169
|
+
const entries = await fs.readdir(SESSIONS_DIR).catch(() => []);
|
|
170
|
+
if (!entries.length) {
|
|
171
|
+
return { deleted: 0, remaining: 0 };
|
|
172
|
+
}
|
|
173
|
+
const cutoff = includeAll ? Number.NEGATIVE_INFINITY : Date.now() - hours * 60 * 60 * 1000;
|
|
174
|
+
let deleted = 0;
|
|
175
|
+
for (const entry of entries) {
|
|
176
|
+
const dir = sessionDir(entry);
|
|
177
|
+
let createdMs;
|
|
178
|
+
const meta = await readSessionMetadata(entry);
|
|
179
|
+
if (meta?.createdAt) {
|
|
180
|
+
const parsed = Date.parse(meta.createdAt);
|
|
181
|
+
if (!Number.isNaN(parsed)) {
|
|
182
|
+
createdMs = parsed;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
if (createdMs == null) {
|
|
186
|
+
try {
|
|
187
|
+
const stats = await fs.stat(dir);
|
|
188
|
+
createdMs = stats.birthtimeMs || stats.mtimeMs;
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (includeAll || (createdMs != null && createdMs < cutoff)) {
|
|
195
|
+
await fs.rm(dir, { recursive: true, force: true });
|
|
196
|
+
deleted += 1;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
const remaining = Math.max(entries.length - deleted, 0);
|
|
200
|
+
return { deleted, remaining };
|
|
201
|
+
}
|
|
202
|
+
export async function wait(ms) {
|
|
203
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
204
|
+
}
|
|
205
|
+
export { ORACLE_HOME, SESSIONS_DIR, MAX_STATUS_LIMIT };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
let cachedVersion = null;
|
|
5
|
+
export function getCliVersion() {
|
|
6
|
+
if (cachedVersion) {
|
|
7
|
+
return cachedVersion;
|
|
8
|
+
}
|
|
9
|
+
cachedVersion = readVersionFromPackage();
|
|
10
|
+
return cachedVersion;
|
|
11
|
+
}
|
|
12
|
+
function readVersionFromPackage() {
|
|
13
|
+
const modulePath = fileURLToPath(import.meta.url);
|
|
14
|
+
let currentDir = path.dirname(modulePath);
|
|
15
|
+
const filesystemRoot = path.parse(currentDir).root;
|
|
16
|
+
// biome-ignore lint/nursery/noUnnecessaryConditions: deliberate sentinel loop to walk up directories
|
|
17
|
+
while (true) {
|
|
18
|
+
const candidate = path.join(currentDir, 'package.json');
|
|
19
|
+
try {
|
|
20
|
+
const raw = readFileSync(candidate, 'utf8');
|
|
21
|
+
const parsed = JSON.parse(raw);
|
|
22
|
+
const version = typeof parsed.version === 'string' && parsed.version.trim().length > 0
|
|
23
|
+
? parsed.version.trim()
|
|
24
|
+
: '0.0.0';
|
|
25
|
+
return version;
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
const code = error instanceof Error && 'code' in error ? error.code : undefined;
|
|
29
|
+
if (code && code !== 'ENOENT') {
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (currentDir === filesystemRoot) {
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
currentDir = path.dirname(currentDir);
|
|
37
|
+
}
|
|
38
|
+
return '0.0.0';
|
|
39
|
+
}
|