# PowerMem — automated Claude Code setup

This file is a **prompt for Claude Code**. Open Claude Code in your terminal and say:

> Read and follow `apps/claude-code-plugin/SETUP.md` to set up PowerMem memory for Claude Code.

Claude Code will then run the steps below: detect whether you are in the PowerMem
source tree or not, ask you for the few required secrets, and wire PowerMem up as a
**globally enabled** plugin so every `claude` session (interactive AND non-interactive
`claude -p`) uses it automatically — no per-session `--plugin-dir` flag.

---

## Installed plugin initialization

Use this section when the `memory-powermem` plugin is already installed from a
Claude Code marketplace and the user runs:

```text
/memory-powermem:init
```

In this mode, **do not** run the source/developer install flow below: do not build
hook binaries, do not stage the plugin, do not run `claude plugin marketplace add`,
do not run `claude plugin install`, and do not build the dashboard. The plugin is
already installed; this section only prepares the PowerMem backend that the plugin
connects to.

Installed-plugin init ensures `uv` is available, then starts the backend with
the uvx-style launcher. The package spec depends on the storage backend: the
default SQLite path uses `powermem[server,extras]` (pulls `sentence-transformers`
for the local `huggingface` embedder), while the OceanBase path uses
`powermem[server,seekdb]`. It does not create a plugin-local venv. Therefore the
PyPI release used by this flow must already contain the backend capabilities
required by the plugin, including the local embedding dependencies. If the user
is validating a plugin change that depends on unpublished backend code, use
`POWERMEM_INIT_PACKAGE` to pass that exact Git branch or commit to `uvx --from`
instead of the PyPI package.

Installed-plugin init is idempotent and uses plugin-local state:

```text
$HOME/.powermem/
  .env
  runtime.env
  powermem.pid
  powermem-server.log
  seekdb_data/
```

Follow these steps:

**Always use a two-step invocation: discover or reuse `CLAUDE_PLUGIN_ROOT`
first, then run the script.** Never write `VAR=val sh "$VAR/..."` on one line —
the shell expands `$VAR` before the assignment, producing an empty path.

```bash
# If CLAUDE_PLUGIN_ROOT is not already set, find the plugin root:
if [ -z "${CLAUDE_PLUGIN_ROOT:-}" ]; then
  export CLAUDE_PLUGIN_ROOT=$(find ~/.claude/plugins/cache/powermem/memory-powermem -maxdepth 2 -name scripts -type d 2>/dev/null | head -1 | xargs dirname)
fi
sh "$CLAUDE_PLUGIN_ROOT/scripts/..."
```

1. If the skill was just installed or updated, ask the user to run `/reload-plugins`
   first, then retry `/memory-powermem:init`.
2. Run `sh "$CLAUDE_PLUGIN_ROOT/scripts/status.sh"` and inspect whether config,
   uv, managed PID, Python versions, and health are present.
3. If `.env` is missing, run init with auto-detection first:

   ```bash
   sh "$CLAUDE_PLUGIN_ROOT/scripts/init.sh"
   ```

   The script reads the current process environment first and attempts to derive
   the supported Anthropic configuration. It uses `ANTHROPIC_API_KEY` first; only
   when that is absent does it use `ANTHROPIC_AUTH_TOKEN` together with
   `ANTHROPIC_BASE_URL`. It also reads `ANTHROPIC_MODEL` from the environment.
   If the environment does not provide a complete config, it falls back to
   `~/.claude/settings.json` using the same Anthropic keys. It writes the
   plugin-local `.env` with the full PowerMem backend defaults: SQLite storage
   (default for coding agent; set `POWERMEM_INIT_DATABASE_PROVIDER=oceanbase` for
   OceanBase/seekdb production use), local HuggingFace embedding (no API key,
   `sentence-transformers` from `powermem[extras]`), server settings, and logging
   settings.
4. If init reports missing values, ask the user only for those missing values. Do
   not invent credentials. Re-run init with the matching environment variables:

   ```bash
   POWERMEM_INIT_LLM_PROVIDER=anthropic \
   POWERMEM_INIT_LLM_MODEL=anthropic/claude-sonnet-4.6 \
   POWERMEM_INIT_LLM_API_KEY=... \
   sh "$CLAUDE_PLUGIN_ROOT/scripts/init.sh"
   ```

   For a bearer-token gateway, use:

   ```bash
   POWERMEM_INIT_LLM_PROVIDER=anthropic \
   POWERMEM_INIT_LLM_MODEL=anthropic/claude-sonnet-4.6 \
   POWERMEM_INIT_LLM_AUTH_TOKEN=... \
   POWERMEM_INIT_LLM_BASE_URL=https://your-gateway.example.com \
   sh "$CLAUDE_PLUGIN_ROOT/scripts/init.sh"
   ```

   Optional variables:
   - `POWERMEM_INIT_DATABASE_PROVIDER`: storage backend — `sqlite` (default, coding
     agent) or `oceanbase` (production/cluster). Invalid values fall back to `sqlite`.
   - `POWERMEM_INIT_LLM_BASE_URL` for a custom provider gateway.
   - `POWERMEM_INIT_PACKAGE` to test unpublished backend code through
     `uvx --from` instead of PyPI `powermem`. Match the extras to the storage
     backend: `powermem[server,extras]` for SQLite (default, includes
     `sentence-transformers`), `powermem[server,seekdb]` for OceanBase. Example:
     `powermem[server,extras] @ git+https://github.com/oceanbase/powermem.git@<branch-or-sha>`.
   - `POWERMEM_INIT_PYTHON` to force a specific Python >= 3.11.
   - `POWERMEM_INIT_PORT` to force the managed server port.
5. Never print API keys, auth tokens, or other credentials. Mask any secret in
   summaries.
6. After init succeeds, run `sh "$CLAUDE_PLUGIN_ROOT/scripts/status.sh"` again and
   report the base URL.
7. The hook launcher reads `runtime.env`, so once init writes a base URL, prompt
   recall and session-save hooks use that backend automatically.

The default local embedding model (`all-MiniLM-L6-v2`) is downloaded
automatically by PowerMem at startup — cache hit loads from disk via
`SentenceTransformer(local_files_only=True)`; cache miss on CN networks
downloads through ModelScope and bridges into the HuggingFace hub cache;
cache miss elsewhere downloads from HuggingFace with a 30s timeout.
`POWERMEM_INIT_PRELOAD_MODEL` is deprecated and now a no-op; init prints a
deprecation message if it is set.

