Metadata-Version: 2.4
Name: caffeinated-whale-cli
Version: 0.35.0
Summary: A CLI tool to help manage Frappe Docker instances.
Author-email: Christopher McKay <mckay.christopher73@outlook.com>
License: MIT
Project-URL: Homepage, https://github.com/karotkriss/caffeinated-whale-cli
Project-URL: Issues, https://github.com/karotkriss/caffeinated-whale-cli/issues
Classifier: Development Status :: 4 - Beta
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Topic :: System :: Systems Administration
Classifier: Topic :: Software Development :: Build Tools
Classifier: Intended Audience :: Developers
Classifier: Environment :: Console
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: docker>=7.1.0
Requires-Dist: typer>=0.16.0
Requires-Dist: rich>=14.0.0
Requires-Dist: questionary>=2.1.0
Requires-Dist: toml>=0.10.2
Requires-Dist: peewee>=3.17.0
Requires-Dist: black>=25.1.0
Provides-Extra: dev
Requires-Dist: ruff>=0.8.0; extra == "dev"
Requires-Dist: mypy>=1.13.0; extra == "dev"
Requires-Dist: pytest>=8.0.0; extra == "dev"
Requires-Dist: pytest-cov>=6.0.0; extra == "dev"
Requires-Dist: types-requests>=2.33.0.20260518; extra == "dev"
Requires-Dist: types-toml>=0.10.8.20260518; extra == "dev"

# Caffeinated Whale CLI

A command-line interface (CLI) for managing Frappe/ERPNext Docker instances during local development. Simplify container management, bench operations, and development workflows with an intuitive set of commands.

## Features

- **One-Command Setup** - Initialize complete Frappe/ERPNext environments with `cwcli init`
- **Smart Port Management** - Automatic port conflict detection and resolution
- **Project Discovery** - Scan and list all Frappe Docker projects
- **Cross-Project Search** - Find apps and sites across all instances with `cwcli where`
- **Container Lifecycle** - Start, stop, and restart projects with ease
- **Development Tools** - VS Code integration, log viewing, and command execution
- **Cache System** - Fast project inspection with SQLite-based caching and configuration storage
- **Multi-Bench Support** - Address individual benches in a multi-bench instance by numeric index or a durable label with `--bench`
- **Default Site Support** - Optional `--site` flag when default site is configured
- **Backup & Restore** - Interactive site restoration with automatic file archive detection and P2P transfer support
- **Update Management** - App updates with automatic migrations and lock cleanup
- **Auto-Inspection** - Background process to keep project cache fresh automatically
- **System Integration** - Auto-start on system boot with platform-specific configurations
- **Contextual Tips** - Helpful tips displayed during long-running operations to help you discover features

## Installation

Ensure you have Python 3.10+ and `pip` installed.

```bash
pip install caffeinated-whale-cli
```

### Troubleshooting

After installation, if you see an error like `'cwcli' is not recognized...`, the installation directory is not in your system's `PATH`.

To fix this:

1. **Find the script's location:** Run `pip show -f caffeinated-whale-cli` and look for the location of `cwcli.exe` (or `cwcli` on macOS/Linux). It's typically in a `Scripts` or `bin` folder within your Python installation directory.
2. **Add to PATH:** Follow your operating system's instructions to add this directory to your `PATH` environment variable.
3. **Restart your terminal:** Close and reopen your terminal for changes to take effect.

## Quick Start

```bash
# Initialize a new Frappe project (downloads compose, starts containers, creates bench & site)
cwcli init my-project

# Initialize with ERPNext and custom port
cwcli init my-project --port 10000 --install-erpnext

# List all Frappe projects
cwcli ls

# Start a project (with automatic port conflict detection)
cwcli start my-project

# View bench logs in real-time
cwcli logs my-project

# Open project in VS Code
cwcli open my-project

# Update apps and migrate sites
cwcli update my-project --app erpnext --build
```

## Command Reference

### `init` - Initialize New Project

Creates a complete Frappe development environment in a single step. Downloads compose files, starts containers, initializes bench, and creates a site.

```bash
cwcli init [OPTIONS] [PROJECT_NAME]
```

**Arguments:**

| Argument | Description |
|----------|-------------|
| `PROJECT_NAME` | Docker Compose project name. If not provided, will prompt interactively |

**Options:**

| Option | Description |
|--------|-------------|
| `-P`, `--port INTEGER` | Starting port for the project (default: 8000). Creates ports {port}-{port+5} for web servers and {port+1000}-{port+1005} for socketio |
| `-b`, `--bench TEXT` | Bench directory name inside the container (default: frappe-bench) |
| `-s`, `--site TEXT` | Primary site name, must end with .localhost (default: development.localhost) |
| `--bench-parent TEXT` | Directory inside container where bench is created (default: /workspace) |
| `--frappe-branch TEXT` | Frappe branch for bench init (default: version-15) |
| `--db-root-password TEXT` | MariaDB root password (default: 123) |
| `--admin-password TEXT` | Administrator password for the site (default: admin) |
| `--install-erpnext` | Install ERPNext application after initialization |
| `--erpnext-branch TEXT` | ERPNext branch to use (default: version-15) |
| `--auto-start` | Automatically start containers if not running |
| `--reuse-bench` / `--no-reuse-bench` | Pre-answer the existing-bench question non-interactively: `--reuse-bench` reuses the bench and skips `bench init`; `--no-reuse-bench` requires a fresh `--bench` name and errors if it already exists. Default: ask interactively (a non-TTY without either flag refuses). Distinct from `--auto-start`, which controls container startup |
| `-v`, `--verbose` | Show verbose output with streaming command execution |

**What It Does:**

1. Creates project directory at `~/.cwcli/projects/{project_name}/conf/`
2. Downloads `docker-compose.yml` from frappe_docker GitHub repository
3. Customizes port mappings based on `--port` flag
4. Resolves the latest stable `frappe/bench` image tag from Docker Hub (never uses `:latest`)
5. Pulls Docker images and starts containers
6. Pins the correct Python version via `PYENV_VERSION` for the branch (installs via pyenv if missing)
7. Pins the correct Node.js version via nvm for older branches (installs via nvm if missing)
8. Installs `yarn` globally for the activated Node.js version (older branches only)
9. Initializes Frappe bench with specified branch
10. Pins `setuptools<82` inside the bench virtualenv for `version-13` (retains `pkg_resources`)
11. Configures database and Redis connections
12. Creates site with admin credentials
13. Enables developer mode and server scripts
14. Optionally installs ERPNext

**Branch-Specific Runtime Setup:**

| Branch | Python | Node.js | Bench Image |
|--------|--------|---------|-------------|
| `version-15` | 3.12.x (via pyenv) | Default | Latest stable from Docker Hub |
| `version-14` | 3.10.x (via pyenv) | 16 (via nvm) + yarn | Latest stable from Docker Hub |
| `version-13` | 3.9.x (via pyenv) | 14 (via nvm) + yarn | Latest stable from Docker Hub |
| Other | Default | Default | Latest stable from Docker Hub |

