@alfe.ai/openclaw-sync 0.3.6 → 0.3.8

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.
package/README.md CHANGED
@@ -9,15 +9,15 @@ Syncs an agent's local workspace (config, conversations, memory) to/from S3 with
9
9
  ```
10
10
  src/
11
11
  ├── index.ts # Package exports
12
- ├── config.ts # .alfesync/config.json management
13
- ├── manifest.ts # Local/remote manifest + hash-based diff engine
14
- ├── ignore.ts # .alfesyncignore parsing (micromatch)
15
- ├── api-client.ts # Typed HTTP client (presigned URLs, manifest, stats)
12
+ ├── manifest.ts # Atomic, cross-process-locked local manifest + diffing
13
+ ├── ignore.ts # Forced defaults + .alfesyncignore parsing
14
+ ├── path-contract.ts # Canonical private paths + symlink containment
16
15
  ├── uploader.ts # S3 upload via presigned URLs with retries
17
16
  ├── downloader.ts # S3 download via presigned URLs with retries
18
- ├── watcher.ts # Chokidar file watcher with debounce
17
+ ├── watcher.ts # Chokidar watcher, debounce, live ignore reload
19
18
  ├── sync-engine.ts # Orchestration (push/pull/fullSync)
20
19
  ├── plugin.ts # OpenClaw plugin lifecycle + gateway IPC
20
+ ├── shared-sync.ts # Org/team/project shared-file mirror
21
21
  └── cli/
22
22
  └── index.ts # Commander-based CLI (alfesync)
23
23
  ```
@@ -2,6 +2,7 @@
2
2
  const require_sync_engine = require("../sync-engine.cjs");
3
3
  let node_fs_promises = require("node:fs/promises");
4
4
  let node_path = require("node:path");
5
+ let node_module = require("node:module");
5
6
  let _alfe_ai_config = require("@alfe.ai/config");
6
7
  let _alfe_ai_agent_api_client = require("@alfe.ai/agent-api-client");
7
8
  let commander = require("commander");
@@ -21,6 +22,7 @@ let commander = require("commander");
21
22
  * prune - Delete cloud files matching .alfesyncignore (dry-run by default)
22
23
  * restore - Restore agent workspace from remote
23
24
  */
25
+ const pkg = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href)("../../package.json");
24
26
  function buildClient() {
25
27
  if (!(0, _alfe_ai_config.configExists)()) throw new Error("Alfe not configured — run `alfe login` first.");
26
28
  const config = (0, _alfe_ai_config.resolveConfig)();
@@ -34,7 +36,7 @@ function buildClient() {
34
36
  };
35
37
  }
36
38
  const program = new commander.Command();
