#!/usr/bin/env bash # setup.sh — install mem on macOS / Linux (latent-memory daily journal) # # Usage: # curl -fsSL https://raw.githubusercontent.com/karmugilen/mem/main/setup.sh | bash # ./setup.sh # ./setup.sh --dir ~/my-journal --no-agents # # Windows users: use setup.ps1 instead # irm https://raw.githubusercontent.com/karmugilen/mem/main/setup.ps1 | iex # set -euo pipefail REPO_URL="${MEM_REPO_URL:-https://github.com/karmugilen/mem.git}" INSTALL_DIR="${MEM_INSTALL_DIR:-$HOME/.local/share/mem-tool}" BIN_DIR="${MEM_BIN_DIR:-$HOME/.local/bin}" DATA_DIR="${MEM_HOME:-$HOME/mem}" INSTALL_AGENTS=1 CLONE_IF_NEEDED=1 NONINTERACTIVE=0 AGENTS_ONLY=0 PYTHON_CMD="" usage() { cat <<'EOF' setup.sh — install mem (macOS / Linux) Built on the latent memory concept: tiny day-wise Markdown cues shared across all projects — far fewer tokens than RAG or chat dumps. Options: --dir PATH Journal data home (default: ~/mem) --bin-dir PATH Where to put the mem symlink (default: ~/.local/bin) --install-dir PATH Tool source location (default: ~/.local/share/mem-tool) --no-agents Skip Claude / Grok / AGENTS instruction snippets --agents-only Only (re)install agent instruction files --no-clone Use files next to this script (local/dev install) -y, --yes Non-interactive -h, --help Show this help Windows: use setup.ps1 irm https://raw.githubusercontent.com/karmugilen/mem/main/setup.ps1 | iex EOF } log() { printf '==> %s\n' "$*"; } warn() { printf 'warn: %s\n' "$*" >&2; } die() { printf 'error: %s\n' "$*" >&2; exit 1; } need_cmd() { command -v "$1" >/dev/null 2>&1 || die "need '$1' on PATH" } find_python() { local c for c in python3 python; do if command -v "$c" >/dev/null 2>&1; then if "$c" -c 'import sys; raise SystemExit(0 if sys.version_info >= (3, 8) else 1)' 2>/dev/null; then PYTHON_CMD="$c" return fi fi done die "Python 3.8+ not found. Install python3 (macOS: brew install python; Linux: apt/dnf/pacman)." } # --- args ------------------------------------------------------------------- while [[ $# -gt 0 ]]; do case "$1" in --dir) DATA_DIR="$2"; shift 2 ;; --bin-dir) BIN_DIR="$2"; shift 2 ;; --install-dir) INSTALL_DIR="$2"; shift 2 ;; --no-agents) INSTALL_AGENTS=0; shift ;; --agents-only) AGENTS_ONLY=1; shift ;; --no-clone) CLONE_IF_NEEDED=0; shift ;; -y|--yes) NONINTERACTIVE=1; shift ;; -h|--help) usage; exit 0 ;; *) die "unknown option: $1 (try --help)" ;; esac done # Expand ~ if user passed a literal tilde-quoted path oddly DATA_DIR="${DATA_DIR/#\~/$HOME}" BIN_DIR="${BIN_DIR/#\~/$HOME}" INSTALL_DIR="${INSTALL_DIR/#\~/$HOME}" # --- resolve source tree ---------------------------------------------------- SCRIPT_DIR="" if [[ -n "${BASH_SOURCE[0]:-}" && -f "${BASH_SOURCE[0]}" ]]; then SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" fi resolve_source() { if [[ -n "$SCRIPT_DIR" && -f "$SCRIPT_DIR/bin/mem" ]]; then echo "$SCRIPT_DIR" return fi if [[ "$CLONE_IF_NEEDED" -eq 0 ]]; then die "bin/mem not found next to setup.sh (use a full clone, or drop --no-clone)" fi need_cmd git if [[ -f "$INSTALL_DIR/bin/mem" ]]; then log "updating existing install at $INSTALL_DIR" git -C "$INSTALL_DIR" pull --ff-only 2>/dev/null || true echo "$INSTALL_DIR" return fi log "cloning $REPO_URL → $INSTALL_DIR" mkdir -p "$(dirname "$INSTALL_DIR")" git clone --depth 1 "$REPO_URL" "$INSTALL_DIR" echo "$INSTALL_DIR" } install_agents() { local src="$1" local tpl="$src/templates" [[ -d "$tpl" ]] || { warn "no templates/ in $src — skip agents"; return; } local claude_md="$HOME/.claude/CLAUDE.md" local skill_dir="$HOME/.claude/skills/mem" mkdir -p "$HOME/.claude" "$skill_dir" 2>/dev/null || true if [[ -f "$tpl/CLAUDE.md.snippet" ]]; then if [[ -f "$claude_md" ]] && grep -q 'mem last 7\|daily journal memory\|Journal memory (mem)' "$claude_md" 2>/dev/null; then log "Claude instructions already present in $claude_md" elif [[ -f "$claude_md" ]]; then { echo "" echo "" cat "$tpl/CLAUDE.md.snippet" } >> "$claude_md" log "appended mem instructions to $claude_md" else cat "$tpl/CLAUDE.md.snippet" > "$claude_md" log "wrote $claude_md" fi fi if [[ -f "$tpl/SKILL.md" ]]; then cp "$tpl/SKILL.md" "$skill_dir/SKILL.md" log "installed Claude skill → $skill_dir/SKILL.md" fi local grok_skill="$HOME/.grok/skills/mem" if [[ -d "$HOME/.grok" ]] || command -v grok >/dev/null 2>&1; then mkdir -p "$grok_skill" if [[ -f "$tpl/SKILL.md" ]]; then cp "$tpl/SKILL.md" "$grok_skill/SKILL.md" log "installed Grok skill → $grok_skill/SKILL.md" fi fi if [[ -f "$tpl/AGENTS.md" ]]; then cp "$tpl/AGENTS.md" "$DATA_DIR/AGENTS.md" log "wrote $DATA_DIR/AGENTS.md" fi if [[ -f "$tpl/DATA_README.md" ]]; then cp "$tpl/DATA_README.md" "$DATA_DIR/README.md" fi } # --- main ------------------------------------------------------------------- find_python log "using Python: $PYTHON_CMD ($("$PYTHON_CMD" -c 'import sys; print("%d.%d"%sys.version_info[:2])'))" if [[ "$AGENTS_ONLY" -eq 1 ]]; then SRC="$(resolve_source)" mkdir -p "$DATA_DIR" install_agents "$SRC" log "done (agents only)" exit 0 fi SRC="$(resolve_source)" [[ -f "$SRC/bin/mem" ]] || die "bin/mem missing in $SRC" chmod +x "$SRC/bin/mem" 2>/dev/null || true log "data home: $DATA_DIR" log "tool source: $SRC" log "bin link: $BIN_DIR/mem" log "platform: $(uname -s 2>/dev/null || echo unix)" mkdir -p "$DATA_DIR/journal" "$BIN_DIR" if [[ ! -d "$DATA_DIR/.git" ]]; then need_cmd git git -C "$DATA_DIR" init -q cat > "$DATA_DIR/.gitignore" <<'EOF' .obsidian/ .DS_Store *~ *.swp EOF git -C "$DATA_DIR" add -A git -C "$DATA_DIR" commit -q -m "mem: initial journal" || true log "initialized git repo in $DATA_DIR (autocommit on every write)" else log "git repo already present in $DATA_DIR" fi # Prefer symlink; fall back to a small wrapper if symlink fails (some FS) if ln -sfn "$SRC/bin/mem" "$BIN_DIR/mem" 2>/dev/null; then log "linked $BIN_DIR/mem → $SRC/bin/mem" else cat > "$BIN_DIR/mem" < "$DATA_DIR/env.sh" </dev/null; then { echo "" echo "# mem CLI (added by setup.sh)" echo "export PATH=\"$BIN_DIR:\$PATH\"" } >> "$rc" log "appended PATH export to $rc" fi done # Create a minimal zshrc on macOS if nothing exists yet if [[ "$(uname -s 2>/dev/null)" == "Darwin" && ! -f "$HOME/.zshrc" ]]; then { echo "# mem CLI (added by setup.sh)" echo "export PATH=\"$BIN_DIR:\$PATH\"" } > "$HOME/.zshrc" log "created $HOME/.zshrc with PATH for mem" fi fi ;; esac if [[ "$INSTALL_AGENTS" -eq 1 ]]; then install_agents "$SRC" fi export MEM_HOME="$DATA_DIR" export PATH="$BIN_DIR:$PATH" log "verifying..." if ! "$PYTHON_CMD" "$SRC/bin/mem" help >/dev/null; then die "mem help failed" fi "$PYTHON_CMD" "$SRC/bin/mem" path MEM_HOME="$DATA_DIR" "$PYTHON_CMD" "$SRC/bin/mem" log "mem: installed — ready to use as latent memory" cat <