If startup fails with `No module named 'sentence_transformers'`, the backend
package resolved by `uvx --from` does not include the local embedding dependency.
Publish a backend build that includes it, or set `POWERMEM_INIT_PACKAGE` to a Git
branch/commit that does.

To test connectivity:
```bash
curl -s -m 10 -o /dev/null -w "ModelScope: HTTP %{http_code}\n" \
  https://www.modelscope.cn/api/v1/models/AI-ModelScope/all-MiniLM-L6-v2
curl -s -m 10 -o /dev/null -w "HuggingFace: HTTP %{http_code}\n" \
  https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2
```

---

Set up PowerMem memory for Claude Code on this machine **globally**. Do the whole
integration autonomously and ask me for any secret you need — never invent credentials.

**🔒 DATA SAFETY — API Key Masking (MANDATORY):**
When displaying ANY `.env` content — current values, proposed changes, confirmation
summaries, or any other output — you MUST mask `LLM_API_KEY`, `LLM_AUTH_TOKEN`,
and any other secret
values (passwords, tokens, keys):
- **Key ≥ 10 chars:** show only first 4 + last 4 characters (e.g. `sk-a…b12x`)
- **Key < 10 chars:** show `***`
Non-secret values (provider, model, base URLs, storage type, etc.) may be shown in full.

**🔒 Shell commands MUST also avoid printing secrets.** Terminal output is visible to
the user and Claude Code cannot retroactively redact it. Follow these rules:
- When reading env vars or `.env`, use inline masking so the shell never prints plaintext:
  `echo "${VAR:0:4}...${VAR: -4}"` to show first 4 + last 4, or `[ -n "$VAR" ] && echo "set" || echo "empty"` just to check existence.
- When you need to `cat .env` or `grep` for secrets, pipe through sed to mask before printing:
  `cat .env | sed -E 's/(API_KEY=).*/\1***REDACTED***/'`
- Never run `echo $LLM_API_KEY`, `echo $LLM_AUTH_TOKEN`, `env | grep KEY`,
  `cat .env` (unmasked), or any command
  that would print a secret value directly to stdout.
- Use `read` with `-s` (silent) when prompting for secrets interactively.

**⚠️ `.env` changes always require user approval.** Before modifying `.env` for any
reason (LLM config, embedder settings, storage switches, ...), show the user the
**masked** current values (using the rules above), propose the exact change, and WAIT
for confirmation. Never patch `.env` silently.

This procedure is **idempotent**: it is safe to re-run. Each step must detect existing
state and either skip, reuse, or refresh it instead of failing or duplicating work.

1. DETECT CONTEXT. The current directory is the PowerMem source tree if a
   pyproject.toml here has name = "powermem" (or src/powermem/ and
   apps/claude-code-plugin/ both exist). Tell me which path you will take:
     - SOURCE  -> build & deploy from this checkout and install the Claude Code
                  plugin GLOBALLY in HTTP mode (hooks -> REST; needs Go 1.22+).
     - PYPI/MCP -> install PowerMem from PyPI with uv and connect via the
                   powermem-mcp server (the plugin itself is NOT on PyPI).

**⚠️ RULE: Every time you need to modify `.env` — for any reason, even a
single variable — you MUST stop and ask the user what value to use. Show
the **masked** current content of the relevant lines (per the 🔒 DATA SAFETY
rules above), propose the change, and WAIT for the user's confirmation before
writing. Never silently patch `.env`.**

   1a. DETECT REGION (run before any network operations). Detect whether this
   machine is in China — this determines uv install source, model download source,
   and Python package index:

   ```bash
   CC=$(curl -s -m 5 https://ipinfo.io/country 2>/dev/null || echo "UNKNOWN")
   echo "Region: $CC"
   ```

   - **If `CC=CN`**: install uv through the USTC GitHub Release mirror, use
     **ModelScope** for model downloads, and set the Python package index to
     `https://pypi.tuna.tsinghua.edu.cn/simple/`.
   - **If `CC != CN` (or `UNKNOWN`)**: install uv through the official Astral
     installer, use **HuggingFace** for model downloads, and use the default PyPI
     index.
   - Store `CC` in a shell variable — every uv install, package install, and model
     download step below branches on this value. Re-check on every re-run (region
     may change if the machine moves or VPN state changes).

   **uv installation (run before any Python package install):**

   ```bash
   if ! command -v uv >/dev/null 2>&1; then
     if [ "$CC" = "CN" ]; then
       export UV_DOWNLOAD_URL=https://mirrors.ustc.edu.cn/github-release/astral-sh/uv/LatestRelease/
       curl -sL https://mirrors.ustc.edu.cn/github-release/astral-sh/uv/LatestRelease/uv-installer.sh | sh
     else
       curl -LsSf https://astral.sh/uv/install.sh | sh
     fi
     export PATH="$HOME/.local/bin:$HOME/.cargo/bin:$PATH"
   fi
   uv --version
   ```

   **uv pip conventions (apply to ALL package installs in this file):**

   a. **CN mirror** — if `CC=CN`, add
      `--default-index https://pypi.tuna.tsinghua.edu.cn/simple` to every
      `uv pip install` command. Non-CN (or UNKNOWN) uses the default PyPI index
      with no extra flags.

   b. **Retry — on failure, retry up to 3 times.** If a `uv pip install` command
      fails, do NOT immediately escalate to the user. Retry the EXACT same command
      up to 2 more times (3 total attempts). If all 3 fail, only then report the
      error. Between retries, wait 2 seconds:
      ```bash
      for i in 1 2 3; do
        uv pip install --python "$POWERMEM_PYTHON" ... && break
        echo "uv pip install attempt $i failed, retrying in 2s..."
        sleep 2
      done
      ```
      This applies to editable installs, dependency installs, model download
      dependencies, etc.

   c. **All four package install locations** affected by (a)-(b):
      1. `uv pip install --python "$POWERMEM_PYTHON" -e '.[server,seekdb]'`
      2. `uv pip install --python "$POWERMEM_PYTHON" -q modelscope`
      3. `uv pip install --python "$POWERMEM_PYTHON" "powermem[server,seekdb]"`
      4. `uv pip install --python "$POWERMEM_PYTHON" -q huggingface_hub`

