|
21 | 21 | WORKSPACE = Path(__file__).parent |
22 | 22 | BACKUPS_DIR = WORKSPACE / "backups" |
23 | 23 |
|
| 24 | +# External paths to include in backup (outside WORKSPACE), mapped to ZIP prefix. |
| 25 | +# auto-memory path is derived from workspace location to avoid hardcoding. |
| 26 | +# Use as_posix() so the slug is consistent across platforms. |
| 27 | +_workspace_slug = WORKSPACE.as_posix().replace("/", "-") # matches Claude's project slug format |
| 28 | +EXTERNAL_PATHS = { |
| 29 | + Path.home() / ".claude" / "projects" / _workspace_slug / "memory": "_external/auto-memory", |
| 30 | +} |
| 31 | + |
| 32 | +# Optional macOS LaunchAgent plist — opt-in via env var so upstream code doesn't |
| 33 | +# bake a deployment-specific identifier. Set BACKUP_LAUNCHAGENT_PLIST to the |
| 34 | +# absolute path of your plist file (e.g. ~/Library/LaunchAgents/com.example.plist). |
| 35 | +_launchagent_plist = os.environ.get("BACKUP_LAUNCHAGENT_PLIST", "").strip() |
| 36 | +if _launchagent_plist: |
| 37 | + _la_path = Path(_launchagent_plist).expanduser() |
| 38 | + EXTERNAL_PATHS[_la_path] = f"_external/launchagents/{_la_path.name}" |
| 39 | + |
| 40 | + |
24 | 41 | # Directories to exclude wherever they appear in the path (reconstructible / heavy) |
25 | 42 | EXCLUDE_DIRS = { |
26 | 43 | "node_modules", |
@@ -248,6 +265,23 @@ def backup_local(s3_upload: bool = False, s3_bucket: str = None) -> Path: |
248 | 265 | print(f"{YELLOW}No gitignored files found to backup.{RESET}") |
249 | 266 | sys.exit(0) |
250 | 267 |
|
| 268 | + # Collect external files (e.g. auto-memory outside workspace) |
| 269 | + # Each entry in EXTERNAL_PATHS can be a file or a directory. |
| 270 | + external_files: list[tuple[Path, str]] = [] |
| 271 | + for ext_root, zip_prefix in EXTERNAL_PATHS.items(): |
| 272 | + if not ext_root.exists(): |
| 273 | + continue |
| 274 | + if ext_root.is_file(): |
| 275 | + # Single-file entry: zip_prefix is the full path inside the ZIP |
| 276 | + external_files.append((ext_root, zip_prefix)) |
| 277 | + else: |
| 278 | + # Directory entry: rglob all files, preserving relative structure |
| 279 | + for f in sorted(ext_root.rglob("*")): |
| 280 | + if f.is_file(): |
| 281 | + rel_in_zip = zip_prefix + "/" + f.relative_to(ext_root).as_posix() |
| 282 | + external_files.append((f, rel_in_zip)) |
| 283 | + |
| 284 | + |
251 | 285 | BACKUPS_DIR.mkdir(exist_ok=True) |
252 | 286 | timestamp = datetime.now().strftime("%Y%m%d-%H%M%S") |
253 | 287 | zip_name = f"evonexus-backup-{timestamp}.zip" |
|
0 commit comments