@prometheus-ai/utils 0.5.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/dist/types/abortable.d.ts +27 -0
- package/dist/types/async.d.ts +6 -0
- package/dist/types/cli.d.ts +117 -0
- package/dist/types/color.d.ts +102 -0
- package/dist/types/dirs.d.ts +171 -0
- package/dist/types/env.d.ts +55 -0
- package/dist/types/fetch-retry.d.ts +80 -0
- package/dist/types/format.d.ts +37 -0
- package/dist/types/frontmatter.d.ts +25 -0
- package/dist/types/fs-error.d.ts +31 -0
- package/dist/types/glob.d.ts +28 -0
- package/dist/types/hook-fetch.d.ts +16 -0
- package/dist/types/index.d.ts +29 -0
- package/dist/types/json.d.ts +4 -0
- package/dist/types/logger.d.ts +66 -0
- package/dist/types/mermaid-ascii.d.ts +11 -0
- package/dist/types/mime.d.ts +29 -0
- package/dist/types/peek-file.d.ts +29 -0
- package/dist/types/postmortem.d.ts +29 -0
- package/dist/types/procmgr.d.ts +25 -0
- package/dist/types/prompt.d.ts +18 -0
- package/dist/types/ptree.d.ts +108 -0
- package/dist/types/ring.d.ts +93 -0
- package/dist/types/sanitize-text.d.ts +14 -0
- package/dist/types/snowflake.d.ts +25 -0
- package/dist/types/stream.d.ts +68 -0
- package/dist/types/tab-spacing.d.ts +9 -0
- package/dist/types/temp.d.ts +14 -0
- package/dist/types/type-guards.d.ts +3 -0
- package/dist/types/which.d.ts +37 -0
- package/package.json +61 -0
- package/src/abortable.ts +73 -0
- package/src/async.ts +50 -0
- package/src/cli.ts +432 -0
- package/src/color.ts +302 -0
- package/src/dirs.ts +584 -0
- package/src/env.ts +172 -0
- package/src/fetch-retry.ts +325 -0
- package/src/format.ts +113 -0
- package/src/frontmatter.ts +128 -0
- package/src/fs-error.ts +56 -0
- package/src/glob.ts +189 -0
- package/src/hook-fetch.ts +30 -0
- package/src/index.ts +49 -0
- package/src/json.ts +10 -0
- package/src/logger.ts +417 -0
- package/src/mermaid-ascii.ts +31 -0
- package/src/mime.ts +159 -0
- package/src/peek-file.ts +188 -0
- package/src/postmortem.ts +196 -0
- package/src/procmgr.ts +195 -0
- package/src/prompt.ts +471 -0
- package/src/ptree.ts +390 -0
- package/src/ring.ts +169 -0
- package/src/sanitize-text.ts +38 -0
- package/src/snowflake.ts +136 -0
- package/src/stream.ts +403 -0
- package/src/tab-spacing.ts +342 -0
- package/src/temp.ts +77 -0
- package/src/type-guards.ts +11 -0
- package/src/which.ts +232 -0
package/src/dirs.ts
ADDED
|
@@ -0,0 +1,584 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized path helpers for Prometheus config directories.
|
|
3
|
+
*
|
|
4
|
+
* Uses PROMETHEUS_CONFIG_DIR (default ".prometheus") for the config root and
|
|
5
|
+
* PROMETHEUS_CODING_AGENT_DIR to override the agent directory.
|
|
6
|
+
*
|
|
7
|
+
* On Linux, if XDG_DATA_HOME / XDG_STATE_HOME / XDG_CACHE_HOME environment
|
|
8
|
+
* variables are set, paths are redirected to XDG-compliant locations under the
|
|
9
|
+
* app name. This requires running config migration first to
|
|
10
|
+
* move data to the new locations. No filesystem existence checks are performed
|
|
11
|
+
* — if the env var is set, prometheus trusts that the migration has been done.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import * as fs from "node:fs";
|
|
15
|
+
import * as os from "node:os";
|
|
16
|
+
import * as path from "node:path";
|
|
17
|
+
import { engines, version } from "../package.json" with { type: "json" };
|
|
18
|
+
|
|
19
|
+
/** CLI app name (e.g. "prometheus") */
|
|
20
|
+
export const APP_NAME: string = "prometheus";
|
|
21
|
+
|
|
22
|
+
/** Human-readable app name for UI and protocol titles. */
|
|
23
|
+
export const APP_DISPLAY_NAME: string = "Prometheus";
|
|
24
|
+
|
|
25
|
+
/** Config directory name (e.g. ".prometheus") */
|
|
26
|
+
export const CONFIG_DIR_NAME: string = ".prometheus";
|
|
27
|
+
|
|
28
|
+
/** Version (e.g. "1.0.0") */
|
|
29
|
+
export const VERSION: string = version;
|
|
30
|
+
|
|
31
|
+
/** Minimum Bun version */
|
|
32
|
+
export const MIN_BUN_VERSION: string = engines.bun.replace(/[^0-9.]/g, "");
|
|
33
|
+
|
|
34
|
+
// =============================================================================
|
|
35
|
+
// Project directory
|
|
36
|
+
// =============================================================================
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* On macOS, strip /private prefix only when both paths resolve to the same location.
|
|
40
|
+
* This preserves aliases like /private/tmp -> /tmp without rewriting unrelated paths.
|
|
41
|
+
*/
|
|
42
|
+
function standardizeMacOSPath(p: string): string {
|
|
43
|
+
if (process.platform !== "darwin" || !p.startsWith("/private/")) return p;
|
|
44
|
+
const stripped = p.slice("/private".length);
|
|
45
|
+
try {
|
|
46
|
+
if (fs.realpathSync(p) === fs.realpathSync(stripped)) {
|
|
47
|
+
return stripped;
|
|
48
|
+
}
|
|
49
|
+
} catch {}
|
|
50
|
+
return p;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function resolveEquivalentPath(inputPath: string): string {
|
|
54
|
+
const resolvedPath = path.resolve(inputPath);
|
|
55
|
+
try {
|
|
56
|
+
return fs.realpathSync(resolvedPath);
|
|
57
|
+
} catch {
|
|
58
|
+
return resolvedPath;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function normalizePathForComparison(inputPath: string): string {
|
|
63
|
+
const resolvedPath = resolveEquivalentPath(inputPath);
|
|
64
|
+
return process.platform === "win32" ? resolvedPath.toLowerCase() : resolvedPath;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function pathIsWithin(root: string, candidate: string): boolean {
|
|
68
|
+
const normalizedRoot = normalizePathForComparison(root);
|
|
69
|
+
const normalizedCandidate = normalizePathForComparison(candidate);
|
|
70
|
+
const relative = path.relative(normalizedRoot, normalizedCandidate);
|
|
71
|
+
return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function relativePathWithinRoot(root: string, candidate: string): string | null {
|
|
75
|
+
if (!pathIsWithin(root, candidate)) return null;
|
|
76
|
+
const normalizedRoot = normalizePathForComparison(root);
|
|
77
|
+
const normalizedCandidate = normalizePathForComparison(candidate);
|
|
78
|
+
const relative = path.relative(normalizedRoot, normalizedCandidate);
|
|
79
|
+
return relative || null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
let projectDir = standardizeMacOSPath(process.cwd());
|
|
83
|
+
|
|
84
|
+
/** Get the project directory. */
|
|
85
|
+
export function getProjectDir(): string {
|
|
86
|
+
return projectDir;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Set the project directory. */
|
|
90
|
+
export function setProjectDir(dir: string): void {
|
|
91
|
+
projectDir = standardizeMacOSPath(path.resolve(dir));
|
|
92
|
+
process.chdir(projectDir);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Get the config directory name relative to home (e.g. ".prometheus" or PROMETHEUS_CONFIG_DIR override). */
|
|
96
|
+
export function getConfigDirName(): string {
|
|
97
|
+
return process.env.PROMETHEUS_CONFIG_DIR || CONFIG_DIR_NAME;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Get the config agent directory name relative to home (e.g. ".prometheus/agent" or PROMETHEUS_CONFIG_DIR + "/agent"). */
|
|
101
|
+
export function getConfigAgentDirName(): string {
|
|
102
|
+
return `${getConfigDirName()}/agent`;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// =============================================================================
|
|
106
|
+
// DirResolver — cached, XDG-aware path resolution
|
|
107
|
+
// =============================================================================
|
|
108
|
+
|
|
109
|
+
type XdgCategory = "data" | "state" | "cache";
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Resolves and caches all prometheus directory paths. On Linux, when XDG environment
|
|
113
|
+
* variables are set, paths are redirected under $XDG_*_HOME/prometheus/. A new
|
|
114
|
+
* instance is created whenever the agent directory changes, which naturally
|
|
115
|
+
* invalidates all cached paths.
|
|
116
|
+
*/
|
|
117
|
+
class DirResolver {
|
|
118
|
+
readonly configRoot: string;
|
|
119
|
+
readonly agentDir: string;
|
|
120
|
+
|
|
121
|
+
// Per-category base dirs. Without XDG, all three equal configRoot / agentDir.
|
|
122
|
+
// With XDG on Linux, they point to $XDG_*_HOME/prometheus/.
|
|
123
|
+
readonly #rootDirs: Record<XdgCategory, string>;
|
|
124
|
+
readonly #agentDirs: Record<XdgCategory, string>;
|
|
125
|
+
|
|
126
|
+
readonly #rootCache = new Map<string, string>();
|
|
127
|
+
readonly #agentCache = new Map<string, string>();
|
|
128
|
+
|
|
129
|
+
constructor(agentDirOverride?: string) {
|
|
130
|
+
this.configRoot = path.join(os.homedir(), getConfigDirName());
|
|
131
|
+
|
|
132
|
+
const defaultAgent = path.join(this.configRoot, "agent");
|
|
133
|
+
this.agentDir = agentDirOverride ? path.resolve(agentDirOverride) : defaultAgent;
|
|
134
|
+
const isDefault = this.agentDir === defaultAgent;
|
|
135
|
+
|
|
136
|
+
// XDG is a Linux convention. On other platforms, or for non-default
|
|
137
|
+
// profiles, all categories resolve to the legacy paths.
|
|
138
|
+
let xdgData: string | undefined;
|
|
139
|
+
let xdgState: string | undefined;
|
|
140
|
+
let xdgCache: string | undefined;
|
|
141
|
+
if ((process.platform === "linux" || process.platform === "darwin") && isDefault) {
|
|
142
|
+
const resolveIf = (envVar: string) => {
|
|
143
|
+
const value = process.env[envVar];
|
|
144
|
+
if (value) {
|
|
145
|
+
try {
|
|
146
|
+
const joined = path.join(value, APP_NAME);
|
|
147
|
+
if (fs.existsSync(joined)) {
|
|
148
|
+
return joined;
|
|
149
|
+
}
|
|
150
|
+
} catch {}
|
|
151
|
+
}
|
|
152
|
+
return undefined;
|
|
153
|
+
};
|
|
154
|
+
xdgData = resolveIf("XDG_DATA_HOME");
|
|
155
|
+
xdgState = resolveIf("XDG_STATE_HOME");
|
|
156
|
+
xdgCache = resolveIf("XDG_CACHE_HOME");
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
this.#rootDirs = {
|
|
160
|
+
data: xdgData ?? this.configRoot,
|
|
161
|
+
state: xdgState ?? this.configRoot,
|
|
162
|
+
cache: xdgCache ?? this.configRoot,
|
|
163
|
+
};
|
|
164
|
+
// XDG flattens the agent/ prefix: ~/.prometheus/agent/sessions → $XDG_DATA_HOME/prometheus/sessions
|
|
165
|
+
this.#agentDirs = {
|
|
166
|
+
data: xdgData ?? this.agentDir,
|
|
167
|
+
state: xdgState ?? this.agentDir,
|
|
168
|
+
cache: xdgCache ?? this.agentDir,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** Config-root subdirectory, with optional XDG override. */
|
|
173
|
+
rootSubdir(subdir: string, xdg?: XdgCategory): string {
|
|
174
|
+
const cached = this.#rootCache.get(subdir);
|
|
175
|
+
if (cached) return cached;
|
|
176
|
+
const base = xdg ? this.#rootDirs[xdg] : this.configRoot;
|
|
177
|
+
const result = path.join(base, subdir);
|
|
178
|
+
this.#rootCache.set(subdir, result);
|
|
179
|
+
return result;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** Agent subdirectory, with optional XDG override. */
|
|
183
|
+
agentSubdir(userAgentDir: string | undefined, subdir: string, xdg?: XdgCategory): string {
|
|
184
|
+
if (!userAgentDir || userAgentDir === this.agentDir) {
|
|
185
|
+
const cached = this.#agentCache.get(subdir);
|
|
186
|
+
if (cached) return cached;
|
|
187
|
+
const base = xdg ? this.#agentDirs[xdg] : this.agentDir;
|
|
188
|
+
const result = path.join(base, subdir);
|
|
189
|
+
this.#agentCache.set(subdir, result);
|
|
190
|
+
return result;
|
|
191
|
+
}
|
|
192
|
+
return path.join(userAgentDir, subdir);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
let dirs = new DirResolver(process.env.PROMETHEUS_CODING_AGENT_DIR);
|
|
197
|
+
|
|
198
|
+
// Anchor home for the resolver. Captured at module load to stay stable across
|
|
199
|
+
// test mocks of `os.homedir()`. `getPluginsDir(home)` compares against this so
|
|
200
|
+
// production callers (`home === RESOLVER_HOME`) hit the XDG-aware resolver while
|
|
201
|
+
// tests passing a temp HOME short-circuit to a deterministic path.
|
|
202
|
+
const RESOLVER_HOME = os.homedir();
|
|
203
|
+
|
|
204
|
+
// =============================================================================
|
|
205
|
+
// Root directories
|
|
206
|
+
// =============================================================================
|
|
207
|
+
|
|
208
|
+
/** Get the config root directory (~/.prometheus). */
|
|
209
|
+
export function getConfigRootDir(): string {
|
|
210
|
+
return dirs.configRoot;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/** Set the coding agent directory. Creates a fresh resolver, invalidating all cached paths. */
|
|
214
|
+
export function setAgentDir(dir: string): void {
|
|
215
|
+
dirs = new DirResolver(dir);
|
|
216
|
+
process.env.PROMETHEUS_CODING_AGENT_DIR = dir;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/** Get the agent config directory (~/.prometheus/agent). */
|
|
220
|
+
export function getAgentDir(): string {
|
|
221
|
+
return dirs.agentDir;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** Get the project-local config directory (.prometheus). */
|
|
225
|
+
export function getProjectAgentDir(cwd: string = getProjectDir()): string {
|
|
226
|
+
return path.join(cwd, CONFIG_DIR_NAME);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// =============================================================================
|
|
230
|
+
// Config-root subdirectories (~/.prometheus/*)
|
|
231
|
+
// =============================================================================
|
|
232
|
+
|
|
233
|
+
/** Get the reports directory (~/.prometheus/reports). */
|
|
234
|
+
export function getReportsDir(): string {
|
|
235
|
+
return dirs.rootSubdir("reports", "state");
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/** Get the logs directory (~/.prometheus/logs). */
|
|
239
|
+
export function getLogsDir(): string {
|
|
240
|
+
return dirs.rootSubdir("logs", "state");
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/** Get the path to a dated log file (~/.prometheus/logs/prometheus.YYYY-MM-DD.log). */
|
|
244
|
+
export function getLogPath(date = new Date()): string {
|
|
245
|
+
return path.join(getLogsDir(), `${APP_NAME}.${date.toISOString().slice(0, 10)}.log`);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Get the plugins directory (~/.prometheus/plugins or its XDG equivalent).
|
|
250
|
+
*
|
|
251
|
+
* No-arg form (production callers) goes through the XDG-aware DirResolver so
|
|
252
|
+
* reads and writes always agree. The optional `home` parameter is for test
|
|
253
|
+
* isolation: when it differs from `os.homedir()` it short-circuits the resolver
|
|
254
|
+
* and returns `<home>/<configDir>/plugins` so tests with a temp HOME get a
|
|
255
|
+
* deterministic path. Passing `os.homedir()` explicitly is identical to the
|
|
256
|
+
* no-arg form — XDG semantics are preserved.
|
|
257
|
+
*/
|
|
258
|
+
export function getPluginsDir(home?: string): string {
|
|
259
|
+
if (home !== undefined && home !== RESOLVER_HOME) {
|
|
260
|
+
return path.join(home, getConfigDirName(), "plugins");
|
|
261
|
+
}
|
|
262
|
+
return dirs.rootSubdir("plugins", "data");
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/** Where npm installs packages (~/.prometheus/plugins/node_modules). */
|
|
266
|
+
export function getPluginsNodeModules(home?: string): string {
|
|
267
|
+
return path.join(getPluginsDir(home), "node_modules");
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/** Plugin manifest (~/.prometheus/plugins/package.json). */
|
|
271
|
+
export function getPluginsPackageJson(home?: string): string {
|
|
272
|
+
return path.join(getPluginsDir(home), "package.json");
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/** Plugin lock file (~/.prometheus/plugins/prometheus-plugins.lock.json). */
|
|
276
|
+
export function getPluginsLockfile(home?: string): string {
|
|
277
|
+
return path.join(getPluginsDir(home), "prometheus-plugins.lock.json");
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/** Get the remote mount directory (~/.prometheus/remote). */
|
|
281
|
+
export function getRemoteDir(): string {
|
|
282
|
+
return dirs.rootSubdir("remote", "data");
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/** Get the agent-managed worktrees directory (~/.prometheus/wt). */
|
|
286
|
+
export function getWorktreesDir(): string {
|
|
287
|
+
return dirs.rootSubdir("wt", "data");
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/** Get the SSH control socket directory (~/.prometheus/ssh-control). */
|
|
291
|
+
export function getSshControlDir(): string {
|
|
292
|
+
return dirs.rootSubdir("ssh-control", "state");
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/** Get the remote host info directory (~/.prometheus/remote-host). */
|
|
296
|
+
export function getRemoteHostDir(): string {
|
|
297
|
+
return dirs.rootSubdir("remote-host", "data");
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/** Get the managed Python venv directory (~/.prometheus/python-env). */
|
|
301
|
+
export function getPythonEnvDir(): string {
|
|
302
|
+
return dirs.rootSubdir("python-env", "data");
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/** Get the shared Python gateway state directory (~/.prometheus/agent/python-gateway; XDG default: $XDG_STATE_HOME/prometheus/python-gateway). */
|
|
306
|
+
export function getPythonGatewayDir(): string {
|
|
307
|
+
return dirs.agentSubdir(undefined, "python-gateway", "state");
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/** Get the puppeteer sandbox directory (~/.prometheus/puppeteer). */
|
|
311
|
+
export function getPuppeteerDir(): string {
|
|
312
|
+
return dirs.rootSubdir("puppeteer", "cache");
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Stable 7-character hex digest of an absolute filesystem path.
|
|
317
|
+
*
|
|
318
|
+
* Used to pack the project identity into a single short fs-safe segment
|
|
319
|
+
* (e.g. PR-checkout and task-isolation worktree dirs under `~/.prometheus/wt/`).
|
|
320
|
+
* Bun.hash is non-cryptographic — collision space is ~2^28, which is fine
|
|
321
|
+
* for naming a handful of repos on a single machine. Same input on the
|
|
322
|
+
* same Bun runtime yields the same output.
|
|
323
|
+
*/
|
|
324
|
+
export function hashPath(absPath: string): string {
|
|
325
|
+
return Bun.hash(path.resolve(absPath)).toString(16).padStart(16, "0").slice(-7);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/** Get the path to a single worktree directory (~/.prometheus/wt/<segment>). */
|
|
329
|
+
export function getWorktreeDir(segment: string): string {
|
|
330
|
+
return path.join(getWorktreesDir(), segment);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/** Get the GPU cache path (~/.prometheus/gpu_cache.json). */
|
|
334
|
+
export function getGpuCachePath(): string {
|
|
335
|
+
return dirs.rootSubdir("gpu_cache.json", "cache");
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Get the GitHub view cache database path (~/.prometheus/cache/github-cache.db).
|
|
340
|
+
* Honors the `PROMETHEUS_GITHUB_CACHE_DB` env var when set so tests can isolate the
|
|
341
|
+
* cache file without touching the rest of the config root.
|
|
342
|
+
*/
|
|
343
|
+
export function getGithubCacheDbPath(): string {
|
|
344
|
+
const override = process.env.PROMETHEUS_GITHUB_CACHE_DB;
|
|
345
|
+
if (override) return override;
|
|
346
|
+
return dirs.rootSubdir(path.join("cache", "github-cache.db"), "cache");
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Get the encrypted auth-broker snapshot cache path (~/.prometheus/cache/auth-broker-snapshot.enc).
|
|
351
|
+
* Honors the `PROMETHEUS_AUTH_BROKER_SNAPSHOT_CACHE` env var when set so tests and
|
|
352
|
+
* operators can isolate or relocate the cache file.
|
|
353
|
+
*/
|
|
354
|
+
export function getAuthBrokerSnapshotCachePath(): string {
|
|
355
|
+
const override = process.env.PROMETHEUS_AUTH_BROKER_SNAPSHOT_CACHE;
|
|
356
|
+
if (override) return override;
|
|
357
|
+
return dirs.rootSubdir(path.join("cache", "auth-broker-snapshot.enc"), "cache");
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/** Get the local FastEmbed model cache directory (~/.prometheus/cache/fastembed). */
|
|
361
|
+
export function getFastembedCacheDir(): string {
|
|
362
|
+
return dirs.rootSubdir(path.join("cache", "fastembed"), "cache");
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/** Get the natives directory (~/.prometheus/natives). */
|
|
366
|
+
export function getNativesDir(): string {
|
|
367
|
+
return dirs.rootSubdir("natives", "cache");
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/** Get the stats database path (~/.prometheus/stats.db). */
|
|
371
|
+
export function getStatsDbPath(): string {
|
|
372
|
+
return dirs.rootSubdir("stats.db", "data");
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/** Get the autoresearch state directory (~/.prometheus/autoresearch). */
|
|
376
|
+
export function getAutoresearchDir(): string {
|
|
377
|
+
return dirs.rootSubdir("autoresearch", "state");
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/** Get the per-project autoresearch state directory (~/.prometheus/autoresearch/<encoded-project>). */
|
|
381
|
+
export function getAutoresearchProjectDir(encodedProject: string): string {
|
|
382
|
+
return path.join(getAutoresearchDir(), encodedProject);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/** Get the per-project autoresearch SQLite database path (~/.prometheus/autoresearch/<encoded-project>.db). */
|
|
386
|
+
export function getAutoresearchDbPath(encodedProject: string): string {
|
|
387
|
+
return path.join(getAutoresearchDir(), `${encodedProject}.db`);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/** Get the per-run artifact directory (~/.prometheus/autoresearch/<encoded-project>/runs/<runId>). */
|
|
391
|
+
export function getAutoresearchRunDir(encodedProject: string, runId: number): string {
|
|
392
|
+
return path.join(getAutoresearchProjectDir(encodedProject), "runs", String(runId).padStart(4, "0"));
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// =============================================================================
|
|
396
|
+
// Agent subdirectories (~/.prometheus/agent/*)
|
|
397
|
+
// =============================================================================
|
|
398
|
+
|
|
399
|
+
/** Get the path to agent.db (SQLite database for settings and auth storage). */
|
|
400
|
+
export function getAgentDbPath(agentDir?: string): string {
|
|
401
|
+
return dirs.agentSubdir(agentDir, "agent.db", "data");
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/** Get the path to history.db (SQLite database for session history). */
|
|
405
|
+
export function getHistoryDbPath(agentDir?: string): string {
|
|
406
|
+
return dirs.agentSubdir(agentDir, "history.db", "data");
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/** Get the path to models.db (model cache database). */
|
|
410
|
+
export function getModelDbPath(agentDir?: string): string {
|
|
411
|
+
return dirs.agentSubdir(agentDir, "models.db", "data");
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/** Get the tiny title model cache directory (~/.prometheus/agent/cache/tiny-models). */
|
|
415
|
+
export function getTinyModelsCacheDir(agentDir?: string): string {
|
|
416
|
+
return dirs.agentSubdir(agentDir, path.join("cache", "tiny-models"), "cache");
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/** Get the sessions directory (~/.prometheus/agent/sessions). */
|
|
420
|
+
export function getSessionsDir(agentDir?: string): string {
|
|
421
|
+
return dirs.agentSubdir(agentDir, "sessions", "data");
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/** Get the content-addressed blob store directory (~/.prometheus/agent/blobs). */
|
|
425
|
+
export function getBlobsDir(agentDir?: string): string {
|
|
426
|
+
return dirs.agentSubdir(agentDir, "blobs", "data");
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/** Get the custom themes directory (~/.prometheus/agent/themes). */
|
|
430
|
+
export function getCustomThemesDir(agentDir?: string): string {
|
|
431
|
+
return dirs.agentSubdir(agentDir, "themes");
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/** Get the tools directory (~/.prometheus/agent/tools). */
|
|
435
|
+
export function getToolsDir(agentDir?: string): string {
|
|
436
|
+
return dirs.agentSubdir(agentDir, "tools");
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/** Get the slash commands directory (~/.prometheus/agent/commands). */
|
|
440
|
+
export function getCommandsDir(agentDir?: string): string {
|
|
441
|
+
return dirs.agentSubdir(agentDir, "commands");
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/** Get the prompts directory (~/.prometheus/agent/prompts). */
|
|
445
|
+
export function getPromptsDir(agentDir?: string): string {
|
|
446
|
+
return dirs.agentSubdir(agentDir, "prompts");
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/** Get the user-level Python modules directory (~/.prometheus/agent/modules). */
|
|
450
|
+
export function getAgentModulesDir(agentDir?: string): string {
|
|
451
|
+
return dirs.agentSubdir(agentDir, "modules");
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/** Get the memories directory (~/.prometheus/agent/memories). */
|
|
455
|
+
export function getMemoriesDir(agentDir?: string): string {
|
|
456
|
+
return dirs.agentSubdir(agentDir, "memories", "state");
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/** Get the terminal sessions directory (~/.prometheus/agent/terminal-sessions). */
|
|
460
|
+
export function getTerminalSessionsDir(agentDir?: string): string {
|
|
461
|
+
return dirs.agentSubdir(agentDir, "terminal-sessions", "state");
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/** Get the crash log path (~/.prometheus/agent/prometheus-crash.log). */
|
|
465
|
+
export function getCrashLogPath(agentDir?: string): string {
|
|
466
|
+
return dirs.agentSubdir(agentDir, `${APP_NAME}-crash.log`, "state");
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/** Get the debug log path (~/.prometheus/agent/prometheus-debug.log). */
|
|
470
|
+
export function getDebugLogPath(agentDir?: string): string {
|
|
471
|
+
return dirs.agentSubdir(agentDir, `${APP_NAME}-debug.log`, "state");
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// =============================================================================
|
|
475
|
+
// Project subdirectories (.prometheus/*)
|
|
476
|
+
// =============================================================================
|
|
477
|
+
|
|
478
|
+
/** Get the project-level Python modules directory (.prometheus/modules). */
|
|
479
|
+
export function getProjectModulesDir(cwd: string = getProjectDir()): string {
|
|
480
|
+
return path.join(getProjectAgentDir(cwd), "modules");
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/** Get the project-level prompts directory (.prometheus/prompts). */
|
|
484
|
+
export function getProjectPromptsDir(cwd: string = getProjectDir()): string {
|
|
485
|
+
return path.join(getProjectAgentDir(cwd), "prompts");
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/** Get the project-level plugin overrides path (.prometheus/plugin-overrides.json). */
|
|
489
|
+
export function getProjectPluginOverridesPath(cwd: string = getProjectDir()): string {
|
|
490
|
+
return path.join(getProjectAgentDir(cwd), "plugin-overrides.json");
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// =============================================================================
|
|
494
|
+
// MCP config paths
|
|
495
|
+
// =============================================================================
|
|
496
|
+
|
|
497
|
+
/** Get the primary MCP config file path (first candidate). */
|
|
498
|
+
export function getMCPConfigPath(scope: "user" | "project", cwd: string = getProjectDir()): string {
|
|
499
|
+
if (scope === "user") {
|
|
500
|
+
return path.join(getAgentDir(), "mcp.json");
|
|
501
|
+
}
|
|
502
|
+
return path.join(getProjectAgentDir(cwd), "mcp.json");
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/** Get the SSH config file path. */
|
|
506
|
+
export function getSSHConfigPath(scope: "user" | "project", cwd: string = getProjectDir()): string {
|
|
507
|
+
if (scope === "user") {
|
|
508
|
+
return path.join(getAgentDir(), "ssh.json");
|
|
509
|
+
}
|
|
510
|
+
return path.join(getProjectAgentDir(cwd), "ssh.json");
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// =============================================================================
|
|
514
|
+
// Install identity
|
|
515
|
+
// =============================================================================
|
|
516
|
+
|
|
517
|
+
let cachedInstallId: string | null = null;
|
|
518
|
+
|
|
519
|
+
const INSTALL_ID_FILE = "install-id";
|
|
520
|
+
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* Persistent per-install UUID stored at `~/.prometheus/install-id`.
|
|
524
|
+
*
|
|
525
|
+
* Generated lazily on first call and persisted with `O_CREAT|O_EXCL` so
|
|
526
|
+
* concurrent first-call races don't clobber each other (loser re-reads the
|
|
527
|
+
* winner's id). Survives independently of agent state: deleting
|
|
528
|
+
* `~/.prometheus/agent/` does not regenerate it. Server-side dedup for grievance
|
|
529
|
+
* pushes (and similar telemetry) keys on this id.
|
|
530
|
+
*/
|
|
531
|
+
export function getInstallId(): string {
|
|
532
|
+
if (cachedInstallId) return cachedInstallId;
|
|
533
|
+
const filePath = path.join(getConfigRootDir(), INSTALL_ID_FILE);
|
|
534
|
+
|
|
535
|
+
let observedInvalid = false;
|
|
536
|
+
try {
|
|
537
|
+
const existing = fs.readFileSync(filePath, "utf8").trim();
|
|
538
|
+
if (UUID_RE.test(existing)) {
|
|
539
|
+
cachedInstallId = existing;
|
|
540
|
+
return existing;
|
|
541
|
+
}
|
|
542
|
+
// File present but unparseable — fall through and overwrite below.
|
|
543
|
+
observedInvalid = existing.length > 0;
|
|
544
|
+
} catch {}
|
|
545
|
+
|
|
546
|
+
const next = crypto.randomUUID();
|
|
547
|
+
try {
|
|
548
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
549
|
+
// If we already saw garbage in the file, unlink first so O_EXCL doesn't
|
|
550
|
+
// trip on it. Ignored if the unlink races against another writer.
|
|
551
|
+
if (observedInvalid) {
|
|
552
|
+
try {
|
|
553
|
+
fs.unlinkSync(filePath);
|
|
554
|
+
} catch {}
|
|
555
|
+
}
|
|
556
|
+
const fd = fs.openSync(filePath, fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL, 0o600);
|
|
557
|
+
try {
|
|
558
|
+
fs.writeSync(fd, `${next}\n`);
|
|
559
|
+
} finally {
|
|
560
|
+
fs.closeSync(fd);
|
|
561
|
+
}
|
|
562
|
+
} catch (err) {
|
|
563
|
+
// Lost the create race — re-read whatever the winner wrote.
|
|
564
|
+
if ((err as NodeJS.ErrnoException).code === "EEXIST") {
|
|
565
|
+
try {
|
|
566
|
+
const existing = fs.readFileSync(filePath, "utf8").trim();
|
|
567
|
+
if (UUID_RE.test(existing)) {
|
|
568
|
+
cachedInstallId = existing;
|
|
569
|
+
return existing;
|
|
570
|
+
}
|
|
571
|
+
} catch {}
|
|
572
|
+
}
|
|
573
|
+
// Any other failure: keep the generated id in-memory so the rest of
|
|
574
|
+
// this process has a stable value; future processes will retry.
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
cachedInstallId = next;
|
|
578
|
+
return next;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
/** Test-only: clear cached install id. Never call from production code. */
|
|
582
|
+
export function __resetInstallIdCacheForTests(): void {
|
|
583
|
+
cachedInstallId = null;
|
|
584
|
+
}
|