{"id":50295167,"url":"https://github.com/icanhasjonas/icloud-tools","last_synced_at":"2026-05-28T08:32:08.752Z","repository":{"id":352417715,"uuid":"1215061399","full_name":"icanhasjonas/icloud-tools","owner":"icanhasjonas","description":"CLI tool for managing iCloud Drive files on macOS. Replacement for brctl download/evict (removed in Sonoma 14+).","archived":false,"fork":false,"pushed_at":"2026-04-19T13:34:46.000Z","size":38,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-19T14:45:10.472Z","etag":null,"topics":["brctl","cli","command-line","dataless-files","file-management","homebrew","icloud","icloud-drive","macos","swift"],"latest_commit_sha":null,"homepage":null,"language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/icanhasjonas.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-19T12:33:18.000Z","updated_at":"2026-04-19T13:34:50.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/icanhasjonas/icloud-tools","commit_stats":null,"previous_names":["icanhasjonas/icloud-tools"],"tags_count":8,"template":false,"template_full_name":null,"purl":"pkg:github/icanhasjonas/icloud-tools","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/icanhasjonas%2Ficloud-tools","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/icanhasjonas%2Ficloud-tools/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/icanhasjonas%2Ficloud-tools/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/icanhasjonas%2Ficloud-tools/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/icanhasjonas","download_url":"https://codeload.github.com/icanhasjonas/icloud-tools/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/icanhasjonas%2Ficloud-tools/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33601380,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-28T02:00:06.440Z","response_time":99,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["brctl","cli","command-line","dataless-files","file-management","homebrew","icloud","icloud-drive","macos","swift"],"created_at":"2026-05-28T08:32:07.568Z","updated_at":"2026-05-28T08:32:08.743Z","avatar_url":"https://github.com/icanhasjonas.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# icloud\n\nCLI tool for managing iCloud Drive files on macOS.\n\nApple removed `brctl download` and `brctl evict` in macOS Sonoma 14+, and `fileproviderctl materialize` in 14.4. No replacement shipped. This tool fills the gap using Foundation APIs that still work from unsigned binaries — no entitlements, no code signing, no sandbox.\n\n## Why\n\n- **rsync deadlocks** on dataless (cloud-only) files: `mmap: Resource deadlock avoided`. You need to download first.\n- **No CLI to batch-download** a directory before going offline.\n- **No CLI to see** what's local vs cloud-only, or what's eating disk.\n- **No CLI to evict** files and free space.\n- **\"Keep Downloaded\" (pin)** is Finder-only — no CLI equivalent existed until now.\n\n## Install\n\n### Homebrew\n\n```bash\nbrew tap icanhasjonas/tap\nbrew install icloud-tools\n```\n\n### From source\n\n```bash\ngit clone https://github.com/icanhasjonas/icloud-tools.git\ncd icloud-tools\nswift build -c release\ncp .build/release/icloud ~/.local/bin/\n```\n\nRequires macOS 14+ and Swift 6.0+.\n\n## Usage\n\n### Status\n\nShow iCloud status for files. Defaults to cwd if inside iCloud Drive, otherwise the iCloud Drive root.\n\n```bash\nicloud status                    # cwd or iCloud Drive root\nicloud status ~/Desktop          # specific directory\nicloud status -r Documents/      # recursive\nicloud status --cloud            # only cloud-only files\nicloud status --local            # only local files\nicloud status --sort size        # sort by size\nicloud status --json             # JSON output\nicloud status -v                 # verbose (show resolved paths)\n```\n\nOutput uses ANSI colors: green = local, dim = cloud/dir, yellow = syncing, cyan P = pinned.\n\nSymlinked paths (e.g. `~/.icloud -\u003e ~/Library/Mobile Documents/com~apple~CloudDocs/`) are resolved automatically and display paths preserve the symlink prefix you passed in.\n\n### Move\n\nMove files with download-first semantics. Dataless files are downloaded before moving, preventing the rsync mmap deadlock.\n\n```bash\nicloud mv file.pdf ~/Desktop/             # move single file\nicloud mv a.pdf b.pdf dest/               # move multiple into directory\nicloud mv -v dir/ dest/                   # verbose with per-file progress\nicloud mv -f src.pdf existing.pdf         # force overwrite (atomic backup+restore)\nicloud mv -n src.pdf existing.pdf         # skip if exists (no-clobber)\nicloud mv -d src.pdf dest/                # dry-run preview\nicloud mv -j 5 big-dir/ dest/             # 5 concurrent downloads (default 3)\nicloud mv -t 300 huge.zip dest/           # raise baseline timeout to 5 min\nicloud mv --json a b dest/ | jq -s .      # NDJSON -\u003e array\n```\n\n**Force semantics (`-f`)**: the existing destination file is moved to a hidden backup, then the operation runs. On success, backup is removed. On failure, backup is restored. If restore itself fails, the error includes the surviving backup path.\n\n**Merge semantics**: when you move a directory into another directory that already has a same-named subdirectory, contents **merge per-file**. Files present at the destination but not in the source are **never deleted**. Conflicts (same relative path) respect `-f` / `-n` just like single-file conflicts. **Directories at the destination are never deleted or replaced.** Attempting to replace a directory with a file errors out.\n\n### Copy\n\nSame flags as `mv`, plus `-r` for directories.\n\n```bash\nicloud cp file.pdf ~/Desktop/             # copy single file\nicloud cp -r Documents/ ~/backup/         # recursive directory copy (merge into existing)\nicloud cp -vf *.pdf dest/                 # verbose, force overwrite\nicloud cp -d src.pdf dest/                # dry-run preview\nicloud cp -j 10 huge/ dest/               # 10 concurrent downloads\nicloud cp --json -r dir/ dest/ | jq -s .  # NDJSON -\u003e array\n```\n\n### Download\n\nTriggers iCloud download and waits for completion. Files that are mid-download or dataless-but-marked-local are waited on correctly, not skipped.\n\n```bash\nicloud download file.pdf               # download and wait\nicloud download -rv Documents/         # recursive + verbose\nicloud download --dry-run -r .         # preview, no download\nicloud download -j 10 -r Documents/    # 10 concurrent downloads\nicloud download -t 300 big.zip         # raise baseline timeout to 5 min\nicloud download --json -r dir/ | jq -s .\n```\n\n### Evict\n\nMakes files cloud-only by removing the local copy. Pinned files are skipped; unpin first.\n\n```bash\nicloud evict file.pdf            # free local copy\nicloud evict -rv old-projects/   # recursive + verbose\nicloud evict --dry-run -r .      # preview without evicting\nicloud evict --json big.zip | jq -s .\n```\n\n### Pin / Unpin\n\n`pin` sets the `com.apple.fileprovider.pinned#PX` xattr (same mechanism as Finder's \"Keep Downloaded\"). Pinned files are protected from automatic eviction on disk pressure and from `icloud evict`.\n\n```bash\nicloud pin important.pdf                     # single file\nicloud pin -r Documents/                     # recursive\nicloud pin --from-tag Green                  # all files with Finder tag \"Green\"\nicloud pin --from-tag Green+Important -r .   # AND: both tags required\nicloud pin --from-tag Red --from-tag Blue    # OR: either tag matches\nicloud pin --dry-run --from-tag Green        # preview\n\nicloud unpin important.pdf                   # allow system to evict\nicloud unpin -r Documents/\nicloud unpin --from-tag Green\n```\n\nTag filters: repeat `--from-tag` for OR, use `+` inside one expression for AND.\n\n## Parallel downloads \u0026 timeouts\n\n`mv`, `cp`, and `download` all support:\n\n- `-j, --max-concurrent \u003cn\u003e` — concurrent downloads. Default **3**. iCloud handles the concurrency on its end; we fire all `startDownloadingUbiquitousItem` calls and poll the batch.\n- `-t, --timeout \u003csec\u003e` — baseline timeout floor per file. Default **120**.\n\nEffective per-file timeout = `max(baseline, sizeMB × 1.2)`:\n\n| file size | effective timeout |\n|-----------|-------------------|\n| 1 MB      | 120 s (baseline)  |\n| 100 MB    | 120 s             |\n| 1 GB      | 20 min            |\n| 2 GB      | 40 min            |\n| 10 GB     | ~3 hours          |\n\nOne slow file doesn't stall the batch — each tracks its own deadline.\n\n## Output modes\n\nThe same command picks a renderer based on how you invoke it:\n\n| invocation                          | renderer         | example                                       |\n|-------------------------------------|------------------|-----------------------------------------------|\n| interactive terminal, no `-v`       | **TTYQuiet**     | `⇣ file (1 MB)` → `✓ file (2.3s)` → `src =\u003e dst` |\n| interactive terminal, `-v`          | **TTYVerbose**   | per-file header + `  downloading...` + `  moved to …` |\n| piped to another command (non-tty)  | **LineStream**   | `DL→ / DL✓ / MV✓ / CP✓` one line per event, grep-friendly |\n| `--json`                            | **JSON (NDJSON)** | `{\"event\":\"op.done\",\"src\":\"…\",\"dst\":\"…\",…}` per line |\n\n### The `=\u003e` arrow\n\n`MV✓ src =\u003e dst` and `src =\u003e dst` print **only after** post-op `fileExists` + size verification on the destination. If the destination is missing or size-mismatched, the tool emits `opFail` and throws. No lying success.\n\n### JSON events\n\n```bash\nicloud mv --json a b dest/            # one NDJSON record per event\nicloud mv --json a b dest/ | jq -s .  # slurp into an array\nicloud cp --json -r dir/ dest/ | jq 'select(.event == \"op.done\")'\n```\n\nEvent stream types: `phase.start`, `phase.end`, `discovered`, `download.start`, `download.tick`, `download.done`, `download.fail`, `op.done`, `op.fail`, `op.skipped`, `op.would`.\n\n## How It Works\n\n- **Status:** `URLResourceValues` for download status, file size, allocated size. Dataless files have `fileAllocatedSize == 0 \u0026\u0026 fileSize \u003e 0`.\n- **Download:** `FileManager.startDownloadingUbiquitousItem(at:)` triggers async download; we fire-and-poll all files in the batch concurrently.\n- **Evict:** `FileManager.evictUbiquitousItem(at:)` makes files cloud-only.\n- **Pin:** `setxattr` / `removexattr` for `com.apple.fileprovider.pinned#PX` (value `0x31`) — the same mechanism Finder uses for \"Keep Downloaded\".\n- **Move/Copy:** downloads dataless files first (parallel, size-scaled timeout), then performs the operation. For directory-into-existing-directory, per-file merge with conflict handling. Destinations are stat'd post-op to verify existence and size before reporting success.\n\nNo private APIs. No entitlements. Just Foundation.\n\n## Data-loss covenants\n\nThese are why the tool exists in its current shape:\n\n1. **Never lie about success.** `=\u003e` never prints without post-op verification.\n2. **Never delete a destination directory.** `-f` only replaces at the file level.\n3. **Never swallow syscall errors on the data path.** `setxattr`, `moveItem`, `copyItem`, enumeration, all check their returns.\n4. **Download waits for all dataless states.** Not just `.cloud` — `.downloading` and dataless `.local` too.\n\n30 unit tests pin these covenants. Run with `swift test`.\n\n## Limitations\n\n- No per-byte download progress. APFS uses atomic extent swaps, so `fileAllocatedSize` jumps from 0 to full on completion. The tool shows file-level progress (queued / downloading / downloaded) for batch downloads.\n- Only works on files under `~/Library/Mobile Documents/`. Desktop/Documents folders only apply if they're synced to iCloud Drive.\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ficanhasjonas%2Ficloud-tools","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ficanhasjonas%2Ficloud-tools","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ficanhasjonas%2Ficloud-tools/lists"}