From efc62d1d0276fef49d3685cb4fe8fe7a28e165b5 Mon Sep 17 00:00:00 2001 From: Ethan Date: Fri, 15 May 2026 15:03:29 -0700 Subject: [PATCH] Sync integrations snapshot 04083f6 --- .claude-plugin/marketplace.json | 4 +- .github/ISSUE_TEMPLATE/config.yml | 2 +- README.md | 9 +- .../claude-code/.claude-plugin/plugin.json | 10 +- plugins/claude-code/README.md | 26 ++-- plugins/claude-code/package.json | 4 +- .../scripts/__tests__/auth-header.sh | 140 ++++++++++++++++++ .../scripts/__tests__/load-env-defaults.sh | 123 +++++++++++++++ .../claude-code/scripts/lib/atomicmemory.sh | 22 ++- plugins/codex/.codex-plugin/plugin.json | 2 +- plugins/codex/package.json | 2 +- plugins/codex/skills/atomicmemory/SKILL.md | 2 +- plugins/cursor/package.json | 2 +- plugins/hermes/CHANGELOG.md | 7 + plugins/hermes/README.md | 8 +- plugins/hermes/install.mjs | 12 +- plugins/hermes/package.json | 2 +- plugins/hermes/plugin.yaml | 2 +- plugins/hermes/pyproject.toml | 2 +- .../hermes/tests/test_install_path_imports.py | 28 +++- plugins/openclaw/README.md | 2 +- plugins/openclaw/openclaw.plugin.json | 2 +- plugins/openclaw/package.json | 2 +- .../openclaw/skills/atomicmemory/skill.yaml | 2 +- 24 files changed, 368 insertions(+), 49 deletions(-) create mode 100755 plugins/claude-code/scripts/__tests__/auth-header.sh create mode 100755 plugins/claude-code/scripts/__tests__/load-env-defaults.sh diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 9356d42..d21c100 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -12,9 +12,9 @@ "name": "claude-code", "source": "./plugins/claude-code", "description": "Persistent semantic memory for Claude Code — user preferences, project context, prior decisions, and codebase facts that survive across sessions.", - "version": "0.1.12", + "version": "0.1.13", "category": "productivity", - "homepage": "https://docs.atomicmemory.ai/integrations/coding-agents/claude-code", + "homepage": "https://docs.atomicstrata.ai/integrations/coding-agents/claude-code", "license": "Apache-2.0" } ] diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 70f88ac..3e57724 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,7 +1,7 @@ blank_issues_enabled: true contact_links: - name: Documentation - url: https://docs.atomicmemory.ai/integrations/ + url: https://docs.atomicstrata.ai/integrations/ about: Read the AtomicMemory integration docs. - name: Discussions url: https://github.com/atomicstrata/atomicmemory-integrations/discussions diff --git a/README.md b/README.md index 9f03b43..c902122 100644 --- a/README.md +++ b/README.md @@ -185,12 +185,13 @@ claude plugin update claude-code@atomicmemory Then fully restart Claude Code. Running Claude sessions can keep the previous hook registration in memory. Do not treat `~/.claude/plugins/cache/...` as the source of truth; it is only Claude's installed cache. -Required env before launching Claude Code: +Environment is **optional** for local mode — the plugin defaults to a local AtomicMemory core at `http://127.0.0.1:3050`, derives `ATOMICMEMORY_SCOPE_USER` from the host OS, and uses `ATOMICMEMORY_CAPTURE_LEVEL=balanced`. See `plugins/claude-code/README.md` for the full defaults table. + +Set these before launching Claude Code only when you need to override a default (e.g. to talk to a hosted AtomicMemory service): ```bash export ATOMICMEMORY_API_URL="https://memory.yourco.com" export ATOMICMEMORY_API_KEY="am_live_..." -export ATOMICMEMORY_PROVIDER="atomicmemory" export ATOMICMEMORY_SCOPE_USER="$USER" export ATOMICMEMORY_CAPTURE_LEVEL="balanced" ``` @@ -278,8 +279,8 @@ For dev installs, symlink the plugin into Hermes' memory directory. Hermes installs the published Python SDK from `plugins/hermes/plugin.yaml`: ```bash -mkdir -p "$HERMES_HOME/plugins/memory" -ln -s "$(pwd)/plugins/hermes" "$HERMES_HOME/plugins/memory/atomicmemory" +mkdir -p "$HERMES_HOME/plugins" +ln -s "$(pwd)/plugins/hermes" "$HERMES_HOME/plugins/atomicmemory" hermes memory setup # select "atomicmemory" hermes memory status # confirm "atomicmemory" is active ``` diff --git a/plugins/claude-code/.claude-plugin/plugin.json b/plugins/claude-code/.claude-plugin/plugin.json index 2284170..02e26bd 100644 --- a/plugins/claude-code/.claude-plugin/plugin.json +++ b/plugins/claude-code/.claude-plugin/plugin.json @@ -1,22 +1,22 @@ { "name": "atomicmemory", - "version": "0.1.12", + "version": "0.1.13", "description": "Persistent semantic memory for Claude Code — user preferences, project context, prior decisions, and codebase facts that survive across sessions.", "author": { "name": "AtomicMemory", "url": "https://atomicmem.ai" }, - "homepage": "https://docs.atomicmemory.ai/integrations/coding-agents/claude-code", + "homepage": "https://docs.atomicstrata.ai/integrations/coding-agents/claude-code", "license": "Apache-2.0", "mcpServers": { "atomicmemory": { "command": "npx", "args": ["-y", "--package=@atomicmemory/mcp-server@^0.1.1", "atomicmemory-mcp"], "env": { - "ATOMICMEMORY_API_URL": "${ATOMICMEMORY_API_URL}", - "ATOMICMEMORY_API_KEY": "${ATOMICMEMORY_API_KEY}", + "ATOMICMEMORY_API_URL": "${ATOMICMEMORY_API_URL:-}", + "ATOMICMEMORY_API_KEY": "${ATOMICMEMORY_API_KEY:-}", "ATOMICMEMORY_PROVIDER": "${ATOMICMEMORY_PROVIDER:-atomicmemory}", - "ATOMICMEMORY_SCOPE_USER": "${ATOMICMEMORY_SCOPE_USER}", + "ATOMICMEMORY_SCOPE_USER": "${ATOMICMEMORY_SCOPE_USER:-}", "ATOMICMEMORY_SCOPE_AGENT": "${ATOMICMEMORY_SCOPE_AGENT:-}", "ATOMICMEMORY_SCOPE_NAMESPACE": "${ATOMICMEMORY_SCOPE_NAMESPACE:-}", "ATOMICMEMORY_SCOPE_THREAD": "${ATOMICMEMORY_SCOPE_THREAD:-}" diff --git a/plugins/claude-code/README.md b/plugins/claude-code/README.md index be62b81..2be6777 100644 --- a/plugins/claude-code/README.md +++ b/plugins/claude-code/README.md @@ -16,24 +16,32 @@ brew install jq sudo apt-get install -y jq ``` -### 2. Export shell env vars +### 2. Configure (optional in local mode) -The MCP server and the lifecycle hook scripts read their config from the shell environment. Export these in `~/.zshrc` / `~/.bashrc` before launching Claude Code: +The MCP server and the lifecycle hook scripts read their config from the shell environment. None of the `ATOMICMEMORY_*` variables are required to run the plugin against a local AtomicMemory core — the documented defaults are: + +| Var | Local-mode default | +|---|---| +| `ATOMICMEMORY_API_URL` | `http://127.0.0.1:3050` | +| `ATOMICMEMORY_API_KEY` | not required | +| `ATOMICMEMORY_PROVIDER` | `atomicmemory` | +| `ATOMICMEMORY_SCOPE_USER` | derived from the host OS user | +| `ATOMICMEMORY_CAPTURE_LEVEL` | `balanced` | + +Set them only when you need to override a default (for example, to talk to a hosted AtomicMemory service): ```bash export ATOMICMEMORY_API_URL="https://memory.yourco.com" export ATOMICMEMORY_API_KEY="am_live_…" -export ATOMICMEMORY_PROVIDER="atomicmemory" export ATOMICMEMORY_SCOPE_USER="$USER" export ATOMICMEMORY_CAPTURE_LEVEL="balanced" # minimal|balanced|full -# Optional: +# Optional scope: # export ATOMICMEMORY_SCOPE_NAMESPACE="" # export ATOMICMEMORY_SCOPE_AGENT="claude-code" # export ATOMICMEMORY_SCOPE_THREAD="" ``` -`ATOMICMEMORY_API_URL`, `ATOMICMEMORY_API_KEY`, `ATOMICMEMORY_PROVIDER`, `ATOMICMEMORY_SCOPE_USER`, and `ATOMICMEMORY_CAPTURE_LEVEL` are required for the Claude Code plugin and hooks. Optional scope vars narrow retrieval and lifecycle record metadata. -If `ATOMICMEMORY_SCOPE_USER` is empty, the MCP server derives a local user from the host OS; set it explicitly when multiple operators share a machine or when you need a stable cross-machine identity. +Set `ATOMICMEMORY_SCOPE_USER` explicitly when multiple operators share a machine or when you need a stable cross-machine identity; otherwise the MCP server derives one from the host OS. #### Local extraction with Claude Code auth @@ -54,8 +62,8 @@ for hosted/team deployments where a server would run under one operator's Claude Code subscription. Embeddings still use core's configured embedding provider; select a local embedding provider separately for a fully local setup. -- `_API_URL` / `_API_KEY` / `_PROVIDER` / `_SCOPE_USER` — needed by **both** the MCP server (for `memory_search` / `memory_ingest` / `memory_package` tool calls) and lifecycle hooks. -- `_CAPTURE_LEVEL` — controls lifecycle write volume. Valid values are `minimal`, `balanced`, and `full`. +- `_API_URL` / `_API_KEY` / `_PROVIDER` / `_SCOPE_USER` — read by **both** the MCP server (for `memory_search` / `memory_ingest` / `memory_package` tool calls) and lifecycle hooks. All optional in local mode (see defaults above). +- `_CAPTURE_LEVEL` — controls lifecycle write volume. Valid values are `minimal`, `balanced`, and `full`. Defaults to `balanced` when unset; invalid values still fail closed. - `_SCOPE_NAMESPACE` — used by both, as a per-project isolation boundary. - `_SCOPE_AGENT` / `_SCOPE_THREAD` — forwarded to the MCP server as the request scope. The direct prompt-search path uses the core fast-search endpoint's supported user/namespace scope. @@ -73,7 +81,7 @@ Optional capture controls: - `ATOMICMEMORY_TASK_MAX_DESCRIPTION_CHARS=600` controls the maximum cleaned task description excerpt stored by `TaskCompleted`. If set, it must be a positive integer. - `ATOMICMEMORY_SEMANTIC_PROMPTS_ENABLED=false` disables extra Stop prompts that ask Claude to extract semantic learnings. If set, it must be `true` or `false`. -If required config is missing, helper tools are unavailable, or numeric/boolean env vars are invalid, hooks surface the error instead of running in a degraded mode. +If a helper tool is unavailable, an explicit `ATOMICMEMORY_*` value is invalid (bogus capture level, non-numeric integer var, non-boolean flag), or core is unreachable, hooks surface the error instead of running in a degraded mode. ### 3. Install the plugin diff --git a/plugins/claude-code/package.json b/plugins/claude-code/package.json index 0df8183..bfcbbfb 100644 --- a/plugins/claude-code/package.json +++ b/plugins/claude-code/package.json @@ -1,6 +1,6 @@ { "name": "@atomicmemory/claude-code-plugin", - "version": "0.1.12", + "version": "0.1.13", "description": "AtomicMemory plugin for Claude Code — persistent semantic memory across sessions.", "private": false, "license": "Apache-2.0", @@ -17,6 +17,6 @@ "README.md" ], "scripts": { - "test": "bash scripts/__tests__/quick-ingest-body.sh" + "test": "bash scripts/__tests__/quick-ingest-body.sh && bash scripts/__tests__/load-env-defaults.sh && bash scripts/__tests__/auth-header.sh" } } diff --git a/plugins/claude-code/scripts/__tests__/auth-header.sh b/plugins/claude-code/scripts/__tests__/auth-header.sh new file mode 100755 index 0000000..ed60eeb --- /dev/null +++ b/plugins/claude-code/scripts/__tests__/auth-header.sh @@ -0,0 +1,140 @@ +#!/usr/bin/env bash +# +# Unit gate for the Bearer auth header behavior of the hooks' +# direct-to-core HTTP calls in +# `plugins/claude-code/scripts/lib/atomicmemory.sh`. +# +# Shadows `curl` with a bash function that captures argv to a file, +# then exercises `am_post_quick_ingest` and `am_search_fast` with +# AM_API_KEY both set and unset. Asserts: +# 1. With AM_API_KEY set, both curl invocations include +# `-H Authorization: Bearer `. +# 2. With AM_API_KEY empty, neither invocation includes the +# Authorization header. +# +# Matches core's `requireBearer` middleware contract +# (atomicmemory-core/src/middleware/require-bearer.ts). + +set -uo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +LIB_PATH="$SCRIPT_DIR/../lib/atomicmemory.sh" + +if [ ! -f "$LIB_PATH" ]; then + printf 'fixture path missing: %s\n' "$LIB_PATH" >&2 + exit 1 +fi + +unset ATOMICMEMORY_API_KEY +unset ATOMICMEMORY_API_URL +export USER="${USER:-test-user}" + +# shellcheck source=../lib/atomicmemory.sh +source "$LIB_PATH" + +ARGV_LOG="$(mktemp)" +trap 'rm -f "$ARGV_LOG"' EXIT + +# Shadow curl with a bash function that writes its full argv to the +# log file (one arg per line, with a record separator between calls) +# and emits a 200 OK so the caller path completes happily. +curl() { + printf '%s\n' "$@" >>"$ARGV_LOG" + printf '---END---\n' >>"$ARGV_LOG" + for arg in "$@"; do + case "$arg" in + -w|--write-out) printf '200' ;; + esac + done +} +export -f curl + +PASS_COUNT=0 +FAIL_COUNT=0 + +assert() { + local name="$1" + local condition="$2" + if [ "$condition" = "true" ]; then + printf ' ✓ %s\n' "$name" + PASS_COUNT=$((PASS_COUNT + 1)) + else + printf ' ✗ %s\n' "$name" >&2 + FAIL_COUNT=$((FAIL_COUNT + 1)) + fi +} + +reset_log() { + : >"$ARGV_LOG" +} + +argv_contains_header() { + local header_value="$1" + awk -v target="$header_value" ' + /^-H$/ { capture = 1; next } + capture { if ($0 == target) { found = 1 } capture = 0 } + END { exit (found ? 0 : 1) } + ' "$ARGV_LOG" +} + +# --------------------------------------------------------------------------- +# Case 1: AM_API_KEY set → Authorization header present in both calls +# --------------------------------------------------------------------------- +printf '\nCase 1: AM_API_KEY set → Bearer auth header on hook curls\n' +export ATOMICMEMORY_API_URL="https://memory.example.com" +export ATOMICMEMORY_API_KEY="am_live_secret" +am_load_env || { printf 'am_load_env failed\n' >&2; exit 1; } + +reset_log +body='{"user_id":"u","conversation":"c","source_site":"claude-code","source_url":"atomicmemory://test","skip_extraction":true}' +am_post_quick_ingest "$body" >/dev/null 2>&1 || true +argv_contains_header "Authorization: Bearer am_live_secret" && cond=true || cond=false +assert "ingest curl includes Authorization: Bearer " "$cond" + +reset_log +am_search_fast "what did we decide" 3 >/dev/null 2>&1 || true +argv_contains_header "Authorization: Bearer am_live_secret" && cond=true || cond=false +assert "search curl includes Authorization: Bearer " "$cond" + +unset ATOMICMEMORY_API_KEY +unset ATOMICMEMORY_API_URL + +# --------------------------------------------------------------------------- +# Case 2: AM_API_KEY unset → no Authorization header on either call +# --------------------------------------------------------------------------- +printf '\nCase 2: AM_API_KEY unset → no Authorization header\n' +am_load_env || { printf 'am_load_env failed\n' >&2; exit 1; } + +reset_log +am_post_quick_ingest "$body" >/dev/null 2>&1 || true +grep -q '^Authorization:' "$ARGV_LOG" && cond=false || cond=true +assert "ingest curl has no Authorization header" "$cond" + +reset_log +am_search_fast "what did we decide" 3 >/dev/null 2>&1 || true +grep -q '^Authorization:' "$ARGV_LOG" && cond=false || cond=true +assert "search curl has no Authorization header" "$cond" + +# --------------------------------------------------------------------------- +# Case 3: AM_API_URL override propagates to the curl call +# --------------------------------------------------------------------------- +printf '\nCase 3: AM_API_URL override propagates to the wire\n' +export ATOMICMEMORY_API_URL="https://memory.example.com" +am_load_env || { printf 'am_load_env failed\n' >&2; exit 1; } + +reset_log +am_post_quick_ingest "$body" >/dev/null 2>&1 || true +grep -qx 'https://memory.example.com/v1/memories/ingest/quick' "$ARGV_LOG" && cond=true || cond=false +assert "ingest URL uses override host" "$cond" + +reset_log +am_search_fast "q" 3 >/dev/null 2>&1 || true +grep -qx 'https://memory.example.com/v1/memories/search/fast' "$ARGV_LOG" && cond=true || cond=false +assert "search URL uses override host" "$cond" + +unset ATOMICMEMORY_API_URL + +printf '\n--- %d passed, %d failed ---\n' "$PASS_COUNT" "$FAIL_COUNT" +if [ "$FAIL_COUNT" -gt 0 ]; then + exit 1 +fi diff --git a/plugins/claude-code/scripts/__tests__/load-env-defaults.sh b/plugins/claude-code/scripts/__tests__/load-env-defaults.sh new file mode 100755 index 0000000..cfe7c43 --- /dev/null +++ b/plugins/claude-code/scripts/__tests__/load-env-defaults.sh @@ -0,0 +1,123 @@ +#!/usr/bin/env bash +# +# Unit gate for `am_load_env` defaults in +# `plugins/claude-code/scripts/lib/atomicmemory.sh`. +# +# Locks the documented local-mode contract from +# https://docs.atomicstrata.ai/integrations/coding-agents/claude-code/local: +# - ATOMICMEMORY_CAPTURE_LEVEL defaults to "balanced" +# - ATOMICMEMORY_PROVIDER defaults to "atomicmemory" +# - ATOMICMEMORY_API_URL defaults to "http://127.0.0.1:3050" +# - ATOMICMEMORY_SCOPE_USER is auto-derived from the OS user +# A fresh install with no ATOMICMEMORY_* host env vars set MUST succeed +# so PostCompact (and the rest of the lifecycle hooks) do not exit 1. +# +# Runs entirely in-process: no Docker, no curl, no network. + +set -uo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +LIB_PATH="$SCRIPT_DIR/../lib/atomicmemory.sh" + +if [ ! -f "$LIB_PATH" ]; then + printf 'fixture path missing: %s\n' "$LIB_PATH" >&2 + exit 1 +fi + +unset ATOMICMEMORY_PROVIDER +unset ATOMICMEMORY_API_URL +unset ATOMICMEMORY_API_KEY +unset ATOMICMEMORY_CAPTURE_LEVEL +unset ATOMICMEMORY_SCOPE_USER +unset ATOMICMEMORY_SCOPE_AGENT +unset ATOMICMEMORY_SCOPE_NAMESPACE +unset ATOMICMEMORY_SCOPE_THREAD +export USER="${USER:-test-user}" + +# shellcheck source=../lib/atomicmemory.sh +source "$LIB_PATH" + +PASS_COUNT=0 +FAIL_COUNT=0 + +assert() { + local name="$1" + local condition="$2" + if [ "$condition" = "true" ]; then + printf ' ✓ %s\n' "$name" + PASS_COUNT=$((PASS_COUNT + 1)) + else + printf ' ✗ %s\n' "$name" >&2 + FAIL_COUNT=$((FAIL_COUNT + 1)) + fi +} + +printf '\nCase: am_load_env succeeds with no ATOMICMEMORY_* host vars\n' +set +e +am_load_env +exit_code=$? +set -e +[ "$exit_code" -eq 0 ] && cond=true || cond=false +assert "exit 0 on fresh-install env" "$cond" +[ "$AM_PROVIDER" = "atomicmemory" ] && cond=true || cond=false +assert "AM_PROVIDER defaults to atomicmemory" "$cond" +[ "$AM_CAPTURE_LEVEL" = "balanced" ] && cond=true || cond=false +assert "AM_CAPTURE_LEVEL defaults to balanced (matches docs)" "$cond" +[ "$AM_API_URL" = "http://127.0.0.1:3050" ] && cond=true || cond=false +assert "AM_API_URL defaults to local core URL" "$cond" +[ -z "$AM_API_KEY" ] && cond=true || cond=false +assert "AM_API_KEY empty when unset (local mode)" "$cond" +[ -n "$AM_SCOPE_USER" ] && cond=true || cond=false +assert "AM_SCOPE_USER auto-derives a non-empty value" "$cond" + +printf '\nCase: explicit ATOMICMEMORY_API_URL / API_KEY overrides for hosted mode\n' +export ATOMICMEMORY_API_URL="https://memory.example.com" +export ATOMICMEMORY_API_KEY="am_live_test" +set +e +am_load_env +exit_code=$? +set -e +[ "$exit_code" -eq 0 ] && cond=true || cond=false +assert "exit 0 with explicit hosted overrides" "$cond" +[ "$AM_API_URL" = "https://memory.example.com" ] && cond=true || cond=false +assert "AM_API_URL honors ATOMICMEMORY_API_URL override" "$cond" +[ "$AM_API_KEY" = "am_live_test" ] && cond=true || cond=false +assert "AM_API_KEY exposed from ATOMICMEMORY_API_KEY" "$cond" +unset ATOMICMEMORY_API_URL +unset ATOMICMEMORY_API_KEY + +printf '\nCase: ATOMICMEMORY_API_URL trailing slash is stripped\n' +export ATOMICMEMORY_API_URL="https://memory.example.com/" +set +e +am_load_env +set -e +[ "$AM_API_URL" = "https://memory.example.com" ] && cond=true || cond=false +assert "trailing slash stripped from API_URL" "$cond" +unset ATOMICMEMORY_API_URL + +printf '\nCase: explicit ATOMICMEMORY_CAPTURE_LEVEL overrides default\n' +export ATOMICMEMORY_CAPTURE_LEVEL="full" +set +e +am_load_env +exit_code=$? +set -e +[ "$exit_code" -eq 0 ] && cond=true || cond=false +assert "exit 0 with explicit capture level" "$cond" +[ "$AM_CAPTURE_LEVEL" = "full" ] && cond=true || cond=false +assert "explicit value wins over default" "$cond" +unset ATOMICMEMORY_CAPTURE_LEVEL + +printf '\nCase: invalid ATOMICMEMORY_CAPTURE_LEVEL still rejected\n' +export ATOMICMEMORY_CAPTURE_LEVEL="nonsense" +set +e +am_load_env 2>/dev/null +exit_code=$? +set -e +[ "$exit_code" -ne 0 ] && cond=true || cond=false +assert "exit non-zero on bogus capture level" "$cond" +unset ATOMICMEMORY_CAPTURE_LEVEL + +printf '\n--- %d passed, %d failed ---\n' "$PASS_COUNT" "$FAIL_COUNT" +if [ "$FAIL_COUNT" -gt 0 ]; then + exit 1 +fi diff --git a/plugins/claude-code/scripts/lib/atomicmemory.sh b/plugins/claude-code/scripts/lib/atomicmemory.sh index 1858bfa..cfbac13 100755 --- a/plugins/claude-code/scripts/lib/atomicmemory.sh +++ b/plugins/claude-code/scripts/lib/atomicmemory.sh @@ -41,12 +41,13 @@ am_load_env() { } AM_PROVIDER="${ATOMICMEMORY_PROVIDER:-atomicmemory}" - AM_API_URL="http://127.0.0.1:3050" + AM_API_URL="${ATOMICMEMORY_API_URL:-http://127.0.0.1:3050}" + AM_API_KEY="${ATOMICMEMORY_API_KEY:-}" AM_SCOPE_USER="${ATOMICMEMORY_SCOPE_USER:-$(am_default_scope_user)}" AM_SCOPE_AGENT="${ATOMICMEMORY_SCOPE_AGENT:-}" AM_SCOPE_NAMESPACE="${ATOMICMEMORY_SCOPE_NAMESPACE:-}" AM_SCOPE_THREAD="${ATOMICMEMORY_SCOPE_THREAD:-}" - AM_CAPTURE_LEVEL="${ATOMICMEMORY_CAPTURE_LEVEL:-}" + AM_CAPTURE_LEVEL="${ATOMICMEMORY_CAPTURE_LEVEL:-balanced}" AM_API_URL="${AM_API_URL%/}" @@ -127,6 +128,19 @@ am_scope_json() { + (if $thread != "" then {thread: $thread} else {} end)' } +am_auth_curl_args() { + # Populate the global AM_AUTH_CURL_ARGS array with the Bearer + # auth header pair when AM_API_KEY is set, or leave it empty. + # Matches the SDK's wire convention and core's `requireBearer` + # middleware (`Authorization: Bearer `). Callers expand + # with `${AM_AUTH_CURL_ARGS[@]+"${AM_AUTH_CURL_ARGS[@]}"}` so the + # empty-array case is safe under `set -u` on bash 3.2. + AM_AUTH_CURL_ARGS=() + if [ -n "${AM_API_KEY:-}" ]; then + AM_AUTH_CURL_ARGS=("-H" "Authorization: Bearer $AM_API_KEY") + fi +} + am_search_fast() { local query="${1:-}" local limit="${2:-5}" @@ -152,9 +166,11 @@ am_search_fast() { local timeout_seconds timeout_seconds=$(am_positive_int ATOMICMEMORY_SEARCH_TIMEOUT_SECONDS 3) || return 1 + am_auth_curl_args curl -s --max-time "$timeout_seconds" \ -X POST "$AM_API_URL/v1/memories/search/fast" \ -H "Content-Type: application/json" \ + ${AM_AUTH_CURL_ARGS[@]+"${AM_AUTH_CURL_ARGS[@]}"} \ -d "$body" } @@ -466,11 +482,13 @@ am_post_quick_ingest() { local http_code local timeout_seconds timeout_seconds=$(am_positive_int ATOMICMEMORY_WRITE_TIMEOUT_SECONDS 8) || return 1 + am_auth_curl_args http_code=$(curl -sS --max-time "$timeout_seconds" \ -o /dev/null \ -w "%{http_code}" \ -X POST "$AM_API_URL/v1/memories/ingest/quick" \ -H "Content-Type: application/json" \ + ${AM_AUTH_CURL_ARGS[@]+"${AM_AUTH_CURL_ARGS[@]}"} \ -d "$body") || return 1 case "$http_code" in diff --git a/plugins/codex/.codex-plugin/plugin.json b/plugins/codex/.codex-plugin/plugin.json index 6c14c60..fa6d60e 100644 --- a/plugins/codex/.codex-plugin/plugin.json +++ b/plugins/codex/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "atomicmemory", - "version": "0.1.12", + "version": "0.1.13", "description": "AtomicMemory memory layer for Codex. Pluggable semantic memory — swap backends through the SDK's MemoryProvider model by config, not code change.", "author": { "name": "AtomicMemory", diff --git a/plugins/codex/package.json b/plugins/codex/package.json index 3fc736b..1a8a2ac 100644 --- a/plugins/codex/package.json +++ b/plugins/codex/package.json @@ -1,6 +1,6 @@ { "name": "@atomicmemory/codex-plugin", - "version": "0.1.12", + "version": "0.1.13", "description": "AtomicMemory plugin for OpenAI Codex — plugin manifest, MCP server config, and memory protocol skill.", "private": true, "license": "Apache-2.0", diff --git a/plugins/codex/skills/atomicmemory/SKILL.md b/plugins/codex/skills/atomicmemory/SKILL.md index 492b3eb..12d3432 100644 --- a/plugins/codex/skills/atomicmemory/SKILL.md +++ b/plugins/codex/skills/atomicmemory/SKILL.md @@ -10,7 +10,7 @@ description: > license: Apache-2.0 metadata: author: AtomicMemory - version: "0.1.12" + version: "0.1.13" category: ai-memory tags: "memory, semantic-search, codex, pluggable" --- diff --git a/plugins/cursor/package.json b/plugins/cursor/package.json index 67fe694..9f51a24 100644 --- a/plugins/cursor/package.json +++ b/plugins/cursor/package.json @@ -1,6 +1,6 @@ { "name": "@atomicmemory/cursor-plugin", - "version": "0.1.12", + "version": "0.1.13", "description": "AtomicMemory integration for Cursor - MCP configuration and project rules for persistent semantic memory.", "private": true, "license": "Apache-2.0", diff --git a/plugins/hermes/CHANGELOG.md b/plugins/hermes/CHANGELOG.md index e8cb9c6..723905b 100644 --- a/plugins/hermes/CHANGELOG.md +++ b/plugins/hermes/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## 0.1.13 - 2026-05-15 + +### Fixed + +- Installer now writes the provider to `$HERMES_HOME/plugins/atomicmemory/`, the path Hermes actually scans for user-installed memory providers. Previously the files landed under `$HERMES_HOME/plugins/memory/atomicmemory/`, where Hermes' discovery never looked, so `hermes memory setup` did not list AtomicMemory as a choice. +- Normalized the npm `bin` path in `package.json` so the binary resolves on platforms that reject the `./install.mjs` form. + ## 0.1.12 - 2026-05-14 ### Fixed diff --git a/plugins/hermes/README.md b/plugins/hermes/README.md index bb7fc21..5d758df 100644 --- a/plugins/hermes/README.md +++ b/plugins/hermes/README.md @@ -13,7 +13,7 @@ has touched (Claude Code, Codex, the web extension, etc.). Set ``` Hermes Agent (Python) - → plugins/memory/atomicmemory/__init__.py + → $HERMES_HOME/plugins/atomicmemory/__init__.py → AtomicMemoryClient (Python protocol) → PythonSdkAtomicMemoryClient → published atomicmemory Python SDK MemoryClient @@ -53,8 +53,8 @@ For source development, symlink the checkout instead: ```bash cd /path/to/atomicmemory-integrations -mkdir -p "$HERMES_HOME/plugins/memory" -ln -s "$(pwd)/plugins/hermes" "$HERMES_HOME/plugins/memory/atomicmemory" +mkdir -p "$HERMES_HOME/plugins" +ln -s "$(pwd)/plugins/hermes" "$HERMES_HOME/plugins/atomicmemory" ``` ## Config @@ -153,7 +153,7 @@ run while AtomicMemory is temporarily unavailable. | Symptom | Likely cause | |---|---| -| Provider does not appear in `hermes memory setup` | Wrong install path. User-installed memory providers must live under `$HERMES_HOME/plugins/memory//`. | +| Provider does not appear in `hermes memory setup` | Wrong install path. User-installed memory providers must live directly under `$HERMES_HOME/plugins//` (the `plugins/memory/` layout is for providers bundled inside hermes-agent itself). | | `is_available()` returns False | `ATOMICMEMORY_API_URL` unset, or the Hermes Python environment did not install the `atomicmemory` dependency from `plugin.yaml`. | | Import fails at startup | The Hermes Python environment is missing the SDK dependency from `plugin.yaml`. | | Calls fail with `PROVIDER_UNSUPPORTED` while `memory_scope=siloed` | The configured SDK provider is not the AtomicMemory core (e.g. it's `mem0`). Either switch `ATOMICMEMORY_PROVIDER=atomicmemory` or move to `memory_scope=shared`. | diff --git a/plugins/hermes/install.mjs b/plugins/hermes/install.mjs index 9610f8e..1e9d802 100644 --- a/plugins/hermes/install.mjs +++ b/plugins/hermes/install.mjs @@ -2,8 +2,10 @@ /** * Install the AtomicMemory Hermes provider from the published npm package. * - * Hermes memory providers are filesystem plugins under - * `$HERMES_HOME/plugins/memory/`. The npm package already contains the + * Hermes discovers user-installed memory providers as direct children of + * `$HERMES_HOME/plugins/` (bundled providers live under + * `plugins/memory/` inside hermes-agent itself, but user-installed + * plugins are flat under `plugins/`). The npm package already contains the * Python provider files, so this installer copies only that managed provider * surface into the active Hermes profile without requiring a Git checkout. */ @@ -53,7 +55,7 @@ function parseArgs(argv) { function defaultTarget() { const hermesHome = process.env.HERMES_HOME || defaultHermesHome(); - return join(hermesHome, 'plugins', 'memory', 'atomicmemory'); + return join(hermesHome, 'plugins', 'atomicmemory'); } function defaultHermesHome() { @@ -90,10 +92,10 @@ function printHelp() { console.log(`Usage: atomicmemory-hermes [install] [--target ] Installs the AtomicMemory Hermes memory provider into: - $HERMES_HOME/plugins/memory/atomicmemory + $HERMES_HOME/plugins/atomicmemory When HERMES_HOME is unset, defaults to: - $HOME/.hermes/plugins/memory/atomicmemory`); + $HOME/.hermes/plugins/atomicmemory`); } try { diff --git a/plugins/hermes/package.json b/plugins/hermes/package.json index 3e7a1bb..7a9b3f0 100644 --- a/plugins/hermes/package.json +++ b/plugins/hermes/package.json @@ -1,6 +1,6 @@ { "name": "@atomicmemory/hermes-plugin", - "version": "0.1.12", + "version": "0.1.13", "description": "AtomicMemory native Hermes memory provider — Python SDK-backed, cross-tool memory by default.", "publishConfig": { "access": "public", diff --git a/plugins/hermes/plugin.yaml b/plugins/hermes/plugin.yaml index 3d81e98..818b50f 100644 --- a/plugins/hermes/plugin.yaml +++ b/plugins/hermes/plugin.yaml @@ -1,5 +1,5 @@ name: atomicmemory -version: 0.1.12 +version: 0.1.13 description: "AtomicMemory native Hermes memory provider — Python SDK-backed, cross-tool memory by default." pip_dependencies: - "atomicmemory>=1.0.1,<2.0.0" diff --git a/plugins/hermes/pyproject.toml b/plugins/hermes/pyproject.toml index d8c529d..867baa7 100644 --- a/plugins/hermes/pyproject.toml +++ b/plugins/hermes/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "atomicmemory-hermes" -version = "0.1.12" +version = "0.1.13" description = "AtomicMemory native Hermes memory provider." readme = "README.md" requires-python = ">=3.10" diff --git a/plugins/hermes/tests/test_install_path_imports.py b/plugins/hermes/tests/test_install_path_imports.py index beb04e0..84e9820 100644 --- a/plugins/hermes/tests/test_install_path_imports.py +++ b/plugins/hermes/tests/test_install_path_imports.py @@ -1,9 +1,10 @@ """Regression: package must import under the installed Hermes path. -Hermes loads memory provider plugins from `$HERMES_HOME/plugins/memory//`. -After install, the directory is named `atomicmemory/` — not `plugins/hermes/` -— so any `from plugins.hermes. import ...` import will fail with -ModuleNotFoundError. All in-package imports must be relative. +Hermes loads user-installed memory provider plugins from +`$HERMES_HOME/plugins//`. After install, the directory is named +`atomicmemory/` — not `plugins/hermes/` — so any +`from plugins.hermes. import ...` import will fail with ModuleNotFoundError. +All in-package imports must be relative. This test simulates the installed layout by copying every shipped file into a temp dir named `atomicmemory/`, then loading it as a package via importlib. @@ -57,6 +58,25 @@ def test_install_mjs_copies_provider_files(self) -> None: self.assertFalse((target / "install.mjs").exists()) self.assertFalse((target / "pyproject.toml").exists()) + def test_install_mjs_default_target_matches_hermes_discovery(self) -> None: + # Hermes scans `$HERMES_HOME/plugins//` for user-installed memory + # providers (see hermes-agent/plugins/memory/__init__.py). Installing one + # level deeper (e.g. plugins/memory/atomicmemory/) hides the provider + # from `hermes memory setup`. + with tempfile.TemporaryDirectory() as tmp: + result = subprocess.run( + ["node", str(PLUGIN_ROOT / "install.mjs"), "install"], + capture_output=True, + check=True, + text=True, + env={"HERMES_HOME": tmp, "PATH": __import__("os").environ.get("PATH", "")}, + ) + expected = Path(tmp) / "plugins" / "atomicmemory" + self.assertIn(f"Installed AtomicMemory Hermes provider to {expected}", result.stdout) + self.assertTrue((expected / "__init__.py").exists()) + self.assertFalse((Path(tmp) / "plugins" / "memory").exists(), + "installer must not nest under plugins/memory/") + def test_pyproject_declares_hermes_entry_point(self) -> None: pyproject = (PLUGIN_ROOT / "pyproject.toml").read_text(encoding="utf-8") diff --git a/plugins/openclaw/README.md b/plugins/openclaw/README.md index 521fb80..ae80a3b 100644 --- a/plugins/openclaw/README.md +++ b/plugins/openclaw/README.md @@ -13,7 +13,7 @@ cd atomicmemory-integrations/plugins/openclaw openclaw plugins install . ``` -See the [full documentation](https://docs.atomicmemory.ai/integrations/coding-agents/openclaw) for config details. +See the [full documentation](https://docs.atomicstrata.ai/integrations/coding-agents/openclaw) for config details. ## Configure diff --git a/plugins/openclaw/openclaw.plugin.json b/plugins/openclaw/openclaw.plugin.json index 2f06dfe..1466713 100644 --- a/plugins/openclaw/openclaw.plugin.json +++ b/plugins/openclaw/openclaw.plugin.json @@ -1,7 +1,7 @@ { "id": "atomicmemory", "name": "AtomicMemory", - "version": "0.1.12", + "version": "0.1.13", "description": "Persistent semantic memory for OpenClaw agents — cross-channel user memory and deterministic session snapshots via the AtomicMemory SDK's pluggable MemoryProvider model.", "kind": "memory", "skills": ["./skills/atomicmemory"], diff --git a/plugins/openclaw/package.json b/plugins/openclaw/package.json index 42a9f6f..aa58a46 100644 --- a/plugins/openclaw/package.json +++ b/plugins/openclaw/package.json @@ -1,6 +1,6 @@ { "name": "@atomicmemory/openclaw-plugin", - "version": "0.1.12", + "version": "0.1.13", "description": "AtomicMemory plugin for OpenClaw — persistent semantic memory and deterministic session snapshots across channels.", "type": "module", "main": "dist/index.js", diff --git a/plugins/openclaw/skills/atomicmemory/skill.yaml b/plugins/openclaw/skills/atomicmemory/skill.yaml index d4714a6..12de2b2 100644 --- a/plugins/openclaw/skills/atomicmemory/skill.yaml +++ b/plugins/openclaw/skills/atomicmemory/skill.yaml @@ -1,5 +1,5 @@ name: atomicmemory -version: 0.1.12 +version: 0.1.13 author: name: AtomicMemory url: https://atomicmem.ai