# CLI reference

> All `rift` subcommands, flags, defaults, stdout/stderr behavior, hidden `--database` and `--shell-cwd` flags, and exit codes.

- Repository: anomalyco/rift
- GitHub: https://github.com/anomalyco/rift
- Human docs: https://grok-wiki.com/public/docs/anomalyco-rift-ea3fd5dbf662
- Complete Markdown: https://grok-wiki.com/public/docs/anomalyco-rift-ea3fd5dbf662/llms-full.txt

## Source Files

- `crates/cli/src/main.rs`
- `README.md`
- `specs.md`
- `npm/rift-snapshot/bin/rift.js`

---

---
title: "CLI reference"
description: "All `rift` subcommands, flags, defaults, stdout/stderr behavior, hidden `--database` and `--shell-cwd` flags, and exit codes."
---

The `rift` binary is a thin Clap-driven wrapper around the `rift` core `Manager`. It parses eight subcommands, opens a SQLite registry (default or `--database`), delegates filesystem and registry work to the core library, and prints machine-readable paths on stdout while reserving human-oriented status for stderr. The npm `rift-snapshot` package forwards `process.argv` to a bundled prebuilt binary with inherited stdio.

## Command summary

| Subcommand | Positional argument | Default path | Opens registry |
| --- | --- | --- | --- |
| `shell-init <SHELL>` | — | — | No |
| `init [AT]` | `AT` workspace directory | Current working directory | Yes |
| `create [FROM]` | `FROM` source workspace | Current working directory | Yes |
| `remove [AT]` | `AT` workspace to remove | Current working directory | Yes |
| `list [OF]` | `OF` parent workspace | Current working directory | Yes |
| `ancestors [OF]` | `OF` child workspace | Current working directory | Yes |
| `gc` | — | — | Yes |

Workspace operations resolve paths by walking upward from the positional argument (or cwd) until a `.rift` marker is found, except `rift init` which applies Git-root or `--here` selection before calling core `init` on an exact path.

## Global and hidden flags

Two flags are defined on the root `Cli` parser and hidden from `--help`:

<ParamField body="--database" type="path">
Optional SQLite registry path. When omitted, the CLI opens `{data_local_dir}/rift/rift.sqlite` (typically `~/.local/share/rift/rift.sqlite` on Linux when `XDG_DATA_HOME` is unset). Must appear **before** the subcommand; it is not marked `global`.
</ParamField>

<ParamField body="--shell-cwd" type="boolean" default="false">
Global hidden flag consumed by shell wrappers from `rift shell-init`. When set, `init`, `create`, and `remove` emit a target directory on **stdout** for the wrapper to `cd` into, and move human-readable status lines to **stderr**. Other subcommands ignore it.
</ParamField>

## `rift shell-init`

```
rift shell-init <SHELL>
```

<ParamField body="SHELL" type="enum" required>
Shell integration target. Accepted values: `bash`, `zsh`, `nushell`.
</ParamField>

Prints a shell function definition to **stdout**. The function intercepts `init`, `create`, and `remove`, re-invokes the real `rift` binary with `--shell-cwd`, and `cd`s into the path printed on stdout when non-empty. All other subcommands pass through unchanged.

Does not open the registry. Always exits `0` on success.

<RequestExample>

```bash
eval "$(rift shell-init zsh)"
```

</RequestExample>

## `rift init`

```
rift init [OPTIONS] [AT]
```

Prepares and registers a source workspace for copy-on-write child creation.

<ParamField body="AT" type="path">
Directory to initialize. Defaults to the current working directory after canonicalization.
</ParamField>

<ParamField body="--here" type="boolean" default="false">
Initialize exactly `AT` (or cwd). Without this flag, the CLI selects the nearest existing managed ancestor, restores a missing marker on a known root, or falls back to the nearest Git root (directory containing `.git`).
</ParamField>

### Defaults and behavior

- Opens the registry before running.
- On Linux btrfs, first-time init of an ordinary directory may convert it into a btrfs subvolume with progress on stderr.
- On other supported platforms, init registers the directory in place after verifying copy-on-write support.
- If the workspace is already registered and the marker exists, reports `Already initialized` without conversion.
- If the marker was deleted but the registry entry remains, restores the marker.

