UNPKG

@phren/cli

Version:

Knowledge layer for AI agents - CLI, MCP server, and data layer

167 lines (166 loc) • 9.01 kB
import { homeDir } from "./home-paths.js"; export type InstallMode = "shared" | "project-local"; export type SyncMode = "managed-git" | "workspace-git"; export interface PhrenRootManifest { version: 1; installMode: InstallMode; syncMode: SyncMode; workspaceRoot?: string; primaryProject?: string; } export interface InstallContext extends PhrenRootManifest { phrenPath: string; } export declare const ROOT_MANIFEST_FILENAME = "phren.root.yaml"; export { homeDir }; export declare function homePath(...parts: string[]): string; export declare function expandHomePath(input: string): string; export declare function defaultPhrenPath(): string; export declare function rootManifestPath(phrenPath: string): string; export interface AtomicWriteOptions { /** * POSIX mode for the written file, applied to the temp file *before* the * rename. Callers writing secrets must pass this rather than chmod-ing after * the fact: a post-rename chmod leaves the file readable at the default * umask-derived mode (0644 on a stock install) for the whole write window, * which is exactly long enough for another local user to read it. */ mode?: number; } export declare function atomicWriteText(filePath: string, content: string | Uint8Array, opts?: AtomicWriteOptions): void; /** * Create (or tighten) a directory that holds per-user private data — runtime * logs, session transcripts, credential stores. * * Plain `fs.mkdirSync(dir, { recursive: true })` yields 0755 under the stock * 0022 umask, which makes every file inside world-listable and world-readable * on a shared machine. Passing `mode` to mkdirSync is not enough on its own: * mkdirSync's mode applies only to directories it actually *creates*, so a * directory that some earlier code path already made at 0755 keeps that mode * forever. That is not hypothetical — `runtimeFile()` created `~/.phren/.runtime` * with no mode long before `auth/profiles.ts` ever asked for 0700, so the * credential directory shipped world-readable in practice. * * Only ever narrows: if the directory is already 0700 or tighter it is left * alone. Best-effort — a chmod failure (foreign filesystem, Windows) must not * break the caller, since the directory itself is usable either way. */ export declare function ensurePrivateDir(dir: string): string; export declare function isInstallMode(value: unknown): value is InstallMode; export declare function readRootManifest(phrenPath: string): PhrenRootManifest | null; export declare function writeRootManifest(phrenPath: string, manifest: PhrenRootManifest): void; export declare function resolveInstallContext(phrenPath: string): InstallContext; export declare function findNearestPhrenPath(startDir?: string): string | null; export declare function findPhrenPath(): string | null; export declare function ensurePhrenPath(): string; export declare function findPhrenPathWithArg(arg?: string): string; export declare function isProjectLocalMode(phrenPath: string): boolean; export declare function runtimeDir(phrenPath: string): string; /** Unlink a file, ignoring ENOENT. Rethrows any other error. */ export declare function tryUnlink(filePath: string): void; export declare function sessionsDir(phrenPath: string): string; export declare function runtimeFile(phrenPath: string, name: string): string; /** * Per-user root for the FTS snapshot cache: `os.tmpdir()/phren-fts-<uid>`. * * Canonical definition of the path lives here, with every other phren path * helper. `shared/index.ts` currently has its own private copy — see * ensureFtsCacheRootPrivate() for why that matters and what still needs to * change there. */ export declare function ftsCacheRoot(): string; /** * Make the FTS snapshot cache root private, creating it if needed. * * The snapshot is a SQLite export of the *entire* indexed store — the full * text of every finding, note, task and reference doc. It was being written * as an 0644 file inside an 0755 directory: * * drwxr-xr-x $TMPDIR/phren-fts-501 * -rw-r--r-- $TMPDIR/phren-fts-501/<storeKey>/<hash>.db * * On macOS `os.tmpdir()` is a per-user `/var/folders/…/T` at 0700, which is * the only reason this is not already a leak there. On Linux and WSL * `os.tmpdir()` is `/tmp` — mode 1777, world-readable — so any local account * can read another user's whole knowledge base. phren ships cross-platform on * npm, so that is a live exposure, not a theoretical one. * * A 0700 root closes it completely: POSIX denies traversal into the directory, * so the modes of the per-store subdirectory and the .db files inside it stop * being reachable at all. Called once per process from the CLI and MCP-server * entrypoints, which both creates it correctly on a clean machine and repairs * the 0755 root on machines that already have one. * * Still worth doing for defence in depth, in the file that actually writes * them: `shared/index.ts` should pass `{ recursive: true, mode: 0o700 }` to * the `fs.mkdirSync(cacheDir, …)` calls and `{ mode: 0o600 }` to the * `fs.writeFileSync(cacheFile, db.export())` calls. That file is owned by * another change in flight, so it is reported rather than edited here. */ export declare function ensureFtsCacheRootPrivate(): string; export declare function installPreferencesFile(phrenPath: string): string; export declare function runtimeHealthFile(phrenPath: string): string; export declare function shellStateFile(phrenPath: string): string; export declare function sessionMetricsFile(phrenPath: string): string; export declare function memoryScoresFile(phrenPath: string): string; export declare function memoryUsageLogFile(phrenPath: string): string; /** * Live "lookup events" log: one JSONL entry per memory a search lands on. * Distinct from memory-usage.log (injection scoring) — this feed powers the * real-time activity surfaces in the web UI and the VS Code extension. */ export declare function lookupEventsLogFile(phrenPath: string): string; export declare function sessionMarker(phrenPath: string, name: string): string; export declare function debugLog(msg: string): void; /** Always-on structured error log (no PHREN_DEBUG gate). */ export declare function errorLog(tool: string, msg: string): void; /** * Truncate an append-only .jsonl to its last `keepLines` lines once it exceeds * `maxBytes`. Best-effort: any IO error (including ENOENT) is ignored so callers * never fail on rotation. */ export declare function rotateJsonlIfLarge(filePath: string, maxBytes?: number, keepLines?: number): void; export declare function appendIndexEvent(phrenPath: string, event: Record<string, unknown>): void; /** Resolve the canonical findings file for a project directory. */ export declare function resolveFindingsPath(projectDir: string): string | undefined; export declare function listInvalidProjectDirs(phrenPath: string): string[]; export declare function normalizeProjectNameForCreate(name: string): string; /** * The single source of truth for turning a source directory into a project * slug. Previously copy-pasted at five call sites, which is how the same repo * could end up registered twice under near-identical names. * * Runs of non-slug characters collapse to one hyphen and leading/trailing * hyphens are trimmed, so `My.App`, `My..App` and `My App` all yield `my-app` * instead of the old `my-app` / `my--app` / `my-app` split. */ export declare function projectSlugFromPath(sourcePath: string): string; /** * Collapse a project name to the key used for duplicate detection: lowercase * with every separator removed. `Max4LivePlugins`, `max4liveplugins` and * `max4live-plugins` all map to `max4liveplugins`, so the second spelling of a * repo can be recognized as the project that already exists. */ export declare function canonicalProjectKey(name: string): string; export declare function findProjectNameCaseInsensitive(phrenPath: string, name: string): string | null; /** * Existing project directories whose name canonicalizes to the same key as * `name` (see {@link canonicalProjectKey}). Exact matches are returned first so * callers can prefer them. */ export declare function findProjectNamesByCanonicalKey(phrenPath: string, name: string): string[]; export declare function findArchivedProjectNameCaseInsensitive(phrenPath: string, name: string): string | null; export declare function getProjectDirs(phrenPath: string, profile?: string): string[]; /** Claude's own memory directory is indexed only when asked: by default phren indexes phren. */ export declare function nativeMemoryEnabled(env?: NodeJS.ProcessEnv): boolean; export declare function collectNativeMemoryFiles(): Array<{ project: string; file: string; fullPath: string; }>; export declare function computePhrenLiveStateToken(phrenPath: string): string; export declare function getPhrenPath(): string; export declare function qualityMarkers(phrenPathLocal: string): { done: string; lock: string; };