mirror of
https://github.com/lukaszraczylo/claude-mnemonic.git
synced 2026-06-05 23:03:55 +00:00
Initial commit
This commit is contained in:
Executable
+424
@@ -0,0 +1,424 @@
|
||||
#!/bin/bash
|
||||
# Claude Mnemonic - Remote Installation Script
|
||||
# Usage: curl -sSL https://raw.githubusercontent.com/lukaszraczylo/claude-mnemonic/main/scripts/install.sh | bash
|
||||
#
|
||||
# Or with a specific version:
|
||||
# curl -sSL https://raw.githubusercontent.com/lukaszraczylo/claude-mnemonic/main/scripts/install.sh | bash -s -- v1.0.0
|
||||
|
||||
set -e
|
||||
|
||||
# Configuration
|
||||
GITHUB_REPO="lukaszraczylo/claude-mnemonic"
|
||||
INSTALL_DIR="$HOME/.claude/plugins/marketplaces/claude-mnemonic"
|
||||
CACHE_DIR="$HOME/.claude/plugins/cache/claude-mnemonic/claude-mnemonic"
|
||||
PLUGINS_FILE="$HOME/.claude/plugins/installed_plugins.json"
|
||||
SETTINGS_FILE="$HOME/.claude/settings.json"
|
||||
MARKETPLACES_FILE="$HOME/.claude/plugins/known_marketplaces.json"
|
||||
PLUGIN_KEY="claude-mnemonic@claude-mnemonic"
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
info() {
|
||||
echo -e "${BLUE}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
success() {
|
||||
echo -e "${GREEN}[OK]${NC} $1"
|
||||
}
|
||||
|
||||
warn() {
|
||||
echo -e "${YELLOW}[WARN]${NC} $1"
|
||||
}
|
||||
|
||||
error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Detect OS and architecture
|
||||
detect_platform() {
|
||||
local os arch
|
||||
|
||||
case "$(uname -s)" in
|
||||
Darwin)
|
||||
os="darwin"
|
||||
;;
|
||||
Linux)
|
||||
os="linux"
|
||||
;;
|
||||
MINGW*|MSYS*|CYGWIN*)
|
||||
os="windows"
|
||||
;;
|
||||
*)
|
||||
error "Unsupported operating system: $(uname -s)"
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$(uname -m)" in
|
||||
x86_64|amd64)
|
||||
arch="amd64"
|
||||
;;
|
||||
arm64|aarch64)
|
||||
arch="arm64"
|
||||
;;
|
||||
*)
|
||||
error "Unsupported architecture: $(uname -m)"
|
||||
;;
|
||||
esac
|
||||
|
||||
# Check for unsupported combinations
|
||||
if [[ "$os" == "linux" && "$arch" == "arm64" ]]; then
|
||||
error "Linux ARM64 is not currently supported due to CGO cross-compilation limitations"
|
||||
fi
|
||||
|
||||
echo "${os}_${arch}"
|
||||
}
|
||||
|
||||
# Get the latest release version from GitHub
|
||||
get_latest_version() {
|
||||
local version
|
||||
version=$(curl -sS "https://api.github.com/repos/${GITHUB_REPO}/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/')
|
||||
|
||||
if [[ -z "$version" ]]; then
|
||||
error "Failed to fetch latest version from GitHub"
|
||||
fi
|
||||
|
||||
echo "$version"
|
||||
}
|
||||
|
||||
# Download and extract the release
|
||||
download_release() {
|
||||
local version="$1"
|
||||
local platform="$2"
|
||||
local tmp_dir
|
||||
|
||||
tmp_dir=$(mktemp -d)
|
||||
trap "rm -rf $tmp_dir" EXIT
|
||||
|
||||
# Construct download URL
|
||||
local archive_name="claude-mnemonic_${version#v}_${platform}.tar.gz"
|
||||
local download_url="https://github.com/${GITHUB_REPO}/releases/download/${version}/${archive_name}"
|
||||
|
||||
info "Downloading ${archive_name}..."
|
||||
|
||||
if ! curl -sSL -o "$tmp_dir/release.tar.gz" "$download_url"; then
|
||||
error "Failed to download release from: $download_url"
|
||||
fi
|
||||
|
||||
info "Extracting archive..."
|
||||
if ! tar -xzf "$tmp_dir/release.tar.gz" -C "$tmp_dir"; then
|
||||
error "Failed to extract archive"
|
||||
fi
|
||||
|
||||
# Stop existing worker if running
|
||||
info "Stopping existing worker (if running)..."
|
||||
pkill -9 -f 'claude-mnemonic.*worker' 2>/dev/null || true
|
||||
pkill -9 -f '\.claude/plugins/.*/worker' 2>/dev/null || true
|
||||
lsof -ti :37777 | xargs kill -9 2>/dev/null || true
|
||||
sleep 1
|
||||
|
||||
# Create installation directories
|
||||
info "Installing to ${INSTALL_DIR}..."
|
||||
mkdir -p "$INSTALL_DIR/hooks"
|
||||
mkdir -p "$INSTALL_DIR/.claude-plugin"
|
||||
|
||||
# Copy binaries
|
||||
cp "$tmp_dir/worker" "$INSTALL_DIR/"
|
||||
cp "$tmp_dir/mcp-server" "$INSTALL_DIR/"
|
||||
cp "$tmp_dir/hooks/"* "$INSTALL_DIR/hooks/"
|
||||
|
||||
# Copy plugin configuration
|
||||
cp "$tmp_dir/.claude-plugin/"* "$INSTALL_DIR/.claude-plugin/"
|
||||
|
||||
# Make binaries executable
|
||||
chmod +x "$INSTALL_DIR/worker"
|
||||
chmod +x "$INSTALL_DIR/mcp-server"
|
||||
chmod +x "$INSTALL_DIR/hooks/"*
|
||||
|
||||
success "Binaries installed to ${INSTALL_DIR}"
|
||||
}
|
||||
|
||||
# Register the plugin with Claude Code
|
||||
register_plugin() {
|
||||
local version="$1"
|
||||
local timestamp
|
||||
timestamp=$(date -u +"%Y-%m-%dT%H:%M:%S.000Z")
|
||||
|
||||
# Ensure directories exist
|
||||
mkdir -p "$HOME/.claude/plugins"
|
||||
mkdir -p "${CACHE_DIR}/${version}"
|
||||
|
||||
# Create JSON files if they don't exist
|
||||
[[ ! -f "$PLUGINS_FILE" ]] && echo '{"version": 2, "plugins": {}}' > "$PLUGINS_FILE"
|
||||
[[ ! -f "$SETTINGS_FILE" ]] && echo '{}' > "$SETTINGS_FILE"
|
||||
[[ ! -f "$MARKETPLACES_FILE" ]] && echo '{}' > "$MARKETPLACES_FILE"
|
||||
|
||||
# Check for jq
|
||||
if ! command -v jq &> /dev/null; then
|
||||
warn "jq is not installed. Plugin registration requires jq."
|
||||
warn "Please install jq: brew install jq (macOS) or apt-get install jq (Linux)"
|
||||
warn "Then run: $0 --register-only"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local cache_path="${CACHE_DIR}/${version}"
|
||||
|
||||
# Copy files to cache directory
|
||||
mkdir -p "$cache_path/.claude-plugin"
|
||||
mkdir -p "$cache_path/hooks"
|
||||
cp -r "$INSTALL_DIR/"* "$cache_path/" 2>/dev/null || true
|
||||
|
||||
# Register in installed_plugins.json
|
||||
local plugin_entry
|
||||
plugin_entry=$(cat <<EOF
|
||||
[{
|
||||
"scope": "user",
|
||||
"installPath": "$cache_path",
|
||||
"version": "${version#v}",
|
||||
"installedAt": "$timestamp",
|
||||
"lastUpdated": "$timestamp",
|
||||
"isLocal": true
|
||||
}]
|
||||
EOF
|
||||
)
|
||||
|
||||
jq --arg key "$PLUGIN_KEY" --argjson entry "$plugin_entry" \
|
||||
'.plugins[$key] = $entry' "$PLUGINS_FILE" > "${PLUGINS_FILE}.tmp" \
|
||||
&& mv "${PLUGINS_FILE}.tmp" "$PLUGINS_FILE"
|
||||
|
||||
success "Plugin registered in installed_plugins.json"
|
||||
|
||||
# Enable in settings.json
|
||||
jq --arg key "$PLUGIN_KEY" \
|
||||
'.enabledPlugins //= {} | .enabledPlugins[$key] = true' "$SETTINGS_FILE" > "${SETTINGS_FILE}.tmp" \
|
||||
&& mv "${SETTINGS_FILE}.tmp" "$SETTINGS_FILE"
|
||||
|
||||
success "Plugin enabled in settings.json"
|
||||
|
||||
# Register marketplace
|
||||
local marketplace_entry
|
||||
marketplace_entry=$(cat <<EOF
|
||||
{
|
||||
"source": {
|
||||
"source": "directory",
|
||||
"path": "$INSTALL_DIR"
|
||||
},
|
||||
"installLocation": "$INSTALL_DIR",
|
||||
"lastUpdated": "$timestamp"
|
||||
}
|
||||
EOF
|
||||
)
|
||||
|
||||
jq --arg key "claude-mnemonic" --argjson entry "$marketplace_entry" \
|
||||
'.[$key] = $entry' "$MARKETPLACES_FILE" > "${MARKETPLACES_FILE}.tmp" \
|
||||
&& mv "${MARKETPLACES_FILE}.tmp" "$MARKETPLACES_FILE"
|
||||
|
||||
success "Marketplace registered in known_marketplaces.json"
|
||||
}
|
||||
|
||||
# Start the worker service
|
||||
start_worker() {
|
||||
local worker_path="$INSTALL_DIR/worker"
|
||||
|
||||
if [[ ! -x "$worker_path" ]]; then
|
||||
error "Worker binary not found at $worker_path"
|
||||
fi
|
||||
|
||||
info "Starting worker service..."
|
||||
nohup "$worker_path" > /tmp/claude-mnemonic-worker.log 2>&1 &
|
||||
|
||||
sleep 2
|
||||
|
||||
if curl -sS http://localhost:37777/health > /dev/null 2>&1; then
|
||||
success "Worker started successfully at http://localhost:37777"
|
||||
else
|
||||
warn "Worker may not have started properly. Check /tmp/claude-mnemonic-worker.log"
|
||||
fi
|
||||
}
|
||||
|
||||
# Check optional dependencies for semantic search
|
||||
check_optional_deps() {
|
||||
local missing_deps=()
|
||||
local install_hints=""
|
||||
|
||||
# Check for Python 3.13+
|
||||
if command -v python3 &> /dev/null; then
|
||||
local py_version=$(python3 -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")' 2>/dev/null)
|
||||
if [[ "$py_version" < "3.13" ]]; then
|
||||
missing_deps+=("Python 3.13+ (found $py_version)")
|
||||
fi
|
||||
else
|
||||
missing_deps+=("Python 3.13+")
|
||||
fi
|
||||
|
||||
# Check for uvx
|
||||
if ! command -v uvx &> /dev/null; then
|
||||
missing_deps+=("uvx")
|
||||
fi
|
||||
|
||||
if [[ ${#missing_deps[@]} -gt 0 ]]; then
|
||||
echo ""
|
||||
warn "Optional dependencies missing (needed for semantic search):"
|
||||
for dep in "${missing_deps[@]}"; do
|
||||
echo " - $dep"
|
||||
done
|
||||
echo ""
|
||||
|
||||
# Detect OS and show appropriate install command
|
||||
case "$(uname -s)" in
|
||||
Darwin)
|
||||
info "Install on macOS:"
|
||||
echo " brew install python3"
|
||||
echo " pip3 install uvx"
|
||||
;;
|
||||
Linux)
|
||||
info "Install on Linux:"
|
||||
echo " sudo apt install python3 python3-pip"
|
||||
echo " pip3 install uvx"
|
||||
;;
|
||||
MINGW*|MSYS*|CYGWIN*)
|
||||
info "Install on Windows:"
|
||||
echo " winget install Python.Python.3"
|
||||
echo " pip install uvx"
|
||||
;;
|
||||
esac
|
||||
echo ""
|
||||
info "Note: Requires Python 3.13+. Most package managers install the latest version."
|
||||
echo ""
|
||||
info "Semantic search will be disabled until these are installed."
|
||||
info "Core functionality (SQLite storage, full-text search) will work."
|
||||
echo ""
|
||||
else
|
||||
success "Optional dependencies found (semantic search enabled)"
|
||||
fi
|
||||
}
|
||||
|
||||
# Main installation flow
|
||||
main() {
|
||||
local version="${1:-}"
|
||||
|
||||
echo ""
|
||||
echo "╔═══════════════════════════════════════════════════════════╗"
|
||||
echo "║ Claude Mnemonic - Installation Script ║"
|
||||
echo "║ Persistent Memory System for Claude Code CLI ║"
|
||||
echo "╚═══════════════════════════════════════════════════════════╝"
|
||||
echo ""
|
||||
|
||||
# Check required dependencies
|
||||
if ! command -v curl &> /dev/null; then
|
||||
error "curl is required but not installed"
|
||||
fi
|
||||
|
||||
if ! command -v tar &> /dev/null; then
|
||||
error "tar is required but not installed"
|
||||
fi
|
||||
|
||||
# Detect platform
|
||||
local platform
|
||||
platform=$(detect_platform)
|
||||
info "Detected platform: $platform"
|
||||
|
||||
# Get version
|
||||
if [[ -z "$version" ]]; then
|
||||
info "Fetching latest release..."
|
||||
version=$(get_latest_version)
|
||||
fi
|
||||
info "Installing version: $version"
|
||||
|
||||
# Download and install
|
||||
download_release "$version" "$platform"
|
||||
|
||||
# Register plugin
|
||||
if register_plugin "$version"; then
|
||||
success "Plugin registered successfully"
|
||||
else
|
||||
warn "Plugin registration incomplete - please install jq and run again"
|
||||
fi
|
||||
|
||||
# Start worker
|
||||
start_worker
|
||||
|
||||
# Check optional dependencies
|
||||
check_optional_deps
|
||||
|
||||
echo ""
|
||||
echo "╔═══════════════════════════════════════════════════════════╗"
|
||||
echo "║ Installation Complete! ║"
|
||||
echo "╠═══════════════════════════════════════════════════════════╣"
|
||||
echo "║ Dashboard: http://localhost:37777 ║"
|
||||
echo "║ Logs: /tmp/claude-mnemonic-worker.log ║"
|
||||
echo "║ ║"
|
||||
echo "║ Start a new Claude Code CLI session to activate memory. ║"
|
||||
echo "╚═══════════════════════════════════════════════════════════╝"
|
||||
echo ""
|
||||
}
|
||||
|
||||
# Handle --register-only flag
|
||||
if [[ "${1:-}" == "--register-only" ]]; then
|
||||
version=$(cat "$INSTALL_DIR/.claude-plugin/plugin.json" 2>/dev/null | grep '"version"' | sed -E 's/.*"([^"]+)".*/\1/' || echo "1.0.0")
|
||||
register_plugin "v$version"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Handle --uninstall flag
|
||||
if [[ "${1:-}" == "--uninstall" ]]; then
|
||||
KEEP_DATA=false
|
||||
[[ "${2:-}" == "--keep-data" ]] && KEEP_DATA=true
|
||||
|
||||
echo ""
|
||||
echo "╔═══════════════════════════════════════════════════════════╗"
|
||||
echo "║ Claude Mnemonic - Uninstallation ║"
|
||||
echo "╚═══════════════════════════════════════════════════════════╝"
|
||||
echo ""
|
||||
|
||||
info "Stopping worker processes..."
|
||||
pkill -9 -f 'claude-mnemonic.*worker' 2>/dev/null || true
|
||||
pkill -9 -f '\.claude/plugins/.*/worker' 2>/dev/null || true
|
||||
lsof -ti :37777 | xargs kill -9 2>/dev/null || true
|
||||
sleep 1
|
||||
|
||||
info "Removing plugin directories..."
|
||||
rm -rf "$INSTALL_DIR"
|
||||
rm -rf "$CACHE_DIR"
|
||||
success "Plugin directories removed"
|
||||
|
||||
# Remove from JSON files (if jq is available)
|
||||
if command -v jq &> /dev/null; then
|
||||
info "Cleaning up Claude Code configuration..."
|
||||
if [[ -f "$PLUGINS_FILE" ]]; then
|
||||
jq 'del(.plugins["'"$PLUGIN_KEY"'"])' "$PLUGINS_FILE" > "${PLUGINS_FILE}.tmp" && mv "${PLUGINS_FILE}.tmp" "$PLUGINS_FILE"
|
||||
fi
|
||||
if [[ -f "$SETTINGS_FILE" ]]; then
|
||||
jq 'del(.enabledPlugins["'"$PLUGIN_KEY"'"])' "$SETTINGS_FILE" > "${SETTINGS_FILE}.tmp" && mv "${SETTINGS_FILE}.tmp" "$SETTINGS_FILE"
|
||||
fi
|
||||
if [[ -f "$MARKETPLACES_FILE" ]]; then
|
||||
jq 'del(.["claude-mnemonic"])' "$MARKETPLACES_FILE" > "${MARKETPLACES_FILE}.tmp" && mv "${MARKETPLACES_FILE}.tmp" "$MARKETPLACES_FILE"
|
||||
fi
|
||||
success "Configuration cleaned up"
|
||||
else
|
||||
warn "jq not found - configuration files not cleaned up"
|
||||
fi
|
||||
|
||||
# Handle data directory
|
||||
DATA_DIR="$HOME/.claude-mnemonic"
|
||||
if [[ -d "$DATA_DIR" ]]; then
|
||||
if [[ "$KEEP_DATA" == "true" ]]; then
|
||||
warn "Keeping data directory: $DATA_DIR"
|
||||
else
|
||||
info "Removing data directory..."
|
||||
rm -rf "$DATA_DIR"
|
||||
success "Data directory removed"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
success "Claude Mnemonic uninstalled successfully"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user