### Stdout and stderr

| Outcome | Stdout | Stderr |
| --- | --- | --- |
| Successful conversion | Empty, unless `--shell-cwd` and cwd is inside the initialized tree → initialized path | Progress lines during btrfs conversion; final `Ready <path>` |
| Already initialized | Empty | `Already initialized <path>` |
| Marker restored | Empty | `Restored marker <path>` |
| Registered without conversion | Empty | `Ready <path>` |
| Converted, cwd inside tree, no `--shell-cwd` | Empty | `run cd <path> to enter the initialized workspace` |
| Converted, cwd inside tree, `--shell-cwd` | `<path>` | Progress and `Ready` lines |

Btrfs conversion progress on stderr includes `Initializing`, `First-time setup can take a moment.`, `New rifts will be instant.`, `Creating BTRFS subvolume...`, and `Importing workspace...` as applicable.

## `rift create`

```
rift create [OPTIONS] [FROM]
```

Creates a copy-on-write child workspace from a managed source.

<ParamField body="FROM" type="path">
Source workspace. Defaults to cwd. Resolved upward to the nearest `.rift` marker.
</ParamField>

<ParamField body="--name" type="string">
Child directory name. When omitted, generates a random `adjective-noun` name (for example `amber-brook`).
</ParamField>

<ParamField body="--into" type="path">
Custom storage parent directory. When omitted, uses `<parent-of-root>/.rifts/<root-basename>/`.
</ParamField>

<ParamField body="--copy-all" type="boolean" default="false">
Use exact copy mode (`CopyMode::All`). Default is filtered copy (`CopyMode::Filtered`), which omits heavyweight regenerable artifacts such as `node_modules`, `target`, `.venv`, `.next`, `dist`, `build`, and `coverage`.
</ParamField>

<ParamField body="--no-hooks" type="boolean" default="false">
Skip `.rift.toml` postcreate hooks (`HookMode::Skip`). Default runs configured hooks after registration (`HookMode::Run`).
</ParamField>

### Stdout and stderr

| Mode | Stdout | Stderr |
| --- | --- | --- |
| Default | New workspace path (one line) | Empty |
| `--shell-cwd` | New workspace path (one line) | `created <path>` |

On Git repositories, the child receives detached `HEAD` at the source commit with preserved index and working-tree state. If a postcreate hook fails, the workspace remains registered and the command exits `1` with a `postcreate hook failed at <path>: \`<command>\` <message>` error on stderr.

## `rift remove`

```
rift remove [OPTIONS] [AT]
```

Logically deletes a created rift (moves to `.trash`) or unregisters a source root.

<ParamField body="AT" type="path">
Workspace to remove. Defaults to cwd, resolved upward via `.rift`.
</ParamField>

<ParamField body="--children" type="boolean" default="false">
Trash all managed descendants of `AT` but preserve `AT` itself. Maps to core `remove_all`.
</ParamField>

<ParamField body="-f, --force" type="boolean" default="false">
Required when removing a registered **source root** (workspace with no parent). Without `-f`, the CLI exits `1` before touching the filesystem.
</ParamField>

### Stdout and stderr

**Single remove** (default, not `--children`):

| Case | Stdout | Stderr |
| --- | --- | --- |
| Remove created rift, no `--shell-cwd` | Empty | Empty |
| Remove created rift, `--shell-cwd`, cwd inside removed tree | Parent workspace path (if applicable) | `removed <path>` |
| Unregister source root (`-f`) | Empty (unless `--shell-cwd` emits destination) | `Unregistered <path>`; with `--shell-cwd`, may also print destination |

**`--children` mode:**

| Mode | Per removed descendant | If cwd inside a removed path |
| --- | --- | --- |
| Default | One path per line on **stdout** | — |
| `--shell-cwd` | `removed <path>` on **stderr** | `AT` path on **stdout** |

<Warning>
Unregistering a source root with `-f` deletes the `.rift` marker, trashes all registered descendants, and removes the active registry tree. The source directory itself remains on disk.
</Warning>

Force-required error on stderr:

```text
This is the root workspace.

Unregistering it removes Rift metadata and trashes all child rifts.
Run `rift remove -f` to continue.
```