2. COLLECT CONFIG (idempotent). If a .env already exists in the working directory
   with LLM_PROVIDER / LLM_API_KEY or LLM_AUTH_TOKEN / LLM_MODEL set to real
   values (not placeholders
   like `your_api_key_here`), REUSE it — skip directly to step 3a/3b. Only collect
   what is missing. Use zero-config defaults for everything else (storage = embedded
   seekdb, embedder = local all-MiniLM-L6-v2) unless I say otherwise.

   **2a. Auto-detect or manual?** Use AskUserQuestion (single-select):

   | Option | Description |
   |--------|-------------|
   | Yes, auto-detect | Auto-detect LLM config from the current process environment, then `~/.claude/settings.json`, then ask only for missing fields |

   If the user selects "Yes, auto-detect" (or "Other" and types "yes"/"auto"):

   **Auto-detection priority chain**:
   1. **Current process environment variables** — check these first:
      - `ANTHROPIC_API_KEY`
      - `ANTHROPIC_AUTH_TOKEN`
      - `ANTHROPIC_BASE_URL`
      - `ANTHROPIC_MODEL`
   2. **`~/.claude/settings.json`** — use `env.ANTHROPIC_*`, `env.LLM_*`, and
      top-level `model` as fallback sources
   3. **Manual input** — ask only for fields that are still missing

   PowerMem supports Claude Code's Anthropic API-key path and bearer-token gateway
   path. Do not treat `ANTHROPIC_AUTH_TOKEN` as `LLM_API_KEY`; copy it to
   `LLM_AUTH_TOKEN` and require `ANTHROPIC_BASE_URL`. A token without a base URL
   is incomplete; fall back to API-key mode or ask for the base URL instead of
   sending the token to Anthropic's default endpoint. Do not migrate
   `CLAUDE_CODE_OAUTH_TOKEN`, `/login` credentials, `apiKeyHelper`, Bedrock,
   Vertex, or Foundry as either `LLM_API_KEY` or `LLM_AUTH_TOKEN`.

   **Step 1 — Check OS environment variables.** Run these checks silently (do not
   print the values, only note whether each field was found):

   | Field | Check |
   |-------|-------|
   | LLM_PROVIDER | If `ANTHROPIC_API_KEY` or `ANTHROPIC_AUTH_TOKEN` is set → `anthropic`; otherwise infer from `ANTHROPIC_MODEL` prefix if present |
   | LLM_MODEL | `$ANTHROPIC_MODEL` |
   | LLM_API_KEY | `$ANTHROPIC_API_KEY` |
   | LLM_AUTH_TOKEN | `$ANTHROPIC_AUTH_TOKEN`, only when `ANTHROPIC_API_KEY` is absent |
   | LLM_BASE_URL | `$ANTHROPIC_BASE_URL` |

   If environment model detection fails, read `env.ANTHROPIC_MODEL`,
   `env.LLM_MODEL`, or top-level `model` from `~/.claude/settings.json`. Keep
   the model exactly as configured. Do not strip a `<provider>/` prefix and do
   not rewrite dotted versions:
     - `"anthropic/claude-opus-4.6"` → `LLM_PROVIDER=anthropic`, `LLM_MODEL=anthropic/claude-opus-4.6`
     - `"anthropic/claude-sonnet-4.6"` → `LLM_PROVIDER=anthropic`, `LLM_MODEL=anthropic/claude-sonnet-4.6`

   **Credentials** — preserve PowerMem's environment precedence: API key first,
   bearer-token gateway second. If environment credentials are incomplete, fall
   back to the old `~/.claude/settings.json` credential flow. Read:
     - `ANTHROPIC_API_KEY`
     - `ANTHROPIC_AUTH_TOKEN`, only if `ANTHROPIC_API_KEY` is absent
     - fallback `env.ANTHROPIC_AUTH_TOKEN` / `env.ANTHROPIC_API_KEY` from
       `~/.claude/settings.json`

   **Base URL** — read directly:
     - `ANTHROPIC_BASE_URL`
     - fallback `env.ANTHROPIC_BASE_URL` or `env.LLM_BASE_URL` from
       `~/.claude/settings.json`
     - If `ANTHROPIC_AUTH_TOKEN` is used, `ANTHROPIC_BASE_URL` is required.
     - If `ANTHROPIC_API_KEY` is used and the base URL is absent, leave it blank —
       PowerMem will use the provider's default endpoint.

   **Step 3 — For any fields still missing after environment and settings
   detection,** ask as a plain chat question (one at a time, per 2b–2e below).
   Only ask for what is actually missing.

   After detection/manual input, show a **masked** summary of the merged result
   (per 🔒 DATA SAFETY rules), noting the source of each field
   (env / settings.json / manual).
   Then jump to **2f**.

   If the user does NOT select auto-detect, fall back to the manual flow:

   **2b.** Ask: "Any custom base URL? (paste it, or say `no` to use the default)"

   **2c.** Ask: "What provider id? (e.g. openai, anthropic, qwen, deepseek, ollama)"

   **2d.** Ask for the credential: API key for direct provider access, or auth
   token for an Anthropic-compatible gateway. Skip if provider is `ollama` or `vllm`.

   **2e.** Ask: "Which model? (e.g. gpt-4o-mini, claude-sonnet-4-6, qwen-plus)"

   **For 2b–2e, ask each question as a plain chat message, one at a time.**
   Do NOT use AskUserQuestion for these free-text inputs.

   **2f. Confirm and write.** Show a **masked** summary of what will be written (per
   🔒 DATA SAFETY rules above), then WAIT for explicit "yes" before writing. Copy
   `.env.example` if `.env` does not exist, then fill `LLM_PROVIDER` /
   `LLM_API_KEY` or `LLM_AUTH_TOKEN`
   / `LLM_MODEL`. If a base URL was given, write it to the provider-prefixed
   `*_LLM_BASE_URL` (e.g. `OPENAI_LLM_BASE_URL`) — verify spelling against
   `.env.example.full`; a typo is silently ignored.

