chore: auto-commit (97 archivos)
- .claude/CLAUDE.md - .claude/agents/fn-recopilador/SKILL.md - .claude/rules/INDEX.md - .claude/rules/cpp_apps.md - bash/functions/infra/build_cpp_windows.sh - cpp/CMakeLists.txt - cpp/PATTERNS.md - cpp/framework/app_base.cpp - cpp/framework/app_base.h - dev/issues/README.md - ... Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
# keepass_dump
|
||||
# ------------
|
||||
# Exporta toda la BD KeePassXC como array JSON. Una sola apertura del .kdbx.
|
||||
# Cada elemento: {path, title, username, password, url, notes}.
|
||||
#
|
||||
# REQUIERE:
|
||||
# - keepassxc-cli instalado
|
||||
# - python3 (stdlib xml.etree)
|
||||
# - KEEPASS_DB (env): ruta absoluta al .kdbx
|
||||
# - master password en pass o env KEEPASS_PASSWORD
|
||||
#
|
||||
# USO (sourced):
|
||||
# source keepass_dump.sh
|
||||
# data=$(keepass_dump)
|
||||
# echo "$data" | jq '.[] | select(.path | startswith("Servers/"))'
|
||||
|
||||
keepass_dump() {
|
||||
local db="${KEEPASS_DB:-}"
|
||||
if [ -z "$db" ] || [ ! -f "$db" ]; then
|
||||
echo "keepass_dump: KEEPASS_DB no valida: $db" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
local master
|
||||
if [ -n "${KEEPASS_PASSWORD:-}" ]; then
|
||||
master="$KEEPASS_PASSWORD"
|
||||
else
|
||||
master=$(pass show "${KEEPASS_MASTER_ENTRY:-meta/keepassxc-master}" 2>/dev/null | head -n1)
|
||||
if [ -z "$master" ]; then
|
||||
echo "keepass_dump: no master pass" >&2
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
local xml
|
||||
xml=$(printf '%s\n' "$master" | keepassxc-cli export -q -f xml "$db" 2>/dev/null)
|
||||
if [ $? -ne 0 ] || [ -z "$xml" ]; then
|
||||
echo "keepass_dump: export xml fallo (master incorrecta?)" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
printf '%s' "$xml" | python3 -c '
|
||||
import sys, json, re
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
root = ET.fromstring(sys.stdin.read())
|
||||
out = []
|
||||
|
||||
def clean(s):
|
||||
if not s:
|
||||
return ""
|
||||
s = s.strip().rstrip("/")
|
||||
s = s.replace("/", "_")
|
||||
s = re.sub(r"\s+", "_", s)
|
||||
s = re.sub(r"_+", "_", s)
|
||||
s = s.strip("_")
|
||||
return s
|
||||
|
||||
def walk(group, path):
|
||||
name_el = group.find("Name")
|
||||
raw_name = name_el.text if name_el is not None and name_el.text else ""
|
||||
name = clean(raw_name)
|
||||
new_path = path + [name] if name and name != "Root" else path
|
||||
for entry in group.findall("Entry"):
|
||||
rec = {}
|
||||
for s in entry.findall("String"):
|
||||
k_el = s.find("Key")
|
||||
v_el = s.find("Value")
|
||||
if k_el is None or k_el.text is None:
|
||||
continue
|
||||
rec[k_el.text] = (v_el.text if v_el is not None and v_el.text else "")
|
||||
title = clean(rec.get("Title", ""))
|
||||
full = "/".join(new_path + [title]) if title else "/".join(new_path)
|
||||
out.append({
|
||||
"path": full,
|
||||
"title": title,
|
||||
"username": rec.get("UserName", ""),
|
||||
"password": rec.get("Password", ""),
|
||||
"url": rec.get("URL", ""),
|
||||
"notes": rec.get("Notes", ""),
|
||||
})
|
||||
for sub in group.findall("Group"):
|
||||
walk(sub, new_path)
|
||||
|
||||
root_grp = root.find("Root/Group")
|
||||
if root_grp is not None:
|
||||
walk(root_grp, [])
|
||||
|
||||
print(json.dumps(out, ensure_ascii=False))
|
||||
'
|
||||
}
|
||||
Reference in New Issue
Block a user