## `rift list`

```
rift list [OF]
```

<ParamField body="OF" type="path">
Parent workspace. Defaults to cwd.
</ParamField>

Prints one direct active child workspace path per line on **stdout**, in registry order. Empty output when there are no children. No stderr on success.

## `rift ancestors`

```
rift ancestors [OF]
```

<ParamField body="OF" type="path">
Child workspace. Defaults to cwd.
</ParamField>

Prints ancestor workspace paths on **stdout**, nearest parent first, up to the registered source root. One path per line.

## `rift gc`

```
rift gc
```

Physically deletes directories previously moved to Rift-owned `.trash` storage and prunes registry entries for active workspaces whose directories were removed outside Rift (when safe). Prints each deleted or pruned path on **stdout**, one per line.

## Exit codes

| Code | Source | When |
| --- | --- | --- |
| `0` | Rust CLI | Successful command completion |
| `1` | Rust CLI | Any runtime error (`rift::Error`, I/O, or `ForceRequired`); message on stderr |
| `2` | Clap | Parse errors: unknown subcommand, missing required arguments, invalid flag usage |
| `1` | npm `rift.js` | Unsupported platform/arch, missing prebuilt binary, or spawn failure |
| Child status | npm `rift.js` | Forwards the bundled binary exit code; treats `null` as `1` |

The Rust `main` function always prints errors to **stderr** via `eprintln!` and calls `std::process::exit(1)`. Clap handles its own usage errors before `run()` executes.

### CLI error message remapping

Three core errors are rewritten for CLI users; all other `rift::Error` variants use `Display` as-is on stderr:

| Core error | CLI stderr message |
| --- | --- |
| `InitializationRequired` | `this workspace must be initialized first; run \`rift init\` from its root folder` |
| `WorkspaceNotInitialized` | `no initialized workspace found; run \`rift init\` from the root folder` |
| `MissingMarker` | `this workspace is missing its \`.rift\` marker; run \`rift init\` to restore it` |

Common unmapped errors include `copy-on-write cloning unavailable: …`, `unsafe Git source: …`, `postcreate hook failed at …`, `cannot remove subtree while a recorded rift path is missing: …`, and `rift directory already exists: …`.

## npm launcher (`rift-snapshot`)

The `rift-snapshot` npm package installs `bin/rift.js`, which:

1. Resolves `prebuilds/<platform>-<arch>/rift` (or `rift.exe` on Windows).
2. Spawns the binary with `process.argv.slice(2)` and `stdio: "inherit"`.
3. Exits with the child process status.

Unsupported platform or missing binary messages go to stderr with exit `1`. All CLI flags and subcommands are identical to the native binary; hidden flags are not exposed in `--help` but are accepted when passed explicitly.

## `--shell-cwd` protocol

Shell wrappers generated by `shell-init` depend on a strict stdout/stderr split:

```text
rift --shell-cwd <subcommand> [args]
  │
  ├─ stdout: single path line to cd into (may be empty)
  └─ stderr: human status (created/removed/ready messages)
```

| Subcommand | stdout when `--shell-cwd` | stderr when `--shell-cwd` |
| --- | --- | --- |
| `init` (converted, cwd inside) | Initialized workspace path | Progress and `Ready` lines |
| `create` | New child path | `created <path>` |
| `remove` (created rift) | Parent path if cwd was inside removed tree | `removed <path>` |
| `remove --children` | `AT` if cwd inside any removed descendant | `removed <path>` per descendant |
| `remove -f` (root) | Destination if cwd inside removed tree | `Unregistered <path>` |

The wrapper runs `cd` only when stdout is non-empty after trimming.

## Related pages

<CardGroup>
<Card title="Quickstart" href="/quickstart">
Initialize a workspace, create a child, list it, and remove it with expected stdout signals.
</Card>
<Card title="Shell integration" href="/shell-integration">
Install Bash, Zsh, or Nushell wrappers via `rift shell-init`.
</Card>
<Card title="Error codes" href="/error-codes">
Complete `RiftError` catalog with FFI codes and path-bearing variants.
</Card>
<Card title="Installation" href="/installation">
Install via npm global package or Cargo build script.
</Card>
</CardGroup>
