From d3361691b62039b2f6124ca64c716b05c3320cbc Mon Sep 17 00:00:00 2001 From: Sam & Claude Date: Sun, 21 Jun 2026 19:59:48 +0200 Subject: [PATCH] =?UTF-8?q?feat(skills):=20add=20bitwarden-cli-vault=20?= =?UTF-8?q?=E2=80=94=20bw=20CLI=20read/write/update/delete?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Session-based operations with no interactive prompts. Covers: - Session setup from provider.env - Read (list, get by name, get by ID) - Create (base64-encoded JSON, with collection) - Update (get → modify → pipe to edit) - Delete - Upsert pattern (create if absent, update if exists) - Rebuild authorized_keys from vault items Proven working: full round-trip of key creation → vault publish → read back → delete on OSA 2026-06-21. --- skills/bitwarden-cli-vault/SKILL.md | 184 ++++++++++++++++++++++++++++ 1 file changed, 184 insertions(+) create mode 100644 skills/bitwarden-cli-vault/SKILL.md diff --git a/skills/bitwarden-cli-vault/SKILL.md b/skills/bitwarden-cli-vault/SKILL.md new file mode 100644 index 0000000..725cd18 --- /dev/null +++ b/skills/bitwarden-cli-vault/SKILL.md @@ -0,0 +1,184 @@ +--- +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`