3a. SOURCE path (global install):
    - Ensure uv is installed using Step 1a, then create/reuse an explicit Python
      environment and install PowerMem with uv:
      ```bash
      uv venv venv --python python3.11
      POWERMEM_PYTHON="$(pwd)/venv/bin/python"
      export PATH="$(pwd)/venv/bin:$PATH"
      uv pip install --python "$POWERMEM_PYTHON" -e '.[server,seekdb]'
      ```
      ⚠️ Both extras are required: `[server]` adds fastapi/uvicorn and
      fastmcp for the HTTP API plus MCP transports;
      `[seekdb]` adds the embedded seekdb storage backend (default).
    - Immediately after `uv pip install`, detect which Python interpreter was used. Read
      the shebang from the freshly-installed `powermem-server` entry point — this is
      the only reliable way to guarantee that the model-download script, the uv call
      inside it, and the server all use the exact same interpreter and site-packages.
      On many Linux systems the default `python` points to a version below 3.11 and `python3` may point to a different minor
      version than what uv used; relying on bare `python` will silently use
      the wrong environment and the download will fail with an ImportError:
        POWERMEM_PYTHON=$(head -1 "$(command -v powermem-server)" \
          | sed 's|#!||;s| .*||')
        echo "Using interpreter: $POWERMEM_PYTHON"   # e.g. /usr/bin/python3.11
    - Start the embedding model download in the background immediately after the
      uv package install, so it runs in parallel with the remaining setup steps (hook build,
      plugin stage/install). Branch on the region detected in Step 1a:

      **CN region (ModelScope path):**
        uv pip install --python "$POWERMEM_PYTHON" -q modelscope && $POWERMEM_PYTHON -c "
        from modelscope import snapshot_download
        snapshot_download('AI-ModelScope/all-MiniLM-L6-v2')
        import os, shutil, urllib.request, json
        src = os.path.expanduser('~/.cache/modelscope/hub/models/AI-ModelScope/all-MiniLM-L6-v2')
        hub = os.path.expanduser('~/.cache/huggingface/hub/models--sentence-transformers--all-MiniLM-L6-v2')
        try:
            resp = urllib.request.urlopen('https://huggingface.co/api/models/sentence-transformers/all-MiniLM-L6-v2', timeout=5)
            rev = json.load(resp)['sha']
        except Exception:
            rev = 'fa97f6e7cb1a59073dff9e9d8ba1c7c1591cc08d'
        snap = os.path.join(hub, 'snapshots', rev)
        os.makedirs(snap, exist_ok=True)
        os.makedirs(os.path.join(hub, 'refs'), exist_ok=True)
        open(os.path.join(hub, 'refs', 'main'), 'w').write(rev)
        skip = {'configuration.json', 'data_config.json'}
        [shutil.copytree(os.path.join(src,n), os.path.join(snap,n))
         if os.path.isdir(os.path.join(src,n))
         else shutil.copy2(os.path.join(src,n), os.path.join(snap,n))
         for n in os.listdir(src)
         if n not in skip and not os.path.exists(os.path.join(snap,n))]
        print('Model download and cache bridge complete.')
        " >> /tmp/powermem-model-download.log 2>&1 &

      **Non-CN region (HuggingFace path):**
        uv pip install --python "$POWERMEM_PYTHON" -q huggingface_hub && $POWERMEM_PYTHON -c "
        from huggingface_hub import snapshot_download
        snapshot_download('sentence-transformers/all-MiniLM-L6-v2')
        print('Model download complete.')
        " >> /tmp/powermem-model-download.log 2>&1 &

      Store the background PID:
        POWERMEM_MODEL_DL_PID=$!
    - Ask whether to build the Web Dashboard EARLY — before starting the server —
      so the build runs in parallel with model download and server startup. Use
      AskUserQuestion (single-select):

      | Option | Description |
      |--------|-------------|
      | Yes, build dashboard | Run `make build-dashboard` in background, parallel with model download and server startup |
      | No, skip | Continue without building the dashboard |

      If the user selects "Yes, build dashboard", launch it in the BACKGROUND
      immediately (do NOT wait for it — model download starts in parallel too):
        make build-dashboard >> /tmp/powermem-dashboard-build.log 2>&1 &
        DASHBOARD_BUILD_PID=$!
    - Build the hook binaries FIRST — they get copied into Claude's plugin cache at
      install time, so they must exist on disk before step "install":
        if Go 1.22+ is present:  make build-claude-hook
        else tell me, and offer to install Go or fall back to the PYPI/MCP path below.
    - Ensure the plugin's root .mcp.json stays empty ({}) — default HTTP mode.
    - STAGE the plugin into a stable, Claude-owned location so the marketplace does
      NOT depend on this checkout — you can move or delete the repo afterwards and
      memory keeps working. Copy the whole plugin dir (built binaries included) into
      ~/.claude/marketplaces/powermem:
        DEST="$HOME/.claude/marketplaces/powermem"
        mkdir -p "$DEST"
        rsync -a --delete "<ABS_PATH>/apps/claude-code-plugin/" "$DEST/"
          # no rsync? rm -rf "$DEST" && cp -a "<ABS_PATH>/apps/claude-code-plugin/." "$DEST/"
      The binaries from `make build-claude-hook` must already be on disk before this
      copy. Re-copy on every re-run so the staged dir tracks your latest build.
    - Register the marketplace from the STAGED dir (it ships
      .claude-plugin/marketplace.json) — never from the repo:
        claude plugin marketplace add "$DEST"
      If it reports "already on disk", refresh it instead:
        claude plugin marketplace update powermem
    - Install + enable the plugin globally (user scope). Install auto-enables it:
        claude plugin install memory-powermem@powermem --scope user
      IMPORTANT idempotency rule: a plain re-install is a no-op and does NOT refresh
      the cached copy. If you just rebuilt the binaries or changed the plugin, force
      a refresh regardless of whether the plugin was previously installed:
        claude plugin uninstall memory-powermem@powermem 2>/dev/null || true
        claude plugin install   memory-powermem@powermem --scope user
      The `|| true` swallows the "not found" error when the plugin was never installed
      — uninstall fails with exit code 1 in that case, which would abort a script.
      (Enablement is preserved across uninstall+reinstall.)
    - Wait for the background model download (started above) to finish before
      starting the server. If it is already done this is a no-op:
        if [ -n "${POWERMEM_MODEL_DL_PID:-}" ] && kill -0 "$POWERMEM_MODEL_DL_PID" 2>/dev/null; then
          echo "Waiting for model download to finish..."
          wait "$POWERMEM_MODEL_DL_PID"
        fi
        grep -q "complete" /tmp/powermem-model-download.log 2>/dev/null \
          || { echo "Model download failed. Check /tmp/powermem-model-download.log"; exit 1; }
    - Start the API server only if it is not already healthy (idempotent).
      ⚠️ STARTUP TIME: first launch can take 60–120s (local embedder load/download + uvicorn bind).
      Exit code 7 means the port is not yet bound — do NOT kill and restart.
        curl -s http://localhost:8848/api/v1/system/health | grep -q healthy \
          || { powermem-server --host 0.0.0.0 --port 8848 &
               echo "Waiting for server (first launch can take 60–120s)..."
               for i in $(seq 1 30); do
                 sleep 5
                 curl -s -m 3 http://localhost:8848/api/v1/system/health \
                   | grep -q healthy \
                   && echo "Server ready after $((i*5))s." && break
                 echo "  still starting... ($((i*5))s)"
               done; }
    - **After the server is healthy**, auto-detect dashboard availability:
      ```bash
      # Wait for dashboard build to finish (if started):
      if [ -n "${DASHBOARD_BUILD_PID:-}" ] && kill -0 "$DASHBOARD_BUILD_PID" 2>/dev/null; then
        echo "Waiting for dashboard build to finish..."
        wait "$DASHBOARD_BUILD_PID"
      fi
      # Auto-detect whether dashboard is available:
      if curl -s -m 3 http://localhost:8848/dashboard/ | grep -q '<title>PowerMem'; then
        echo "Dashboard is available at http://localhost:8848/dashboard/"
        # open in browser if possible
        command -v xdg-open >/dev/null 2>&1 && xdg-open http://localhost:8848/dashboard/ &
        command -v open >/dev/null 2>&1 && open http://localhost:8848/dashboard/ &
      else
        echo "Dashboard build may have failed. Check /tmp/powermem-dashboard-build.log"
      fi
      ```
      The running server is not restarted. Dashboard assets are served from the
      static files directory configured in the server.

    - Confirm the plugin is enabled:  claude plugin list  (look for
      memory-powermem@powermem). Do NOT print a --plugin-dir command — it is global
      now; every `claude` and `claude -p` loads it automatically.