Missing Python or Node.js versions are automatically installed inside the container.
`version-13` also pins `setuptools<82` in the bench virtualenv after init.

Site creation picks the MariaDB flag per branch: `version-13` and `version-14` use `--no-mariadb-socket`,
while `version-15` and newer use `--mariadb-user-host-login-scope=%` (a flag that only exists in bench/Frappe 15+).

**Examples:**

```bash
# Initialize with interactive prompts
cwcli init

# Initialize with project name
cwcli init my-project

# Initialize with custom port (avoids conflicts with other projects)
cwcli init my-project --port 10000

# Initialize with ERPNext
cwcli init my-project --install-erpnext

# Full customization
cwcli init my-project \
  --port 12000 \
  --bench custom-bench \
  --site myapp.localhost \
  --frappe-branch version-15 \
  --install-erpnext \
  --erpnext-branch version-15 \
  --admin-password secretpass

# Verbose mode for debugging
cwcli init my-project -v
```

**Example Output:**

```
✓ Successfully initialized bench 'frappe-bench' in 8m 32s
Bench path: /workspace/frappe-bench
Next steps: Run `cwcli open my-project` to open the project in vscode or exec with docker.
```

**Port Conflict Handling:**

If the requested ports are in use, you'll see:

```
Error: The following ports are already in use: 8000-8005

Tip: Use the --port flag to select a different starting port.
Example: cwcli init my-project --port 10000
```

**Reusing Existing Bench:**

If a bench already exists at the specified path (the devcontainer image ships a default `/workspace/frappe-bench`, so a fresh `init` usually hits this):

```
Bench 'frappe-bench' already exists at /workspace/frappe-bench.
? Reuse the existing bench 'frappe-bench' and continue with site setup? (Y/n)
```

Answer `Y` to reuse it as-is (bench initialization is skipped and site setup continues on the existing bench).
Answer `n` to set up a fresh bench instead; you're prompted for a different bench name and site setup continues there:

```
? Enter a different bench name to create (leave blank to cancel): my-bench
```

Leaving the name blank (or cancelling either prompt) makes no changes and exits cleanly:

```
No changes made.
```

**Non-interactive (agents/automation):** pass a flag so `init` never prompts.
Re-running `init` against the devcontainer's shipped bench, pass `--reuse-bench`:

```bash
# Reuse the existing bench and run site setup with zero prompts (skips bench init)
cwcli init my-project --reuse-bench --auto-start

# Require a brand-new bench; error out if the name already exists
cwcli init my-project --bench fresh-bench --no-reuse-bench
```

Without a reuse flag, a non-interactive session (non-TTY) where the bench already exists refuses honestly with exit 1 instead of hanging, and names `--reuse-bench`/`--no-reuse-bench`.

---

### `ls` - List Projects

Scans Docker for Frappe/ERPNext projects and displays their status and ports.

```bash
cwcli ls [OPTIONS]
```

**Options:**

| Option | Description |
|--------|-------------|
| `-v`, `--verbose` | Display all ports individually, without condensing them into ranges |
| `-q`, `--quiet` | Only display project names, one per line (useful for scripting) |
| `--json` | Output the list of instances as a raw JSON string |

**Example Output:**

```
┌──────────────┬─────────┬──────────────────┐
│ Project Name │ Status  │ Ports            │
├──────────────┼─────────┼──────────────────┤
│ frappe-one   │ running │ 8000-8005, 9000  │
│ frappe-two   │ exited  │                  │
└──────────────┴─────────┴──────────────────┘
```

---

### `where` - Search Apps and Sites

Searches all cached instances for apps or sites matching a string. Useful for finding which projects have a specific app installed or contain a particular site.

```bash
cwcli where [OPTIONS] SEARCH
```

**Arguments:**

| Argument | Description |
|----------|-------------|
| `SEARCH` | Search string to match against app or site names (case-insensitive) |

**Options:**

| Option | Description |
|--------|-------------|
| `-a`, `--apps` | Search only for apps |
| `-s`, `--sites` | Search only for sites |
| `-i`, `--installed` | Show only installed apps (not just available). Only applies to app search |
| `--json` | Output results as JSON |

**Examples:**

```bash
# Find all instances with 'erpnext'
cwcli where erpnext

# Find apps matching 'payments'
cwcli where payments --apps

# Find sites matching 'local'
cwcli where local --sites

# JSON output for scripting
cwcli where frappe --json
```

**Example Output:**

```
                Apps matching 'frappe'
┏━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Project    ┃ App     ┃ Version  ┃ Branch     ┃ Site                  ┃
┡━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━┩
│ my-project │ frappe  │ 15.93.0  │ version-15 │ development.localhost │
│ test       │ frappe  │ 15.88.2  │ version-15 │ development.localhost │
└────────────┴─────────┴──────────┴────────────┴───────────────────────┘

Found 2 matches.
```

**Notes:**
- Searches use cached project data. Run `cwcli inspect <project>` to populate/refresh the cache.
- When an app exists both as "available" and "installed" in the same project, only the installed version is shown (with version/branch info).
- Use `--installed` to exclude apps that are available but not yet installed on any site.

---

### `start` - Start Containers

Starts a project's containers with **automatic port conflict detection and resolution**.

```bash
cwcli start [OPTIONS] [PROJECT_NAME]...
```

**Arguments:**

| Argument | Description |
|----------|-------------|
| `PROJECT_NAME` | The name(s) of the Frappe project(s) to start (can be piped from stdin) |

**Options:**

