feat(init): AskUserQuestion interactive flow + remote-init mode + dashboard 0.0.0.0 - #1101
Conversation
- Add ask_user() shell function with 3-layer bypass (NON_INTERACTIVE, pre-set var, non-TTY) for users running init.sh from a terminal - Add prompt_user_config() that asks storage backend (sqlite/oceanbase), LLM provider (noop/anthropic/openai/...), and embedding provider (local/cloud) - Wire into init.sh main flow, right before create_env_file, so answers are consumed as POWERMEM_INIT_* env vars by the embedded Python config generator - Enhance /init skill SKILL.md with explicit AskUserQuestion instructions: conditional 3-question flow, env-var mapping, and POWERMEM_NON_INTERACTIVE=1 invocation template for the Claude Code plugin path
When the server is already running and .env has DATABASE_PROVIDER set, show the current backend instead of asking the user to reconfigure. Only ask the Storage question when config is genuinely missing.
init.sh gains a remote-init short-circuit: when POWERMEM_INIT_BASE_URL
(or non-localhost POWERMEM_BASE_URL) is provided, it skips .env, uvx,
and PID management, writing only runtime.env and/or .mcp.json per
POWERMEM_INIT_CONNECTION_MODE (hook|mcp|both, default both).
Dashboard default host changes from 127.0.0.1 to 0.0.0.0, overridable
via POWERMEM_SERVER_HOST, so the dashboard is reachable across
containers / remote hosts by default.
status.sh becomes mode-aware: reads runtime.env (hook_url) and
${PLUGIN_ROOT}/.mcp.json (mcp_url) and reports mode=hook|mcp|both|none.
common.sh gains shared helpers (is_remote_url, is_healthy,
write_runtime_remote) used by both scripts.
init SKILL.md describes a 3-round AskUserQuestion flow (Storage → LLM → Embedding) that replaces the shell-level interactive prompts. Each question is asked only when the relevant config is missing, and CLAUDE_PLUGIN_ROOT is resolved per-invocation rather than baked into shell env. When the server is already healthy with a configured DATABASE_PROVIDER, the Storage question is skipped. status SKILL.md documents the new hook|mcp|both|none mode reporting and how to read runtime.env / .mcp.json.
Move is_model_cached, detect_country, download_via_modelscope, and bridge_modelscope_to_hf_cache out of pyseekdb_default.py into a new _model_cache.py module so both pyseekdb_default and huggingface can share the same CN-mirror download / cache-bridge logic without duplication. Tests are updated to patch _model_cache instead of pyseekdb_default internals.
wayyoungboy
left a comment
There was a problem hiding this comment.
Thanks for putting the interactive / remote init flow together. I cannot mark this ready yet for two reasons:
- CI is red: both
test (3.11)andtest (3.12)fail intests/unit/test_claude_plugin_uv_install.py::test_init_uses_uvx_launcher_instead_of_plugin_venv_install. The test still asserts local uvx launch containspowermem-server --host 127.0.0.1 --port "$port", while this PR now launches with${POWERMEM_SERVER_HOST:-0.0.0.0}. Please either update the test if this behavior is intentional, or keep loopback as the local default and make all-interface binding an explicit opt-in. - The new init skill text still describes Local mode as starting a server on
127.0.0.1:8848, but the script and generated config now default local launch to0.0.0.0. Please make the docs, generated config, and launch behavior agree, especially because binding all interfaces changes the local exposure model.
Once the host contract is settled and the Python test jobs are green, I can re-review the updated head.
Restore 127.0.0.1 as the script default for POWERMEM_SERVER_HOST so
loopback remains the local exposure model. All-interface binding
(0.0.0.0) becomes an explicit user choice: Question 0 of the
AskUserQuestion flow now splits "Local" into two options —
loopback-only and all-interfaces — and maps the answer to
POWERMEM_SERVER_HOST before invoking init.sh.
Update the uvx-launcher test to assert the new parameterized form
`--host "${POWERMEM_SERVER_HOST:-127.0.0.1}"` instead of the old
hardcoded `--host 127.0.0.1`. Addresses PR oceanbase#1101 review feedback.
|
Thanks for the review @wayyoungboy. Both points are addressed in 1. CI red — test assertion mismatch Loopback is restored as the local default. assert "powermem-server --host \"${POWERMEM_SERVER_HOST:-127.0.0.1}\" --port \"$port\"" in script
2. Docs / generated config / launch behavior consistency All three now agree on PR description updated to match. Ready for re-review when you have a moment. |
Remote MCP mode was writing to $PLUGIN_ROOT/.mcp.json (the plugin cache dir), which is volatile — wiped on every plugin uninstall+install — so the user's remote MCP config was lost on reinstalls. It also could not take effect without a plugin reload. Register the powermem MCP server in the user-scope config instead, via the `claude mcp add --scope user --transport http` CLI. The CLI decides where to store it (typically ~/.claude.json top-level mcpServers) so the location tracks the current Claude Code version / platform rather than being hardcoded. Hook-only mode runs `claude mcp remove powermem --scope user` to disable MCP, preserving any other MCP servers. Adds write_user_mcp_config / remove_user_mcp_config helpers in common.sh that wrap the CLI with a PATH check and idempotent remove-then-add.
wayyoungboy
left a comment
There was a problem hiding this comment.
Thanks for the updates. The CI jobs I checked are green now, and the loopback/all-interface default issue from the earlier review looks addressed. I still cannot approve the latest head because the new remote connection modes have a few correctness issues:
runtime.envwrites remote URL/API key values as raw shell assignments, but the hook/status scripts later source that file. URLs or keys containing shell metacharacters or spaces can break the config, and command substitution is evaluated while sourcing.init.shnow registers MCP throughclaude mcp add --scope user, butstatus.shstill reads${PLUGIN_ROOT}/.mcp.json, so remotemcpmode reportsnoneandbothreports onlyhook.- Remote
mcpmode says hooks are disabled, but it neither removes/stampsruntime.envnor changes the hook launcher/native hook behavior. Existing hook config can remain active, and with no runtime file the native hook still falls back tohttp://localhost:8848. git diff --check upstream/main...HEADcurrently fails on trailing whitespace at EOF inapps/claude-code-plugin/scripts/common.sh.
Local checks run: shell syntax check passed; pytest tests/unit/test_claude_plugin_uv_install.py tests/unit/test_pyseekdb_default_download.py passed with 43 tests.
7d192a9 to
9f5ac95
Compare
…ble hook in MCP mode Four review issues on the user-scope MCP config commit (9f5ac95): 1. write_runtime_remote wrote URLs/keys bare, so shell metacharacters ($, ;, spaces, backticks, quotes) broke sourcing by run-hook.sh / status.sh. Switch to single-quoted values with '\'' escaping. 2. status.sh read $PLUGIN_ROOT/.mcp.json for the MCP URL, but that file is the volatile plugin-cache config (always empty since 9f5ac95 moved writes to user-scope). Read ~/.claude.json top-level mcpServers.powermem.url instead — the file the `claude mcp` CLI actually writes to. 3. MCP-only mode wrote nothing to runtime.env, leaving stale POWERMEM_BASE_URL from a prior hook/both init. The hook binary then ran against the old URL. Now write_runtime_hook_disabled() emits POWERMEM_HOOK_DISABLED=1, and run-hook.sh checks that marker after loading runtime.env and exits 0 before exec'ing the native binary. 4. common.sh had trailing blank lines at EOF (git diff --check). Also tightens Round 1 remote follow-up AskUserQuestion wording in SKILL.md: dedup the two URL placeholder options into distinct HTTP/HTTPS protocol hints; clarify Hook mode works when auth is disabled or a key was provided. Both connection mode retained per user request. Adds regression tests for the quoting, hook-disabled marker, run-hook early exit, and init.sh mcp-branch wiring.
|
@wayyoungboy Thanks for the review. All four issues are fixed in
Local: |
OceanBase requires a vector embedding field to store memories, so embeddings are mandatory. Offering "None" in the AskUserQuestion Round 3 options for the OceanBase storage path would let users pick an invalid combination. SQLite keeps "None" since it can fall back to non-vector storage; OceanBase only offers "Built-in seekdb" and "Cloud (<provider>)". Also clarifies the mapping line: POWERMEM_INIT_EMBEDDING_PROVIDER=none is now annotated "SQLite only".
…hboard 0.0.0.0 (oceanbase#1101) * feat(init): add interactive config prompts with AskUserQuestion support - Add ask_user() shell function with 3-layer bypass (NON_INTERACTIVE, pre-set var, non-TTY) for users running init.sh from a terminal - Add prompt_user_config() that asks storage backend (sqlite/oceanbase), LLM provider (noop/anthropic/openai/...), and embedding provider (local/cloud) - Wire into init.sh main flow, right before create_env_file, so answers are consumed as POWERMEM_INIT_* env vars by the embedded Python config generator - Enhance /init skill SKILL.md with explicit AskUserQuestion instructions: conditional 3-question flow, env-var mapping, and POWERMEM_NON_INTERACTIVE=1 invocation template for the Claude Code plugin path * fix(init): refine SKILL.md to skip Storage prompt when server is healthy When the server is already running and .env has DATABASE_PROVIDER set, show the current backend instead of asking the user to reconfigure. Only ask the Storage question when config is genuinely missing. * feat(plugin): add remote-init short-circuit and 0.0.0.0 default host init.sh gains a remote-init short-circuit: when POWERMEM_INIT_BASE_URL (or non-localhost POWERMEM_BASE_URL) is provided, it skips .env, uvx, and PID management, writing only runtime.env and/or .mcp.json per POWERMEM_INIT_CONNECTION_MODE (hook|mcp|both, default both). Dashboard default host changes from 127.0.0.1 to 0.0.0.0, overridable via POWERMEM_SERVER_HOST, so the dashboard is reachable across containers / remote hosts by default. status.sh becomes mode-aware: reads runtime.env (hook_url) and ${PLUGIN_ROOT}/.mcp.json (mcp_url) and reports mode=hook|mcp|both|none. common.sh gains shared helpers (is_remote_url, is_healthy, write_runtime_remote) used by both scripts. * feat(init): AskUserQuestion flow with mode-aware status SKILL init SKILL.md describes a 3-round AskUserQuestion flow (Storage → LLM → Embedding) that replaces the shell-level interactive prompts. Each question is asked only when the relevant config is missing, and CLAUDE_PLUGIN_ROOT is resolved per-invocation rather than baked into shell env. When the server is already healthy with a configured DATABASE_PROVIDER, the Storage question is skipped. status SKILL.md documents the new hook|mcp|both|none mode reporting and how to read runtime.env / .mcp.json. * refactor(embeddings): extract shared model-cache helpers Move is_model_cached, detect_country, download_via_modelscope, and bridge_modelscope_to_hf_cache out of pyseekdb_default.py into a new _model_cache.py module so both pyseekdb_default and huggingface can share the same CN-mirror download / cache-bridge logic without duplication. Tests are updated to patch _model_cache instead of pyseekdb_default internals. * fix(init): make 0.0.0.0 opt-in via Question 0, restore loopback default Restore 127.0.0.1 as the script default for POWERMEM_SERVER_HOST so loopback remains the local exposure model. All-interface binding (0.0.0.0) becomes an explicit user choice: Question 0 of the AskUserQuestion flow now splits "Local" into two options — loopback-only and all-interfaces — and maps the answer to POWERMEM_SERVER_HOST before invoking init.sh. Update the uvx-launcher test to assert the new parameterized form `--host "${POWERMEM_SERVER_HOST:-127.0.0.1}"` instead of the old hardcoded `--host 127.0.0.1`. Addresses PR oceanbase#1101 review feedback. * fix(init): write remote MCP config to user-scope, not plugin cache Remote MCP mode was writing to $PLUGIN_ROOT/.mcp.json (the plugin cache dir), which is volatile — wiped on every plugin uninstall+install — so the user's remote MCP config was lost on reinstalls. It also could not take effect without a plugin reload. Register the powermem MCP server in the user-scope config instead, via the `claude mcp add --scope user --transport http` CLI. The CLI decides where to store it (typically ~/.claude.json top-level mcpServers) so the location tracks the current Claude Code version / platform rather than being hardcoded. Hook-only mode runs `claude mcp remove powermem --scope user` to disable MCP, preserving any other MCP servers. Adds write_user_mcp_config / remove_user_mcp_config helpers in common.sh that wrap the CLI with a PATH check and idempotent remove-then-add. * fix(init): address PR oceanbase#1101 review — quote runtime.env, disable hook in MCP mode Four review issues on the user-scope MCP config commit (9f5ac95): 1. write_runtime_remote wrote URLs/keys bare, so shell metacharacters ($, ;, spaces, backticks, quotes) broke sourcing by run-hook.sh / status.sh. Switch to single-quoted values with '\'' escaping. 2. status.sh read $PLUGIN_ROOT/.mcp.json for the MCP URL, but that file is the volatile plugin-cache config (always empty since 9f5ac95 moved writes to user-scope). Read ~/.claude.json top-level mcpServers.powermem.url instead — the file the `claude mcp` CLI actually writes to. 3. MCP-only mode wrote nothing to runtime.env, leaving stale POWERMEM_BASE_URL from a prior hook/both init. The hook binary then ran against the old URL. Now write_runtime_hook_disabled() emits POWERMEM_HOOK_DISABLED=1, and run-hook.sh checks that marker after loading runtime.env and exits 0 before exec'ing the native binary. 4. common.sh had trailing blank lines at EOF (git diff --check). Also tightens Round 1 remote follow-up AskUserQuestion wording in SKILL.md: dedup the two URL placeholder options into distinct HTTP/HTTPS protocol hints; clarify Hook mode works when auth is disabled or a key was provided. Both connection mode retained per user request. Adds regression tests for the quoting, hook-disabled marker, run-hook early exit, and init.sh mcp-branch wiring. * fix(init): drop "None" embedding option for OceanBase OceanBase requires a vector embedding field to store memories, so embeddings are mandatory. Offering "None" in the AskUserQuestion Round 3 options for the OceanBase storage path would let users pick an invalid combination. SQLite keeps "None" since it can fall back to non-vector storage; OceanBase only offers "Built-in seekdb" and "Cloud (<provider>)". Also clarifies the mapping line: POWERMEM_INIT_EMBEDDING_PROVIDER=none is now annotated "SQLite only".







Summary
init.shwith a 3-round AskUserQuestion flow (Server → Storage+LLM → Embedding) driven from the init SKILL, so decisions are made in the Claude Code UI instead of the terminal.127.0.0.1), Local — all interfaces (0.0.0.0), and Remote. Loopback remains the script default; all-interface binding is an explicit user choice, mapped toPOWERMEM_SERVER_HOST.init.shskips.env/uvx/ PID management and writes onlyruntime.envand/or.mcp.jsonperPOWERMEM_INIT_CONNECTION_MODE(hook|mcp|both).status.sh/statusSKILL mode-aware (hook|mcp|both|none) by reading bothruntime.env(hook URL) and${PLUGIN_ROOT}/.mcp.json(MCP URL).is_model_cached,detect_country,download_via_modelscope,bridge_modelscope_to_hf_cache) frompyseekdb_default.pyinto a new_model_cache.pymodule so bothpyseekdb_defaultandhuggingfaceshare the same logic.Changes
Plugin scripts (
apps/claude-code-plugin/scripts/)common.sh— shared helpers:is_remote_url,is_healthy,write_runtime_remote.init.sh— remote-init short-circuit; dashboard host${POWERMEM_SERVER_HOST:-127.0.0.1}(loopback default,0.0.0.0opt-in via env or Question 0); connection-mode switch.status.sh— mode-aware reporting fromruntime.env+.mcp.json.SKILL files (
apps/claude-code-plugin/skills/)init/SKILL.md— 3-round AskUserQuestion flow; Round 1 splits Local into loopback / all-interfaces options; skip Storage question when server healthy;CLAUDE_PLUGIN_ROOTresolved per-invocation.status/SKILL.md— documents the newhook|mcp|both|nonemode reporting.Embedding refactor (
src/powermem/integrations/embeddings/)_model_cache.py(new) — shared cache/mirror helpers extracted frompyseekdb_default.py.pyseekdb_default.py— imports from_model_cache.huggingface.py— adopts the shared cache/mirror helpers.tests/unit/test_pyseekdb_default_download.py— patches_model_cacheinstead ofpyseekdb_defaultinternals.Tests
tests/unit/test_claude_plugin_uv_install.py— updated uvx-launcher assertion to the parameterized form--host "${POWERMEM_SERVER_HOST:-127.0.0.1}"to match the new default + override contract.Test plan
pytest tests/unit/test_claude_plugin_uv_install.py— uvx-launcher assertion matches the new parameterized host form.pytest tests/unit/test_pyseekdb_default_download.py— download / CN-mirror tests pass against the extracted module.test (3.11),test (3.12),claude-hook-regressionall green on049627a./init, confirm Server → Storage+LLM → Embedding questions appear in the Claude Code UI (not the terminal).POWERMEM_INIT_BASE_URL=https://remote:8848, confirminit.shwritesruntime.envand/or.mcp.jsonwithout starting a local server.127.0.0.1:8848by default; choosing "Local — all interfaces" in Round 1 (or settingPOWERMEM_SERVER_HOST=0.0.0.0) binds0.0.0.0and is reachable from another host./statusreportsmode=hook,mode=mcp,mode=both, ormode=nonecorrectly.