3b. PYPI/MCP path:
    - Install the server extra in the environment that Claude will use, then:
      uv venv venv --python python3.11
      POWERMEM_PYTHON="$(pwd)/venv/bin/python"
      export PATH="$(pwd)/venv/bin:$PATH"
      uv pip install --python "$POWERMEM_PYTHON" "powermem[server,seekdb]"
      powermem-mcp --help
    - Register the MCP server globally so it persists across sessions (stdio = no
      port), run from the directory holding the .env. Idempotent: if `claude mcp get
      powermem` already exists, remove it first, then add:
        claude mcp remove powermem 2>/dev/null; claude mcp add powermem -- powermem-mcp stdio
    - If registration succeeds but Claude reports the MCP server as failed, debug the
      MCP process before retrying setup:
        claude mcp list
        claude mcp get powermem
        command -v powermem-mcp
        powermem-mcp --help
      Then run the MCP server directly from the same directory that contains `.env`
      and capture stderr separately:
        powermem-mcp stdio >/tmp/powermem-mcp.stdout 2>/tmp/powermem-mcp.stderr &
        MCP_PID=$!; sleep 10; kill "$MCP_PID" 2>/dev/null || true; wait "$MCP_PID" 2>/dev/null || true
        sed -n '1,120p' /tmp/powermem-mcp.stderr
        sed -n '1,40p' /tmp/powermem-mcp.stdout
      Expected: stderr may contain normal startup diagnostics; stdout must be empty
      until a JSON-RPC request arrives. If stdout contains banners, warnings, stack
      traces, or any non-JSON text, Claude cannot parse the MCP stream. Fix that
      output pollution before claiming the MCP path works.