| Option | Description |
|--------|-------------|
| `--bench TEXT` | Which bench runs `bench start`: its numeric index or label (see [Working with Multiple Benches](#working-with-multiple-benches)). Defaults to the first bench in a multi-bench project (a note lists the rest) |
| `-y`, `--yes` | Auto-confirm stopping conflicting Frappe projects to free their ports (non-interactive) |
| `-v`, `--verbose` | Enable verbose diagnostic output |

**Features:**

- **Port Conflict Detection:** Automatically checks if required ports are available
- **Interactive Resolution:** Offers to stop conflicting Frappe projects (use `--yes` to auto-confirm)
- **Process Identification:** Shows which processes are using ports (cross-platform)
- **Smart Error Messages:** Provides actionable guidance for resolution
- **Multi-Bench Aware:** On a multi-bench project, `bench start` runs in the first bench by default (a note lists the others); pick another with `--bench <index|label>`

**Example:**

```bash
# Start a single project
cwcli start frappe-one

# Start multiple projects
cwcli start frappe-one frappe-two

# Pipe from ls
cwcli ls --quiet | cwcli start
```

**Port Conflict Example:**

```
Warning: Some ports needed by 'frappe-one' are in use by other Frappe projects:
  • Project 'frappe-two': 8000-8005

? Stop project 'frappe-two' to free up its ports? (Y/n)
```

---

### `stop` - Stop Containers

Stops a running project's containers.

```bash
cwcli stop [OPTIONS] [PROJECT_NAME]...
```

**Arguments:**

| Argument | Description |
|----------|-------------|
| `PROJECT_NAME` | The name(s) of the Frappe project(s) to stop (can be piped from stdin) |

**Options:**

| Option | Description |
|--------|-------------|
| `-v`, `--verbose` | Enable verbose diagnostic output |

**Example:**

```bash
# Stop a single project
cwcli stop frappe-one

# Stop multiple projects
cwcli stop frappe-one frappe-two
```

---

### `restart` - Restart Containers

Restarts a project's containers and bench instance.

```bash
cwcli restart [OPTIONS] [PROJECT_NAME]...
```

**Arguments:**

| Argument | Description |
|----------|-------------|
| `PROJECT_NAME` | The name(s) of the Frappe project(s) to restart (can be piped from stdin) |

**Options:**

| Option | Description |
|--------|-------------|
| `-v`, `--verbose` | Enable verbose diagnostic output |

**Example:**

```bash
cwcli restart frappe-one
```

**Example Output:**

```
Attempting to restart 1 project(s)...
✓ Instance 'frappe-one' stopped.
✓ Instance 'frappe-one' started.
✓ Started bench (logs: /tmp/bench-frappe-one.log)
View logs with: cwcli logs frappe-one
```

---

### `rm` - Remove Project

Removes a Frappe project: its containers, its named Docker volumes, and its local project directory.

**WARNING:** This action is destructive and cannot be undone.
Before deleting anything, the command re-caches the project, backs up the databases and files for all sites (a live `bench backup --with-files`), and archives the `docker-compose.yml`, `site_config.json`, and the project's `conf/` directory into a timestamped folder under `~/.cwcli/archive/`. The backup and the config archive are written first, and the named volumes and project directory are only deleted once both have succeeded. When volumes are being deleted (the default `--volumes`), a backup that cannot be fully created and verified blocks removal: each backup artifact is copied out of the container to the host archive and the database dump must be present and non-empty, and if it is not, the command aborts before any container is removed and exits non-zero, so data is never destroyed without a confirmed backup. Aborting before removal keeps the whole project intact (containers, volumes, directory, and cache), so a retry can still take a live backup from the running container. Under `--no-volumes` no volume data is destroyed, so a failed backup does not block the container, directory, and cache cleanup.

The recache and the live `bench backup` only run when the project's containers are actually running, since both shell into the frappe container. If the project is stopped (or its containers are already gone), `cwcli rm` does not start it just to back it up; it warns that no fresh database backup could be taken, skips the recache, still archives `conf/`, and then proceeds to remove the named volumes and project directory.

```bash
cwcli rm [OPTIONS] [PROJECT_NAME]...
```

**Arguments:**

| Argument | Description |
|----------|-------------|
| `PROJECT_NAME` | The name(s) of the Frappe project(s) to remove (can be piped from stdin) |

**Options:**

| Option | Description |
|--------|-------------|
| `--volumes` / `--no-volumes` | Remove the named Docker volumes (databases, sites, files). Default: `--volumes`. Use `--no-volumes` to keep them |
| `--no-backup` | Skip database backups before removal, including the backup safety gate (also skips recaching; faster but risky) |
| `-y`, `--yes` | Skip the confirmation prompt and proceed with removal |
| `-v`, `--verbose` | Enable verbose diagnostic output |

By default `cwcli rm` removes the containers, removes the project's named Docker volumes (where the databases, sites, and files live), deletes the local project directory at `~/.cwcli/projects/{project_name}/`, and clears the project from the cache.

`--no-volumes` preserves the named Docker volumes so the project can be recreated from existing data, but still removes the containers, the local project directory, and the cache entry.
The project directory is always removed because it is cwcli configuration, not data.

If a project has no running containers but still has orphaned named volumes or a lingering local directory (for example after a partial removal), `cwcli rm` cleans up that leftover state instead of reporting the project as not found.

An invalid project name (empty, `.`, `..`, an absolute path, or one containing a path separator) is rejected before anything is removed, since such a name could otherwise escape the projects directory. `cwcli rm` exits non-zero whenever any removal step fails - a blocked backup, a rejected name, or a volume, directory, or container that could not be removed - and does not print the success summary for a project that was not fully removed; the project stays in the cache (and `cwcli ls`) so the leftover state remains visible and can be retried.

**Examples:**

```bash
# Remove everything (with confirmation)
cwcli rm my-project

# Keep the named volumes, remove containers and project directory only
cwcli rm my-project --no-volumes

# Skip backups (not recommended)
cwcli rm my-project --no-backup

# Skip the confirmation prompt
cwcli rm my-project --yes

# Remove multiple projects via pipe
cwcli ls | cwcli rm
```

---

### `logs` - View Bench Logs

View bench logs in real-time from the log file inside the container.

```bash
cwcli logs [OPTIONS] PROJECT_NAME
```

**Arguments:**

| Argument | Description |
|----------|-------------|
| `PROJECT_NAME` | The name of the Frappe project to view logs for (required) |

**Options:**

| Option | Description |
|--------|-------------|
| `-f`, `--follow` / `--no-follow` | Follow log output in real-time (default: follow) |
| `-n`, `--lines INTEGER` | Number of lines to show from the end of the logs (default: 100) |
| `-y`, `--yes` | Auto-start stopped containers without prompting |
| `-v`, `--verbose` | Enable verbose diagnostic output |

**Examples:**

```bash
# Follow logs in real-time (default)
cwcli logs frappe-one

# Show last 50 lines and exit
cwcli logs frappe-one --no-follow --lines 50

# Show last 200 lines and follow
cwcli logs frappe-one -n 200
```

**Note:** Logs are stored at `/tmp/bench-{project_name}.log` inside the container.

---

### `inspect` - Inspect Project Structure

Inspects a project to find all bench instances, sites, and apps within it. Results are cached for faster subsequent operations.

**Freshness:** When a project is already cached and its containers are running, `inspect` runs a lightweight, read-only freshness pass over the known benches (a quick `ls` of `apps/`/`sites/`) before showing results.
This means an app you just installed is picked up automatically, without a manual `cwcli inspect --update`.
If nothing changed, the cached data is served unchanged; if that pass detects a change, it transparently falls back to a full re-inspect so the per-site installed-app lists are refreshed too.
Use `--no-refresh` to skip the pass and return the cached data as-is (fastest, but possibly stale), or `--update` to force a full re-inspect.

**Security Note:** The cache never stores Frappe secrets. Site and common configurations are whitelist-filtered before they are written, so database passwords, per-site encryption keys, admin/root passwords, and Redis URLs are stripped and never persisted (nothing reads them back from the cache - every credential consumer reads live from the container or from CLI flags/prompts). Any cache written before this behavior shipped is cleaned in place on the next run. As defense-in-depth, the cache is also stored with restricted filesystem permissions (directory: `0700`, database: `0600`) so only the current user can read it; still, do not share the cache directory (`~/.cwcli/cache/`) with untrusted users.

```bash
cwcli inspect [OPTIONS] PROJECT_NAME
```

**Arguments:**

| Argument | Description |
|----------|-------------|
| `PROJECT_NAME` | The Docker Compose project to inspect (required) |

**Options:**

| Option | Description |
|--------|-------------|
| `-v`, `--verbose` | Enable verbose diagnostic output |
| `-j`, `--json` | Output the result as a JSON object |
| `-u`, `--update` | Update the cache by re-inspecting the project |
| `--no-refresh` | Return cached data as-is, skipping the lightweight freshness pass (fastest; may be stale) |
| `-a`, `--show-apps` | Show available apps in the output tree |
| `-i`, `--interactive` | Prompt for a durable [label](#working-with-multiple-benches) for each bench (persisted to the cache and a marker file inside the bench). Requires a running container to write the marker |
| `-y`, `--yes` | Auto-start stopped containers without prompting (non-interactive) |

**What It Caches:**
- Bench instances and their paths
- Sites and installed apps for each bench
- Site configurations (non-secret keys only, e.g. database name, developer mode settings; passwords and encryption keys are stripped)
- Common site configuration (non-secret keys only, e.g. ports, default site, developer mode; Redis URLs and other secrets are stripped)
- Default-site pointer from `sites/currentsite.txt` (written by `bench use`), so the `(default)` marker and default-site resolution work even when `common_site_config.json` has no `default_site` key
- Default site is labeled with `(default)` in output (resolved from either source)

**Example Output:**

Each bench is shown with its numeric index (its default `--bench` selector) and any user label:

```
Project frappe-one
├── Bench [0] 'primary' at /workspace/frappe-bench
│   ├── Site: frappe-one.localhost (default)
│   │   ├── App: frappe (v15.0.0, develop)
│   │   └── App: erpnext (v15.0.0, version-15)
│   └── Site: site2.localhost
│       ├── App: frappe (v15.0.0, develop)
│       └── App: erpnext (v15.0.0, version-15)
└── Bench [1] at /workspace/frappe-bench-2
    └── Site: site3.localhost
        └── App: frappe (v15.0.0, develop)
```

**Benefits:**
- Enables default site feature: `unlock` command can omit `--site` flag
- Faster subsequent operations (uses cached data)
- Stores configurations for programmatic access
- Assigns numeric indices to benches so `--bench` can target one in a multi-bench project (see [Working with Multiple Benches](#working-with-multiple-benches))

**Examples:**

```bash
# Inspect and cache project structure
cwcli inspect frappe-one

# Force refresh the cache
cwcli inspect frappe-one --update

# Return cached data as-is, skipping the freshness pass (fastest)
cwcli inspect frappe-one --no-refresh

# Show available apps
cwcli inspect frappe-one --show-apps

# Get JSON output
cwcli inspect frappe-one --json

# Interactively label each bench (durable handle for --bench)
cwcli inspect frappe-one --interactive
```

---

### `label` - Manage Bench Labels

Assigns, clears, or lists per-bench user labels for a project. A user label is a durable, human-friendly handle for a bench in a [multi-bench](#working-with-multiple-benches) project (numeric indices are positional and can shift), and is the value you pass to `--bench` on the bench-operating commands.

```bash
cwcli label [OPTIONS] PROJECT_NAME [SELECTOR] [NEW_LABEL]
```

**Arguments:**

| Argument | Description |
|----------|-------------|
| `PROJECT_NAME` | The Docker Compose project name (required) |
| `SELECTOR` | Which bench to label: its numeric index or an existing label. Omit to list the benches |
| `NEW_LABEL` | The new label to assign. Omit and pass `--clear` to remove the label |

**Options:**

| Option | Description |
|--------|-------------|
| `--clear` | Remove the selected bench's user label (revert to its numeric index) |
| `-v`, `--verbose` | Enable verbose output |

**Label rules:**

- May contain only letters, digits, dot (`.`), dash (`-`), and underscore (`_`), up to 64 characters
- May **not** be purely numeric (numeric selectors are reserved for bench indices)
- Must be unique within the project

**Behavior:**

- Run `cwcli inspect <project>` first so the benches are cached; without a cache the command errors.
- Listing benches (bare `cwcli label <project>`) is read-only and needs no running container.
- Setting or clearing a label writes it to **both** the SQLite cache and a marker file (`<bench-root>/.cwcli/.bench-label`) inside the bench, so labels survive a cache wipe and can be rebuilt by a full `cwcli inspect --update`. Because the marker lives inside the bench, setting or clearing a label requires a running frappe container; it will **not** auto-start a stopped project.

**Examples:**

```bash
# List benches with their indices and labels
cwcli label my-project

# Label bench index 1 as 'staging'
cwcli label my-project 1 staging

# Rename label 'staging' to 'prod'
cwcli label my-project staging prod

# Remove bench 1's label
cwcli label my-project 1 --clear
```

---

### `open` - Open in VS Code or Docker Exec

Opens a project's frappe container in VS Code (with Dev Containers) or executes into it.

```bash
cwcli open [OPTIONS] PROJECT_NAME
```

**Arguments:**

| Argument | Description |
|----------|-------------|
| `PROJECT_NAME` | The Docker Compose project name to open (required) |

**Options:**

| Option | Description |
|--------|-------------|
| `--bench TEXT` | Which bench to open: its numeric index or label (see [Working with Multiple Benches](#working-with-multiple-benches)) |
| `-p`, `--path TEXT` | Explicit bench directory inside the container (lower-level alternative to `--bench`; cannot be combined with it) |
| `-a`, `--app TEXT` | App name to open (opens the app's directory within the bench) |
| `--code` | Open with VS Code directly (skips interactive prompt) |
| `--code-insiders` | Open with VS Code Insiders directly (skips interactive prompt) |
| `--docker` | Open with Docker exec directly (skips interactive prompt) |
| `-y`, `--yes` | Auto-start stopped containers without prompting |
| `-v`, `--verbose` | Enable verbose diagnostic output |

**Features:**

- Auto-detects VS Code and VS Code Insiders installations
- Interactive editor selection menu (when no editor flag specified)
- Direct editor selection via `--code`, `--code-insiders`, or `--docker` flags
- Automatically installs required VS Code extensions (Docker and Dev Containers)
- Uses cached bench paths from `inspect` command
- Docker exec opens in bench directory (respects working directory)
- Falls back to Docker exec if VS Code is unavailable

**Examples:**

```bash
# Open project with interactive prompt (uses cached bench path)
cwcli open frappe-one

# Open directly with VS Code (skip prompt)
cwcli open frappe-one --code

# Open directly with Docker exec (skip prompt)
cwcli open frappe-one --docker

# Open specific app directory with VS Code Insiders
cwcli open frappe-one --app erpnext --code-insiders

# Open custom path
cwcli open frappe-one --path /workspace/custom-bench
```

**Interactive Prompt:**

When no editor flag is specified, you'll see:

```
How would you like to open this instance?
❯ VS Code - Open in development container
  Docker - Execute interactive shell in container
```

---

### `update` - Update Apps and Migrate

Updates specified Frappe apps and migrates all sites where they are installed.

```bash
cwcli update [OPTIONS] PROJECT_NAME
```

**Arguments:**

| Argument | Description |
|----------|-------------|
| `PROJECT_NAME` | The name of the project to update (required) |

**Options:**

| Option | Description |
|--------|-------------|
| `-a`, `--app TEXT` | App name(s) to update (specify multiple apps after `--app` or use `--app` multiple times) |
| `--bench TEXT` | Which bench to target: its numeric index or label (see [Working with Multiple Benches](#working-with-multiple-benches)) |
| `-p`, `--path TEXT` | Explicit bench directory inside the container (lower-level alternative to `--bench`; cannot be combined with it) |
| `-v`, `--verbose` | Enable verbose output with streaming command execution |
| `-c`, `--clear-cache` | Clear cache for all affected sites after migration |
| `-w`, `--clear-website-cache` | Clear website cache for all affected sites after migration |
| `-b`, `--build` | Build assets after updating apps |
| `--skip-maintenance` | Skip enabling maintenance mode for affected sites during update |
| `-y`, `--yes` | Auto-start stopped containers without prompting |

**What It Does:**

1. Runs `git pull` in each specified app directory
2. Identifies all sites where the updated apps are installed
3. Enables maintenance mode for affected sites (to prevent user access during updates)
4. Runs `bench --site <site> migrate` for each affected site
5. (Optional) Runs `bench build --app <app>` for successfully updated apps
6. (Optional) Runs `bench --site <site> clear-cache` for affected sites
7. (Optional) Runs `bench --site <site> clear-website-cache` for affected sites
8. Automatically clears locks folder for all affected sites to prevent stale locks
9. Disables maintenance mode for affected sites after completion

**Examples:**

```bash
# Update a single app (with maintenance mode enabled by default)
cwcli update frappe-one --app erpnext

# Update multiple apps
cwcli update frappe-one --app frappe --app erpnext

# Update with build and cache clearing
cwcli update frappe-one --app erpnext --build --clear-cache --clear-website-cache

# Update without maintenance mode
cwcli update frappe-one --app erpnext --skip-maintenance

# Update with maintenance mode and all options
cwcli update frappe-one --app erpnext --build --clear-cache --clear-website-cache

# Update with verbose output
cwcli update frappe-one --app custom_app -v
```

**Note:** Maintenance mode is **enabled by default** for all affected sites during updates to prevent user access and potential data corruption. Use `--skip-maintenance` only if you need to update during operational hours when user access is critical.

**Example Output:**

```
Updating project: frappe-one

Updating 1 app(s) for project 'frappe-one'

→ Updating app: erpnext
✓ Successfully updated 'erpnext'
  Found 2 site(s) with 'erpnext' installed

Migrating 2 affected site(s)

✓ Migration complete for all affected sites

✓ Successfully updated 1 app(s)
```

---

### `unlock` - Unlock Site

Removes the locks folder for a specified site to unlock it.

```bash
cwcli unlock [OPTIONS] PROJECT_NAME
```

**Arguments:**

| Argument | Description |
|----------|-------------|
| `PROJECT_NAME` | The Docker Compose project name (required) |

**Options:**

| Option | Description |
|--------|-------------|
| `-s`, `--site TEXT` | Site name to unlock. If not provided, uses the default site (from `common_site_config.json`'s `default_site` or `sites/currentsite.txt`) |
| `--bench TEXT` | Which bench to target: its numeric index or label (see [Working with Multiple Benches](#working-with-multiple-benches)) |
| `-p`, `--path TEXT` | Explicit bench directory inside the container (lower-level alternative to `--bench`; cannot be combined with it) |
| `-y`, `--yes` | Auto-start stopped containers without prompting |
| `-v`, `--verbose` | Enable verbose output and stream rm command output |

**What It Does:**

Removes the `{bench_path}/sites/{site_name}/locks` directory, which can help resolve issues when a site is stuck in a locked state due to incomplete migrations or background jobs.

**Smart Defaults:**
- If `--site` is not specified, automatically uses your bench's default site (from `common_site_config.json`'s `default_site`, or `sites/currentsite.txt` when that key is absent)
- Shows "Using default site: {site}" when using the default
- Run `cwcli inspect {project}` first to cache the configuration

**Examples:**

```bash
# Unlock a specific site
cwcli unlock my-project --site development.localhost

# Use default site (no --site flag needed)
cwcli unlock my-project
# Output: Using default site: development.localhost

# Verbose mode with default site
cwcli unlock my-project -v
```

**When to Use:**

- After a migration fails or is interrupted
- When you see "This document is currently locked and queued for execution" errors
- When background jobs don't complete properly

**Examples:**

```bash
# Unlock a site
cwcli unlock my-project --site development.localhost

# Unlock with verbose output to see files being removed
cwcli unlock my-project --site development.localhost -v
```

**Example Output:**

```
✓ Successfully unlocked site 'development.localhost'
Removed locks folder: /workspace/frappe-bench/sites/development.localhost/locks
```

**Note:** The `update` command automatically clears locks after completion, so manual unlocking is typically only needed for interrupted operations.

---

### `backup` - Back Up a Site

Runs `bench backup` for a site inside the project's frappe container, optionally including public and private files.

```bash
cwcli backup [OPTIONS] PROJECT_NAME
```

**Arguments:**

| Argument | Description |
|----------|-------------|
| `PROJECT_NAME` | The Docker Compose project name (required) |

**Options:**

| Option | Description |
|--------|-------------|
| `-s`, `--site TEXT` | Site name to back up. If not provided, uses the default site (from `common_site_config.json`'s `default_site` or `sites/currentsite.txt`) |
| `--bench TEXT` | Which bench to target: its numeric index or label (see [Working with Multiple Benches](#working-with-multiple-benches)) |
| `-p`, `--path TEXT` | Explicit bench directory inside the container (lower-level alternative to `--bench`; cannot be combined with it) |
| `--with-files` | Include public and private files in the backup |
| `-y`, `--yes` | Auto-start stopped containers without prompting |
| `-v`, `--verbose` | Enable verbose output |

**Examples:**

```bash
# Back up the default site (database only)
cwcli backup my-project

# Back up a specific site
cwcli backup my-project --site example.com

# Include public and private files
cwcli backup my-project --with-files

# Back up a specific bench in a multi-bench project
cwcli backup my-project --bench staging --with-files
```

---

### `restore` - Restore Site from Backup

Interactively restore a site from a backup with automatic detection of file archives and encryption keys. Supports both local restoration and peer-to-peer backup transfers via sendme.

```bash
cwcli restore [OPTIONS] PROJECT_NAME
```

**Arguments:**

| Argument | Description |
|----------|-------------|
| `PROJECT_NAME` | The Docker Compose project name (required) |

**Options:**

| Option | Description |
|--------|-------------|
| `-s`, `--site TEXT` | Site name to restore. If not provided, uses the default site (from `common_site_config.json`'s `default_site` or `sites/currentsite.txt`) |
| `--latest` | Non-interactive: select the most recent backup set for the target site (bypasses the backup menu). A non-TTY without any selector exits non-zero instead of hanging |
| `--backup-file TEXT` | Non-interactive: select the backup set whose database file matches this filename (or full container path). Bypasses the backup menu |
| `--bench TEXT` | Which bench to target: its numeric index or label (see [Working with Multiple Benches](#working-with-multiple-benches)) |
| `-p`, `--path TEXT` | Explicit bench directory inside the container (lower-level alternative to `--bench`; cannot be combined with it) |
| `--mariadb-root-username TEXT` | MariaDB root username (defaults to `root`; prompted interactively when omitted) |
| `--mariadb-root-password TEXT` | MariaDB root password. Prompted interactively when omitted; a non-TTY without this flag refuses rather than proceeding with an empty password |
| `--admin-password TEXT` | Set administrator password after restore |
| `--send` | **P2P Mode:** Share backup with another machine via peer-to-peer transfer |
| `--receive` | **P2P Mode:** Receive backup from another machine via peer-to-peer transfer |
| `--no-recache` | **Deprecated no-op:** the missing-apps check now reads app availability live from the bench, so it never re-caches. Kept for backward compatibility |
| `--no-migrate` | Skip the post-restore `bench migrate` and instance restart. By default a successful restore runs `bench migrate` (bringing the restored DB to the code's schema) then restarts the instance |
| `-y`, `--yes` | Skip the interactive confirmation prompts on both the normal and `--receive` restore paths (the destructive-restore confirmation and the missing-apps prompt). A non-TTY without `--yes` refuses these and exits non-zero. Does not remove the sendme-ticket or MariaDB-credential prompts |
| `-v`, `--verbose` | Enable verbose output and show restore command details |

**What It Does:**

1. Scans all backup files across all sites in the bench
2. Presents an interactive menu with backups grouped by target site
3. Shows badges indicating backup contents: `[FILES]`, `[PRIVATE]`, `[DATABASE ONLY]`
4. Warns if the selected backup needs apps this bench does not have (read from the backup's own database dump, so a missing app is caught before the restore)
5. Automatically detects and restores public/private file archives
6. Restores encryption key from backup's site_config if available
7. Runs `bench migrate` and restarts the instance after a successful restore, so the restored database is brought to the code's schema and the app comes back up cleanly (skip with `--no-migrate`; a failed migrate is surfaced and exits non-zero but does not undo the restore)
8. Displays helpful error messages on failure with common causes

**Interactive Features:**

- **Smart Grouping**: Backups from the target site shown first, followed by backups from other sites
- **Visual Badges**: Clear indicators of what each backup contains
- **Secure Password Prompt**: Uses questionary for consistent, secure password input
- **Confirmation Dialog**: Warns about data replacement before restore
- **TipSpinner**: Shows helpful tips during restore operation

**Examples:**

```bash
# Restore with interactive backup selection (uses default site)
cwcli restore my-project

# Restore specific site
cwcli restore my-project --site production.localhost

# Restore with verbose output for debugging
cwcli restore my-project -v

# Provide password via command line (not recommended for production)
cwcli restore my-project --mariadb-root-password "secret123"
```

**Non-interactive / scripted restores:**

The backup-selection menu and both confirmation prompts (the destructive-restore and missing-apps warnings) can be skipped so a `cwcli restore` runs to completion with no prompts.
Pass `--latest` or `--backup-file <name>` to pick a backup without the menu, `--yes` to skip the confirmations, and `--mariadb-root-password` (and `--mariadb-root-username` if not `root`) for credentials.
A non-TTY missing any required selector or confirmation exits non-zero with a message naming the flag to pass, so a missing piece never hangs or silently succeeds.

```bash
# Restore the newest backup for the default site, fully non-interactive
cwcli restore my-project --latest --yes --mariadb-root-password "secret123"

# Restore a specific backup file by name or full container path
cwcli restore my-project --backup-file 20251112_105638-development_localhost-database.sql.gz \
  --yes --mariadb-root-password "secret123"

# Restore into a specific site non-interactively
cwcli restore my-project --site production.localhost --latest --yes \
  --mariadb-root-password "secret123"
```

> A declined confirmation (interactive `n`, or Ctrl-C), or a non-TTY run without `--yes` at a confirmation, exits non-zero rather than `0`.
> `--latest` and `--backup-file` are mutually exclusive, and neither applies to `--send`/`--receive` (those modes never reach the backup menu).


**Example Session:**

```
Using default site: development.localhost

? Select a backup to restore: (Use arrow keys)

=== Backups from site: development.localhost ===
 > 2025-11-12 10:56:38  [FILES] [PRIVATE]
   2025-11-12 09:30:01  [DATABASE ONLY]
   2025-11-11 23:15:42  [FILES] [PRIVATE]

⚠ Warning: This will replace all data in site 'development.localhost'
Backup: 20251112_105638-development_localhost-database.sql.gz
From: 2025-11-12 10:56:38
Will restore: Database, Public files, Private files

? Are you sure you want to restore? (y/N) y
? MariaDB root username: root
MariaDB root password: ********

✓ Successfully restored site 'development.localhost'
From backup: 20251112_105638-development_localhost-database.sql.gz
Including file archives
Updated encryption_key from backup site_config

✓ Migrated site 'development.localhost'

Restarting instance...
✓ Instance restarted (logs: /workspace/frappe-bench/logs/web.dev.log)
```

**P2P Backup Transfer:**

Share backups between machines using peer-to-peer connections (powered by sendme/Iroh):

**Sending a Backup:**
```bash
# On the source machine
cwcli restore my-project --send

# Select backup from interactive menu
# Ticket automatically copied to clipboard
✓ Transfer ticket copied to clipboard!

Instructions for the receiver:
  1. Run: cwcli restore <project_name> --receive
  2. Paste the ticket when prompted
```

**Receiving a Backup:**
```bash
# On the destination machine
cwcli restore my-project --receive

# Paste the ticket from sender
? Enter the sendme ticket: blob...

# Files download with hash verification
✓ Files downloaded successfully

# Before overwriting the site, cwcli confirms the destructive restore
⚠ Warning: This will replace all data in site 'development.localhost'
Backup: 20251112_105638-development_localhost-database.sql.gz
? Are you sure you want to restore? (y/N) y
# Restore process begins
```

For scripted (non-interactive) receives, pass `-y`/`--yes` to skip the confirmation and the missing-apps prompt. Without a TTY and without `--yes`, cwcli refuses and exits non-zero rather than silently overwriting the site's data.

**P2P Transfer Features:**
- **Hash-verified transfers** - BLAKE3 cryptographic verification ensures data integrity
- **Resumable downloads** - Interrupted transfers can resume from where they stopped
- **NAT traversal** - Works behind firewalls and corporate networks
- **No cloud intermediary** - Direct peer-to-peer connections
- **Cross-platform** - Works on macOS, Linux, and Windows
- **Automatic setup** - sendme binary auto-installed on first use
- **Multiple receivers** - Same ticket can be used by multiple machines

**Use Cases:**
- Share production backups with development team
- Transfer large backups without cloud storage limits
- Migrate data between data centers
- Distribute backups to multiple environments simultaneously

**Security:**

- All command inputs (site name, paths, MariaDB username) are shell-quoted to prevent command injection
- MariaDB and admin passwords are passed to the container via the environment, never interpolated into the command, so they never appear in the container process list (`ps`/`docker top`) or in verbose output
- Backup file existence verified before restore
- P2P transfers are hash-verified (BLAKE3) to prevent tampering
- Treat transfer tickets like passwords (they grant download access)

**When to Use:**

- Restore from scheduled backups after issues
- Migrate data between environments
- Recover from data corruption or accidental deletion
- Test backup integrity
- **Share backups between machines without cloud storage**

---

### `run` - Execute Bench Commands

Executes bench commands inside a project's frappe container.

```bash
cwcli run [OPTIONS] PROJECT_NAME BENCH_ARGS...
```

**Arguments:**

| Argument | Description |
|----------|-------------|
| `PROJECT_NAME` | The Docker Compose project name (required) |
| `BENCH_ARGS` | Bench command and arguments to run (required) |

**Options:**

| Option | Description |
|--------|-------------|
| `--bench TEXT` | Which bench to target: its numeric index or label (see [Working with Multiple Benches](#working-with-multiple-benches)) |
| `-p`, `--path TEXT` | Explicit bench directory inside the container (lower-level alternative to `--bench`; cannot be combined with it). Falls back to the single cached bench, or `/workspace/frappe-bench` when nothing is cached |
| `-y`, `--yes` | Auto-start stopped containers without prompting |
| `-v`, `--verbose` | Enable verbose output |

**Examples:**

```bash
# Run bench migrate
cwcli run frappe-one migrate

# Run bench with specific site
cwcli run frappe-one --site development.localhost migrate

# Execute a custom bench command
cwcli run frappe-one console

# Target a specific bench in a multi-bench project
cwcli run frappe-one migrate --bench staging

# Use custom bench path
cwcli run frappe-one migrate --path /workspace/custom-bench
```

---

### `status` - Check Health Status

Checks the health status of a Frappe project instance.

```bash
cwcli status [OPTIONS] PROJECT_NAME
```

**Arguments:**

| Argument | Description |
|----------|-------------|
| `PROJECT_NAME` | The Docker Compose project name to check (required) |

**Options:**

| Option | Description |
|--------|-------------|
| `-v`, `--verbose` | Show the health-check command, raw curl output, and explain the reported status |

**Status Values:**

- **`offline`** - Container is not running
- **`online`** - Container is running but HTTP probe failed
- **`running`** - Container is running and HTTP probe succeeded

**Example:**

```bash
cwcli status frappe-one
```

---

### `config` - Manage Configuration

Manages the CLI configuration and cache.

```bash
cwcli config [SUBCOMMAND]
```

#### Subcommands

##### `config path` - Show Config Path

Displays the path to the configuration file.

```bash
cwcli config path
```

##### `config add-path` - Add Custom Bench Path

Adds a custom bench search path to the configuration.

```bash
cwcli config add-path PATH
```

**Arguments:**

| Argument | Description |
|----------|-------------|
| `PATH` | The absolute path to add to the custom search paths (required) |

**Example:**

```bash
cwcli config add-path /home/user/custom-bench
```

##### `config remove-path` - Remove Custom Bench Path

Removes a custom bench search path from the configuration.

```bash
cwcli config remove-path PATH
```

**Arguments:**

| Argument | Description |
|----------|-------------|
| `PATH` | The path to remove from the custom search paths (required) |

**Example:**

```bash
cwcli config remove-path /home/user/custom-bench
```

##### `config cache` - Manage Cache

Manages the cache for project inspection data.

```bash
cwcli config cache [SUBCOMMAND]
```

**Cache Subcommands:**

- **`clear [PROJECT_NAME]`** - Clear cache for a specific project or the entire cache
  - Options:
    - `-a`, `--all` - Clear the entire cache
    - `-y`, `--yes` - Skip the confirmation prompt (required to clear `--all` non-interactively; a non-TTY without `--yes` refuses rather than wiping the cache silently)
  - Example: `cwcli config cache clear frappe-one`
  - Example: `cwcli config cache clear --all`
  - Example: `cwcli config cache clear --all --yes`

- **`path`** - Display the path to the cache file
  - Example: `cwcli config cache path`

- **`list`** - List all projects currently in the cache
  - Example: `cwcli config cache list`

##### `config auto-inspect` - Automatic Project Inspection

Manages automatic background inspection of running Frappe projects to keep cached data fresh.

```bash
cwcli config auto-inspect [SUBCOMMAND]
```

**Auto-Inspect Subcommands:**

- **`enable`** - Enable automatic project inspection
  - Options:
    - `--interval INTEGER` - Inspection interval in seconds (minimum 60, default 3600)
    - `--startup` - Also enable automatic startup on system boot/login
  - Example: `cwcli config auto-inspect enable --interval 1800 --startup`

- **`disable`** - Disable automatic inspection and stop background process
  - Example: `cwcli config auto-inspect disable`

- **`start`** - Start the auto-inspect background process
  - Options:
    - `--startup` - Also enable automatic startup on system boot
  - Example: `cwcli config auto-inspect start --startup`

- **`stop`** - Stop the auto-inspect background process
  - Example: `cwcli config auto-inspect stop`

- **`restart`** - Restart the auto-inspect background process
  - Example: `cwcli config auto-inspect restart`

- **`status`** - Show detailed status (enabled, interval, process state, PID, startup)
  - Example: `cwcli config auto-inspect status`

- **`logs`** - View recent background process logs
  - Options: `--lines INTEGER` - Number of log lines to show (default 20)
  - Example: `cwcli config auto-inspect logs --lines 50`

- **`set-interval`** - Change the inspection interval
  - Example: `cwcli config auto-inspect set-interval 7200`

- **`install-startup`** - Install platform-specific startup configuration
  - Creates LaunchAgent (macOS), systemd service (Linux), or Task Scheduler task (Windows)
  - Example: `cwcli config auto-inspect install-startup`

- **`uninstall-startup`** - Remove startup configuration
  - Example: `cwcli config auto-inspect uninstall-startup`

**What it does:**

The auto-inspect feature runs a background daemon process that periodically inspects all running Frappe projects. This keeps your project cache fresh for:
- Tab completion (project names, apps, sites)

##### `config tips` - Manage Contextual Tips

Control the display of helpful tips during long-running operations.

```bash
cwcli config tips [enable|disable|status]
```

**Subcommands:**

- **`enable`** - Enable contextual tips during long operations
  - Example: `cwcli config tips enable`

- **`disable`** - Disable contextual tips
  - Example: `cwcli config tips disable`

- **`status`** - Show whether contextual tips are enabled
  - Example: `cwcli config tips status`

**What it does:**

When enabled (default), cwcli displays rotating helpful tips alongside spinners during long-running operations like `inspect`, `update`, and `open`. Tips help you discover features and best practices while waiting for operations to complete.

**Examples of tips shown:**

- 💡 Add VS Code to PATH via Command Palette: 'Shell Command: Install code command in PATH'
- 💡 Install tab completion with 'cwcli --install-completion' for faster workflows
- 💡 Use 'cwcli inspect <project>' to cache project structure for faster commands
- 💡 cwcli automatically detects and resolves port conflicts when starting projects
- Project status queries
- Other commands that rely on cached data

**Quick Setup:**

```bash
# Enable with 1-hour interval and auto-start on boot
cwcli config auto-inspect enable --interval 3600 --startup

# Start the background process
cwcli config auto-inspect start

# Check status
cwcli config auto-inspect status

# View logs
cwcli config auto-inspect logs
```

**Notes:**
- Background process survives terminal closure
- Process stops on system restart unless startup is enabled
- Logs stored in `~/.cwcli/run/auto-inspect.log`
- PID file stored in `~/.cwcli/run/auto-inspect.pid`

---

## Working with Multiple Benches

A single cwcli project (one Docker Compose instance) can hold more than one bench directory inside its frappe container. This is a **multi-bench** instance, and cwcli lets you address an individual bench without spelling out its full container path.

**How benches are addressed:**

- **Numeric index** - Every bench discovered by `cwcli inspect` gets an index (`0`, `1`, `2`, ...) from a stable, sorted-by-path order. `cwcli inspect` shows it next to each bench (for example `Bench [1] at /workspace/frappe-bench-2`). An index is positional, so it can shift when a bench is added or removed.
- **User label** - A durable, human-friendly handle you assign with the [`label`](#label---manage-bench-labels) command or `cwcli inspect -i`. Because it does not move when benches are renumbered, a label is the reliable way to target a bench in scripts. Labels may not be purely numeric (that would collide with an index) and must be unique within a project.

**The `--bench` selector:**

Pass `--bench <index|label>` to target one bench. The selector resolves a matching user label first, then a numeric index:

```bash
cwcli run my-project migrate --bench 1          # by index
cwcli update my-project --app erpnext --bench staging   # by label
```

`--bench` is available on `run`, `backup`, `update`, `open`, `unlock`, `restore`, and `start`. Setting labels lives in the [`label`](#label---manage-bench-labels) command.

**No silent guessing on data commands:**

If a project has more than one bench and you do not pass `--bench` (or `--path`), the data commands (`run`, `backup`, `update`, `open`, `unlock`, `restore`) stop and list the benches instead of guessing:

```
Error: project 'my-project' has multiple benches; specify one with --bench <index|label>:
  [0] 'primary' /workspace/frappe-bench
  [1] 'staging' /workspace/frappe-bench-2
```

Single-bench projects are unaffected: with only one bench, that bench is used automatically and `--bench` is optional.

**`start` is the exception:** `cwcli start` keeps working on a multi-bench project by running `bench start` in the first bench (index `0`) and printing a note listing the others; use `--bench` to start a different one.

**`--path` escape hatch:** `-p`/`--path` still accepts an explicit bench directory for cases outside the cached set. It takes precedence over `--bench`, but the two cannot be combined (that is an error).

**Label persistence and recovery:** A user label is stored in both the SQLite cache and a marker file (`<bench-root>/.cwcli/.bench-label`) inside the bench. Because the marker lives in the bench itself, a full `cwcli inspect --update` rebuilds labels from the markers even after the cache is cleared.

---

## Tips and Tricks

### Piping Commands

Many commands support piping project names from stdin:

```bash
# Start all projects
cwcli ls --quiet | cwcli start

# Stop specific projects using grep
cwcli ls --quiet | grep "frappe-" | cwcli stop
```

### Scripting with JSON

Use JSON output for programmatic access:

```bash
# Get project data as JSON
cwcli ls --json | jq '.[] | select(.status=="running")'

# Parse inspect output (each bench carries its numeric "index" for --bench)
cwcli inspect frappe-one --json | jq '.bench_instances[0].sites'
```

### Verbose Mode for Debugging

Use `-v` flag on any command to see detailed diagnostic output:

```bash
cwcli start frappe-one -v
cwcli update frappe-one --app erpnext -v
```

### Shell Completion

cwcli supports intelligent tab completion for project names, apps, and sites across all shells (Bash, Zsh, Fish, PowerShell).

**One-time setup:**

```bash
# Install completion for your current shell
cwcli --install-completion

# Restart your shell or source your shell config
source ~/.bashrc  # For Bash
source ~/.zshrc   # For Zsh
```

**What gets completed:**

- **Project names** - All commands that accept project names (start, stop, restart, inspect, label, logs, open, status, run, update, unlock)
- **App names** - Commands with `--app` option (open, update)
- **Site names** - Commands with `--site` option (unlock)

**Examples:**

```bash
# Press TAB after typing partial project name
cwcli start frap<TAB>
# Completes to: cwcli start frappe-one

# Press TAB to see available apps for a project
cwcli update frappe-one --app <TAB>
# Shows: erpnext  frappe  hrms  custom_app

# Press TAB to see available sites
cwcli unlock frappe-one --site <TAB>
# Shows: site1.localhost  site2.localhost
```

**How it works:**

- **Projects**: Queried from Docker containers in real-time
- **Apps/Sites**: Loaded from cached project data (run `cwcli inspect` first)
- **Fast & Context-aware**: Completions adapt based on the project specified

**Troubleshooting:**

If completion doesn't work:
1. Ensure you've run `cwcli --install-completion`
2. Restart your shell
3. For apps/sites, run `cwcli inspect <project>` to populate the cache

## Architecture

The CLI uses:

- **Docker SDK for Python** - Container management
- **Typer** - CLI framework with type hints
- **Rich** - Terminal formatting and spinners
- **Questionary** - Interactive prompts
- **Peewee ORM** - SQLite-based caching

**Data Directories:**
- **Projects**: `~/.cwcli/projects/` - Project directories created by `cwcli init`
- **Config**: `~/.cwcli/config/` - Configuration files
- **Cache**: `~/.cwcli/cache/cwc-cache.db` - Project inspection cache
- **Runtime**: `~/.cwcli/run/` - PID and log files for background services

## License

This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.

## Contributing

Contributions are welcome! Please see our [Contributing Guide](./CONTRIBUTING.md) for detailed information.

**Quick Links:**
- [Git Workflow](./docs/contributing/git-workflow.md) - Complete contribution workflow
- [Commit Messages](./docs/contributing/commit-messages.md) - Conventional commit standards
- [Code Quality](./docs/contributing/code-quality.md) - Formatting with Black, linting with Ruff
- [Testing Guide](./docs/testing/guide.md) - How to write and run tests
- [CI/CD](./docs/contributing/ci-cd.md) - GitHub Actions workflows

**Getting Started:**

```bash
# Clone the repository
git clone https://github.com/karotkriss/caffeinated-whale-cli.git
cd caffeinated-whale-cli

# Install dependencies
uv sync --all-extras

# Run tests
uv run pytest --cov

# Format and lint
uv run black src/ tests/
uv run ruff check src/ --fix
```

For questions or issues, please open an issue on [GitHub](https://github.com/karotkriss/caffeinated-whale-cli).
