# stiva > CLI that moves large files out of a cloud-synced folder into a local archive. It verifies the copy before removing the original, leaves a readable breadcrumb where the file used to be, and keeps a manifest so the file can be found again or put back months later. Requires Python 3.11 or later. No third-party packages. Code: https://github.com/nerln/stiva ## When to use it When the user needs to free space in Google Drive, Dropbox or OneDrive, wants the material on an external disk or a NAS instead, and wants to find again what was moved. Do not use it as a backup. It moves files, so after the operation the content sits in one place only. If the destination can be an S3 bucket, froster (https://github.com/dirkpetersen/froster) is more mature. ## Required sequence The commands go in this order. Skipping `inspect` makes execution impossible, because `run --execute` demands the token that only `inspect` prints. 1. `stiva plan --archive [--min 250MB] [--older-than 12m] [-o plan.toml]` Writes a TOML plan with one item per candidate file, each with its size and last modification in a comment. Touches nothing. 2. Have the user fill in the `reason` field of every item and delete the items to keep. The reason ends up in the breadcrumb and in the manifest. It is what makes the move understandable six months later. 3. `stiva inspect plan.toml` Preflight. Touches nothing. Reports: total size, how many bytes are cloud-only shells that must be downloaded, free space on the archive, items with no reason, Google native files rejected, paths that do not exist. Exits 0 and prints `Token: <16 hex characters>` when the plan can run. Exits 1 with no token when there are problems. 4. `stiva run plan.toml` Dry run. This is the default behavior and it moves nothing. 5. `stiva run plan.toml --execute --token ` Runs for real. For each item: copy, verify, write breadcrumb and manifest, then remove the original. Exits 2 when the token is missing or does not match. Exits 1 when at least one verification failed. Full example: ``` stiva plan ~/Drive --archive /Volumes/SSD/Archive --min 250MB --older-than 12m -o plan.toml stiva inspect plan.toml stiva run plan.toml stiva run plan.toml --execute --token 2319a599aefabc5a ``` ## Afterwards - `stiva status` sums up what was moved, how much it weighs, on which disk. - `stiva find "2019 campaign"` says where a file went and whether that disk is connected right now. The argument matches part of a name or part of a reason. - `stiva restore "big.zip"` puts the item back in its original place and deletes the breadcrumb. The copy in the archive stays. ## Things to tell the user, not to hide - Deleting from a synced service does not free quota until the trash is emptied by hand. The trash keeps everything for thirty days. The space appears only after that. - Files the user does not own do not count against their quota. Moving them achieves nothing. - After the move the content exists in one copy. Irreplaceable material needs a second copy, and stiva does not make it. ## The token, and why it exists `inspect` computes a fingerprint of the plan over source, archive and the path/reason pairs. If the plan changes after inspection, the token stops being valid and execution halts with code 2. The token exists to stop the plan being edited and the deletion happening in the same move. If you are working on behalf of a person: do not get around it by generating the token and running again without making them reread the plan. The point of the gate is that a human has read the list before anything is removed. ## Plan format ```toml version = 1 source = "/path/to/the/synced/folder" [archive] root = "/Volumes/SSD/Archive" label = "SSD2TB" [defaults] verify = "size" # or "sha256" breadcrumbs = true [[item]] path = "Projects/Backups/archive-2019.zip" reason = "2019 export, never opened again" ``` ## Files left in place Where a file was, stiva writes `.MOVED.md`. In the folder it writes `WHERE-DID-THE-FILES-GO.md`, listing what left and why. ## Claude Code sessions follow the folder When `run` moves a folder that had Claude Code sessions stored under `~/.claude/projects/`, stiva re-points them at the new location on its own: it copies the session folder under the new slug, rewrites the `cwd` field in the transcripts, updates the key in `~/.claude.json` and leaves the old folder untouched. Only `cwd` is rewritten. The text of the messages stays as it was, even where it mentions the old path. That text is history, and history is not to be falsified. Turn this off with `claude_sessions = false` in the plan defaults. For moves done by hand, outside stiva: ``` python3 claude_workspace.py --orphans # list the folders with no home python3 claude_workspace.py /old/path /new/path --execute # re-point ``` ## Two registries, not one Claude Code keeps its history in two places. The CLI stores it under a folder named after the working path, inside `~/.claude/projects/`. The desktop app keeps a separate record: one JSON file per session under `~/Library/Application Support/Claude/claude-code-sessions/`, with a `cwd` and an `originCwd` field inside. Fixing the first one does not fix the second: those sessions in the app keep pointing at a folder that is gone. ``` python3 ccd_paths.py --list # what points at a folder that is gone python3 ccd_paths.py /old/path /new/path --execute --wait ``` `--wait` holds off until the app is closed before writing. While it is running, it rewrites its own files from memory and takes the correction back out. ## Exit codes - 0 all good - 1 problems in the preflight, or at least one verification failed during the run - 2 token missing or not valid ## Where it keeps its state `~/.local/state/stiva/manifest.jsonl`, append-only, one JSON line per move. It can be pointed elsewhere with the `STIVA_STATE` environment variable, which is useful in tests.