4. VERIFY with a real round-trip — do not claim success without data. Run the exact
   commands below and substitute nothing except the noted placeholder. Do NOT mark
   this step done until you have seen a non-empty search result actually come back.

   SOURCE/HTTP path — run a/b/c/d/e in order:

   a. Confirm the server answers (output must contain "status":"healthy"):
        curl -s -m 5 http://localhost:8848/api/v1/system/health

   b. WRITE a probe memory. CRITICAL SCHEMA: the request body is a single "content"
      STRING field — NOT a mem0-style "messages" array. Sending {"messages":[...]}
      returns HTTP 422 `{"detail":[{"type":"missing","loc":["body","content"]...}]}`.
      Use a unique user_id so the probe is isolated from real data:
        curl -s -m 60 -X POST http://localhost:8848/api/v1/memories \
          -H 'Content-Type: application/json' \
          -d '{"content":"PowerMem setup probe: my favorite test fruit is dragonfruit-zx9.","user_id":"powermem_setup_probe"}'
      Expected: JSON with "success": true and a data[0].memory_id (a long numeric
      string). The call can take 10-30s because the LLM extracts facts — KEEP the
      -m 60 timeout and WAIT; do not background it or abort early. Save the returned
      data[0].memory_id (a.k.a. data[0].id) — you need it for cleanup in (e).

   c. SEARCH it back. CRITICAL SCHEMA: the body field is "query" (not "question" or
      "text"), with the SAME user_id you wrote with:
        curl -s -m 30 -X POST http://localhost:8848/api/v1/memories/search \
          -H 'Content-Type: application/json' \
          -d '{"query":"what is my favorite test fruit","user_id":"powermem_setup_probe","limit":5}'
      Expected: data.total >= 1 and data.results[0].content mentions dragonfruit-zx9.
      If data.total is 0 the round-trip FAILED — do NOT report success. Re-check the
      server log and the embedder, retry the write in (b) once, then escalate to me.

   d. SHOW me both the write JSON and the search JSON (this is the proof of success).

   e. CLEAN UP the probe — delete by the id from (b), then confirm it is gone:
        curl -s -m 10 -X DELETE http://localhost:8848/api/v1/memories/<MEMORY_ID>
      Re-run the search from (c): data.total must now be 0.

   f. BONUS (proves global + headless wiring; do it if you can). Run a headless
      prompt from an UNRELATED dir with NO --plugin-dir, then check the logs for
      the two hook-driven calls it triggers:
        ( cd /tmp && claude -p "Reply with exactly: probe ok" )
      Then in `server.log` (powermem-server), AFTER that run, you MUST see both:
        POST /api/v1/memories/search   <- UserPromptSubmit hook (auto-recall)
        POST /api/v1/memories          <- SessionEnd hook (auto-save)
      Seeing both proves PowerMem loads automatically in every `claude`/`claude -p`.

   PYPI/MCP path: confirm `claude mcp list` shows powermem as "connected" (not
   "failed"). If it shows failed, run the MCP diagnostics below and do not report
   success until the direct MCP process starts cleanly and Claude shows it connected.

   a. Inspect Claude's registered MCP config and status:
        claude mcp list
        claude mcp get powermem
      Verify the command is exactly `powermem-mcp stdio` unless you intentionally
      chose a different binary. Also verify `command -v powermem-mcp` succeeds in the same
      shell where `claude` runs.

   b. Run the MCP server directly and inspect both streams:
        powermem-mcp stdio >/tmp/powermem-mcp.stdout 2>/tmp/powermem-mcp.stderr &
        MCP_PID=$!; sleep 10; kill "$MCP_PID" 2>/dev/null || true; wait "$MCP_PID" 2>/dev/null || true
        sed -n '1,120p' /tmp/powermem-mcp.stderr
        sed -n '1,40p' /tmp/powermem-mcp.stdout
      Interpret the output:
        - stderr has import errors: install/repair the package or virtual environment.
        - stderr has missing LLM/API key/auth token/config errors: fix `.env` after asking the
          user for approval; never silently patch `.env`.
        - stderr has model download or network timeouts: pre-download the model or
          switch to a configured remote embedder.
        - stdout has non-JSON text before any request: remove noisy prints/logging
          from stdout; MCP stdio stdout is protocol-only.
        - both files are empty after timeout: the process likely started and waited
          for JSON-RPC input; continue with Claude-side connection checks.

   c. If direct startup is clean but Claude still says failed, refresh the registration:
        claude mcp remove powermem
        claude mcp add powermem -- powermem-mcp stdio
        claude mcp list
      If it still fails, start Claude with debug logging if available in the installed
      Claude Code version, then look for the first MCP spawn or JSON-RPC parse error.

5. SUMMARIZE: path taken, where .env lives, where the staged marketplace lives
   (~/.claude/marketplaces/powermem — independent of this repo), the server URL,
   how memory is wired
   (HTTP hooks vs MCP tools — recall is auto-injected on UserPromptSubmit, not a
   tool the model calls; writes happen on SessionEnd/PostCompact), confirmation that
   it is enabled globally, and the fact that I just run `claude` (or `claude -p`)
   with nothing extra. Note: the background server does not survive a reboot — offer
   to set up a systemd user service for autostart.

## Re-running / refreshing later

This file is safe to re-run end to end. The only manual-feeling case is refreshing
the cached plugin after you change the plugin or rebuild the Go hooks at the SAME
version: rebuild (`make build-claude-hook`), re-copy the result into the staged
marketplace (`rsync -a --delete <ABS_PATH>/apps/claude-code-plugin/ ~/.claude/marketplaces/powermem/`),
then force-refresh the cache with `claude plugin uninstall memory-powermem@powermem`
followed by `claude plugin install memory-powermem@powermem --scope user` (or bump the
version in .claude-plugin/plugin.json so `claude plugin update memory-powermem` picks it up).

