@alfe.ai/openclaw-sync 0.0.15 → 0.0.17

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.
@@ -1 +1 @@
1
- {"version":3,"file":"sync-engine.js","names":["log","MAX_RETRIES","BASE_DELAY_MS","formatBytes","log"],"sources":["../src/config.ts","../src/manifest.ts","../src/api-client.ts","../src/uploader.ts","../src/downloader.ts","../src/sync-engine.ts"],"sourcesContent":["/**\n * AlfeSync configuration — read/write `.alfesync/config.json` in workspace root.\n */\n\nimport { readFile, writeFile, mkdir } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\nexport interface SyncConfig {\n agentId: string;\n orgId: string;\n token: string;\n workspacePath: string;\n apiUrl: string;\n}\n\nconst CONFIG_DIR = \".alfesync\";\nconst CONFIG_FILE = \"config.json\";\n\n/**\n * Resolve the .alfesync directory path for a given workspace root.\n */\nexport function configDir(workspacePath: string): string {\n return join(workspacePath, CONFIG_DIR);\n}\n\n/**\n * Resolve the config file path for a given workspace root.\n */\nexport function configPath(workspacePath: string): string {\n return join(workspacePath, CONFIG_DIR, CONFIG_FILE);\n}\n\n/**\n * Check if a workspace has been initialized with AlfeSync.\n */\nexport function isInitialized(workspacePath: string): boolean {\n return existsSync(configPath(workspacePath));\n}\n\n/**\n * Read the AlfeSync config from a workspace.\n * Returns null if not initialized.\n */\nexport async function readConfig(\n workspacePath: string,\n): Promise<SyncConfig | null> {\n const path = configPath(workspacePath);\n if (!existsSync(path)) return null;\n\n try {\n const raw = await readFile(path, \"utf-8\");\n const parsed = JSON.parse(raw) as SyncConfig;\n\n // Validate required fields\n if (!parsed.agentId || !parsed.token || !parsed.apiUrl) {\n return null;\n }\n\n return { ...parsed, workspacePath };\n } catch {\n return null;\n }\n}\n\n/**\n * Write the AlfeSync config to a workspace.\n * Creates the .alfesync directory if it doesn't exist.\n */\nexport async function writeConfig(config: SyncConfig): Promise<void> {\n const dir = configDir(config.workspacePath);\n await mkdir(dir, { recursive: true });\n\n const path = configPath(config.workspacePath);\n const data = {\n agentId: config.agentId,\n orgId: config.orgId,\n token: config.token,\n workspacePath: config.workspacePath,\n apiUrl: config.apiUrl,\n };\n\n await writeFile(path, JSON.stringify(data, null, 2) + \"\\n\", \"utf-8\");\n}\n\n/**\n * Load config from workspace, throwing if not initialized.\n */\nexport async function requireConfig(\n workspacePath: string,\n): Promise<SyncConfig> {\n const config = await readConfig(workspacePath);\n if (!config) {\n throw new Error(\n `AlfeSync not initialized in ${workspacePath}. Run: alfesync init`,\n );\n }\n return config;\n}\n","/**\n * AlfeSync manifest — local file manifest at `.alfesync/manifest.json`.\n *\n * Tracks file hashes, sizes, sync timestamps, and storage classes\n * to enable efficient diff-based syncing.\n */\n\nimport { readFile, writeFile, mkdir } from \"node:fs/promises\";\nimport { existsSync, createReadStream } from \"node:fs\";\nimport { createHash } from \"node:crypto\";\nimport { join } from \"node:path\";\nimport { configDir } from \"./config.js\";\n\nexport interface ManifestEntry {\n hash: string;\n size: number;\n lastSynced: string;\n storageClass: \"STANDARD\" | \"GLACIER_IR\";\n}\n\nexport interface LocalManifest {\n files: Record<string, ManifestEntry>;\n}\n\nexport interface RemoteManifest {\n version: 1;\n agentId: string;\n lastSync: string;\n files: Record<\n string,\n {\n hash: string;\n size: number;\n modified: string;\n etag?: string;\n storageClass?: string;\n compressed?: boolean;\n }\n >;\n}\n\nexport interface ManifestDiff {\n /** Files that exist locally but not on remote, or have changed locally */\n toPush: string[];\n /** Files that exist on remote but not locally, or are newer on remote */\n toPull: string[];\n /** Files that exist in both and have diverged (conflict) */\n conflicts: string[];\n /** Files that exist locally but were deleted on remote */\n remoteDeleted: string[];\n}\n\nconst MANIFEST_FILE = \"manifest.json\";\n\n/**\n * Resolve the manifest file path for a given workspace root.\n */\nfunction manifestPath(workspacePath: string): string {\n return join(configDir(workspacePath), MANIFEST_FILE);\n}\n\n/**\n * Read the local manifest. Returns empty manifest if not found.\n */\nexport async function readManifest(\n workspacePath: string,\n): Promise<LocalManifest> {\n const path = manifestPath(workspacePath);\n if (!existsSync(path)) {\n return { files: {} };\n }\n\n try {\n const raw = await readFile(path, \"utf-8\");\n return JSON.parse(raw) as LocalManifest;\n } catch {\n return { files: {} };\n }\n}\n\n/**\n * Write the local manifest.\n */\nexport async function writeManifest(\n workspacePath: string,\n manifest: LocalManifest,\n): Promise<void> {\n const dir = configDir(workspacePath);\n await mkdir(dir, { recursive: true });\n\n const path = manifestPath(workspacePath);\n await writeFile(path, JSON.stringify(manifest, null, 2) + \"\\n\", \"utf-8\");\n}\n\n/**\n * Update a single file entry in the local manifest.\n */\nexport async function updateManifestEntry(\n workspacePath: string,\n relativePath: string,\n entry: ManifestEntry,\n): Promise<void> {\n const manifest = await readManifest(workspacePath);\n manifest.files[relativePath] = entry;\n await writeManifest(workspacePath, manifest);\n}\n\n/**\n * Remove a file entry from the local manifest.\n */\nexport async function removeManifestEntry(\n workspacePath: string,\n relativePath: string,\n): Promise<void> {\n const manifest = await readManifest(workspacePath);\n const remainingFiles = Object.fromEntries(\n Object.entries(manifest.files).filter(([key]) => key !== relativePath),\n );\n manifest.files = remainingFiles;\n await writeManifest(workspacePath, manifest);\n}\n\n/**\n * Compute SHA-256 hash of a file using streaming (memory-efficient).\n * Returns `sha256:<hex>` format.\n */\nexport async function computeFileHash(filePath: string): Promise<string> {\n return new Promise((resolve, reject) => {\n const hash = createHash(\"sha256\");\n const stream = createReadStream(filePath);\n\n stream.on(\"data\", (chunk) => hash.update(chunk));\n stream.on(\"end\", () => { resolve(`sha256:${hash.digest(\"hex\")}`); });\n stream.on(\"error\", reject);\n });\n}\n\n/**\n * Diff the local manifest against the remote manifest.\n *\n * Returns lists of files to push, pull, and conflicts.\n */\nexport function diffManifests(\n local: LocalManifest,\n remote: RemoteManifest,\n): ManifestDiff {\n const toPush: string[] = [];\n const toPull: string[] = [];\n const conflicts: string[] = [];\n const remoteDeleted: string[] = [];\n\n const localPaths = new Set(Object.keys(local.files));\n const remotePaths = new Set(Object.keys(remote.files));\n\n // Files only in local → push\n for (const path of localPaths) {\n if (!remotePaths.has(path)) {\n toPush.push(path);\n }\n }\n\n // Files only in remote → pull\n for (const path of remotePaths) {\n if (!localPaths.has(path)) {\n toPull.push(path);\n }\n }\n\n // Files in both → compare hashes\n for (const path of localPaths) {\n if (!remotePaths.has(path)) continue;\n\n const localEntry = local.files[path];\n const remoteEntry = remote.files[path];\n\n if (localEntry.hash === remoteEntry.hash) {\n // In sync — nothing to do\n continue;\n }\n\n // Hashes differ — determine direction\n const localSyncTime = new Date(localEntry.lastSynced).getTime();\n const remoteModTime = new Date(remoteEntry.modified).getTime();\n\n if (remoteModTime > localSyncTime) {\n // Remote is newer than our last sync — could be a conflict\n // If local file was also modified (hash differs from what we synced),\n // it's a conflict\n conflicts.push(path);\n } else {\n // Local is newer — push\n toPush.push(path);\n }\n }\n\n // Files that were in our manifest but deleted from remote\n for (const path of localPaths) {\n if (\n local.files[path].lastSynced !== \"\" &&\n !remotePaths.has(path)\n ) {\n remoteDeleted.push(path);\n }\n }\n\n return { toPush, toPull, conflicts, remoteDeleted };\n}\n","/**\n * AlfeSync API client — typed HTTP client for the AlfeSync backend.\n */\n\nimport type { RemoteManifest } from \"./manifest.js\";\n\nexport interface ApiClientConfig {\n apiUrl: string;\n token: string;\n agentId: string;\n}\n\nexport interface PresignResult {\n path: string;\n url: string;\n expiresAt: string;\n}\n\nexport interface AgentStats {\n agentId: string;\n standardBytes: number;\n glacierBytes: number;\n fileCount: number;\n lastSyncAt: string | null;\n}\n\nexport interface RegisterAgentResult {\n agent: {\n agentId: string;\n orgId: string;\n displayName: string;\n status: string;\n createdAt: string;\n };\n}\n\nexport interface ConfirmResult {\n filePath: string;\n hash: string;\n size: number;\n storageClass: string;\n syncedAt: string;\n}\n\nexport interface ReconstructResult {\n agentId: string;\n mode: \"full\" | \"active\" | \"memory\";\n fileCount: number;\n totalSize: number;\n files: {\n path: string;\n url: string;\n size: number;\n compressed: boolean;\n expiresAt: string;\n }[];\n generatedAt: string;\n expiresAt: string;\n}\n\nexport interface FileHistoryEntry {\n versionId: string;\n isLatest: boolean;\n lastModified: string;\n size: number;\n etag?: string;\n}\n\n/**\n * Create an AlfeSync API client.\n */\nexport function createApiClient(config: ApiClientConfig) {\n const { apiUrl, token, agentId } = config;\n\n // Normalize URL: strip trailing slash\n const baseUrl = apiUrl.replace(/\\/$/, \"\");\n\n async function request<T>(\n method: string,\n path: string,\n body?: unknown,\n ): Promise<T> {\n const url = `${baseUrl}${path}`;\n const headers: Record<string, string> = {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n };\n\n const response = await fetch(url, {\n method,\n headers,\n body: body ? JSON.stringify(body) : undefined,\n });\n\n if (!response.ok) {\n const text = await response.text();\n let errorMsg: string;\n try {\n const parsed = JSON.parse(text) as Record<string, unknown>;\n errorMsg = (typeof parsed.error === 'string' ? parsed.error : undefined)\n ?? (typeof parsed.message === 'string' ? parsed.message : undefined)\n ?? text;\n } catch {\n errorMsg = text;\n }\n throw new Error(\n `AlfeSync API error (${String(response.status)}): ${errorMsg}`,\n );\n }\n\n const json = (await response.json()) as { success: boolean; data: T };\n if (!json.success) {\n throw new Error(\"AlfeSync API returned unsuccessful response\");\n }\n return json.data;\n }\n\n return {\n /**\n * Get the remote manifest for this agent.\n */\n async getManifest(): Promise<RemoteManifest> {\n return request<RemoteManifest>(\n \"GET\",\n `/sync/agents/${agentId}/manifest`,\n );\n },\n\n /**\n * Request a presigned PUT URL for uploading a file.\n */\n async presignPut(\n filePath: string,\n contentType = \"application/octet-stream\",\n ): Promise<PresignResult> {\n const result = await request<{ urls: PresignResult[] }>(\n \"POST\",\n `/sync/agents/${agentId}/presign`,\n {\n files: [{ path: filePath, operation: \"put\", contentType }],\n },\n );\n return result.urls[0];\n },\n\n /**\n * Request presigned PUT URLs for multiple files.\n */\n async presignPutBatch(\n files: { path: string; contentType?: string }[],\n ): Promise<PresignResult[]> {\n const result = await request<{ urls: PresignResult[] }>(\n \"POST\",\n `/sync/agents/${agentId}/presign`,\n {\n files: files.map((f) => ({\n path: f.path,\n operation: \"put\" as const,\n contentType: f.contentType ?? \"application/octet-stream\",\n })),\n },\n );\n return result.urls;\n },\n\n /**\n * Confirm a presigned upload completed — updates DynamoDB.\n */\n async confirmUpload(\n filePath: string,\n hash: string,\n size: number,\n storageClass: \"STANDARD\" | \"GLACIER_IR\" = \"STANDARD\",\n ): Promise<ConfirmResult> {\n return request<ConfirmResult>(\n \"POST\",\n `/sync/agents/${agentId}/files/${filePath}/confirm`,\n { hash, size, storageClass },\n );\n },\n\n /**\n * Request a presigned GET URL for downloading a file.\n */\n async presignGet(filePath: string): Promise<PresignResult> {\n const result = await request<{ urls: PresignResult[] }>(\n \"POST\",\n `/sync/agents/${agentId}/presign`,\n {\n files: [{ path: filePath, operation: \"get\" }],\n },\n );\n return result.urls[0];\n },\n\n /**\n * Request presigned GET URLs for multiple files.\n */\n async presignGetBatch(paths: string[]): Promise<PresignResult[]> {\n const result = await request<{ urls: PresignResult[] }>(\n \"POST\",\n `/sync/agents/${agentId}/presign`,\n {\n files: paths.map((p) => ({ path: p, operation: \"get\" as const })),\n },\n );\n return result.urls;\n },\n\n /**\n * Get agent storage stats.\n */\n async getStats(): Promise<AgentStats> {\n return request<AgentStats>(\n \"GET\",\n `/sync/agents/${agentId}/stats`,\n );\n },\n\n /**\n * Register this agent with the sync service.\n */\n async registerAgent(\n displayName?: string,\n ): Promise<RegisterAgentResult> {\n return request<RegisterAgentResult>(\n \"POST\",\n \"/sync/agents\",\n { agentId, displayName },\n );\n },\n\n /**\n * Get file version history.\n */\n async getFileHistory(filePath: string): Promise<FileHistoryEntry[]> {\n const result = await request<{\n filePath: string;\n versions: FileHistoryEntry[];\n }>(\"GET\", `/sync/agents/${agentId}/files/${filePath}/versions`);\n return result.versions;\n },\n\n /**\n * Generate a reconstruction bundle.\n */\n async reconstruct(\n mode: \"full\" | \"active\" | \"memory\" = \"full\",\n ): Promise<ReconstructResult> {\n return request<ReconstructResult>(\n \"POST\",\n `/sync/agents/${agentId}/reconstruct`,\n { mode },\n );\n },\n };\n}\n\nexport type ApiClient = ReturnType<typeof createApiClient>;\n","/**\n * AlfeSync uploader — upload changed files to S3 via presigned URLs.\n *\n * Flow per file:\n * 1. Request presigned PUT URL from API\n * 2. PUT file content directly to S3\n * 3. Notify API of completion (confirm endpoint)\n * 4. Update local manifest\n *\n * Retries 3x with exponential backoff on transient failures.\n */\n\nimport { readFile, stat } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { createLogger } from \"@auriclabs/logger\";\nimport type { ApiClient } from \"./api-client.js\";\n\nconst log = createLogger(\"SyncUploader\");\nimport { computeFileHash, updateManifestEntry } from \"./manifest.js\";\nimport type { ManifestEntry } from \"./manifest.js\";\n\nconst MAX_RETRIES = 3;\nconst BASE_DELAY_MS = 1000;\n\nexport interface UploadResult {\n path: string;\n success: boolean;\n hash?: string;\n size?: number;\n error?: string;\n}\n\n/**\n * Determine the storage class based on file path.\n */\nfunction getStorageClass(\n relativePath: string,\n): \"STANDARD\" | \"GLACIER_IR\" {\n return relativePath.startsWith(\"sessions/archive/\") ||\n relativePath.startsWith(\"context/archive/\")\n ? \"GLACIER_IR\"\n : \"STANDARD\";\n}\n\n/**\n * Determine MIME type from file path.\n */\nfunction getContentType(relativePath: string): string {\n if (relativePath.endsWith(\".json\")) return \"application/json\";\n if (relativePath.endsWith(\".md\")) return \"text/markdown\";\n if (relativePath.endsWith(\".txt\")) return \"text/plain\";\n if (relativePath.endsWith(\".gz\")) return \"application/gzip\";\n if (relativePath.endsWith(\".yaml\") || relativePath.endsWith(\".yml\"))\n return \"text/yaml\";\n return \"application/octet-stream\";\n}\n\n/**\n * Upload a single file with retry logic.\n */\nasync function uploadFileWithRetry(\n workspacePath: string,\n relativePath: string,\n client: ApiClient,\n): Promise<UploadResult> {\n const absolutePath = join(workspacePath, relativePath);\n let lastError: Error | undefined;\n\n for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {\n try {\n // Compute hash and get file size\n const [hash, fileStat] = await Promise.all([\n computeFileHash(absolutePath),\n stat(absolutePath),\n ]);\n const size = fileStat.size;\n const storageClass = getStorageClass(relativePath);\n const contentType = getContentType(relativePath);\n\n // 1. Get presigned PUT URL\n const presigned = await client.presignPut(relativePath, contentType);\n\n // 2. PUT to S3 directly\n const fileContent = await readFile(absolutePath);\n const putResponse = await fetch(presigned.url, {\n method: \"PUT\",\n headers: {\n \"Content-Type\": contentType,\n },\n body: fileContent,\n });\n\n if (!putResponse.ok) {\n throw new Error(\n `S3 PUT failed (${String(putResponse.status)}): ${await putResponse.text()}`,\n );\n }\n\n // 3. Confirm upload with API\n await client.confirmUpload(relativePath, hash, size, storageClass);\n\n // 4. Update local manifest\n const entry: ManifestEntry = {\n hash,\n size,\n lastSynced: new Date().toISOString(),\n storageClass,\n };\n await updateManifestEntry(workspacePath, relativePath, entry);\n\n return { path: relativePath, success: true, hash, size };\n } catch (err) {\n lastError = err instanceof Error ? err : new Error(String(err));\n\n if (attempt < MAX_RETRIES) {\n // Exponential backoff: 1s, 2s, 4s\n const delay = BASE_DELAY_MS * Math.pow(2, attempt);\n await new Promise((resolve) => setTimeout(resolve, delay));\n }\n }\n }\n\n return {\n path: relativePath,\n success: false,\n error: lastError?.message ?? \"Unknown error\",\n };\n}\n\n/**\n * Upload multiple files to S3.\n *\n * Uploads are performed in parallel with a concurrency limit.\n */\nexport async function uploadFiles(\n workspacePath: string,\n relativePaths: string[],\n client: ApiClient,\n options: { concurrency?: number; quiet?: boolean } = {},\n): Promise<UploadResult[]> {\n const { concurrency = 5, quiet = false } = options;\n const results: UploadResult[] = [];\n\n // Process in batches for controlled concurrency\n for (let i = 0; i < relativePaths.length; i += concurrency) {\n const batch = relativePaths.slice(i, i + concurrency);\n const batchResults = await Promise.all(\n batch.map((path) => uploadFileWithRetry(workspacePath, path, client)),\n );\n\n for (const result of batchResults) {\n results.push(result);\n if (!quiet) {\n if (result.success) {\n log.info(`Uploaded ${result.path} (${formatBytes(result.size ?? 0)})`);\n } else {\n log.error(`Failed to upload ${result.path}: ${result.error ?? \"Unknown error\"}`);\n }\n }\n }\n }\n\n return results;\n}\n\nfunction formatBytes(bytes: number): string {\n if (bytes < 1024) return `${String(bytes)} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n}\n","/**\n * AlfeSync downloader — download files from S3 via presigned URLs.\n *\n * Flow per file:\n * 1. Request presigned GET URL from API\n * 2. GET file content from S3\n * 3. Write to local disk\n * 4. Update local manifest\n */\n\nimport { writeFile, mkdir } from \"node:fs/promises\";\nimport { join, dirname } from \"node:path\";\nimport { createLogger } from \"@auriclabs/logger\";\nimport type { ApiClient } from \"./api-client.js\";\n\nconst log = createLogger(\"SyncDownloader\");\nimport { updateManifestEntry } from \"./manifest.js\";\nimport type { ManifestEntry, RemoteManifest } from \"./manifest.js\";\n\nconst MAX_RETRIES = 3;\nconst BASE_DELAY_MS = 1000;\n\nexport interface DownloadResult {\n path: string;\n success: boolean;\n size?: number;\n error?: string;\n}\n\n/**\n * Download a single file with retry logic.\n */\nasync function downloadFileWithRetry(\n workspacePath: string,\n relativePath: string,\n client: ApiClient,\n remoteEntry?: RemoteManifest[\"files\"][string],\n): Promise<DownloadResult> {\n let lastError: Error | undefined;\n\n for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {\n try {\n // 1. Get presigned GET URL\n const presigned = await client.presignGet(relativePath);\n\n // 2. GET from S3\n const response = await fetch(presigned.url);\n if (!response.ok) {\n throw new Error(\n `S3 GET failed (${String(response.status)}): ${await response.text()}`,\n );\n }\n\n const buffer = Buffer.from(await response.arrayBuffer());\n\n // 3. Write to disk\n const absolutePath = join(workspacePath, relativePath);\n await mkdir(dirname(absolutePath), { recursive: true });\n await writeFile(absolutePath, buffer);\n\n // 4. Update local manifest\n const entry: ManifestEntry = {\n hash: remoteEntry?.hash ?? \"\",\n size: buffer.length,\n lastSynced: new Date().toISOString(),\n storageClass:\n (remoteEntry?.storageClass ?? \"STANDARD\") as \"STANDARD\" | \"GLACIER_IR\",\n };\n await updateManifestEntry(workspacePath, relativePath, entry);\n\n return { path: relativePath, success: true, size: buffer.length };\n } catch (err) {\n lastError = err instanceof Error ? err : new Error(String(err));\n\n if (attempt < MAX_RETRIES) {\n const delay = BASE_DELAY_MS * Math.pow(2, attempt);\n await new Promise((resolve) => setTimeout(resolve, delay));\n }\n }\n }\n\n return {\n path: relativePath,\n success: false,\n error: lastError?.message ?? \"Unknown error\",\n };\n}\n\n/**\n * Download multiple files from S3.\n *\n * Downloads are performed in parallel with a concurrency limit.\n */\nexport async function downloadFiles(\n workspacePath: string,\n relativePaths: string[],\n client: ApiClient,\n remoteManifest?: RemoteManifest,\n options: { concurrency?: number; quiet?: boolean } = {},\n): Promise<DownloadResult[]> {\n const { concurrency = 5, quiet = false } = options;\n const results: DownloadResult[] = [];\n\n for (let i = 0; i < relativePaths.length; i += concurrency) {\n const batch = relativePaths.slice(i, i + concurrency);\n const batchResults = await Promise.all(\n batch.map((path) =>\n downloadFileWithRetry(\n workspacePath,\n path,\n client,\n remoteManifest?.files[path],\n ),\n ),\n );\n\n for (const result of batchResults) {\n results.push(result);\n if (!quiet) {\n if (result.success) {\n log.info(`Downloaded ${result.path} (${formatBytes(result.size ?? 0)})`);\n } else {\n log.error(`Failed to download ${result.path}: ${result.error ?? \"Unknown error\"}`);\n }\n }\n }\n }\n\n return results;\n}\n\nfunction formatBytes(bytes: number): string {\n if (bytes < 1024) return `${String(bytes)} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n}\n","/**\n * AlfeSync engine — orchestrates push, pull, and full sync operations.\n *\n * Handles:\n * - push(paths[]): upload changed files to S3\n * - pull(): download files newer on remote\n * - fullSync(): bidirectional sync with conflict detection\n *\n * Conflict resolution: if remote file is newer than local manifest entry\n * AND local file has changed → write `.conflict-{timestamp}` alongside original.\n */\n\nimport { existsSync } from \"node:fs\";\nimport { readFile, writeFile } from \"node:fs/promises\";\nimport { join, dirname, basename, extname } from \"node:path\";\nimport { createLogger } from \"@auriclabs/logger\";\n\nconst log = createLogger(\"SyncEngine\");\nimport { createApiClient } from \"./api-client.js\";\nimport { requireConfig } from \"./config.js\";\nimport {\n readManifest,\n computeFileHash,\n diffManifests,\n removeManifestEntry,\n} from \"./manifest.js\";\nimport { loadIgnorePatterns, filterIgnored } from \"./ignore.js\";\nimport { uploadFiles } from \"./uploader.js\";\nimport { downloadFiles } from \"./downloader.js\";\n\nexport interface SyncResult {\n pushed: number;\n pulled: number;\n conflicts: number;\n errors: number;\n}\n\nexport interface SyncEngineOptions {\n workspacePath: string;\n quiet?: boolean;\n filter?: string;\n}\n\n/**\n * Create a sync engine for a workspace.\n */\nexport async function createSyncEngine(workspacePath: string) {\n const config = await requireConfig(workspacePath);\n const client = createApiClient({\n apiUrl: config.apiUrl,\n token: config.token,\n agentId: config.agentId,\n });\n\n return {\n config,\n client,\n\n /**\n * Push changed files to remote.\n *\n * If paths are provided, only push those files.\n * If no paths, detect all changed files and push them.\n */\n async push(\n paths?: string[],\n options: { quiet?: boolean; filter?: string } = {},\n ): Promise<SyncResult> {\n const { quiet = false, filter } = options;\n const ignorePatterns = await loadIgnorePatterns(workspacePath);\n\n let filesToPush: string[];\n\n if (paths && paths.length > 0) {\n filesToPush = filterIgnored(paths, ignorePatterns);\n } else {\n // Detect changed files by comparing local files to manifest\n filesToPush = await detectLocalChanges(\n workspacePath,\n ignorePatterns,\n );\n }\n\n // Apply filter if provided\n if (filter) {\n filesToPush = filesToPush.filter((p) => p.startsWith(filter));\n }\n\n if (filesToPush.length === 0) {\n if (!quiet) log.info(\"Nothing to push\");\n return { pushed: 0, pulled: 0, conflicts: 0, errors: 0 };\n }\n\n if (!quiet) {\n log.info(`Pushing ${String(filesToPush.length)} file(s)`);\n }\n\n const results = await uploadFiles(\n workspacePath,\n filesToPush,\n client,\n { quiet },\n );\n\n const pushed = results.filter((r) => r.success).length;\n const errors = results.filter((r) => !r.success).length;\n\n if (!quiet) {\n log.info(`Push complete: ${String(pushed)} uploaded, ${String(errors)} failed`);\n }\n\n return { pushed, pulled: 0, conflicts: 0, errors };\n },\n\n /**\n * Pull files from remote that are newer than local.\n */\n async pull(\n options: { quiet?: boolean } = {},\n ): Promise<SyncResult> {\n const { quiet = false } = options;\n\n const [localManifest, remoteManifest] = await Promise.all([\n readManifest(workspacePath),\n client.getManifest(),\n ]);\n\n const diff = diffManifests(localManifest, remoteManifest);\n const filesToPull = [...diff.toPull];\n\n if (filesToPull.length === 0) {\n if (!quiet) log.info(\"Nothing to pull\");\n return {\n pushed: 0,\n pulled: 0,\n conflicts: diff.conflicts.length,\n errors: 0,\n };\n }\n\n if (!quiet) {\n log.info(`Pulling ${String(filesToPull.length)} file(s)`);\n }\n\n const results = await downloadFiles(\n workspacePath,\n filesToPull,\n client,\n remoteManifest,\n { quiet },\n );\n\n const pulled = results.filter((r) => r.success).length;\n const errors = results.filter((r) => !r.success).length;\n\n if (!quiet) {\n log.info(`Pull complete: ${String(pulled)} downloaded, ${String(errors)} failed`);\n }\n\n return {\n pushed: 0,\n pulled,\n conflicts: diff.conflicts.length,\n errors,\n };\n },\n\n /**\n * Full bidirectional sync with conflict detection.\n *\n * For conflicts: if remote file is newer than local manifest entry AND\n * local file has changed → write `.conflict-{timestamp}` file alongside\n * the original, then pull the remote version.\n */\n async fullSync(\n options: { quiet?: boolean } = {},\n ): Promise<SyncResult> {\n const { quiet = false } = options;\n\n const [localManifest, remoteManifest] = await Promise.all([\n readManifest(workspacePath),\n client.getManifest(),\n ]);\n\n const ignorePatterns = await loadIgnorePatterns(workspacePath);\n const localChanges = await detectLocalChanges(\n workspacePath,\n ignorePatterns,\n );\n\n const diff = diffManifests(localManifest, remoteManifest);\n\n // Classify conflicts: only files that changed both locally AND remotely\n // are true conflicts. Files in diff.conflicts where only the remote\n // changed (local untouched since last sync) are clean pulls.\n const trueConflicts = diff.conflicts.filter((p) =>\n localChanges.includes(p),\n );\n const remoteOnlyChanges = diff.conflicts.filter(\n (p) => !localChanges.includes(p),\n );\n\n // Handle true conflicts: save local version as .conflict-{timestamp}\n let conflictCount = 0;\n for (const conflictPath of trueConflicts) {\n const absolutePath = join(workspacePath, conflictPath);\n if (existsSync(absolutePath)) {\n const ext = extname(conflictPath);\n const base = basename(conflictPath, ext);\n const dir = dirname(conflictPath);\n const timestamp = new Date()\n .toISOString()\n .replace(/[:.]/g, \"-\");\n const conflictName = `${base}.conflict-${timestamp}${ext}`;\n const conflictAbsolute = join(\n workspacePath,\n dir,\n conflictName,\n );\n\n const content = await readFile(absolutePath);\n await writeFile(conflictAbsolute, content);\n\n if (!quiet) {\n log.warn(`Conflict: ${conflictPath} — saved as ${conflictName}`);\n }\n conflictCount++;\n }\n }\n\n // Push local changes (excluding true conflicts — remote wins, local saved)\n const filesToPush = filterIgnored(\n [...diff.toPush, ...localChanges.filter((p) => !diff.conflicts.includes(p))],\n ignorePatterns,\n );\n\n // Pull remote changes + remote-only changes + true conflicts (remote wins)\n const filesToPull = [\n ...diff.toPull,\n ...remoteOnlyChanges,\n ...trueConflicts,\n ];\n\n const pushResult = { pushed: 0, errors: 0 };\n const pullResult = { pulled: 0, errors: 0 };\n\n if (filesToPush.length > 0) {\n if (!quiet) {\n log.info(`Pushing ${String(filesToPush.length)} file(s)`);\n }\n const results = await uploadFiles(\n workspacePath,\n [...new Set(filesToPush)],\n client,\n { quiet },\n );\n pushResult.pushed = results.filter((r) => r.success).length;\n pushResult.errors = results.filter((r) => !r.success).length;\n }\n\n if (filesToPull.length > 0) {\n if (!quiet) {\n log.info(`Pulling ${String(filesToPull.length)} file(s)`);\n }\n const results = await downloadFiles(\n workspacePath,\n filesToPull,\n client,\n remoteManifest,\n { quiet },\n );\n pullResult.pulled = results.filter((r) => r.success).length;\n pullResult.errors = results.filter((r) => !r.success).length;\n }\n\n const totalErrors = pushResult.errors + pullResult.errors;\n\n if (!quiet) {\n log.info(`Sync complete: ${String(pushResult.pushed)} pushed, ${String(pullResult.pulled)} pulled, ${String(conflictCount)} conflicts, ${String(totalErrors)} errors`);\n }\n\n return {\n pushed: pushResult.pushed,\n pulled: pullResult.pulled,\n conflicts: conflictCount,\n errors: totalErrors,\n };\n },\n\n /**\n * Pull specific files by path without a full manifest diff.\n *\n * Used for notification-driven pulls where we already know which\n * files changed on the remote.\n */\n async pullFiles(\n paths: string[],\n options: { quiet?: boolean } = {},\n ): Promise<SyncResult> {\n const { quiet = false } = options;\n\n if (paths.length === 0) {\n return { pushed: 0, pulled: 0, conflicts: 0, errors: 0 };\n }\n\n // Fetch remote manifest to get hashes for manifest update\n const remoteManifest = await client.getManifest();\n\n // Filter to only files that differ from our local manifest\n const localManifest = await readManifest(workspacePath);\n const filesToPull = paths.filter((p) => {\n if (!(p in remoteManifest.files)) return false; // File doesn't exist on remote\n const remoteEntry = remoteManifest.files[p];\n if (!(p in localManifest.files)) return true; // New file\n const localEntry = localManifest.files[p];\n return localEntry.hash !== remoteEntry.hash;\n });\n\n if (filesToPull.length === 0) {\n if (!quiet) log.info(\"All notified files already in sync\");\n return { pushed: 0, pulled: 0, conflicts: 0, errors: 0 };\n }\n\n if (!quiet) {\n log.info(`Pulling ${String(filesToPull.length)} changed file(s)`);\n }\n\n const results = await downloadFiles(\n workspacePath,\n filesToPull,\n client,\n remoteManifest,\n { quiet },\n );\n\n const pulled = results.filter((r) => r.success).length;\n const errors = results.filter((r) => !r.success).length;\n\n if (!quiet) {\n log.info(`Pull complete: ${String(pulled)} downloaded, ${String(errors)} failed`);\n }\n\n return { pushed: 0, pulled, conflicts: 0, errors };\n },\n\n /**\n * Remove a file locally and from the manifest.\n *\n * Used for notification-driven deletes when a remote file is removed.\n */\n async removeLocalFile(\n filePath: string,\n options: { quiet?: boolean } = {},\n ): Promise<void> {\n const { quiet = false } = options;\n const absolutePath = join(workspacePath, filePath);\n\n try {\n const { unlink } = await import(\"node:fs/promises\");\n if (existsSync(absolutePath)) {\n await unlink(absolutePath);\n if (!quiet) log.info(`Deleted: ${filePath}`);\n }\n } catch (err) {\n if (!quiet) log.error({ err }, `Failed to delete ${filePath}`);\n }\n\n await removeManifestEntry(workspacePath, filePath);\n },\n };\n}\n\n/**\n * Detect files that have changed locally since last sync.\n *\n * Compares current file hashes against the local manifest.\n * Returns list of relative paths that need pushing.\n */\nasync function detectLocalChanges(\n workspacePath: string,\n ignorePatterns: string[],\n): Promise<string[]> {\n const manifest = await readManifest(workspacePath);\n const changed: string[] = [];\n\n // Walk the workspace directory\n const { readdir } = await import(\"node:fs/promises\");\n\n async function walk(dir: string): Promise<void> {\n const entries = await readdir(dir, { withFileTypes: true });\n\n for (const entry of entries) {\n const fullPath = join(dir, entry.name);\n const relativePath = fullPath\n .slice(workspacePath.length + 1)\n .replace(/\\\\/g, \"/\");\n\n if (shouldSkipDir(entry.name)) continue;\n\n if (entry.isDirectory()) {\n // Check if directory is ignored before recursing\n const isIgnored = ignorePatterns.some((p) => {\n if (p.endsWith(\"/**\")) {\n return relativePath.startsWith(p.slice(0, -3));\n }\n return false;\n });\n if (!isIgnored) {\n await walk(fullPath);\n }\n } else if (entry.isFile()) {\n // Check if file is ignored\n const { shouldIgnore } = await import(\"./ignore.js\");\n if (shouldIgnore(relativePath, ignorePatterns)) continue;\n\n if (!(relativePath in manifest.files)) {\n // New file — needs push\n changed.push(relativePath);\n } else {\n const manifestEntry = manifest.files[relativePath];\n // Existing file — check if hash changed\n try {\n const currentHash = await computeFileHash(fullPath);\n if (currentHash !== manifestEntry.hash) {\n changed.push(relativePath);\n }\n } catch {\n // File might have been deleted between listing and hashing\n }\n }\n }\n }\n }\n\n await walk(workspacePath);\n return changed;\n}\n\n/**\n * Directories to always skip during walks.\n */\nfunction shouldSkipDir(name: string): boolean {\n return (\n name === \"node_modules\" ||\n name === \".git\" ||\n name === \".sst\" ||\n name === \".alfesync\" ||\n name === \".build\" ||\n name === \"dist\"\n );\n}\n\nexport type SyncEngine = Awaited<ReturnType<typeof createSyncEngine>>;\n"],"mappings":"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"}
1
+ {"version":3,"file":"sync-engine.js","names":["log","formatBytes","log"],"sources":["../src/manifest.ts","../src/ignore.ts","../src/retry.ts","../src/uploader.ts","../src/downloader.ts","../src/sync-engine.ts"],"sourcesContent":["/**\n * AlfeSync manifest — local file manifest at `~/.alfe/sync/manifest.json`.\n *\n * Tracks file hashes, sizes, sync timestamps, and storage classes\n * to enable efficient diff-based syncing. Lives under `~/.alfe/sync/`\n * so workspace directories stay clean.\n *\n * `workspacePath` is accepted by every function for consistency with the\n * rest of the package, but the manifest itself is workspace-independent\n * (one agent, one workspace, one manifest).\n */\n\nimport { readFile, writeFile, mkdir } from \"node:fs/promises\";\nimport { existsSync, createReadStream } from \"node:fs\";\nimport { createHash } from \"node:crypto\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\n\n/**\n * Local state directory for sync — manifest, etc. Lives under `~/.alfe/sync/`\n * so it stays alongside the rest of Alfe's agent state instead of polluting\n * the workspace.\n */\nconst SYNC_STATE_DIR = join(homedir(), \".alfe\", \"sync\");\n\nexport interface ManifestEntry {\n hash: string;\n size: number;\n lastSynced: string;\n storageClass: \"STANDARD\" | \"GLACIER_IR\";\n}\n\nexport interface LocalManifest {\n files: Record<string, ManifestEntry>;\n}\n\nexport interface RemoteManifest {\n version: 1;\n agentId: string;\n lastSync: string;\n files: Record<\n string,\n {\n hash: string;\n size: number;\n modified: string;\n etag?: string;\n storageClass?: string;\n compressed?: boolean;\n }\n >;\n}\n\nexport interface ManifestDiff {\n /** Files that exist locally but not on remote, or have changed locally */\n toPush: string[];\n /** Files that exist on remote but not locally, or are newer on remote */\n toPull: string[];\n /** Files that exist in both and have diverged (conflict) */\n conflicts: string[];\n /** Files that exist locally but were deleted on remote */\n remoteDeleted: string[];\n}\n\nconst MANIFEST_FILE = \"manifest.json\";\n\n/**\n * Resolve the manifest file path. Lives under `~/.alfe/sync/`, independent\n * of the workspace path — one agent has one manifest.\n */\nfunction manifestPath(): string {\n return join(SYNC_STATE_DIR, MANIFEST_FILE);\n}\n\n/**\n * Read the local manifest. Returns empty manifest if not found.\n *\n * `workspacePath` is accepted for call-site symmetry with the rest of the\n * package but is not used — the manifest path is resolved from\n * `~/.alfe/sync/` regardless of which workspace the call comes from.\n */\nexport async function readManifest(\n workspacePath: string,\n): Promise<LocalManifest> {\n void workspacePath;\n const path = manifestPath();\n if (!existsSync(path)) {\n return { files: {} };\n }\n\n try {\n const raw = await readFile(path, \"utf-8\");\n return JSON.parse(raw) as LocalManifest;\n } catch {\n return { files: {} };\n }\n}\n\n/**\n * Write the local manifest. `workspacePath` is accepted for call-site\n * symmetry but unused (see `readManifest`).\n */\nexport async function writeManifest(\n workspacePath: string,\n manifest: LocalManifest,\n): Promise<void> {\n void workspacePath;\n const path = manifestPath();\n await mkdir(SYNC_STATE_DIR, { recursive: true });\n await writeFile(path, JSON.stringify(manifest, null, 2) + \"\\n\", \"utf-8\");\n}\n\n/**\n * Update a single file entry in the local manifest.\n */\nexport async function updateManifestEntry(\n workspacePath: string,\n relativePath: string,\n entry: ManifestEntry,\n): Promise<void> {\n const manifest = await readManifest(workspacePath);\n manifest.files[relativePath] = entry;\n await writeManifest(workspacePath, manifest);\n}\n\n/**\n * Remove a file entry from the local manifest.\n */\nexport async function removeManifestEntry(\n workspacePath: string,\n relativePath: string,\n): Promise<void> {\n const manifest = await readManifest(workspacePath);\n const remainingFiles = Object.fromEntries(\n Object.entries(manifest.files).filter(([key]) => key !== relativePath),\n );\n manifest.files = remainingFiles;\n await writeManifest(workspacePath, manifest);\n}\n\n/**\n * Compute SHA-256 hash of a file using streaming (memory-efficient).\n * Returns `sha256:<hex>` format.\n */\nexport async function computeFileHash(filePath: string): Promise<string> {\n return new Promise((resolve, reject) => {\n const hash = createHash(\"sha256\");\n const stream = createReadStream(filePath);\n\n stream.on(\"data\", (chunk) => hash.update(chunk));\n stream.on(\"end\", () => { resolve(`sha256:${hash.digest(\"hex\")}`); });\n stream.on(\"error\", reject);\n });\n}\n\n/**\n * Diff the local manifest against the remote manifest.\n *\n * Returns lists of files to push, pull, and conflicts.\n */\nexport function diffManifests(\n local: LocalManifest,\n remote: RemoteManifest,\n): ManifestDiff {\n const toPush: string[] = [];\n const toPull: string[] = [];\n const conflicts: string[] = [];\n const remoteDeleted: string[] = [];\n\n const localPaths = new Set(Object.keys(local.files));\n const remotePaths = new Set(Object.keys(remote.files));\n\n // Files only in local → push\n for (const path of localPaths) {\n if (!remotePaths.has(path)) {\n toPush.push(path);\n }\n }\n\n // Files only in remote → pull\n for (const path of remotePaths) {\n if (!localPaths.has(path)) {\n toPull.push(path);\n }\n }\n\n // Files in both → compare hashes\n for (const path of localPaths) {\n if (!remotePaths.has(path)) continue;\n\n const localEntry = local.files[path];\n const remoteEntry = remote.files[path];\n\n if (localEntry.hash === remoteEntry.hash) {\n // In sync — nothing to do\n continue;\n }\n\n // Hashes differ — determine direction\n const localSyncTime = new Date(localEntry.lastSynced).getTime();\n const remoteModTime = new Date(remoteEntry.modified).getTime();\n\n if (remoteModTime > localSyncTime) {\n // Remote is newer than our last sync — could be a conflict\n // If local file was also modified (hash differs from what we synced),\n // it's a conflict\n conflicts.push(path);\n } else {\n // Local is newer — push\n toPush.push(path);\n }\n }\n\n // Files that were in our manifest but deleted from remote\n for (const path of localPaths) {\n if (\n local.files[path].lastSynced !== \"\" &&\n !remotePaths.has(path)\n ) {\n remoteDeleted.push(path);\n }\n }\n\n return { toPush, toPull, conflicts, remoteDeleted };\n}\n","/**\n * AlfeSync ignore — parse `.alfesyncignore` (gitignore-style glob matching).\n *\n * Uses micromatch for glob pattern matching, compatible with .gitignore syntax.\n */\n\nimport { readFile } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport micromatch from \"micromatch\";\n\n/** Default ignore patterns — always applied */\nconst DEFAULT_IGNORES = [\n \".alfesync/**\",\n \"node_modules/**\",\n \"*.tmp\",\n \".DS_Store\",\n \".git/**\",\n \".sst/**\",\n \".build/**\",\n \"dist/**\",\n];\n\n/**\n * Load ignore patterns from `.alfesyncignore` file + defaults.\n */\nexport async function loadIgnorePatterns(\n workspacePath: string,\n): Promise<string[]> {\n const ignoreFile = join(workspacePath, \".alfesyncignore\");\n const patterns = [...DEFAULT_IGNORES];\n\n if (existsSync(ignoreFile)) {\n try {\n const content = await readFile(ignoreFile, \"utf-8\");\n const lines = content.split(\"\\n\");\n\n for (const line of lines) {\n const trimmed = line.trim();\n // Skip empty lines and comments\n if (!trimmed || trimmed.startsWith(\"#\")) continue;\n patterns.push(trimmed);\n }\n } catch {\n // Ignore read errors — use defaults only\n }\n }\n\n return patterns;\n}\n\n/**\n * Check if a relative path should be ignored.\n */\nexport function shouldIgnore(\n relativePath: string,\n patterns: string[],\n): boolean {\n return micromatch.isMatch(relativePath, patterns, {\n dot: true,\n matchBase: true,\n });\n}\n\n/**\n * Filter a list of relative paths, removing ignored ones.\n */\nexport function filterIgnored(\n paths: string[],\n patterns: string[],\n): string[] {\n return paths.filter((p) => !shouldIgnore(p, patterns));\n}\n","/**\n * Tiny retry helper used by uploader/downloader. Extracted so both\n * use the same timing and surface the same final error shape.\n */\n\nconst DEFAULT_MAX_RETRIES = 3;\nconst DEFAULT_BASE_DELAY_MS = 1000;\n\nexport interface RetryOptions {\n maxRetries?: number;\n baseDelayMs?: number;\n}\n\n/**\n * Run `fn` up to `maxRetries + 1` times with exponential backoff\n * (`base * 2^attempt`). Returns the first successful value, or rethrows\n * the last error.\n */\nexport async function withRetry<T>(\n fn: () => Promise<T>,\n options: RetryOptions = {},\n): Promise<T> {\n const maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;\n const baseDelayMs = options.baseDelayMs ?? DEFAULT_BASE_DELAY_MS;\n let lastError: unknown;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n return await fn();\n } catch (err) {\n lastError = err;\n if (attempt < maxRetries) {\n const delay = baseDelayMs * Math.pow(2, attempt);\n await new Promise((resolve) => setTimeout(resolve, delay));\n }\n }\n }\n\n throw lastError instanceof Error ? lastError : new Error(String(lastError));\n}\n","/**\n * AlfeSync uploader — push changed files to S3.\n *\n * Per file:\n * 1. Ask the agent API for a presigned PUT URL\n * 2. PUT bytes directly to S3 (raw fetch — S3 isn't an Alfe API)\n * 3. Tell the agent API the upload completed (`syncConfirmUpload`)\n * 4. Update the local manifest\n *\n * Each step retries with exponential backoff via `withRetry`.\n */\n\nimport { readFile, stat } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { createLogger } from \"@auriclabs/logger\";\nimport type { AgentApiClient } from \"@alfe.ai/agent-api-client\";\n\nimport { computeFileHash, updateManifestEntry } from \"./manifest.js\";\nimport type { ManifestEntry } from \"./manifest.js\";\nimport { withRetry } from \"./retry.js\";\n\nconst log = createLogger(\"SyncUploader\");\n\nexport interface UploadResult {\n path: string;\n success: boolean;\n hash?: string;\n size?: number;\n error?: string;\n}\n\nconst ARCHIVE_PREFIXES = [\"sessions/archive/\", \"context/archive/\"] as const;\n\nfunction getStorageClass(relativePath: string): \"STANDARD\" | \"GLACIER_IR\" {\n return ARCHIVE_PREFIXES.some((p) => relativePath.startsWith(p))\n ? \"GLACIER_IR\"\n : \"STANDARD\";\n}\n\nfunction getContentType(relativePath: string): string {\n if (relativePath.endsWith(\".json\")) return \"application/json\";\n if (relativePath.endsWith(\".md\")) return \"text/markdown\";\n if (relativePath.endsWith(\".txt\")) return \"text/plain\";\n if (relativePath.endsWith(\".gz\")) return \"application/gzip\";\n if (relativePath.endsWith(\".yaml\") || relativePath.endsWith(\".yml\")) return \"text/yaml\";\n return \"application/octet-stream\";\n}\n\nasync function uploadOne(\n workspacePath: string,\n relativePath: string,\n client: AgentApiClient,\n): Promise<UploadResult> {\n const absolutePath = join(workspacePath, relativePath);\n try {\n return await withRetry(async () => {\n const [hash, fileStat] = await Promise.all([\n computeFileHash(absolutePath),\n stat(absolutePath),\n ]);\n const size = fileStat.size;\n const storageClass = getStorageClass(relativePath);\n const contentType = getContentType(relativePath);\n\n // 1. Presigned PUT URL\n const presigned = await client.syncPresign({\n files: [{ path: relativePath, operation: \"put\", contentType }],\n });\n const url = presigned.urls[0]?.url;\n if (!url) throw new Error(\"No presigned URL returned\");\n\n // 2. Upload bytes directly to S3 (not an Alfe API)\n const fileContent = await readFile(absolutePath);\n const putResponse = await fetch(url, {\n method: \"PUT\",\n headers: { \"Content-Type\": contentType },\n body: fileContent,\n });\n if (!putResponse.ok) {\n throw new Error(\n `S3 PUT failed (${String(putResponse.status)}): ${await putResponse.text()}`,\n );\n }\n\n // 3. Confirm upload via agent API\n await client.syncConfirmUpload({\n filePath: relativePath,\n hash,\n size,\n storageClass,\n });\n\n // 4. Update local manifest\n const entry: ManifestEntry = {\n hash,\n size,\n lastSynced: new Date().toISOString(),\n storageClass,\n };\n await updateManifestEntry(workspacePath, relativePath, entry);\n\n return { path: relativePath, success: true, hash, size };\n });\n } catch (err) {\n return {\n path: relativePath,\n success: false,\n error: err instanceof Error ? err.message : String(err),\n };\n }\n}\n\n/**\n * Upload many files, batched by `concurrency`.\n */\nexport async function uploadFiles(\n workspacePath: string,\n relativePaths: string[],\n client: AgentApiClient,\n options: { concurrency?: number; quiet?: boolean } = {},\n): Promise<UploadResult[]> {\n const { concurrency = 5, quiet = false } = options;\n const results: UploadResult[] = [];\n\n for (let i = 0; i < relativePaths.length; i += concurrency) {\n const batch = relativePaths.slice(i, i + concurrency);\n const batchResults = await Promise.all(\n batch.map((path) => uploadOne(workspacePath, path, client)),\n );\n for (const result of batchResults) {\n results.push(result);\n if (!quiet) {\n if (result.success) {\n log.info(`Uploaded ${result.path} (${formatBytes(result.size ?? 0)})`);\n } else {\n log.error(`Failed to upload ${result.path}: ${result.error ?? \"Unknown error\"}`);\n }\n }\n }\n }\n\n return results;\n}\n\nfunction formatBytes(bytes: number): string {\n if (bytes < 1024) return `${String(bytes)} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n}\n","/**\n * AlfeSync downloader — pull files from S3 to disk.\n *\n * Per file:\n * 1. Ask the agent API for a presigned GET URL\n * 2. GET bytes directly from S3 (raw fetch — S3 isn't an Alfe API)\n * 3. Write to disk\n * 4. Update the local manifest\n */\n\nimport { writeFile, mkdir } from \"node:fs/promises\";\nimport { join, dirname } from \"node:path\";\nimport { createLogger } from \"@auriclabs/logger\";\nimport type { AgentApiClient } from \"@alfe.ai/agent-api-client\";\n\nimport { updateManifestEntry } from \"./manifest.js\";\nimport type { ManifestEntry, RemoteManifest } from \"./manifest.js\";\nimport { withRetry } from \"./retry.js\";\n\nconst log = createLogger(\"SyncDownloader\");\n\nexport interface DownloadResult {\n path: string;\n success: boolean;\n size?: number;\n error?: string;\n}\n\nasync function downloadOne(\n workspacePath: string,\n relativePath: string,\n client: AgentApiClient,\n remoteEntry?: RemoteManifest[\"files\"][string],\n): Promise<DownloadResult> {\n try {\n return await withRetry(async () => {\n const presigned = await client.syncPresign({\n files: [{ path: relativePath, operation: \"get\" }],\n });\n const url = presigned.urls[0]?.url;\n if (!url) throw new Error(\"No presigned URL returned\");\n\n const response = await fetch(url);\n if (!response.ok) {\n throw new Error(\n `S3 GET failed (${String(response.status)}): ${await response.text()}`,\n );\n }\n const buffer = Buffer.from(await response.arrayBuffer());\n\n const absolutePath = join(workspacePath, relativePath);\n await mkdir(dirname(absolutePath), { recursive: true });\n await writeFile(absolutePath, buffer);\n\n const entry: ManifestEntry = {\n hash: remoteEntry?.hash ?? \"\",\n size: buffer.length,\n lastSynced: new Date().toISOString(),\n storageClass:\n (remoteEntry?.storageClass ?? \"STANDARD\") as \"STANDARD\" | \"GLACIER_IR\",\n };\n await updateManifestEntry(workspacePath, relativePath, entry);\n\n return { path: relativePath, success: true, size: buffer.length };\n });\n } catch (err) {\n return {\n path: relativePath,\n success: false,\n error: err instanceof Error ? err.message : String(err),\n };\n }\n}\n\nexport async function downloadFiles(\n workspacePath: string,\n relativePaths: string[],\n client: AgentApiClient,\n remoteManifest?: RemoteManifest,\n options: { concurrency?: number; quiet?: boolean } = {},\n): Promise<DownloadResult[]> {\n const { concurrency = 5, quiet = false } = options;\n const results: DownloadResult[] = [];\n\n for (let i = 0; i < relativePaths.length; i += concurrency) {\n const batch = relativePaths.slice(i, i + concurrency);\n const batchResults = await Promise.all(\n batch.map((path) =>\n downloadOne(workspacePath, path, client, remoteManifest?.files[path]),\n ),\n );\n for (const result of batchResults) {\n results.push(result);\n if (!quiet) {\n if (result.success) {\n log.info(`Downloaded ${result.path} (${formatBytes(result.size ?? 0)})`);\n } else {\n log.error(`Failed to download ${result.path}: ${result.error ?? \"Unknown error\"}`);\n }\n }\n }\n }\n\n return results;\n}\n\nfunction formatBytes(bytes: number): string {\n if (bytes < 1024) return `${String(bytes)} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n}\n","/**\n * AlfeSync engine — orchestrates push, pull, and full sync.\n *\n * - push(paths[]): upload changed files to S3\n * - pull(): download files newer on remote\n * - fullSync(): bidirectional sync with conflict detection\n *\n * Conflict resolution: when a remote file is newer than the local manifest\n * entry AND the local file has also changed, the local copy is renamed to\n * `<name>.conflict-<timestamp>.<ext>` and the remote version wins.\n */\n\nimport { existsSync } from \"node:fs\";\nimport { readFile, writeFile } from \"node:fs/promises\";\nimport { join, dirname, basename, extname } from \"node:path\";\nimport { createLogger } from \"@auriclabs/logger\";\nimport type { AgentApiClient, SyncManifest } from \"@alfe.ai/agent-api-client\";\n\nimport {\n readManifest,\n computeFileHash,\n diffManifests,\n removeManifestEntry,\n} from \"./manifest.js\";\nimport { loadIgnorePatterns, filterIgnored, shouldIgnore } from \"./ignore.js\";\nimport { uploadFiles } from \"./uploader.js\";\nimport { downloadFiles } from \"./downloader.js\";\n\nconst log = createLogger(\"SyncEngine\");\n\nexport interface SyncResult {\n pushed: number;\n pulled: number;\n conflicts: number;\n errors: number;\n}\n\nexport interface SyncEngineOptions {\n workspacePath: string;\n client: AgentApiClient;\n}\n\n/**\n * Construct a sync engine bound to a workspace + agent API client.\n *\n * The client is constructed once in plugin.ts (or the CLI) and passed in,\n * so credentials never leak into multiple places.\n */\nexport function createSyncEngine({ workspacePath, client }: SyncEngineOptions) {\n return {\n workspacePath,\n client,\n\n /**\n * Upload changed files. Pass `paths` for an explicit list, or omit\n * to scan the workspace for anything that drifted from the manifest.\n */\n async push(\n paths?: string[],\n options: { quiet?: boolean; filter?: string } = {},\n ): Promise<SyncResult> {\n const { quiet = false, filter } = options;\n const ignorePatterns = await loadIgnorePatterns(workspacePath);\n\n let filesToPush: string[] =\n paths && paths.length > 0\n ? filterIgnored(paths, ignorePatterns)\n : await detectLocalChanges(workspacePath, ignorePatterns);\n\n if (filter) {\n filesToPush = filesToPush.filter((p) => p.startsWith(filter));\n }\n\n if (filesToPush.length === 0) {\n if (!quiet) log.info(\"Nothing to push\");\n return { pushed: 0, pulled: 0, conflicts: 0, errors: 0 };\n }\n\n if (!quiet) log.info(`Pushing ${String(filesToPush.length)} file(s)`);\n\n const results = await uploadFiles(workspacePath, filesToPush, client, { quiet });\n const pushed = results.filter((r) => r.success).length;\n const errors = results.filter((r) => !r.success).length;\n\n if (!quiet) {\n log.info(`Push complete: ${String(pushed)} uploaded, ${String(errors)} failed`);\n }\n\n return { pushed, pulled: 0, conflicts: 0, errors };\n },\n\n /** Download files newer on remote. */\n async pull(options: { quiet?: boolean } = {}): Promise<SyncResult> {\n const { quiet = false } = options;\n\n const [localManifest, remoteManifest] = await Promise.all([\n readManifest(workspacePath),\n client.syncGetManifest(),\n ]);\n\n const diff = diffManifests(localManifest, remoteManifest);\n if (diff.toPull.length === 0) {\n if (!quiet) log.info(\"Nothing to pull\");\n return { pushed: 0, pulled: 0, conflicts: diff.conflicts.length, errors: 0 };\n }\n\n if (!quiet) log.info(`Pulling ${String(diff.toPull.length)} file(s)`);\n\n const results = await downloadFiles(\n workspacePath,\n [...diff.toPull],\n client,\n remoteManifest,\n { quiet },\n );\n const pulled = results.filter((r) => r.success).length;\n const errors = results.filter((r) => !r.success).length;\n\n if (!quiet) {\n log.info(`Pull complete: ${String(pulled)} downloaded, ${String(errors)} failed`);\n }\n\n return { pushed: 0, pulled, conflicts: diff.conflicts.length, errors };\n },\n\n /**\n * Bidirectional sync. True conflicts (changed on both sides) are saved\n * locally as `.conflict-<ts>` files and the remote version wins.\n */\n async fullSync(options: { quiet?: boolean } = {}): Promise<SyncResult> {\n const { quiet = false } = options;\n\n const [localManifest, remoteManifest] = await Promise.all([\n readManifest(workspacePath),\n client.syncGetManifest(),\n ]);\n\n const ignorePatterns = await loadIgnorePatterns(workspacePath);\n const localChanges = await detectLocalChanges(workspacePath, ignorePatterns);\n\n const diff = diffManifests(localManifest, remoteManifest);\n const trueConflicts = diff.conflicts.filter((p) => localChanges.includes(p));\n const remoteOnlyChanges = diff.conflicts.filter((p) => !localChanges.includes(p));\n\n let conflictCount = 0;\n for (const conflictPath of trueConflicts) {\n const absolutePath = join(workspacePath, conflictPath);\n if (!existsSync(absolutePath)) continue;\n const ext = extname(conflictPath);\n const base = basename(conflictPath, ext);\n const dir = dirname(conflictPath);\n const timestamp = new Date().toISOString().replace(/[:.]/g, \"-\");\n const conflictName = `${base}.conflict-${timestamp}${ext}`;\n const conflictAbsolute = join(workspacePath, dir, conflictName);\n\n const content = await readFile(absolutePath);\n await writeFile(conflictAbsolute, content);\n\n if (!quiet) log.warn(`Conflict: ${conflictPath} — saved as ${conflictName}`);\n conflictCount++;\n }\n\n const filesToPush = filterIgnored(\n [...diff.toPush, ...localChanges.filter((p) => !diff.conflicts.includes(p))],\n ignorePatterns,\n );\n const filesToPull = [\n ...diff.toPull,\n ...remoteOnlyChanges,\n ...trueConflicts,\n ];\n\n const pushResult = { pushed: 0, errors: 0 };\n const pullResult = { pulled: 0, errors: 0 };\n\n if (filesToPush.length > 0) {\n if (!quiet) log.info(`Pushing ${String(filesToPush.length)} file(s)`);\n const results = await uploadFiles(\n workspacePath,\n [...new Set(filesToPush)],\n client,\n { quiet },\n );\n pushResult.pushed = results.filter((r) => r.success).length;\n pushResult.errors = results.filter((r) => !r.success).length;\n }\n\n if (filesToPull.length > 0) {\n if (!quiet) log.info(`Pulling ${String(filesToPull.length)} file(s)`);\n const results = await downloadFiles(\n workspacePath,\n filesToPull,\n client,\n remoteManifest,\n { quiet },\n );\n pullResult.pulled = results.filter((r) => r.success).length;\n pullResult.errors = results.filter((r) => !r.success).length;\n }\n\n const totalErrors = pushResult.errors + pullResult.errors;\n if (!quiet) {\n log.info(\n `Sync complete: ${String(pushResult.pushed)} pushed, ${String(pullResult.pulled)} pulled, ${String(conflictCount)} conflicts, ${String(totalErrors)} errors`,\n );\n }\n return {\n pushed: pushResult.pushed,\n pulled: pullResult.pulled,\n conflicts: conflictCount,\n errors: totalErrors,\n };\n },\n\n /**\n * Pull a known list of files (used for relay-driven notifications,\n * where we already know which paths changed remotely).\n */\n async pullFiles(\n paths: string[],\n options: { quiet?: boolean } = {},\n ): Promise<SyncResult> {\n const { quiet = false } = options;\n if (paths.length === 0) return { pushed: 0, pulled: 0, conflicts: 0, errors: 0 };\n\n const remoteManifest: SyncManifest = await client.syncGetManifest();\n const localManifest = await readManifest(workspacePath);\n\n const filesToPull = paths.filter((p) => {\n if (!(p in remoteManifest.files)) return false;\n if (!(p in localManifest.files)) return true;\n return localManifest.files[p].hash !== remoteManifest.files[p].hash;\n });\n\n if (filesToPull.length === 0) {\n if (!quiet) log.info(\"All notified files already in sync\");\n return { pushed: 0, pulled: 0, conflicts: 0, errors: 0 };\n }\n\n if (!quiet) log.info(`Pulling ${String(filesToPull.length)} changed file(s)`);\n\n const results = await downloadFiles(\n workspacePath,\n filesToPull,\n client,\n remoteManifest,\n { quiet },\n );\n const pulled = results.filter((r) => r.success).length;\n const errors = results.filter((r) => !r.success).length;\n\n if (!quiet) {\n log.info(`Pull complete: ${String(pulled)} downloaded, ${String(errors)} failed`);\n }\n return { pushed: 0, pulled, conflicts: 0, errors };\n },\n\n /**\n * Delete a file locally and from the manifest. Used when the sync relay\n * tells us a file was removed remotely.\n */\n async removeLocalFile(\n filePath: string,\n options: { quiet?: boolean } = {},\n ): Promise<void> {\n const { quiet = false } = options;\n const absolutePath = join(workspacePath, filePath);\n\n try {\n const { unlink } = await import(\"node:fs/promises\");\n if (existsSync(absolutePath)) {\n await unlink(absolutePath);\n if (!quiet) log.info(`Deleted: ${filePath}`);\n }\n } catch (err) {\n if (!quiet) log.error({ err }, `Failed to delete ${filePath}`);\n }\n\n await removeManifestEntry(workspacePath, filePath);\n },\n };\n}\n\nexport type SyncEngine = ReturnType<typeof createSyncEngine>;\n\n/**\n * Walk the workspace and return paths whose hash differs from the manifest\n * (or that are missing from it entirely).\n */\nasync function detectLocalChanges(\n workspacePath: string,\n ignorePatterns: string[],\n): Promise<string[]> {\n const manifest = await readManifest(workspacePath);\n const changed: string[] = [];\n\n const { readdir } = await import(\"node:fs/promises\");\n\n async function walk(dir: string): Promise<void> {\n const entries = await readdir(dir, { withFileTypes: true });\n for (const entry of entries) {\n const fullPath = join(dir, entry.name);\n const relativePath = fullPath\n .slice(workspacePath.length + 1)\n .replace(/\\\\/g, \"/\");\n\n if (shouldSkipDir(entry.name)) continue;\n\n if (entry.isDirectory()) {\n const isIgnored = ignorePatterns.some(\n (p) => p.endsWith(\"/**\") && relativePath.startsWith(p.slice(0, -3)),\n );\n if (!isIgnored) await walk(fullPath);\n } else if (entry.isFile()) {\n if (shouldIgnore(relativePath, ignorePatterns)) continue;\n if (!(relativePath in manifest.files)) {\n changed.push(relativePath);\n continue;\n }\n try {\n const currentHash = await computeFileHash(fullPath);\n if (currentHash !== manifest.files[relativePath].hash) {\n changed.push(relativePath);\n }\n } catch {\n // file may have been deleted between listing and hashing\n }\n }\n }\n }\n\n await walk(workspacePath);\n return changed;\n}\n\n/** Directories the engine never descends into. */\nfunction shouldSkipDir(name: string): boolean {\n return (\n name === \"node_modules\" ||\n name === \".git\" ||\n name === \".sst\" ||\n name === \".build\" ||\n name === \"dist\"\n );\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alfe.ai/openclaw-sync",
3
- "version": "0.0.15",
3
+ "version": "0.0.17",
4
4
  "description": "AlfeSync — agent workspace backup and sync skill for OpenClaw",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -21,6 +21,7 @@
21
21
  "commander": "^13.0.0",
22
22
  "micromatch": "^4.0.8",
23
23
  "ws": "^8.18.0",
24
+ "@alfe.ai/agent-api-client": "0.1.0",
24
25
  "@alfe.ai/config": "0.0.8"
25
26
  },
26
27
  "files": [
package/dist/ignore.cjs DELETED
@@ -1,120 +0,0 @@
1
- //#region \0rolldown/runtime.js
2
- var __create = Object.create;
3
- var __defProp = Object.defineProperty;
4
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
- var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
- var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __exportAll = (all, no_symbols) => {
9
- let target = {};
10
- for (var name in all) __defProp(target, name, {
11
- get: all[name],
12
- enumerable: true
13
- });
14
- if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
15
- return target;
16
- };
17
- var __copyProps = (to, from, except, desc) => {
18
- if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
19
- key = keys[i];
20
- if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
21
- get: ((k) => from[k]).bind(null, key),
22
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
23
- });
24
- }
25
- return to;
26
- };
27
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
28
- value: mod,
29
- enumerable: true
30
- }) : target, mod));
31
- //#endregion
32
- let node_fs_promises = require("node:fs/promises");
33
- let node_fs = require("node:fs");
34
- let node_path = require("node:path");
35
- let micromatch = require("micromatch");
36
- micromatch = __toESM(micromatch);
37
- //#region src/ignore.ts
38
- /**
39
- * AlfeSync ignore — parse `.alfesyncignore` (gitignore-style glob matching).
40
- *
41
- * Uses micromatch for glob pattern matching, compatible with .gitignore syntax.
42
- */
43
- var ignore_exports = /* @__PURE__ */ __exportAll({
44
- filterIgnored: () => filterIgnored,
45
- loadIgnorePatterns: () => loadIgnorePatterns,
46
- shouldIgnore: () => shouldIgnore
47
- });
48
- /** Default ignore patterns — always applied */
49
- const DEFAULT_IGNORES = [
50
- ".alfesync/**",
51
- "node_modules/**",
52
- "*.tmp",
53
- ".DS_Store",
54
- ".git/**",
55
- ".sst/**",
56
- ".build/**",
57
- "dist/**"
58
- ];
59
- /**
60
- * Load ignore patterns from `.alfesyncignore` file + defaults.
61
- */
62
- async function loadIgnorePatterns(workspacePath) {
63
- const ignoreFile = (0, node_path.join)(workspacePath, ".alfesyncignore");
64
- const patterns = [...DEFAULT_IGNORES];
65
- if ((0, node_fs.existsSync)(ignoreFile)) try {
66
- const lines = (await (0, node_fs_promises.readFile)(ignoreFile, "utf-8")).split("\n");
67
- for (const line of lines) {
68
- const trimmed = line.trim();
69
- if (!trimmed || trimmed.startsWith("#")) continue;
70
- patterns.push(trimmed);
71
- }
72
- } catch {}
73
- return patterns;
74
- }
75
- /**
76
- * Check if a relative path should be ignored.
77
- */
78
- function shouldIgnore(relativePath, patterns) {
79
- return micromatch.default.isMatch(relativePath, patterns, {
80
- dot: true,
81
- matchBase: true
82
- });
83
- }
84
- /**
85
- * Filter a list of relative paths, removing ignored ones.
86
- */
87
- function filterIgnored(paths, patterns) {
88
- return paths.filter((p) => !shouldIgnore(p, patterns));
89
- }
90
- //#endregion
91
- Object.defineProperty(exports, "__toESM", {
92
- enumerable: true,
93
- get: function() {
94
- return __toESM;
95
- }
96
- });
97
- Object.defineProperty(exports, "filterIgnored", {
98
- enumerable: true,
99
- get: function() {
100
- return filterIgnored;
101
- }
102
- });
103
- Object.defineProperty(exports, "ignore_exports", {
104
- enumerable: true,
105
- get: function() {
106
- return ignore_exports;
107
- }
108
- });
109
- Object.defineProperty(exports, "loadIgnorePatterns", {
110
- enumerable: true,
111
- get: function() {
112
- return loadIgnorePatterns;
113
- }
114
- });
115
- Object.defineProperty(exports, "shouldIgnore", {
116
- enumerable: true,
117
- get: function() {
118
- return shouldIgnore;
119
- }
120
- });
package/dist/ignore.js DELETED
@@ -1,74 +0,0 @@
1
- import "node:module";
2
- import { readFile } from "node:fs/promises";
3
- import { existsSync } from "node:fs";
4
- import { join } from "node:path";
5
- import micromatch from "micromatch";
6
- //#region \0rolldown/runtime.js
7
- var __defProp = Object.defineProperty;
8
- var __exportAll = (all, no_symbols) => {
9
- let target = {};
10
- for (var name in all) __defProp(target, name, {
11
- get: all[name],
12
- enumerable: true
13
- });
14
- if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
15
- return target;
16
- };
17
- //#endregion
18
- //#region src/ignore.ts
19
- /**
20
- * AlfeSync ignore — parse `.alfesyncignore` (gitignore-style glob matching).
21
- *
22
- * Uses micromatch for glob pattern matching, compatible with .gitignore syntax.
23
- */
24
- var ignore_exports = /* @__PURE__ */ __exportAll({
25
- filterIgnored: () => filterIgnored,
26
- loadIgnorePatterns: () => loadIgnorePatterns,
27
- shouldIgnore: () => shouldIgnore
28
- });
29
- /** Default ignore patterns — always applied */
30
- const DEFAULT_IGNORES = [
31
- ".alfesync/**",
32
- "node_modules/**",
33
- "*.tmp",
34
- ".DS_Store",
35
- ".git/**",
36
- ".sst/**",
37
- ".build/**",
38
- "dist/**"
39
- ];
40
- /**
41
- * Load ignore patterns from `.alfesyncignore` file + defaults.
42
- */
43
- async function loadIgnorePatterns(workspacePath) {
44
- const ignoreFile = join(workspacePath, ".alfesyncignore");
45
- const patterns = [...DEFAULT_IGNORES];
46
- if (existsSync(ignoreFile)) try {
47
- const lines = (await readFile(ignoreFile, "utf-8")).split("\n");
48
- for (const line of lines) {
49
- const trimmed = line.trim();
50
- if (!trimmed || trimmed.startsWith("#")) continue;
51
- patterns.push(trimmed);
52
- }
53
- } catch {}
54
- return patterns;
55
- }
56
- /**
57
- * Check if a relative path should be ignored.
58
- */
59
- function shouldIgnore(relativePath, patterns) {
60
- return micromatch.isMatch(relativePath, patterns, {
61
- dot: true,
62
- matchBase: true
63
- });
64
- }
65
- /**
66
- * Filter a list of relative paths, removing ignored ones.
67
- */
68
- function filterIgnored(paths, patterns) {
69
- return paths.filter((p) => !shouldIgnore(p, patterns));
70
- }
71
- //#endregion
72
- export { shouldIgnore as i, ignore_exports as n, loadIgnorePatterns as r, filterIgnored as t };
73
-
74
- //# sourceMappingURL=ignore.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ignore.js","names":[],"sources":["../src/ignore.ts"],"sourcesContent":["/**\n * AlfeSync ignore — parse `.alfesyncignore` (gitignore-style glob matching).\n *\n * Uses micromatch for glob pattern matching, compatible with .gitignore syntax.\n */\n\nimport { readFile } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport micromatch from \"micromatch\";\n\n/** Default ignore patterns — always applied */\nconst DEFAULT_IGNORES = [\n \".alfesync/**\",\n \"node_modules/**\",\n \"*.tmp\",\n \".DS_Store\",\n \".git/**\",\n \".sst/**\",\n \".build/**\",\n \"dist/**\",\n];\n\n/**\n * Load ignore patterns from `.alfesyncignore` file + defaults.\n */\nexport async function loadIgnorePatterns(\n workspacePath: string,\n): Promise<string[]> {\n const ignoreFile = join(workspacePath, \".alfesyncignore\");\n const patterns = [...DEFAULT_IGNORES];\n\n if (existsSync(ignoreFile)) {\n try {\n const content = await readFile(ignoreFile, \"utf-8\");\n const lines = content.split(\"\\n\");\n\n for (const line of lines) {\n const trimmed = line.trim();\n // Skip empty lines and comments\n if (!trimmed || trimmed.startsWith(\"#\")) continue;\n patterns.push(trimmed);\n }\n } catch {\n // Ignore read errors — use defaults only\n }\n }\n\n return patterns;\n}\n\n/**\n * Check if a relative path should be ignored.\n */\nexport function shouldIgnore(\n relativePath: string,\n patterns: string[],\n): boolean {\n return micromatch.isMatch(relativePath, patterns, {\n dot: true,\n matchBase: true,\n });\n}\n\n/**\n * Filter a list of relative paths, removing ignored ones.\n */\nexport function filterIgnored(\n paths: string[],\n patterns: string[],\n): string[] {\n return paths.filter((p) => !shouldIgnore(p, patterns));\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAYA,MAAM,kBAAkB;CACtB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD;;;;AAKD,eAAsB,mBACpB,eACmB;CACnB,MAAM,aAAa,KAAK,eAAe,kBAAkB;CACzD,MAAM,WAAW,CAAC,GAAG,gBAAgB;AAErC,KAAI,WAAW,WAAW,CACxB,KAAI;EAEF,MAAM,SADU,MAAM,SAAS,YAAY,QAAQ,EAC7B,MAAM,KAAK;AAEjC,OAAK,MAAM,QAAQ,OAAO;GACxB,MAAM,UAAU,KAAK,MAAM;AAE3B,OAAI,CAAC,WAAW,QAAQ,WAAW,IAAI,CAAE;AACzC,YAAS,KAAK,QAAQ;;SAElB;AAKV,QAAO;;;;;AAMT,SAAgB,aACd,cACA,UACS;AACT,QAAO,WAAW,QAAQ,cAAc,UAAU;EAChD,KAAK;EACL,WAAW;EACZ,CAAC;;;;;AAMJ,SAAgB,cACd,OACA,UACU;AACV,QAAO,MAAM,QAAQ,MAAM,CAAC,aAAa,GAAG,SAAS,CAAC"}