// @generated by scripts/build-runtime.mjs; do not edit. // @ts-nocheck -- generated JavaScript uses a .ts extension for Pi's Jiti loader. // src/paths.ts import path from "node:path"; function isDeniedPath(relativePath) { const normalized = toPosix(relativePath); const lower = normalized.toLowerCase(); const segments = lower.split("/"); const base = path.posix.basename(lower); return segments.includes("node_modules") || segments.includes(".git") || segments.includes(".pisync") || segments.includes("pi-sync") || segments.includes(".pi-sync-state-migration.lock") || base === ".env" || base.startsWith(".env.") || base.endsWith(".env") || base.includes("secret") || base.includes("token") || base === "pi-sync.json" || base.startsWith("pi-sync.json.") || base.startsWith(".pi-sync.json.") || base === "pi-sync.local.json" || base.startsWith("pi-sync.local.json.") || base.startsWith(".pi-sync.local.json."); } function isPathInside(parent, child) { const relative = path.relative(path.resolve(parent), path.resolve(child)); return relative === "" || !relative.startsWith("..") && !path.isAbsolute(relative); } function safeJoin(root, relativePath) { const target = path.resolve(root, relativePath); assertWithinRoot(root, target, relativePath); return target; } function assertWithinRoot(root, target, label = target) { const resolvedRoot = path.resolve(root); const resolvedTarget = path.resolve(target); if (resolvedTarget !== resolvedRoot && !resolvedTarget.startsWith(`${resolvedRoot}${path.sep}`)) { throw new Error(`Unsafe path in snapshot: ${label}`); } } function encodeKey(key) { return key.split("/").map(encodeURIComponent).join("/"); } function posixJoin(...parts) { return parts.map((part) => trimSlashes(part)).filter(Boolean).join("/"); } function parentPaths(relativePath) { const results = []; let index = relativePath.lastIndexOf("/"); while (index > 0) { results.push(relativePath.slice(0, index)); index = relativePath.lastIndexOf("/", index - 1); } return results; } function toPosix(value) { return value.split(path.sep).join("/"); } function trimSlashes(value) { return value.replace(/^\/+|\/+$/g, ""); } // src/sync-policy.ts import path2 from "node:path"; var BUILT_IN_SYNC_ROOTS = [ "settings.json", "keybindings.json", "models.json", "AGENTS.md", "APPEND_SYSTEM.md", "skills", "prompts", "themes", "extensions" ]; var DEFAULT_SYNC_INCLUDE = [...BUILT_IN_SYNC_ROOTS]; var SNAPSHOT_SELECTION_VERSION = 1; var MAX_SYNC_INCLUDE_ITEMS = 1024; var MAX_SYNC_INCLUDE_PATH_BYTES = 4096; var MAX_SYNC_INCLUDE_TOTAL_BYTES = 256 * 1024; var BUILT_IN_BY_LOWER = new Map( BUILT_IN_SYNC_ROOTS.map((fileName) => [fileName.toLowerCase(), fileName]) ); var TOP_LEVEL_FILE_PATHS = new Map( BUILT_IN_SYNC_ROOTS.filter((fileName) => fileName.includes(".")).map((fileName) => [ fileName.toLowerCase(), fileName ]) ); var TOP_LEVEL_DIRS = new Set( BUILT_IN_SYNC_ROOTS.filter((fileName) => !fileName.includes(".")) ); var RESERVED_TOP_LEVEL_NAMES = /* @__PURE__ */ new Set([...BUILT_IN_BY_LOWER.keys(), "sessions"]); function normalizeSyncInclude(value) { if (!Array.isArray(value)) { throw new Error("Invalid pi-sync settings: sync.include must be an array."); } if (value.length > MAX_SYNC_INCLUDE_ITEMS) { throw new Error( `Invalid pi-sync settings: sync.include has too many items; limit: ${MAX_SYNC_INCLUDE_ITEMS}.` ); } const result = []; const seen = /* @__PURE__ */ new Set(); const pathRoot = { children: /* @__PURE__ */ new Map() }; let totalBytes = 0; for (const item of value) { if (typeof item !== "string") { throw new Error("Invalid pi-sync settings: sync.include items must be strings."); } const itemBytes = Buffer.byteLength(item, "utf8"); if (itemBytes > MAX_SYNC_INCLUDE_PATH_BYTES) { throw new Error( `Invalid pi-sync settings: sync.include item is too long; limit: ${MAX_SYNC_INCLUDE_PATH_BYTES} bytes.` ); } totalBytes += itemBytes; if (totalBytes > MAX_SYNC_INCLUDE_TOTAL_BYTES) { throw new Error( `Invalid pi-sync settings: sync.include is too large; limit: ${MAX_SYNC_INCLUDE_TOTAL_BYTES} bytes.` ); } const trimmed = item.trim(); const builtIn = BUILT_IN_BY_LOWER.get(trimmed.toLowerCase()); const normalized = builtIn ?? (trimmed.toLowerCase() === "sessions" ? "sessions" : trimmed); if (!builtIn && normalized !== "sessions") validateAgentRelativeInclude(normalized); const identity = normalized.toLowerCase(); if (seen.has(identity)) { throw new Error(`Invalid pi-sync settings: duplicate sync.include item: ${item}`); } addIncludePath(pathRoot, identity, item); seen.add(identity); result.push(normalized); } return result; } function addIncludePath(root, identity, source) { let node = root; for (const segment of identity.split("/")) { if (node.selected !== void 0) throwOverlappingInclude(source); let child = node.children.get(segment); if (!child) { child = { children: /* @__PURE__ */ new Map() }; node.children.set(segment, child); } node = child; } if (node.children.size > 0) throwOverlappingInclude(source); node.selected = identity; } function throwOverlappingInclude(item) { throw new Error( `Invalid pi-sync settings: overlapping sync.include items are ambiguous: ${item}` ); } function validateAgentRelativeInclude(value) { const normalized = toPosix(value); const topLevel = normalized.split("/")[0]?.toLowerCase(); if (!normalized || normalized === "." || normalized === ".." || normalized.startsWith("../") || path2.posix.isAbsolute(normalized) || normalized.includes("\\") || path2.posix.normalize(normalized) !== normalized || // biome-ignore lint/suspicious/noControlCharactersInRegex: Include paths cannot contain controls. /[\u0000-\u001f\u007f-\u009f]/u.test(normalized)) { throw new Error( `Invalid pi-sync settings: sync.include item must be a safe agent-relative path: ${value}` ); } if (isDeniedPath(normalized)) { throw new Error(`Invalid pi-sync settings: ${value} cannot be synced.`); } if (topLevel && RESERVED_TOP_LEVEL_NAMES.has(topLevel)) { throw new Error( `Invalid pi-sync settings: use the canonical ${topLevel} root instead of a nested sync.include path.` ); } } function portableSnapshotSelection(value) { const selection = value; if (!selection || typeof selection !== "object" || Array.isArray(selection) || selection.version !== SNAPSHOT_SELECTION_VERSION || !Object.hasOwn(selection, "include") || Object.keys(selection).some((key) => key !== "version" && key !== "include")) { throw new Error("Invalid snapshot selection policy."); } return { version: SNAPSHOT_SELECTION_VERSION, include: normalizeSyncInclude(selection.include) }; } function snapshotSelectionInclude(snapshot) { return snapshot.selection === void 0 ? void 0 : portableSnapshotSelection(snapshot.selection).include; } function selectionForSnapshot(include) { return { version: SNAPSHOT_SELECTION_VERSION, include: normalizeSyncInclude(include) }; } function sameSyncInclude(left, right) { const normalizedLeft = normalizeSyncInclude(left); const normalizedRight = normalizeSyncInclude(right); return normalizedLeft.length === normalizedRight.length && normalizedLeft.every((item, index) => item === normalizedRight[index]); } function compareSyncInclude(local, remote) { const localInclude = normalizeSyncInclude(local); const remoteInclude = normalizeSyncInclude(remote); const localSet = new Set(localInclude); const remoteSet = new Set(remoteInclude); return { same: sameSyncInclude(localInclude, remoteInclude), remoteOnly: remoteInclude.filter((item) => !localSet.has(item)), localOnly: localInclude.filter((item) => !remoteSet.has(item)) }; } function inspectRemoteSelection(localInclude, snapshot) { const remoteInclude = snapshotSelectionInclude(snapshot); if (!remoteInclude) { return { kind: "legacy", discovered: discoverLegacySnapshotInclude(snapshot) }; } const comparison = compareSyncInclude(localInclude, remoteInclude); return comparison.same ? { kind: "same", include: remoteInclude } : { kind: "different", include: remoteInclude, ...comparison }; } var RemoteSelectionMismatchError = class extends Error { decision; setupName; localInclude; remoteInclude; constructor(setupName, localInclude, remoteInclude, configIdentity = JSON.stringify([setupName, normalizeSyncInclude(localInclude)])) { const local = normalizeSyncInclude(localInclude); const remote = normalizeSyncInclude(remoteInclude); super(formatRemoteSelectionMismatch(setupName, local, remote)); this.name = "RemoteSelectionMismatchError"; this.setupName = setupName; this.localInclude = local; this.remoteInclude = remote; this.decision = { setupName, configIdentity, localInclude: [...local], remoteInclude: [...remote] }; } }; function remoteSelectionMismatch(config, remoteInclude, configIdentity) { return new RemoteSelectionMismatchError( config.setupName, config.include, remoteInclude, configIdentity ); } function formatRemoteSelectionMismatch(setupName, localInclude, remoteInclude) { const comparison = compareSyncInclude(localInclude, remoteInclude); const lines = [ `Synced content differs for sync setup \u201C${stripTerminalControls(setupName)}\u201D.`, `Remote-only: ${comparison.remoteOnly.join(", ") || "none"}`, `This-device-only: ${comparison.localOnly.join(", ") || "none"}` ]; if (comparison.remoteOnly.length === 0 && comparison.localOnly.length === 0) { lines.push( "Only ordering differs.", `Remote order: ${remoteInclude.join(", ") || "none"}`, `This device order: ${localInclude.join(", ") || "none"}` ); } lines.push("Run /sync in TUI to review both content lists and choose what happens next."); return lines.join("\n"); } function stripTerminalControls(value) { return value.replace(/[\u0000-\u001f\u007f-\u009f]/gu, "?"); } function discoverLegacySnapshotInclude(snapshot) { const builtIns = /* @__PURE__ */ new Set(); const custom = /* @__PURE__ */ new Set(); let sessions = false; for (const file of snapshot.files) { const normalized = toPosix(file.path); if (!normalized || file.path.includes("\\") || normalized.length > 4096 || normalized.startsWith("../") || path2.posix.isAbsolute(normalized) || path2.posix.normalize(normalized) !== normalized || // biome-ignore lint/suspicious/noControlCharactersInRegex: Ignore unsafe legacy paths. /[\u0000-\u001f\u007f-\u009f]/u.test(normalized) || isDeniedPath(normalized)) { continue; } const [topLevel, ...rest] = normalized.split("/"); if (!topLevel) continue; if (topLevel === "sessions" && rest.length > 0) { sessions = true; continue; } const builtIn = BUILT_IN_BY_LOWER.get(topLevel.toLowerCase()); if (builtIn && (TOP_LEVEL_DIRS.has(builtIn) && rest.length > 0 || !TOP_LEVEL_DIRS.has(builtIn) && rest.length === 0)) { builtIns.add(builtIn); continue; } if (isSafeCustomIncludePath(topLevel)) custom.add(topLevel); } return [ ...BUILT_IN_SYNC_ROOTS.filter((item) => builtIns.has(item)), ...[...custom].sort((left, right) => left.localeCompare(right)), ...sessions ? ["sessions"] : [] ]; } function syncIncludeSelection(value) { const include = normalizeSyncInclude(value); const builtIns = include.filter( (item) => BUILT_IN_BY_LOWER.has(item.toLowerCase()) ); const custom = include.filter( (item) => item !== "sessions" && !BUILT_IN_BY_LOWER.has(item.toLowerCase()) ); return { include, builtIns, custom, sessions: include.includes("sessions") }; } function customIncludePathsByLower(value) { return new Map( syncIncludeSelection(value).custom.map((relativePath) => [ relativePath.toLowerCase(), relativePath ]) ); } function includeFromSelectionConfig(config) { if (config.include !== void 0) return normalizeSyncInclude(config.include); return [ ...normalizeSyncFiles(config.syncFiles), ...normalizeExtraFiles(config.extraFiles), ...config.syncSessions ? ["sessions"] : [] ]; } function isConfiguredSnapshotPath(relativePath, config, _legacyExtraFiles) { const normalized = toPosix(relativePath); const selection = syncIncludeSelection(includeFromSelectionConfig(config)); if (normalized.startsWith("sessions/")) return selection.sessions; const lower = normalized.toLowerCase(); if (!normalized.includes("/")) { const builtIn = BUILT_IN_BY_LOWER.get(lower); if (builtIn) return selection.builtIns.includes(builtIn) && !TOP_LEVEL_DIRS.has(builtIn); } const topLevel = normalized.slice(0, normalized.indexOf("/")); if (selection.builtIns.includes(topLevel) && TOP_LEVEL_DIRS.has(topLevel)) { return true; } return selection.custom.some((candidate) => { const candidateLower = candidate.toLowerCase(); return lower === candidateLower || lower.startsWith(`${candidateLower}/`); }); } function canonicalSnapshotPathForConfig(relativePath, includePaths) { const normalized = toPosix(relativePath); const lower = normalized.toLowerCase(); return TOP_LEVEL_FILE_PATHS.get(lower) ?? includePaths.get(lower) ?? normalized; } function isPreservableUnmanagedSnapshotPath(relativePath) { const normalized = toPosix(relativePath); if (!normalized || isDeniedPath(normalized)) return false; if (normalized.startsWith("sessions/")) return normalized.endsWith(".jsonl"); if (!normalized.includes("/")) { const lower = normalized.toLowerCase(); return TOP_LEVEL_FILE_PATHS.has(lower) || !RESERVED_TOP_LEVEL_NAMES.has(lower); } return true; } function isSafeCustomIncludePath(relativePath) { try { validateAgentRelativeInclude(relativePath); return true; } catch { return false; } } function normalizeSyncFiles(value) { if (value === void 0) return [...DEFAULT_SYNC_INCLUDE]; if (Array.isArray(value)) { return normalizeSyncInclude(value).filter( (item) => BUILT_IN_BY_LOWER.has(item.toLowerCase()) ); } throw new Error("Invalid pi-sync settings: expected an include array."); } function normalizeExtraFiles(value) { if (!Array.isArray(value)) return []; return value.filter( (item) => typeof item === "string" && isSafeCustomIncludePath(item) ); } export { isDeniedPath, isPathInside, safeJoin, assertWithinRoot, encodeKey, posixJoin, parentPaths, toPosix, BUILT_IN_SYNC_ROOTS, DEFAULT_SYNC_INCLUDE, normalizeSyncInclude, portableSnapshotSelection, snapshotSelectionInclude, selectionForSnapshot, sameSyncInclude, compareSyncInclude, inspectRemoteSelection, RemoteSelectionMismatchError, remoteSelectionMismatch, formatRemoteSelectionMismatch, syncIncludeSelection, customIncludePathsByLower, includeFromSelectionConfig, isConfiguredSnapshotPath, canonicalSnapshotPathForConfig, isPreservableUnmanagedSnapshotPath, isSafeCustomIncludePath, normalizeSyncFiles, normalizeExtraFiles }; //# sourceMappingURL=chunk-YQ6UW7IF.ts.map