--- name: bitwarden-cli-vault description: Use the Bitwarden CLI (bw) to read, write, update, and delete vault items — session-based, no interactive prompts. --- # Bitwarden CLI (bw) — Vault Operations Use the `bw` CLI to automate vault reads and writes. Session-based auth: no interactive master password prompts after initial unlock. ## Prerequisites `provider.env` must contain the 3 bootstrap values: ``` BW_CLIENTID=user.xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx BW_CLIENTSECRET=*** BW_PASSWORD=*** ``` ## Session setup (once per script) ```sh source <(grep -E '^BW_' /usr/local/etc/colibri/provider.env) bw logout >/dev/null 2>&1 bw login --apikey >/dev/null 2>&1 BW_SESSION=$(bw unlock --passwordenv BW_PASSWORD 2>&1 | grep 'export' | sed 's/.*BW_SESSION="\(.*\)".*/\1/') export BW_SESSION ``` All subsequent commands use `--session "$BW_SESSION"`. No password prompts. ## Read ### List all items ```sh bw list items --session "$BW_SESSION" | python3.12 -c "import sys,json; [print(i['name'],i['id']) for i in json.load(sys.stdin)]" ``` ### Get one item by name ```sh ITEM_ID=$(bw list items --search "item-name" --session "$BW_SESSION" | python3.12 -c "import sys,json; print(json.load(sys.stdin)[0]['id'])") bw get item "$ITEM_ID" --session "$BW_SESSION" | python3.12 -c "import sys,json; d=json.load(sys.stdin); print(d['notes'])" ``` ### Get one item by ID ```sh bw get item "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" --session "$BW_SESSION" ``` ### List collections ```sh bw list collections --session "$BW_SESSION" | python3.12 -c "import sys,json; [print(c['name'],c['id']) for c in json.load(sys.stdin)]" ``` ## Create Create a secure note with base64-encoded JSON. Pipe it in — no interactive prompts. ```sh NOTES="your content here" JSON=$(python3.12 -c " import json, base64 item = { 'type': 2, # 2 = secure note 'name': 'item-name', 'notes': '$NOTES' } print(base64.b64encode(json.dumps(item).encode()).decode()) ") echo "$JSON" | bw create item --session "$BW_SESSION" ``` ### Create in a specific collection Add `collectionIds` to the JSON: ```python item = { 'type': 2, 'name': 'item-name', 'notes': '$NOTES', 'collectionIds': ['xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'] } ``` ### Get a collection ID by name ```sh COLLECTION_ID=$(bw list collections --session "$BW_SESSION" | python3.12 -c " import sys,json for c in json.load(sys.stdin): if c['name'] == 'hive-pubkeys': print(c['id']) ") ``` If the collection doesn't exist, create it via the Vaultwarden web UI (no CLI for org-collections without organization admin API). ## Update Get the item, modify the JSON, re-encode, pipe to `bw edit`: ```sh ITEM_ID=$(bw list items --search "item-name" --session "$BW_SESSION" | python3.12 -c "import sys,json; print(json.load(sys.stdin)[0]['id'])") EXISTING=$(bw get item "$ITEM_ID" --session "$BW_SESSION") UPDATED=$(echo "$EXISTING" | python3.12 -c " import json, base64, sys d = json.load(sys.stdin) d['notes'] = 'new content' print(base64.b64encode(json.dumps(d).encode()).decode()) ") echo "$UPDATED" | bw edit item "$ITEM_ID" --session "$BW_SESSION" ``` ## Delete ```sh bw delete item "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" --session "$BW_SESSION" ``` Note: this moves to trash. Use Vaultwarden web UI for permanent deletion. ## Common patterns ### Upsert (create if absent, update if exists) ```sh ITEM_NAME="$(hostname)" NOTES="$PUBKEY" EXISTING_ID=$(bw list items --search "$ITEM_NAME" --session "$BW_SESSION" 2>/dev/null | python3.12 -c " import sys,json data = json.load(sys.stdin) for i in data: if i.get('name') == '$ITEM_NAME': print(i['id']) break " 2>/dev/null) if [ -n "$EXISTING_ID" ]; then # Update CURRENT=$(bw get item "$EXISTING_ID" --session "$BW_SESSION") echo "$CURRENT" | python3.12 -c " import json, base64, sys d = json.load(sys.stdin) d['notes'] = '$NOTES' print(base64.b64encode(json.dumps(d).encode()).decode()) " | bw edit item "$EXISTING_ID" --session "$BW_SESSION" else # Create python3.12 -c " import json, base64 print(base64.b64encode(json.dumps({ 'type': 2, 'name': '$ITEM_NAME', 'notes': '$NOTES' }).encode()).decode()) " | bw create item --session "$BW_SESSION" fi ``` ### Rebuild authorized_keys from vault items ```sh # For mother-sync-hive-keys: pull all pubkeys, wrap with SSH restrictions bw list items --search "colibri@" --session "$BW_SESSION" | python3.12 -c " import json, sys for i in json.load(sys.stdin): pubkey = i.get('notes','') hostname = i.get('name','') if pubkey.startswith('ssh-'): print(f'command=\"colibri-mcp\",restrict,no-pty,no-port-forwarding,no-X11-forwarding,no-agent-forwarding {pubkey} colibri@{hostname}') " > /var/db/colibri/.ssh/authorized_keys.hive chmod 600 /var/db/colibri/.ssh/authorized_keys.hive ``` ## Pitfalls - `bw create`/`edit` need base64-encoded JSON piped to stdin; `--session` must be a flag, not env-only - `bw get`/`list`/`delete` accept `--session` directly - `bw list items --search` does substring match — filter by exact `.name` if multiple results - Session expires after a few hours; re-unlock for long-running scripts - `bw create org-collection` requires organization admin (not available via personal API key) — create collections in web UI - Delete moves to trash; permanent delete requires web UI or `bw delete item --permanent`