dcc0bf550a
- P0: unified soul binary with engram_node_full fix, read-back-verify, search fix - P0: move API keys from plaintext plists to macOS Keychain - P0: fix MCP backend URL (port 8742 → 7770) - P1.6: memory-export/import scripts (AES-256-CBC, versioned .neuronmem format) - P1.7: nightly cultivation digest with sharpness metric (launchd at 23:55) - P2.10: Ollama provider in agentic loop (SOUL_LLM_PROVIDER=ollama) - P3.12: refugee importer for ChatGPT/Screenpipe/generic formats - P3.13: GLM-OCR spike — SHIP IT (mlx-vlm, 1.59GB, photo-to-memory.sh)
163 lines
6.6 KiB
Bash
Executable File
163 lines
6.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# memory-export.sh — Export Neuron engram store as a portable encrypted .neuronmem bundle
|
|
#
|
|
# Usage:
|
|
# ./tools/memory-export.sh [output-path] [--passphrase "your passphrase"]
|
|
#
|
|
# If no passphrase is given, a random one is generated and printed — write it down.
|
|
# If no output path is given, defaults to ./neuron-export-<timestamp>.neuronmem
|
|
|
|
set -euo pipefail
|
|
|
|
# ── Config ─────────────────────────────────────────────────────────────────────
|
|
ENGRAM_SNAPSHOT="${HOME}/.neuron/engram/snapshot.json"
|
|
SOUL_VERSION="1.1.0"
|
|
FORMAT_VERSION="1"
|
|
|
|
# ── Parse args ─────────────────────────────────────────────────────────────────
|
|
OUTPUT_PATH=""
|
|
PASSPHRASE=""
|
|
PASSPHRASE_SET=0
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--passphrase)
|
|
PASSPHRASE="$2"
|
|
PASSPHRASE_SET=1
|
|
shift 2
|
|
;;
|
|
--passphrase=*)
|
|
PASSPHRASE="${1#*=}"
|
|
PASSPHRASE_SET=1
|
|
shift
|
|
;;
|
|
-*)
|
|
echo "Unknown option: $1" >&2
|
|
echo "Usage: $0 [output-path] [--passphrase \"...\"]" >&2
|
|
exit 1
|
|
;;
|
|
*)
|
|
if [[ -z "$OUTPUT_PATH" ]]; then
|
|
OUTPUT_PATH="$1"
|
|
else
|
|
echo "Unexpected argument: $1" >&2
|
|
exit 1
|
|
fi
|
|
shift
|
|
;;
|
|
esac
|
|
done
|
|
|
|
# ── Default output path ────────────────────────────────────────────────────────
|
|
TIMESTAMP="$(date -u +"%Y%m%dT%H%M%SZ")"
|
|
if [[ -z "$OUTPUT_PATH" ]]; then
|
|
OUTPUT_PATH="./neuron-export-${TIMESTAMP}.neuronmem"
|
|
fi
|
|
|
|
# Ensure .neuronmem extension
|
|
if [[ "${OUTPUT_PATH}" != *.neuronmem ]]; then
|
|
OUTPUT_PATH="${OUTPUT_PATH%.neuronmem}.neuronmem"
|
|
fi
|
|
|
|
# ── Validate source ────────────────────────────────────────────────────────────
|
|
if [[ ! -f "$ENGRAM_SNAPSHOT" ]]; then
|
|
echo "ERROR: Engram snapshot not found at: $ENGRAM_SNAPSHOT" >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "Neuron Memory Export"
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
echo "Source: $ENGRAM_SNAPSHOT"
|
|
echo "Output: $OUTPUT_PATH"
|
|
echo ""
|
|
|
|
# ── Generate passphrase if not provided ────────────────────────────────────────
|
|
if [[ $PASSPHRASE_SET -eq 0 ]]; then
|
|
PASSPHRASE="$(openssl rand -base64 32)"
|
|
echo "⚠ No passphrase provided. Generated passphrase:"
|
|
echo ""
|
|
echo " ${PASSPHRASE}"
|
|
echo ""
|
|
echo "⚠ WRITE THIS DOWN. You will need it to import this file."
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
echo ""
|
|
fi
|
|
|
|
# ── Count nodes and edges ──────────────────────────────────────────────────────
|
|
echo "Analyzing snapshot..."
|
|
NODE_COUNT="$(python3 -c "
|
|
import json, sys
|
|
with open('${ENGRAM_SNAPSHOT}') as f:
|
|
d = json.load(f)
|
|
nodes = d.get('nodes', d if isinstance(d, list) else [])
|
|
edges = d.get('edges', [])
|
|
print(len(nodes) if isinstance(nodes, list) else len(nodes))
|
|
" 2>/dev/null || echo "unknown")"
|
|
|
|
echo " Nodes: ${NODE_COUNT}"
|
|
|
|
# ── Compute checksum of source file ───────────────────────────────────────────
|
|
echo "Computing checksum..."
|
|
CHECKSUM="$(openssl dgst -sha256 "$ENGRAM_SNAPSHOT" | awk '{print $NF}')"
|
|
echo " SHA256: ${CHECKSUM:0:16}..."
|
|
|
|
# ── Build bundle in temp dir ───────────────────────────────────────────────────
|
|
WORK_DIR="$(mktemp -d)"
|
|
BUNDLE_DIR="${WORK_DIR}/neuronmem-v${FORMAT_VERSION}"
|
|
mkdir -p "$BUNDLE_DIR"
|
|
|
|
echo "Building bundle..."
|
|
|
|
# Copy snapshot as nodes.json
|
|
cp "$ENGRAM_SNAPSHOT" "${BUNDLE_DIR}/nodes.json"
|
|
|
|
# Write metadata.json
|
|
ISO_TIMESTAMP="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
|
|
cat > "${BUNDLE_DIR}/metadata.json" << METAEOF
|
|
{
|
|
"version": "${FORMAT_VERSION}",
|
|
"exported_at": "${ISO_TIMESTAMP}",
|
|
"node_count": ${NODE_COUNT},
|
|
"soul_version": "${SOUL_VERSION}",
|
|
"sha256": "${CHECKSUM}",
|
|
"format": "neuronmem-v1",
|
|
"encryption": "aes-256-cbc-pbkdf2",
|
|
"source_host": "$(hostname -s 2>/dev/null || echo unknown)"
|
|
}
|
|
METAEOF
|
|
|
|
echo " metadata.json written"
|
|
echo " nodes.json copied ($(du -sh "${BUNDLE_DIR}/nodes.json" | cut -f1))"
|
|
|
|
# ── Create tar.gz ──────────────────────────────────────────────────────────────
|
|
TAR_PATH="${WORK_DIR}/bundle.tar.gz"
|
|
echo "Compressing..."
|
|
(cd "$WORK_DIR" && tar czf "$TAR_PATH" "neuronmem-v${FORMAT_VERSION}/")
|
|
COMPRESSED_SIZE="$(du -sh "$TAR_PATH" | cut -f1)"
|
|
echo " Compressed size: ${COMPRESSED_SIZE}"
|
|
|
|
# ── Encrypt ────────────────────────────────────────────────────────────────────
|
|
echo "Encrypting (AES-256-CBC, PBKDF2, 600k iterations)..."
|
|
openssl enc -aes-256-cbc \
|
|
-pbkdf2 \
|
|
-iter 600000 \
|
|
-salt \
|
|
-in "$TAR_PATH" \
|
|
-out "$OUTPUT_PATH" \
|
|
-pass "pass:${PASSPHRASE}"
|
|
|
|
# ── Cleanup ────────────────────────────────────────────────────────────────────
|
|
rm -rf "$WORK_DIR"
|
|
|
|
# ── Report ─────────────────────────────────────────────────────────────────────
|
|
FINAL_SIZE="$(du -sh "$OUTPUT_PATH" | cut -f1)"
|
|
echo ""
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
echo "Export complete."
|
|
echo " File: $OUTPUT_PATH"
|
|
echo " Size: ${FINAL_SIZE}"
|
|
echo " Nodes: ${NODE_COUNT}"
|
|
echo " Checksum: ${CHECKSUM:0:32}..."
|
|
echo " Timestamp: ${ISO_TIMESTAMP}"
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|