@phren/cli
Version:
Knowledge layer for AI agents - CLI, MCP server, and data layer
134 lines (133 loc) • 7.12 kB
TypeScript
export type StoreRole = "primary" | "team" | "readonly";
export type StoreSyncMode = "managed-git" | "pull-only";
export interface StoreEntry {
/** Immutable 8-char hex identifier. */
id: string;
/** Human-readable name (unique within registry). */
name: string;
/** Absolute path to the store root directory. */
path: string;
/** Store role — determines read/write/sync behavior. */
role: StoreRole;
/** Git remote URL (optional). */
remote?: string;
/** Sync mode for git operations. */
sync: StoreSyncMode;
/** Projects claimed by this store (for write routing in phase 2). */
projects?: string[];
/**
* Whether {@link path} exists on this machine. Computed by
* {@link resolveAllStores}; never persisted to stores.yaml. `undefined` on
* hand-built entries that never went through resolution.
*/
available?: boolean;
}
export interface StoreRegistry {
version: 1;
stores: StoreEntry[];
}
/** Bootstrap metadata committed to a team store repo root. */
export interface TeamBootstrap {
name: string;
description?: string;
default_role?: StoreRole;
}
/**
* The synced registry at the store root. It holds the primary store's entry
* only, so every machine on this personal store agrees on its id. Team and
* readonly stores used to live here too, which pushed one machine's `team
* join` onto every other machine and left doctor failing forever there.
*/
export declare const STORES_FILENAME = "stores.yaml";
/**
* The stores attached on this machine, under `.runtime/` (never synced).
* Using the personal store must never connect a machine to a team store.
*/
export declare const ATTACHED_STORES_FILENAME = "attached-stores.yaml";
export declare function storesFilePath(phrenPath: string): string;
export declare function attachedStoresFilePath(phrenPath: string): string;
export declare function generateStoreId(): string;
/**
* What a registry read actually found: the primary store from the synced
* stores.yaml plus the stores attached on this machine. `registry` is what
* could be honored; `problems` says what couldn't; `lossy` means one of the
* two files EXISTS but the result does not fully represent it (unreadable,
* unparsable, entries skipped, or validation failed). Mutators must refuse to
* write while a read is lossy — writing back would silently destroy the
* entries we skipped.
*/
export interface RegistryReadResult {
registry: StoreRegistry | null;
problems: string[];
lossy: boolean;
}
export declare function readStoreRegistryDetailed(phrenPath: string, options?: {
migrateAttachments?: boolean;
}): RegistryReadResult;
/** Identity carried by the store itself, never an attachment ID or path hash.
* Reading identity must not migrate this machine's attachments. */
export declare function registeredStoreIdentity(phrenPath: string): string | undefined;
/** Explicit, idempotent registration for legacy stores. Callers enforce the
* existing manage_config permission. No task or attachment is rewritten. */
export declare function registerStoreIdentity(phrenPath: string): string;
export declare function readStoreRegistry(phrenPath: string): StoreRegistry | null;
/**
* Writes this machine's view of the stores: the primary entry to the synced
* stores.yaml (only when it changed) and every other store to the machine's
* own attached-stores.yaml.
*/
export declare function writeStoreRegistry(phrenPath: string, registry: StoreRegistry): void;
/** True when a store's directory exists on this machine. */
export declare function storePathExists(storePath: string): boolean;
/**
* Resolve the full list of stores on this machine. This is the **key
* backward-compat function**:
* - The primary comes from the synced stores.yaml, or is implicit when the
* file is missing
* - Then every store attached on this machine (.runtime/attached-stores.yaml)
* - Then PHREN_FEDERATION_PATHS entries as readonly stores
*
* Every returned entry carries `available`, recording whether its path exists
* here. Attached stores whose folder is gone are deliberately **still
* returned**: callers must be able to see that a store claims a project before
* deciding what to do, otherwise its writes would land in the primary store.
*/
export declare function resolveAllStores(phrenPath: string): StoreEntry[];
/** Task discovery must neither migrate attachments nor treat a partial registry
* as proof of an unambiguous task identity. No writes or warnings on this path. */
export declare function readTaskStoreEntries(phrenPath: string): StoreEntry[];
/**
* Non-primary entries in the synced stores.yaml that this machine does not use:
* joined on another machine with an older phren. Attached ones were moved into
* attached-stores.yaml; the rest mean nothing here.
*/
export declare function ignoredSyncedStores(phrenPath: string): StoreEntry[];
/** Stores attached on this machine whose directory is missing. */
export declare function getUnavailableStores(phrenPath: string): StoreEntry[];
/**
* One actionable sentence about a store attached on this machine whose folder
* is gone. Shared by write routing, `phren status`, and `phren doctor` so all
* three name the same store, the same expected path, and the same remedy.
*/
export declare function describeUnavailableStore(store: StoreEntry): string;
/** The primary store (role=primary). Falls back to implicit entry. */
export declare function getPrimaryStore(phrenPath: string): StoreEntry;
/** All stores that can be read (all roles). */
export declare function getReadableStores(phrenPath: string): StoreEntry[];
/** Non-primary stores (for federation search, multi-store sync). */
export declare function getNonPrimaryStores(phrenPath: string): StoreEntry[];
/** Find a store by name. */
export declare function findStoreByName(phrenPath: string, name: string): StoreEntry | undefined;
/** Get project directories for a store, filtered by the store's subscription list (if set). */
export declare function getStoreProjectDirs(store: StoreEntry): string[];
export declare function readTeamBootstrap(storePath: string): TeamBootstrap | null;
/** Attach a store on this machine. Creates stores.yaml for the primary if needed. Uses file locking. */
export declare function addStoreToRegistry(phrenPath: string, entry: StoreEntry): void;
/** Remove a store entry by name. Refuses to remove primary. Uses file locking. */
export declare function removeStoreFromRegistry(phrenPath: string, name: string): StoreEntry;
/** Update the projects[] claim list for a store. Uses file locking. */
export declare function updateStoreProjects(phrenPath: string, storeName: string, projects: string[]): void;
/** Add projects to a store's subscription list. Deduplicates. Uses file locking. */
export declare function subscribeStoreProjects(phrenPath: string, storeName: string, projects: string[]): void;
/** Remove projects from a store's subscription list. Uses file locking. */
export declare function unsubscribeStoreProjects(phrenPath: string, storeName: string, projects: string[]): void;