37
- program.name("alfesync").description("AlfeSync — agent workspace backup and sync").version("1.0.0");
39
+ program.name("alfesync").description("AlfeSync — agent workspace backup and sync").version(pkg.version);
38
40
  program.command("register").description("Register this agent with the sync service (idempotent)").option("-n, --display-name <name>", "Display name for the agent").action(async (opts) => {
39
41
  try {
40
42
  const { client, workspacePath } = buildClient();
@@ -125,8 +127,8 @@ program.command("status").description("Show sync status and pending changes").ac
125
127
  });
126
128
  program.command("conflicts").description("List conflict files in the workspace").action(async () => {
127
129
  try {
128
- const { workspacePath } = buildClient();
129
- const conflicts = await findConflictFiles(workspacePath);
130
+ const { workspacePath, runtime } = buildClient();
131
+ const conflicts = await findConflictFiles(workspacePath, await require_sync_engine.loadIgnorePatterns(workspacePath, runtime));
130
132
  if (conflicts.length === 0) {
131
133
  console.log("No conflict files found.");
132
134
  return;
@@ -189,40 +191,30 @@ program.command("restore").description("Restore agent workspace from remote back
189
191
  if (!opts.quiet) console.log(`Restoring workspace (mode: ${mode})...`);
190
192
  const bundle = await client.syncReconstruct({ mode });
191
193
  if (!opts.quiet) console.log(`Downloading ${String(bundle.fileCount)} files (${formatBytes(bundle.totalSize)})...`);
192
- const { writeFile, mkdir } = await import("node:fs/promises");
193
- const { dirname } = await import("node:path");
194
- let downloaded = 0;
195
- let errors = 0;
196
- for (const file of bundle.files) try {
197
- const response = await fetch(file.url);
198
- if (!response.ok) throw new Error(`HTTP ${String(response.status)}`);
199
- const buffer = Buffer.from(await response.arrayBuffer());
200
- const absolutePath = (0, node_path.join)(workspacePath, file.path);
201
- await mkdir(dirname(absolutePath), { recursive: true });
202
- await writeFile(absolutePath, buffer);
203
- downloaded++;
204
- if (!opts.quiet) console.log(` ↓ ${file.path} (${formatBytes(file.size)})`);
205
- } catch (err) {
206
- errors++;
207
- if (!opts.quiet) console.error(` ✗ ${file.path}: ${err instanceof Error ? err.message : String(err)}`);
208
- }
194
+ const remoteManifest = await client.syncGetManifest();
195
+ const results = await require_sync_engine.downloadFiles(workspacePath, bundle.files.map((file) => file.path), client, remoteManifest, { quiet: opts.quiet });
196
+ const downloaded = results.filter((result) => result.success).length;
197
+ const errors = results.length - downloaded;
209
198
  if (!opts.quiet) console.log(`\nRestore complete: ${String(downloaded)} files downloaded, ${String(errors)} errors.`);
210
199
  } catch (err) {
211
200
  console.error(err instanceof Error ? err.message : String(err));
212
201
  process.exit(1);
213
202
  }
214
203
  });
215
- async function findConflictFiles(dir, base) {
204
+ async function findConflictFiles(dir, ignoreRules, base = "") {
216
205
  const results = [];
217
206
  const entries = await (0, node_fs_promises.readdir)(dir, { withFileTypes: true });
218
207
  for (const entry of entries) {
219
- if (entry.name === "node_modules" || entry.name === ".git") continue;
220
208
  const fullPath = (0, node_path.join)(dir, entry.name);
221
- const relativePath = base ? (0, node_path.join)(base, entry.name) : entry.name;
209
+ const relativePath = base ? `${base}/${entry.name}` : entry.name;
222
210
  if (entry.isDirectory()) {
223
- const sub = await findConflictFiles(fullPath, relativePath);
211
+ if (require_sync_engine.shouldIgnoreDir(relativePath, ignoreRules)) continue;
212
+ const sub = await findConflictFiles(fullPath, ignoreRules, relativePath);
224
213
  results.push(...sub);
225
- } else if (entry.name.includes(".conflict-")) results.push(relativePath);
214
+ } else if (entry.name.includes(".conflict-")) {
215
+ if (require_sync_engine.shouldIgnore(relativePath, ignoreRules)) continue;
216
+ results.push(relativePath);
217
+ }
226
218
  }
227
219
  return results;
228
220
  }
package/dist/cli/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { c as loadIgnorePatterns, f as diffManifests, l as shouldIgnore, p as readManifest, t as createSyncEngine } from "../sync-engine.js";
2
+ import { c as loadIgnorePatterns, f as diffManifests, l as shouldIgnore, p as readManifest, r as downloadFiles, t as createSyncEngine, u as shouldIgnoreDir } from "../sync-engine.js";
3
+ import { createRequire } from "node:module";
3
4
  import { readdir } from "node:fs/promises";
4
5
  import { join } from "node:path";
5
6
  import { configExists, resolveConfig } from "@alfe.ai/config";
@@ -21,6 +22,7 @@ import { Command } from "commander";
21
22
  * prune - Delete cloud files matching .alfesyncignore (dry-run by default)
22
23
  * restore - Restore agent workspace from remote
23
24
  */
25
+ const pkg = createRequire(import.meta.url)("../../package.json");
24
26
  function buildClient() {
25
27
  if (!configExists()) throw new Error("Alfe not configured — run `alfe login` first.");
26
28
  const config = resolveConfig();
@@ -34,7 +36,7 @@ function buildClient() {
34
36
  };
35
37
  }
36
38
  const program = new Command();
37
- program.name("alfesync").description("AlfeSync — agent workspace backup and sync").version("1.0.0");
39
+ program.name("alfesync").description("AlfeSync — agent workspace backup and sync").version(pkg.version);
38
40
  program.command("register").description("Register this agent with the sync service (idempotent)").option("-n, --display-name <name>", "Display name for the agent").action(async (opts) => {
39
41
  try {
40
42
  const { client, workspacePath } = buildClient();
@@ -125,8 +127,8 @@ program.command("status").description("Show sync status and pending changes").ac
125
127
  });
126
128
  program.command("conflicts").description("List conflict files in the workspace").action(async () => {
127
129
  try {
128
- const { workspacePath } = buildClient();
129
- const conflicts = await findConflictFiles(workspacePath);
130
+ const { workspacePath, runtime } = buildClient();
131
+ const conflicts = await findConflictFiles(workspacePath, await loadIgnorePatterns(workspacePath, runtime));
130
132
  if (conflicts.length === 0) {
131
133
  console.log("No conflict files found.");
132
134
  return;
@@ -189,40 +191,30 @@ program.command("restore").description("Restore agent workspace from remote back
189
191
  if (!opts.quiet) console.log(`Restoring workspace (mode: ${mode})...`);
190
192
  const bundle = await client.syncReconstruct({ mode });
191
193
  if (!opts.quiet) console.log(`Downloading ${String(bundle.fileCount)} files (${formatBytes(bundle.totalSize)})...`);
192
- const { writeFile, mkdir } = await import("node:fs/promises");
193
- const { dirname } = await import("node:path");
194
- let downloaded = 0;
195
- let errors = 0;
196
- for (const file of bundle.files) try {
197
- const response = await fetch(file.url);
198
- if (!response.ok) throw new Error(`HTTP ${String(response.status)}`);
199
- const buffer = Buffer.from(await response.arrayBuffer());
200
- const absolutePath = join(workspacePath, file.path);
201
- await mkdir(dirname(absolutePath), { recursive: true });
202
- await writeFile(absolutePath, buffer);
203
- downloaded++;
204
- if (!opts.quiet) console.log(` ↓ ${file.path} (${formatBytes(file.size)})`);
205
- } catch (err) {
206
- errors++;
207
- if (!opts.quiet) console.error(` ✗ ${file.path}: ${err instanceof Error ? err.message : String(err)}`);
208
- }
194
+ const remoteManifest = await client.syncGetManifest();
195
+ const results = await downloadFiles(workspacePath, bundle.files.map((file) => file.path), client, remoteManifest, { quiet: opts.quiet });
196
+ const downloaded = results.filter((result) => result.success).length;
197
+ const errors = results.length - downloaded;
209
198
  if (!opts.quiet) console.log(`\nRestore complete: ${String(downloaded)} files downloaded, ${String(errors)} errors.`);
210
199
  } catch (err) {
211
200
  console.error(err instanceof Error ? err.message : String(err));
212
201
  process.exit(1);
213
202
  }
214
203
  });
215
- async function findConflictFiles(dir, base) {
204
+ async function findConflictFiles(dir, ignoreRules, base = "") {
216
205
  const results = [];
217
206
  const entries = await readdir(dir, { withFileTypes: true });
218
207
  for (const entry of entries) {
219
- if (entry.name === "node_modules" || entry.name === ".git") continue;
220
208
  const fullPath = join(dir, entry.name);
221
- const relativePath = base ? join(base, entry.name) : entry.name;
209
+ const relativePath = base ? `${base}/${entry.name}` : entry.name;
222
210
  if (entry.isDirectory()) {
223
- const sub = await findConflictFiles(fullPath, relativePath);
211
+ if (shouldIgnoreDir(relativePath, ignoreRules)) continue;
212
+ const sub = await findConflictFiles(fullPath, ignoreRules, relativePath);
224
213
  results.push(...sub);
225
- } else if (entry.name.includes(".conflict-")) results.push(relativePath);
214
+ } else if (entry.name.includes(".conflict-")) {
215
+ if (shouldIgnore(relativePath, ignoreRules)) continue;
216
+ results.push(relativePath);
217
+ }
226
218
  }
227
219
  return results;
228
220
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["resolveAlfeConfig"],"sources":["../../src/cli/index.ts"],"sourcesContent":["#!/usr/bin/env node\n\n/**\n * AlfeSync CLI — `alfesync` command-line interface.\n *\n * Credentials come from `~/.alfe/config.toml` (set up via `alfe login`).\n * No separate sync init step is required.\n *\n * Commands:\n * register - Idempotently register this agent with the sync service\n * push - Push local changes to remote\n * pull - Pull remote changes to local\n * status - Show sync status and pending changes\n * conflicts - List conflict files\n * prune - Delete cloud files matching .alfesyncignore (dry-run by default)\n * restore - Restore agent workspace from remote\n */\n\nimport { Command } from \"commander\";\nimport { join } from \"node:path\";\nimport { readdir } from \"node:fs/promises\";\nimport { AgentApiClient } from \"@alfe.ai/agent-api-client\";\nimport { resolveConfig as resolveAlfeConfig, configExists } from \"@alfe.ai/config\";\nimport { createSyncEngine } from \"../sync-engine.js\";\nimport { readManifest, diffManifests } from \"../manifest.js\";\nimport { loadIgnorePatterns, shouldIgnore } from \"../ignore.js\";\n\nfunction buildClient(): { client: AgentApiClient; workspacePath: string; runtime: string } {\n if (!configExists()) {\n throw new Error(\"Alfe not configured — run `alfe login` first.\");\n }\n const config = resolveAlfeConfig();\n return {\n client: new AgentApiClient({ apiKey: config.apiKey, apiUrl: config.apiUrl }),\n workspacePath: config.workspacePath,\n runtime: config.runtime,\n };\n}\n\nconst program = new Command();\n\nprogram\n .name(\"alfesync\")\n .description(\"AlfeSync — agent workspace backup and sync\")\n .version(\"1.0.0\");\n\n// ─── register ─────────────────────────────────────────────\n\nprogram\n .command(\"register\")\n .description(\"Register this agent with the sync service (idempotent)\")\n .option(\"-n, --display-name <name>\", \"Display name for the agent\")\n .action(async (opts: { displayName?: string }) => {\n try {\n const { client, workspacePath } = buildClient();\n const result = await client.syncRegister(\n opts.displayName ? { displayName: opts.displayName } : undefined,\n );\n console.log(\"✓ Agent registered with AlfeSync\");\n console.log(` Agent: ${result.agent.agentId}`);\n console.log(` Org: ${result.agent.tenantId}`);\n console.log(` Workspace: ${workspacePath}`);\n } catch (err) {\n console.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n });\n\n// ─── push ─────────────────────────────────────────────────\n\nprogram\n .command(\"push\")\n .description(\"Push local changes to remote\")\n .option(\"-q, --quiet\", \"Suppress output\")\n .option(\"-f, --filter <prefix>\", \"Only push files matching this prefix\")\n .action(async (opts: { quiet?: boolean; filter?: string }) => {\n try {\n const { client, workspacePath, runtime } = buildClient();\n const engine = createSyncEngine({ workspacePath, client, runtime });\n await engine.push(undefined, { quiet: opts.quiet, filter: opts.filter });\n } catch (err) {\n console.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n });\n\n// ─── pull ─────────────────────────────────────────────────\n\nprogram\n .command(\"pull\")\n .description(\"Pull remote changes to local\")\n .option(\"-q, --quiet\", \"Suppress output\")\n .action(async (opts: { quiet?: boolean }) => {\n try {\n const { client, workspacePath, runtime } = buildClient();\n const engine = createSyncEngine({ workspacePath, client, runtime });\n await engine.pull({ quiet: opts.quiet });\n } catch (err) {\n console.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n });\n\n// ─── status ───────────────────────────────────────────────\n\nprogram\n .command(\"status\")\n .description(\"Show sync status and pending changes\")\n .action(async () => {\n try {\n const { client, workspacePath } = buildClient();\n const [localManifest, remoteManifest, stats] = await Promise.all([\n readManifest(workspacePath),\n client.syncGetManifest(),\n client.syncGetStats(),\n ]);\n const diff = diffManifests(localManifest, remoteManifest);\n\n console.log(\"AlfeSync Status\");\n console.log(\"═══════════════\");\n console.log(`Agent: ${stats.agentId}`);\n console.log(`Workspace: ${workspacePath}`);\n console.log(\"\");\n\n console.log(\"Storage:\");\n console.log(` Standard: ${formatBytes(stats.standardBytes)}`);\n console.log(` Glacier IR: ${formatBytes(stats.glacierBytes)}`);\n console.log(` Total files: ${String(stats.fileCount)}`);\n console.log(` Last sync: ${stats.lastSyncAt ?? \"never\"}`);\n console.log(\"\");\n\n console.log(\"Pending Changes:\");\n console.log(` To push: ${String(diff.toPush.length)}`);\n console.log(` To pull: ${String(diff.toPull.length)}`);\n console.log(` Conflicts: ${String(diff.conflicts.length)}`);\n\n if (diff.toPush.length > 0) {\n console.log(\"\");\n console.log(\"Files to push:\");\n for (const p of diff.toPush.slice(0, 20)) console.log(` ↑ ${p}`);\n if (diff.toPush.length > 20) {\n console.log(` ... and ${String(diff.toPush.length - 20)} more`);\n }\n }\n\n if (diff.toPull.length > 0) {\n console.log(\"\");\n console.log(\"Files to pull:\");\n for (const p of diff.toPull.slice(0, 20)) console.log(` ↓ ${p}`);\n if (diff.toPull.length > 20) {\n console.log(` ... and ${String(diff.toPull.length - 20)} more`);\n }\n }\n\n if (diff.conflicts.length > 0) {\n console.log(\"\");\n console.log(\"Conflicts:\");\n for (const p of diff.conflicts) console.log(` ⚡ ${p}`);\n }\n } catch (err) {\n console.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n });\n\n// ─── conflicts ────────────────────────────────────────────\n\nprogram\n .command(\"conflicts\")\n .description(\"List conflict files in the workspace\")\n .action(async () => {\n try {\n const { workspacePath } = buildClient();\n const conflicts = await findConflictFiles(workspacePath);\n if (conflicts.length === 0) {\n console.log(\"No conflict files found.\");\n return;\n }\n console.log(`Found ${String(conflicts.length)} conflict file(s):`);\n for (const f of conflicts) console.log(` ⚡ ${f}`);\n } catch (err) {\n console.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n });\n\n// ─── prune ────────────────────────────────────────────────\n\nprogram\n .command(\"prune\")\n .description(\n \"Delete cloud files matching .alfesyncignore. Dry-run by default; pass --yes to actually delete.\",\n )\n .option(\"-y, --yes\", \"Actually delete files (default is dry-run)\")\n .action(async (opts: { yes?: boolean }) => {\n try {\n const { client, workspacePath, runtime } = buildClient();\n const ignorePatterns = await loadIgnorePatterns(workspacePath, runtime);\n const remoteManifest = await client.syncGetManifest();\n\n const ignoredPaths: { path: string; size: number }[] = [];\n for (const [path, entry] of Object.entries(remoteManifest.files)) {\n if (shouldIgnore(path, ignorePatterns)) {\n ignoredPaths.push({ path, size: entry.size });\n }\n }\n\n if (ignoredPaths.length === 0) {\n console.log(\"Nothing to prune — no cloud files match .alfesyncignore.\");\n return;\n }\n\n const totalSize = ignoredPaths.reduce((s, e) => s + e.size, 0);\n\n if (!opts.yes) {\n console.log(\n `Dry-run: ${String(ignoredPaths.length)} cloud file(s) would be deleted (${formatBytes(totalSize)}).`,\n );\n console.log(\"\");\n for (const { path, size } of ignoredPaths.slice(0, 50)) {\n console.log(` ✗ ${path} (${formatBytes(size)})`);\n }\n if (ignoredPaths.length > 50) {\n console.log(` ... and ${String(ignoredPaths.length - 50)} more`);\n }\n console.log(\"\");\n console.log(\"Re-run with --yes to delete these files from the cloud.\");\n return;\n }\n\n console.log(\n `Deleting ${String(ignoredPaths.length)} file(s) from cloud (${formatBytes(totalSize)})...`,\n );\n const engine = createSyncEngine({ workspacePath, client, runtime });\n const result = await engine.pushDeletes(\n ignoredPaths.map((e) => e.path),\n { quiet: true },\n );\n console.log(\n `Prune complete: ${String(result.pushed)} deleted, ${String(result.errors)} failed.`,\n );\n } catch (err) {\n console.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n });\n\n// ─── restore ──────────────────────────────────────────────\n\nprogram\n .command(\"restore\")\n .description(\"Restore agent workspace from remote backup\")\n .option(\"-m, --mode <mode>\", \"Restore mode: full, active, memory\", \"full\")\n .option(\"-q, --quiet\", \"Suppress output\")\n .action(async (opts: { mode?: string; quiet?: boolean }) => {\n try {\n const { client, workspacePath } = buildClient();\n const mode = opts.mode as \"full\" | \"active\" | \"memory\";\n if (![\"full\", \"active\", \"memory\"].includes(mode)) {\n console.error(\"Error: mode must be full, active, or memory\");\n process.exit(1);\n }\n\n if (!opts.quiet) console.log(`Restoring workspace (mode: ${mode})...`);\n const bundle = await client.syncReconstruct({ mode });\n\n if (!opts.quiet) {\n console.log(\n `Downloading ${String(bundle.fileCount)} files (${formatBytes(bundle.totalSize)})...`,\n );\n }\n\n const { writeFile, mkdir } = await import(\"node:fs/promises\");\n const { dirname } = await import(\"node:path\");\n\n let downloaded = 0;\n let errors = 0;\n for (const file of bundle.files) {\n try {\n const response = await fetch(file.url);\n if (!response.ok) throw new Error(`HTTP ${String(response.status)}`);\n const buffer = Buffer.from(await response.arrayBuffer());\n const absolutePath = join(workspacePath, file.path);\n await mkdir(dirname(absolutePath), { recursive: true });\n await writeFile(absolutePath, buffer);\n downloaded++;\n if (!opts.quiet) console.log(` ↓ ${file.path} (${formatBytes(file.size)})`);\n } catch (err) {\n errors++;\n if (!opts.quiet) {\n console.error(\n ` ✗ ${file.path}: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n }\n }\n\n if (!opts.quiet) {\n console.log(\n `\\nRestore complete: ${String(downloaded)} files downloaded, ${String(errors)} errors.`,\n );\n }\n } catch (err) {\n console.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n });\n\n// ─── Helpers ──────────────────────────────────────────────\n\nasync function findConflictFiles(dir: string, base?: string): Promise<string[]> {\n const results: string[] = [];\n const entries = await readdir(dir, { withFileTypes: true });\n for (const entry of entries) {\n if (entry.name === \"node_modules\" || entry.name === \".git\") continue;\n const fullPath = join(dir, entry.name);\n const relativePath = base ? join(base, entry.name) : entry.name;\n if (entry.isDirectory()) {\n const sub = await findConflictFiles(fullPath, relativePath);\n results.push(...sub);\n } else if (entry.name.includes(\".conflict-\")) {\n results.push(relativePath);\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 if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;\n}\n\n// ─── Run ──────────────────────────────────────────────────\n\nprogram.parse();\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["resolveAlfeConfig"],"sources":["../../src/cli/index.ts"],"sourcesContent":["#!/usr/bin/env node\n\n/**\n * AlfeSync CLI — `alfesync` command-line interface.\n *\n * Credentials come from `~/.alfe/config.toml` (set up via `alfe login`).\n * No separate sync init step is required.\n *\n * Commands:\n * register - Idempotently register this agent with the sync service\n * push - Push local changes to remote\n * pull - Pull remote changes to local\n * status - Show sync status and pending changes\n * conflicts - List conflict files\n * prune - Delete cloud files matching .alfesyncignore (dry-run by default)\n * restore - Restore agent workspace from remote\n */\n\nimport { Command } from \"commander\";\nimport { createRequire } from \"node:module\";\nimport { join } from \"node:path\";\nimport { readdir } from \"node:fs/promises\";\nimport { AgentApiClient } from \"@alfe.ai/agent-api-client\";\nimport { resolveConfig as resolveAlfeConfig, configExists } from \"@alfe.ai/config\";\nimport { createSyncEngine } from \"../sync-engine.js\";\nimport { readManifest, diffManifests } from \"../manifest.js\";\nimport {\n loadIgnorePatterns,\n shouldIgnore,\n shouldIgnoreDir,\n type IgnoreRules,\n} from \"../ignore.js\";\nimport { downloadFiles } from \"../downloader.js\";\n\nconst require = createRequire(import.meta.url);\nconst pkg = require(\"../../package.json\") as { version: string };\n\nfunction buildClient(): { client: AgentApiClient; workspacePath: string; runtime: string } {\n if (!configExists()) {\n throw new Error(\"Alfe not configured — run `alfe login` first.\");\n }\n const config = resolveAlfeConfig();\n return {\n client: new AgentApiClient({ apiKey: config.apiKey, apiUrl: config.apiUrl }),\n workspacePath: config.workspacePath,\n runtime: config.runtime,\n };\n}\n\nconst program = new Command();\n\nprogram\n .name(\"alfesync\")\n .description(\"AlfeSync — agent workspace backup and sync\")\n .version(pkg.version);\n\n// ─── register ─────────────────────────────────────────────\n\nprogram\n .command(\"register\")\n .description(\"Register this agent with the sync service (idempotent)\")\n .option(\"-n, --display-name <name>\", \"Display name for the agent\")\n .action(async (opts: { displayName?: string }) => {\n try {\n const { client, workspacePath } = buildClient();\n const result = await client.syncRegister(\n opts.displayName ? { displayName: opts.displayName } : undefined,\n );\n console.log(\"✓ Agent registered with AlfeSync\");\n console.log(` Agent: ${result.agent.agentId}`);\n console.log(` Org: ${result.agent.tenantId}`);\n console.log(` Workspace: ${workspacePath}`);\n } catch (err) {\n console.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n });\n\n// ─── push ─────────────────────────────────────────────────\n\nprogram\n .command(\"push\")\n .description(\"Push local changes to remote\")\n .option(\"-q, --quiet\", \"Suppress output\")\n .option(\"-f, --filter <prefix>\", \"Only push files matching this prefix\")\n .action(async (opts: { quiet?: boolean; filter?: string }) => {\n try {\n const { client, workspacePath, runtime } = buildClient();\n const engine = createSyncEngine({ workspacePath, client, runtime });\n await engine.push(undefined, { quiet: opts.quiet, filter: opts.filter });\n } catch (err) {\n console.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n });\n\n// ─── pull ─────────────────────────────────────────────────\n\nprogram\n .command(\"pull\")\n .description(\"Pull remote changes to local\")\n .option(\"-q, --quiet\", \"Suppress output\")\n .action(async (opts: { quiet?: boolean }) => {\n try {\n const { client, workspacePath, runtime } = buildClient();\n const engine = createSyncEngine({ workspacePath, client, runtime });\n await engine.pull({ quiet: opts.quiet });\n } catch (err) {\n console.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n });\n\n// ─── status ───────────────────────────────────────────────\n\nprogram\n .command(\"status\")\n .description(\"Show sync status and pending changes\")\n .action(async () => {\n try {\n const { client, workspacePath } = buildClient();\n const [localManifest, remoteManifest, stats] = await Promise.all([\n readManifest(workspacePath),\n client.syncGetManifest(),\n client.syncGetStats(),\n ]);\n const diff = diffManifests(localManifest, remoteManifest);\n\n console.log(\"AlfeSync Status\");\n console.log(\"═══════════════\");\n console.log(`Agent: ${stats.agentId}`);\n console.log(`Workspace: ${workspacePath}`);\n console.log(\"\");\n\n console.log(\"Storage:\");\n console.log(` Standard: ${formatBytes(stats.standardBytes)}`);\n console.log(` Glacier IR: ${formatBytes(stats.glacierBytes)}`);\n console.log(` Total files: ${String(stats.fileCount)}`);\n console.log(` Last sync: ${stats.lastSyncAt ?? \"never\"}`);\n console.log(\"\");\n\n console.log(\"Pending Changes:\");\n console.log(` To push: ${String(diff.toPush.length)}`);\n console.log(` To pull: ${String(diff.toPull.length)}`);\n console.log(` Conflicts: ${String(diff.conflicts.length)}`);\n\n if (diff.toPush.length > 0) {\n console.log(\"\");\n console.log(\"Files to push:\");\n for (const p of diff.toPush.slice(0, 20)) console.log(` ↑ ${p}`);\n if (diff.toPush.length > 20) {\n console.log(` ... and ${String(diff.toPush.length - 20)} more`);\n }\n }\n\n if (diff.toPull.length > 0) {\n console.log(\"\");\n console.log(\"Files to pull:\");\n for (const p of diff.toPull.slice(0, 20)) console.log(` ↓ ${p}`);\n if (diff.toPull.length > 20) {\n console.log(` ... and ${String(diff.toPull.length - 20)} more`);\n }\n }\n\n if (diff.conflicts.length > 0) {\n console.log(\"\");\n console.log(\"Conflicts:\");\n for (const p of diff.conflicts) console.log(` ⚡ ${p}`);\n }\n } catch (err) {\n console.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n });\n\n// ─── conflicts ────────────────────────────────────────────\n\nprogram\n .command(\"conflicts\")\n .description(\"List conflict files in the workspace\")\n .action(async () => {\n try {\n const { workspacePath, runtime } = buildClient();\n const ignoreRules = await loadIgnorePatterns(workspacePath, runtime);\n const conflicts = await findConflictFiles(workspacePath, ignoreRules);\n if (conflicts.length === 0) {\n console.log(\"No conflict files found.\");\n return;\n }\n console.log(`Found ${String(conflicts.length)} conflict file(s):`);\n for (const f of conflicts) console.log(` ⚡ ${f}`);\n } catch (err) {\n console.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n });\n\n// ─── prune ────────────────────────────────────────────────\n\nprogram\n .command(\"prune\")\n .description(\n \"Delete cloud files matching .alfesyncignore. Dry-run by default; pass --yes to actually delete.\",\n )\n .option(\"-y, --yes\", \"Actually delete files (default is dry-run)\")\n .action(async (opts: { yes?: boolean }) => {\n try {\n const { client, workspacePath, runtime } = buildClient();\n const ignorePatterns = await loadIgnorePatterns(workspacePath, runtime);\n const remoteManifest = await client.syncGetManifest();\n\n const ignoredPaths: { path: string; size: number }[] = [];\n for (const [path, entry] of Object.entries(remoteManifest.files)) {\n if (shouldIgnore(path, ignorePatterns)) {\n ignoredPaths.push({ path, size: entry.size });\n }\n }\n\n if (ignoredPaths.length === 0) {\n console.log(\"Nothing to prune — no cloud files match .alfesyncignore.\");\n return;\n }\n\n const totalSize = ignoredPaths.reduce((s, e) => s + e.size, 0);\n\n if (!opts.yes) {\n console.log(\n `Dry-run: ${String(ignoredPaths.length)} cloud file(s) would be deleted (${formatBytes(totalSize)}).`,\n );\n console.log(\"\");\n for (const { path, size } of ignoredPaths.slice(0, 50)) {\n console.log(` ✗ ${path} (${formatBytes(size)})`);\n }\n if (ignoredPaths.length > 50) {\n console.log(` ... and ${String(ignoredPaths.length - 50)} more`);\n }\n console.log(\"\");\n console.log(\"Re-run with --yes to delete these files from the cloud.\");\n return;\n }\n\n console.log(\n `Deleting ${String(ignoredPaths.length)} file(s) from cloud (${formatBytes(totalSize)})...`,\n );\n const engine = createSyncEngine({ workspacePath, client, runtime });\n const result = await engine.pushDeletes(\n ignoredPaths.map((e) => e.path),\n { quiet: true },\n );\n console.log(\n `Prune complete: ${String(result.pushed)} deleted, ${String(result.errors)} failed.`,\n );\n } catch (err) {\n console.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n });\n\n// ─── restore ──────────────────────────────────────────────\n\nprogram\n .command(\"restore\")\n .description(\"Restore agent workspace from remote backup\")\n .option(\"-m, --mode <mode>\", \"Restore mode: full, active, memory\", \"full\")\n .option(\"-q, --quiet\", \"Suppress output\")\n .action(async (opts: { mode?: string; quiet?: boolean }) => {\n try {\n const { client, workspacePath } = buildClient();\n const mode = opts.mode as \"full\" | \"active\" | \"memory\";\n if (![\"full\", \"active\", \"memory\"].includes(mode)) {\n console.error(\"Error: mode must be full, active, or memory\");\n process.exit(1);\n }\n\n if (!opts.quiet) console.log(`Restoring workspace (mode: ${mode})...`);\n const bundle = await client.syncReconstruct({ mode });\n\n if (!opts.quiet) {\n console.log(\n `Downloading ${String(bundle.fileCount)} files (${formatBytes(bundle.totalSize)})...`,\n );\n }\n\n const remoteManifest = await client.syncGetManifest();\n const results = await downloadFiles(\n workspacePath,\n bundle.files.map((file) => file.path),\n client,\n remoteManifest,\n { quiet: opts.quiet },\n );\n const downloaded = results.filter((result) => result.success).length;\n const errors = results.length - downloaded;\n\n if (!opts.quiet) {\n console.log(\n `\\nRestore complete: ${String(downloaded)} files downloaded, ${String(errors)} errors.`,\n );\n }\n } catch (err) {\n console.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n });\n\n// ─── Helpers ──────────────────────────────────────────────\n\nasync function findConflictFiles(\n dir: string,\n ignoreRules: IgnoreRules,\n base = \"\",\n): Promise<string[]> {\n const results: string[] = [];\n const entries = await readdir(dir, { withFileTypes: true });\n for (const entry of entries) {\n const fullPath = join(dir, entry.name);\n const relativePath = base ? `${base}/${entry.name}` : entry.name;\n if (entry.isDirectory()) {\n if (shouldIgnoreDir(relativePath, ignoreRules)) continue;\n const sub = await findConflictFiles(fullPath, ignoreRules, relativePath);\n results.push(...sub);\n } else if (entry.name.includes(\".conflict-\")) {\n if (shouldIgnore(relativePath, ignoreRules)) continue;\n results.push(relativePath);\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 if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;\n}\n\n// ─── Run ──────────────────────────────────────────────────\n\nprogram.parse();\n"],"mappings":"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"}
@@ -44,6 +44,16 @@ shared/**
44
44
  **/Crashpad/**
45
45
  **/ShaderCache/**
46
46
  **/component_crx_cache/**
47
+ # Alfe headless-browser profile (packages/openclaw-remote points Chromium's
48
+ # userDataDir at `<workspace>/.alfe-browser-profile`). Same live-WAL-SQLite
49
+ # corruption hazard as the runtime-home `browser/` dir — Cookies / History /
50
+ # Web Data / Login Data are live databases — plus hundreds of high-churn cache
51
+ # files that exhaust the inotify watch limit. The `**/Cache/**` entries above
52
+ # only drop the profile's sub-caches; this drops the WHOLE profile (incl. the
53
+ # live SQLite at its root), which is ephemeral runtime state with zero
54
+ # cross-rebuild restore value (the browser relaunches fresh). `**/`-anchored so
55
+ # it matches wherever the profile is created under the workspace.
56
+ **/.alfe-browser-profile/**
47
57
  **/__pycache__/**
48
58
  **/*.pyc
49
59
  **/*.log
package/dist/index.cjs CHANGED
@@ -2,6 +2,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_sync_engine = require("./sync-engine.cjs");
3
3
  const require_plugin = require("./plugin2.cjs");
4
4
  exports.DEFAULT_IGNORES = require_sync_engine.DEFAULT_IGNORES;
5
+ exports.assertNoSymlinkTraversal = require_sync_engine.assertNoSymlinkTraversal;
5
6
  exports.computeFileHash = require_sync_engine.computeFileHash;
6
7
  exports.createSharedSyncEngine = require_plugin.createSharedSyncEngine;
7
8
  exports.createSyncEngine = require_sync_engine.createSyncEngine;
@@ -12,10 +13,12 @@ exports.loadIgnorePatterns = require_sync_engine.loadIgnorePatterns;
12
13
  exports.plugin = require_plugin.plugin;
13
14
  exports.readManifest = require_sync_engine.readManifest;
14
15
  exports.removeManifestEntry = require_sync_engine.removeManifestEntry;
16
+ exports.resolvePrivateWorkspacePath = require_sync_engine.resolvePrivateWorkspacePath;
15
17
  exports.shouldIgnore = require_sync_engine.shouldIgnore;
16
18
  exports.shouldIgnoreDir = require_sync_engine.shouldIgnoreDir;
17
19
  exports.startWatcher = require_plugin.startWatcher;
18
20
  exports.updateManifestEntry = require_sync_engine.updateManifestEntry;
19
21
  exports.uploadFiles = require_sync_engine.uploadFiles;
22
+ exports.validatePrivateRelativePath = require_sync_engine.validatePrivateRelativePath;
20
23
  exports.withRetry = require_sync_engine.withRetry;
21
24
  exports.writeManifest = require_sync_engine.writeManifest;