import { ExtensionAPI } from '@earendil-works/pi-coding-agent'; /** * Options controlling portable-name generation. Derived from the extension * config (see {@link PortableSessionsConfig}). */ interface PortableNameOptions { /** Label replacing the home directory prefix. Default: "HOME". */ homeLabel: string; /** Label replacing the root directory prefix. Default: "ROOT". */ rootLabel: string; /** Map of additional absolute path prefixes to portable labels. */ extraPrefixes: Record; } /** * Normalize a path for prefix matching: resolve to an absolute path and unify * separators to `/` so matching is platform-independent (Windows `\` and `/` * both become `/`). */ declare function toPosixAbsolute(path: string): string; /** * Compute the portable session directory name for a working directory. * * Rules, in priority order: * 1. The longest configured `extraPrefixes` entry whose prefix matches at a * path-segment boundary. * 2. The home directory prefix. * 3. The root prefix (any remaining absolute path). * * The label replaces the matched prefix, and the remainder of the path is * percent-encoded (URL encoding), so the result is reversible and free of * filesystem-hostile characters. Examples (home `/Users/zpan`): * * - `/Users/zpan/my-project` -> `HOME%2Fmy-project` * - `/Users/zpan` -> `HOME` * - `/var/www` -> `ROOT%2Fvar%2Fwww` * - `/Volumes/Backup/data` (with `{"/Volumes/Backup": "BACKUP"}`) -> `BACKUP%2Fdata` */ declare function portableSessionDirName(cwd: string, options: PortableNameOptions): string; /** Result of decoding a portable session directory name. */ interface DecodedPortableName { /** The label that matched (homeLabel, rootLabel, or an extra prefix label). */ label: string; /** The percent-decoded remainder of the path (starts with `/`). */ remainder: string; } /** * Decode a portable session directory name back into its label and decoded * remainder. Returns `null` when the name carries no known label or its * remainder is not valid percent-encoding. */ declare function decodePortableSessionDirName(name: string, options: PortableNameOptions): DecodedPortableName | null; /** * Reconstruct the absolute path a portable session directory name stands for. * Returns `null` when the name cannot be decoded. */ declare function portableSessionDirNameToAbsolute(name: string, options: PortableNameOptions): string | null; /** * Resolved extension configuration. The migration source root (Pi's session * directory) is resolved from Pi itself (see {@link getSessionsRoot}); the * portable storage root is configurable via `portableRoot`. */ interface PortableSessionsConfig { /** Label replacing the home directory prefix. Default: "HOME". */ homeLabel: string; /** Label replacing the root directory prefix. Default: "ROOT". */ rootLabel: string; /** Map of additional absolute path prefixes to portable labels. */ extraPrefixes: Record; /** * After Pi starts, notify which session directories can be migrated and the * command to run. Default: true. */ notifyOnStart: boolean; /** * Root directory holding the portable session directories (outside Pi's own * sessions root). `undefined` means `/portable-sessions`. */ portableRoot: string | undefined; } declare const DEFAULT_CONFIG: PortableSessionsConfig; /** Warnings collected while normalizing/merging raw config values. */ type ConfigWarnings = string[]; /** * Normalize one raw config object (global or project) onto a base config. * Invalid entries are skipped and reported as warnings; the extension keeps * working with defaults rather than failing to load. */ declare function normalizeConfig(raw: unknown, base?: PortableSessionsConfig): { config: PortableSessionsConfig; warnings: ConfigWarnings; }; /** * Load and merge the global config (`/extensions/pi-portable-sessions/config.json`) * and the project config (`/.pi/extensions/pi-portable-sessions/config.json`). * Project values override global values; `extraPrefixes` maps are merged. */ declare function loadConfig(agentDir: string, cwd: string): Promise<{ config: PortableSessionsConfig; warnings: ConfigWarnings; }>; /** Build portable-name options from the resolved config. */ declare function toPortableNameOptions(config: PortableSessionsConfig): PortableNameOptions; /** * Resolve Pi's session root directory, mirroring Pi's own precedence: * * 1. `PI_CODING_AGENT_SESSION_DIR` environment variable. * 2. `sessionDir` in `/settings.json` (relative values resolve * against `cwd`). * 3. The default `/sessions`. * * The CLI `--session-dir` flag cannot be observed from an extension, so it is * not covered; extensions load after Pi has already chosen the directory. */ declare function getSessionsRoot(agentDir: string, cwd: string): Promise; declare const EXTENSION_ID = "pi-portable-sessions"; /** Global config directory: `/extensions/pi-portable-sessions/`. */ declare function getGlobalConfigDir(agentDir: string): string; /** Global config file: `/extensions/pi-portable-sessions/config.json`. */ declare function getGlobalConfigPath(agentDir: string): string; /** Project config file: `/.pi/extensions/pi-portable-sessions/config.json`. */ declare function getProjectConfigPath(cwd: string): string; /** * Compute Pi's default session directory name for a working directory, * mirroring Pi's own encoding: strip the leading separator, then replace `/`, * `\`, and `:` with `-`, wrapped in `--...--`. */ declare function defaultSessionDirName(cwd: string): string; /** Lifecycle state of one migration target directory. */ type MigrationState = "no-sessions" | "already-portable" | "migrated" | "migrated-now" | "would-migrate" | "portable-only" | "conflict"; /** Outcome of migrating one working directory's session directory. */ interface MigrationResult { /** Working directory the session directory belongs to. */ cwd: string; /** Pi's default session directory (the `----` path). */ defaultDir: string; /** The portable directory name (e.g. `HOME%2Fmy-project`). */ portableName: string; /** The absolute portable session directory. */ portableDir: string; state: MigrationState; /** Files copied into an already-existing portable directory. */ filesMerged: number; /** * Same-named `.jsonl` files that already existed in the target directory. */ jsonlConflicts: number; /** Conflicts resolved by the `onJsonlConflict` handler (merged in place). */ jsonlMerged: number; /** * Conflicts left unresolved (no handler, or the handler returned `false`), * whose source file was preserved under a `-conflicted` name to avoid data * loss. */ jsonlPreserved: number; /** Human-readable detail for the summary report. */ note?: string; } /** * Merge handler for two same-named session files. Receives the source file * (the directory being migrated) and the target file (the file that already * exists in the portable directory). Must return `true` when the conflict was * resolved (the target file now contains the merged content), `false` to keep * the existing target file untouched. */ type JsonlConflictHandler = (sourceFile: string, targetFile: string) => Promise; interface MigrateOptions { /** Only compute what would change; do not move files or create links. */ dryRun?: boolean; /** Pi's sessions root (the migration source). Required. */ sessionsRoot?: string; /** * Root directory holding the portable session directories. Defaults to * `/../portable-sessions`. */ portableRoot?: string; /** * Called for every same-named `.jsonl` file that already exists in the * target portable directory, so the caller can merge the contents (e.g. with * a model). When omitted, such files are skipped and reported as conflicts. */ onJsonlConflict?: JsonlConflictHandler; } /** * Migrate one working directory's session directory to its portable name. * * Pi hard-codes the `----` directory name, so the physical * directory is renamed to the portable name and a symlink is left at Pi's * default path pointing at the new location. Pi keeps writing through the * symlink, so the current session, `/resume`, and future startups all keep * working while the on-disk name becomes portable. */ declare function migrateSessionDir(cwd: string, config: PortableSessionsConfig, options?: MigrateOptions): Promise; /** * Migrate every default-named session directory under the sessions root. * Each directory is identified by the `cwd` recorded in its session files' * headers, since Pi's `----` encoding is not reversible. */ declare function migrateAllSessionDirs(config: PortableSessionsConfig, options?: MigrateOptions): Promise; /** One session directory that is a candidate for migration. */ interface PendingMigration { /** Working directory recorded in the session files' headers. */ cwd: string; /** Pi's default `----` directory name. */ defaultDirName: string; /** The portable directory name it would be migrated to. */ portableName: string; } /** * Scan the sessions root for default-named session directories that have not * been migrated yet (real directories, not symlinks) and whose portable name * differs from the default name. Used to notify the user at startup. */ declare function findPendingMigrations(config: PortableSessionsConfig, options?: MigrateOptions): Promise; /** * Migrate the session directories named by `targets`. Each target is either an * absolute working directory or a directory name under the sessions root * (Pi's default `----` name or a portable name). Directories whose * cwd cannot be determined are reported and skipped. */ declare function migrateNamedSessionDirs(targets: string[], config: PortableSessionsConfig, options?: MigrateOptions): Promise; declare function piPortableSessionsExtension(pi: ExtensionAPI): void; export { DEFAULT_CONFIG, EXTENSION_ID, decodePortableSessionDirName, piPortableSessionsExtension as default, defaultSessionDirName, findPendingMigrations, getGlobalConfigDir, getGlobalConfigPath, getProjectConfigPath, getSessionsRoot, loadConfig, migrateAllSessionDirs, migrateNamedSessionDirs, migrateSessionDir, normalizeConfig, portableSessionDirName, portableSessionDirNameToAbsolute, toPortableNameOptions, toPosixAbsolute }; export type { ConfigWarnings, DecodedPortableName, JsonlConflictHandler, MigrateOptions, MigrationResult, MigrationState, PendingMigration, PortableNameOptions, PortableSessionsConfig };