To turn it off without uninstalling: `claude plugin disable memory-powermem@powermem`
(re-enable with `claude plugin enable ...`). To disable only prompt-time search
injection, set POWERMEM_PROMPT_SEARCH=0. The hook talks to POWERMEM_BASE_URL
(default http://localhost:8848).

For the full manual reference, see ../../docs/integrations/claude_code.md


## 🚨 COMPREHENSIVE ERROR HANDLING GUIDE

Every real-world setup encounters issues. This guide documents specific error scenarios
and their resolutions discovered during actual setup attempts:

### Log File Locations
- **powermem-server**: `server.log` (RotatingFileHandler, 10MB max, 5 backups)
- **seekdb**: `seekdb_data/log/seekdb.log` (native C++ engine log)

### Error Resolution Checklist

#### [E001] PEP 668 System Protection
**Problem**: system Python blocks package installation with "externally-managed-environment"
**Fix**: use uv with a virtual environment
```bash
uv venv venv --python python3.11
source venv/bin/activate
# Use the extras matching your storage backend:
#   SQLite (default):  .[server,extras]   (pulls sentence-transformers)
#   OceanBase:         .[server,seekdb]
uv pip install --python "$VIRTUAL_ENV/bin/python" -e '.[server,extras]'
```

#### [E002] Missing Server Dependencies
**Problem**: Server startup fails with missing packages
**Fix**: Install missing dependencies with the extras matching your backend
```bash
# SQLite (default)
uv pip install --python "$POWERMEM_PYTHON" 'powermem[server,extras]'
# OceanBase
uv pip install --python "$POWERMEM_PYTHON" 'powermem[server,seekdb]'
```

#### [E003] SeekDB File Locking
**Problem**: "open seekdb failed OB_ERROR(4000)" or "db opened by other process"
**Fix**: Stop duplicate servers, then remove stale seekdb data only if data loss is acceptable
```bash
pkill -f powermem-server
rm -rf seekdb_data
powermem-server --host 0.0.0.0 --port 8848 &
```

#### [E004] Missing Go for Hooks
**Problem**: `make build-claude-hook` fails
**Fix**: Install Go 1.22+
```bash
brew install go
make build-claude-hook
```

#### [E005] Storage Backend Initialization
**Problem**: 503 errors on API calls despite server health
**Fix**: the Claude Code plugin defaults to embedded OceanBase/seekdb. Stop the
managed server, remove stale seekdb data only if you accept deleting local memories, and
restart init:
```bash
sh "$CLAUDE_PLUGIN_ROOT/scripts/stop.sh"
rm -rf "$HOME/.powermem/seekdb_data"
sh "$CLAUDE_PLUGIN_ROOT/scripts/init.sh"
```

#### [E006] Model Download Timeout
**Problem**: Server hangs or reports "timed out thrown while requesting HEAD" on startup.
**Cause**: The embedding model is not cached and the network is unreachable.
**Fix**: Follow the model pre-download step in Step 3a (branch on region detected in
Step 1a). Quick reference:

**CN region** (ModelScope → HF hub bridge):
```bash
# Detect the correct interpreter first (same one powermem uses):
POWERMEM_PYTHON=$(head -1 "$(command -v powermem-server)" | sed 's|#!||;s| .*||')
uv pip install --python "$POWERMEM_PYTHON" modelscope
$POWERMEM_PYTHON -c "from modelscope import snapshot_download; \
                     snapshot_download('AI-ModelScope/all-MiniLM-L6-v2')"
# Verify (note: models/ subdirectory is required):
ls ~/.cache/modelscope/hub/models/AI-ModelScope/all-MiniLM-L6-v2/
```

**Non-CN region** (HuggingFace direct):
```bash
POWERMEM_PYTHON=$(head -1 "$(command -v powermem-server)" | sed 's|#!||;s| .*||')
uv pip install --python "$POWERMEM_PYTHON" huggingface_hub
$POWERMEM_PYTHON -c "from huggingface_hub import snapshot_download; \
                     snapshot_download('sentence-transformers/all-MiniLM-L6-v2')"
# Verify:
ls ~/.cache/huggingface/hub/models--sentence-transformers--all-MiniLM-L6-v2/snapshots/
```
⚠️ Do NOT use bare `python` here — on many systems the default `python` version is below 3.11.
Using the shebang from `powermem-server` guarantees all three steps (uv install, download,
bridge) run in the same environment.
Then run the bridge script from Step 3a to populate the HuggingFace hub cache
structure — the embedder's cache-detection function checks `~/.cache/huggingface/hub/`,
not the ModelScope layout.

⚠️ **DO NOT** use `sentence_transformers.SentenceTransformer(...)` to download — it can
hang on networks where HuggingFace is unreachable. On CN region, use ModelScope +
HF hub bridge. On non-CN region, use `huggingface_hub.snapshot_download`. Always
check region first (Step 1a).

To confirm which sources are reachable:
```bash
curl -s -m 10 -o /dev/null -w "ModelScope: HTTP %{http_code}\n" \
  https://www.modelscope.cn/api/v1/models/AI-ModelScope/all-MiniLM-L6-v2
curl -s -m 10 -o /dev/null -w "HuggingFace: HTTP %{http_code}\n" \
  https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2
```

#### [E008] curl Exit Code 7 After Server Start
**Problem**: `curl` returns exit code 7 ("Failed to connect") right after launching
`powermem-server`.
**Cause**: The server process is running but not yet listening on port 8848. First
launch can take 60–120s (local embedder load/download + uvicorn bind).
**Fix**: Use the polling loop from Step 3a. Do NOT kill and restart — restarting
resets initialization and makes startup take even longer.

#### [E009] Server Exits Immediately — `fastapi`, `uvicorn`, or `fastmcp` Missing
**Problem**: Server exits immediately after launch with "Missing dependencies" error.
**Cause**: installing only the base project skips optional server dependencies.
`powermem[server]` installs fastapi, uvicorn, and fastmcp for the HTTP API plus
MCP transports.
**Fix**:
```bash
uv pip install --python "$POWERMEM_PYTHON" -e '.[server,seekdb]'
```

#### [E010] Anthropic `temperature` + `top_p` both sent → 400
**Problem**: Memory writes return `success:true` but `data:[]`; `server.log` reports
`Error code: 400 - temperature and top_p cannot both be specified for this model`.
**Cause**: `base.py._get_common_params` populates both `temperature` and `top_p` by
default. The Anthropic API (and most Anthropic-compatible proxies) rejects requests
that include both parameters simultaneously.
**Fix**: In `src/powermem/integrations/llm/anthropic.py`, drop `top_p` from `params`
before calling the API:
```python
params = self._get_supported_params(messages=messages, **kwargs)
params.pop("top_p", None)   # Anthropic rejects requests with both temperature and top_p
```

#### [E007] Claude MCP Server Shows Failed
**Problem**: `claude mcp list` shows `powermem` as failed or disconnected.
**Fix**: Inspect Claude's registered command, then run the MCP server directly and
check stdout/stderr separately:
```bash
claude mcp list
claude mcp get powermem
command -v powermem-mcp
powermem-mcp --help

powermem-mcp stdio >/tmp/powermem-mcp.stdout 2>/tmp/powermem-mcp.stderr &
MCP_PID=$!; sleep 10; kill "$MCP_PID" 2>/dev/null || true; wait "$MCP_PID" 2>/dev/null || true
sed -n '1,120p' /tmp/powermem-mcp.stderr
sed -n '1,40p' /tmp/powermem-mcp.stdout
```
Diagnosis:
- Import errors in stderr mean the package or virtual environment is broken.
- Missing key/config errors mean `.env` must be corrected after user approval.
- Network/model timeout errors mean the embedder or model cache needs attention.
- Any banner, warning, stack trace, or non-JSON text in stdout breaks MCP stdio.
- Empty stdout/stderr after 10 seconds usually means the process started and is
  waiting for JSON-RPC input; re-check Claude-side registration next.

#### [E011] Python Version Below 3.11
**Problem**: package installation fails or venv created with wrong Python version.
PowerMem requires Python >= 3.11 (`pyproject.toml: requires-python = ">=3.11"`).
**Fix**: Verify and use Python 3.11+:
```bash
python3 --version  # must be >= 3.11
# If too old, find and use a newer Python:
python3.11 --version
uv venv venv --python python3.11
source venv/bin/activate
```

#### [E012] uv Missing or Not on PATH
**Problem**: `uv: command not found`
**Fix**: Install uv for your region, then add the install directory to PATH:
```bash
# Non-CN:
curl -LsSf https://astral.sh/uv/install.sh | sh

# CN:
export UV_DOWNLOAD_URL=https://mirrors.ustc.edu.cn/github-release/astral-sh/uv/LatestRelease/
curl -sL https://mirrors.ustc.edu.cn/github-release/astral-sh/uv/LatestRelease/uv-installer.sh | sh

export PATH="$HOME/.local/bin:$HOME/.cargo/bin:$PATH"
uv --version
```

#### [E013] Internal PyPI Mirror Missing Packages
**Problem**: `uv pip install` fails with "No matching distribution found for pyobvector"
(or pyseekdb, onnxruntime, etc.).
**Cause**: The configured Python package index (e.g. internal mirror at `yum.tbsite.net`) does not
mirror all required packages.
**Fix**: If the package is already installed elsewhere (e.g. system Python 3.11), copy
it into the venv:
```bash
source venv/bin/activate
SITE_PKGS=$(python -c "import site; print(site.getsitepackages()[0])")
# Find the missing package in another Python installation:
find ~/.local/lib/python3.11/site-packages -maxdepth 1 -name "pyobvector*" -type d
# Copy both the package and its .dist-info:
cp -r ~/.local/lib/python3.11/site-packages/pyobvector "$SITE_PKGS/"
cp -r ~/.local/lib/python3.11/site-packages/pyobvector-*.dist-info "$SITE_PKGS/"
```
⚠️ After copying packages manually, you must also install their transitive
dependencies — see [E014].

#### [E014] Missing Transitive Dependencies After Manual Copy
**Problem**: Server starts but returns "Memory service unavailable: storage backend
initialization failed" with `ModuleNotFoundError: No module named 'onnxruntime'`
(or `tenacity`, `tokenizers`).
**Cause**: When packages like pyobvector/pyseekdb are copied manually into the venv,
their transitive dependencies (onnxruntime, tenacity, tokenizers, etc.) are not
automatically resolved by the package installer.
**Fix**: Install the missing transitive dependencies:
```bash
source venv/bin/activate
uv pip install --python "$VIRTUAL_ENV/bin/python" onnxruntime tenacity tokenizers
```
To verify no other dependencies are missing after manual copying:
```bash
python -c "import pyobvector" 2>&1   # should produce no output
python -c "import pyseekdb" 2>&1    # should produce no output
```

## PRE-CHECK & PREREQUISITES

1. **Verify Python version**: `python3 --version` (must be >= 3.11, see [E011])
2. **Verify Go version**: `go version` (must be 1.22+)
3. **Verify uv**: `uv --version` (install it with [E012] if missing)
4. **Check mirror access**: if using an internal mirror, verify
   it has `pyobvector`, `pyseekdb`, and `onnxruntime`; see [E013] if not.

## STEP BY STEP PROVEN PATH

Given the encountered errors, here are the tested workarounds for each approach:

### Method A: SOURCE Path (Current Directory Build)
```bash
# Create virtual environment to avoid PEP 668 issues.
uv venv venv --python python3.11
source venv/bin/activate
POWERMEM_PYTHON="$VIRTUAL_ENV/bin/python"

# Install everything with ALL required extras
uv pip install --python "$POWERMEM_PYTHON" -e '.[server,seekdb]'

# Build and stage Claude hooks
make build-claude-hook

# Register marketplace
DEST="$HOME/.claude/marketplaces/powermem"
mkdir -p "$DEST"
rsync -a --delete "$(pwd)/apps/claude-code-plugin/" "$DEST/"
claude plugin marketplace add "$DEST"
claude plugin install memory-powermem@powermem --scope user

# Start server (logs go to server.log automatically)
powermem-server --host 0.0.0.0 --port 8848 &
```

### Method B: PYPI/MCP Path (Recommended for Stability)
```bash
# Clean virtual environment approach
uv venv venv --python python3.11
source venv/bin/activate
POWERMEM_PYTHON="$VIRTUAL_ENV/bin/python"
uv pip install --python "$POWERMEM_PYTHON" 'powermem[server,seekdb]'
claude mcp remove powermem 2>/dev/null
claude mcp add powermem -- powermem-mcp stdio
```

### Method C: Troubleshooting Installation
```bash
# Common troubleshooting commands
lsof -i :8848    # Check if port is in use
pkill -f powermem-server  # Kill any running server
rm -rf seekdb_data  # Reset SeekDB if corrupted and data loss is acceptable

# Check logs
tail -f server.log                    # PowerMem server errors
tail -f seekdb_data/log/seekdb.log    # SeekDB engine errors
```

## FINAL VALIDATION STEPS

After setup, verify the complete round-trip:

```bash
# 1. Health check
curl -s http://localhost:8848/api/v1/system/health

# 2. Write test memory
curl -s -X POST http://localhost:8848/api/v1/memories \
  -H 'Content-Type: application/json' \
  -d '{"content":"PowerMem setup verification complete","user_id":"setup_test"}'

# 3. Search test
curl -s -X POST http://localhost:8848/api/v1/memories/search \
  -H 'Content-Type: application/json' \
  -d '{"query":"setup verification","user_id":"setup_test","limit":1}'
```

## SYSTEMD AUTOSTART (Optional)
```bash
mkdir -p ~/.config/systemd/user
cat > ~/.config/systemd/user/powermem.service << EOF
[Unit]
Description=PowerMem Memory Server
After=network.target

[Service]
Type=simple
WorkingDirectory=$(pwd)
ExecStart=/bin/bash -c 'source venv/bin/activate && powermem-server --host 0.0.0.0 --port 8848'
Restart=always
RestartSec=5

[Install]
WantedBy=default.target
EOF

systemctl --user daemon-reload
systemctl --user enable powermem.service
systemctl --user start powermem.service
```

## SUMMARY

**Path taken**: **[Based on observed errors, recommend PYPI/MCP approach for stability]**
- **.env location**: $(pwd)/.env
- **Virtual environment**: $(pwd)/venv  
- **Plugin marketplace**: ~/.claude/marketplaces/powermem
- **Server URL**: http://localhost:8848
- **Memory system**: seekdb storage with HTTP hooks
- **Global enablement**: Complete via `claude plugin install`
- **Usage**: Run `claude` command with no extra flags needed

**Quick commands for daily use**:
```bash
# Start server
source venv/bin/activate
powermem-server --host 0.0.0.0 --port 8848

# Check status
systemctl --user status powermem.service

# Quick restart
ps aux | grep powrmem
```

Your Claude Code is now configured with automatic memory recall and persistence worldwide.
