smartmemory 1.4.0__tar.gz → 1.4.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {smartmemory-1.4.0 → smartmemory-1.4.3}/CHANGELOG.md +53 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/PKG-INFO +2 -2
- {smartmemory-1.4.0 → smartmemory-1.4.3}/README.md +5 -1
- {smartmemory-1.4.0 → smartmemory-1.4.3}/pyproject.toml +2 -2
- {smartmemory-1.4.0 → smartmemory-1.4.3}/smartmemory_app/cli.py +184 -48
- smartmemory-1.4.3/smartmemory_app/cli_code.py +212 -0
- smartmemory-1.4.3/smartmemory_app/cli_mcp.py +197 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/smartmemory_app/events_server.py +101 -0
- smartmemory-1.4.3/smartmemory_app/launch_metrics.py +66 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/smartmemory_app/local_api.py +209 -17
- {smartmemory-1.4.0 → smartmemory-1.4.3}/smartmemory_app/setup.py +20 -1
- smartmemory-1.4.3/smartmemory_app/skills/remember.md +22 -0
- smartmemory-1.4.3/smartmemory_app/static/assets/local-Clt8rYLQ.js +335 -0
- smartmemory-1.4.3/smartmemory_app/static/assets/local-ZCcXPKXN.css +1 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/smartmemory_app/static/index.html +2 -2
- {smartmemory-1.4.0 → smartmemory-1.4.3}/smartmemory_app/storage.py +21 -2
- {smartmemory-1.4.0 → smartmemory-1.4.3}/smartmemory_app/viewer_server.py +33 -2
- smartmemory-1.4.3/tests/integration/test_cli_new_subcommands.py +240 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/tests/unit/test_cli.py +19 -2
- smartmemory-1.4.3/tests/unit/test_lite_api_contract.py +369 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/tests/unit/test_local_api.py +33 -10
- {smartmemory-1.4.0 → smartmemory-1.4.3}/tests/unit/test_storage.py +48 -0
- smartmemory-1.4.0/smartmemory_app/skills/remember.md +0 -9
- smartmemory-1.4.0/smartmemory_app/static/assets/local-Bmk6TID5.js +0 -334
- smartmemory-1.4.0/smartmemory_app/static/assets/local-CX-jTTRv.css +0 -1
- smartmemory-1.4.0/smartmemory_app/static/assets/local-C_ntKcqJ.js +0 -334
- smartmemory-1.4.0/smartmemory_app/static/assets/local-D0CVH7by.css +0 -1
- smartmemory-1.4.0/smartmemory_app/static/assets/local-D4I7ctUR.js +0 -334
- smartmemory-1.4.0/smartmemory_app/static/assets/local-DhOHKVEA.js +0 -334
- smartmemory-1.4.0/smartmemory_app/static/assets/local-TN7VFhW8.js +0 -334
- smartmemory-1.4.0/smartmemory_app/static/assets/local-YU236KFI.js +0 -334
- smartmemory-1.4.0/smartmemory_app/static/local.html +0 -13
- {smartmemory-1.4.0 → smartmemory-1.4.3}/.github/workflows/publish.yml +0 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/.gitignore +0 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/LICENSE +0 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/LICENSE.agpl-v3 +0 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/LICENSE.header +0 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/plugin.json +0 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/scripts/generate_seed_patterns.py +0 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/smartmemory_app/__init__.py +0 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/smartmemory_app/__main__.py +0 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/smartmemory_app/async_enrichment.py +0 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/smartmemory_app/config.py +0 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/smartmemory_app/daemon.py +0 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/smartmemory_app/data/ai.smartmemory.daemon.plist +0 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/smartmemory_app/data/ai.smartmemory.worker.plist +0 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/smartmemory_app/data/seed_patterns.jsonl +0 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/smartmemory_app/data/seeds/ai-model.jsonl +0 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/smartmemory_app/data/seeds/concept.jsonl +0 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/smartmemory_app/data/seeds/database.jsonl +0 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/smartmemory_app/data/seeds/framework.jsonl +0 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/smartmemory_app/data/seeds/language.jsonl +0 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/smartmemory_app/data/seeds/manifest.json +0 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/smartmemory_app/data/seeds/organization.jsonl +0 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/smartmemory_app/data/seeds/platform.jsonl +0 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/smartmemory_app/data/seeds/protocol.jsonl +0 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/smartmemory_app/data/seeds/service.jsonl +0 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/smartmemory_app/data/seeds/tool.jsonl +0 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/smartmemory_app/enrichment_queue.py +0 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/smartmemory_app/enrichment_worker.py +0 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/smartmemory_app/event_sink.py +0 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/smartmemory_app/hooks/distill.sh +0 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/smartmemory_app/hooks/learn.sh +0 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/smartmemory_app/hooks/observe.sh +0 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/smartmemory_app/hooks/orient.sh +0 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/smartmemory_app/hooks/persist.sh +0 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/smartmemory_app/hooks/recall.sh +0 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/smartmemory_app/lifecycle.py +0 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/smartmemory_app/lifecycle_api.py +0 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/smartmemory_app/lifecycle_config.py +0 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/smartmemory_app/patterns.py +0 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/smartmemory_app/recall_format.py +0 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/smartmemory_app/remote_backend.py +0 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/smartmemory_app/setup_tui.py +0 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/smartmemory_app/skills/ingest.md +0 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/smartmemory_app/skills/orient.md +0 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/smartmemory_app/skills/search.md +0 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/smartmemory_app/static/icons/icon-192x192.svg +0 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/smartmemory_app/static/icons/icon-512x512.svg +0 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/smartmemory_app/static/logo.svg +0 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/smartmemory_app/static/viewer-logo.svg +0 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/smartmemory_app/sync.py +0 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/tests/__init__.py +0 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/tests/conftest.py +0 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/tests/integration/__init__.py +0 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/tests/integration/test_async_enrichment_e2e.py +0 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/tests/integration/test_async_enrichment_sqlite_regression.py +0 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/tests/integration/test_daemon.py +0 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/tests/integration/test_entity_ruler_patterns.py +0 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/tests/integration/test_hook_recall.py +0 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/tests/integration/test_hook_scripts.py +0 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/tests/integration/test_local_api_integration.py +0 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/tests/integration/test_recall_confidence.py +0 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/tests/integration/test_recall_flow.py +0 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/tests/integration/test_viewer_server.py +0 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/tests/unit/__init__.py +0 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/tests/unit/test_async_enrichment.py +0 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/tests/unit/test_config.py +0 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/tests/unit/test_events_server.py +0 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/tests/unit/test_launchd.py +0 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/tests/unit/test_lifecycle.py +0 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/tests/unit/test_patterns.py +0 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/tests/unit/test_recall_format.py +0 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/tests/unit/test_remote_backend.py +0 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/tests/unit/test_server_tools.py +0 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/tests/unit/test_setup.py +0 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/tests/unit/test_setup_tui.py +0 -0
- {smartmemory-1.4.0 → smartmemory-1.4.3}/tests/unit/test_stale_markers.py +0 -0
|
@@ -1,5 +1,52 @@
|
|
|
1
1
|
# Changelog — smartmemory
|
|
2
2
|
|
|
3
|
+
## [Unreleased]
|
|
4
|
+
|
|
5
|
+
### Fixed (MCP `memory_search` broken in local mode, 2026-05-18)
|
|
6
|
+
|
|
7
|
+
- **MCP `memory_search` completely broken in local mode.** `storage.search()` only accepted `(query, top_k, filters, include_reference)`. The MCP server calls it with `memory_type`, `enable_hybrid`, `decompose_query`, `multi_hop`, `max_hops`, and `budget_ms` — any of those raised `TypeError: search() got an unexpected keyword argument`. Every `memory_search` call in local mode crashed. `storage.search` now accepts `memory_type` (forwarded as a post-filter) and `**search_kwargs` (allowlisted to `decompose_query`, `channel_weights`, `multi_hop`, `max_hops`, `budget_ms`, `semantic_hops`); unknown keys such as `enable_hybrid` are silently dropped instead of raising. Regression test added (`test_search_accepts_mcp_recall_kwargs`).
|
|
8
|
+
|
|
9
|
+
### Fixed (DEMO-WALKTHROUGH-1, `smartmemory search` CLI, 2026-05-17)
|
|
10
|
+
|
|
11
|
+
- **`smartmemory search` no longer crashes with `AttributeError: 'str' object has no attribute 'get'`.** The daemon `/memory/search` returns the CORE-CRUD-LIST contract shape `{"items": [...]}`, but `cli.py search_cmd` iterated the dict directly — so `for r in results` yielded the key string `"items"` and `r.get("content")` threw. Now unwraps `results["items"]` (daemon) vs bare list (storage fallback), with a defensive non-dict skip. Regression test added (`test_search_cmd_daemon_items_contract`); fixed two stale `assert_called_once_with` expectations that predated the `include_reference` fallback kwarg.
|
|
12
|
+
|
|
13
|
+
## [1.4.3] - 2026-05-17
|
|
14
|
+
|
|
15
|
+
### Changed (lockstep relock — ships the competitive-response sprint)
|
|
16
|
+
|
|
17
|
+
- **Pinned `smartmemory-core[lite]==0.9.8`** (was `==0.9.1`, 7 versions stale despite the "move in lockstep" comment). The wrapper now actually pulls the shipped sprint core: CORE-BITEMPORAL-1 transaction-time activation, CORE-ATTENTION-FUSION-1 Phase 1, APP-VAULT-SYNC-1 Phase 0, RECALL-CITATIONS-1. Without this, `pip install smartmemory` resolved core 0.9.1 — none of the differentiator. Supersedes the never-published 1.4.2 (committed but no Release was cut; wrapper publishes only on GitHub Release / workflow_dispatch).
|
|
18
|
+
|
|
19
|
+
### Fixed (DEMO-WALKTHROUGH-1, lite-mode graph viewer SSE, 2026-05-17)
|
|
20
|
+
|
|
21
|
+
- **Lite daemon now serves `GET /memory/progress/stream` (SSE).** `smart-memory-graph`'s `useGraphStream` migrated WS→SSE; the lite daemon only exposed the legacy `sm.v1` WebSocket on `:9015`, so the local graph viewer never connected ("Not connected to event stream", 0 nodes).
|
|
22
|
+
- `smartmemory_app/events_server.py`: SSE subscriber registry + `_to_progress_event()` reshaping raw sink items into ProgressEvent contract frames (`kind: graph.node|graph.edge`) + `_fanout_sse()` at the **single** existing `sink._q` drain point. The `ws://:9015` broadcast is byte-for-byte unchanged (SSE is an additional broadcast target, not a second queue consumer — preserves existing WS clients incl. the recorder). Cross-loop hand-off via `call_soon_threadsafe` (same bridge as `InProcessQueueSink.emit`).
|
|
23
|
+
- `smartmemory_app/local_api.py`: `GET /memory/progress/stream` `StreamingResponse` (per-connection queue, 15s keepalive, disconnect cleanup), declared before `/{memory_id}` routes.
|
|
24
|
+
- Verified: one ingest yields 6 `graph.node` + 10 `graph.edge` contract frames with `payload.data.memory_id`+`label`; legacy WS path unaffected.
|
|
25
|
+
- **Events server port now tracks the API port.** `smartmemory_app/viewer_server.py` starts the events WS on `port + 1` instead of a hardcoded `9015`. Default `9014 → 9015` is unchanged; a non-default daemon (e.g. an isolated demo on `9114`) now gets its own events server (`9115`) instead of colliding with the dev daemon's `:9015`. Enables running an isolated demo daemon alongside the launchd dev daemon.
|
|
26
|
+
|
|
27
|
+
### Added (LAUNCH-METRICS-1, Wave 2 Stream I, 2026-05-10)
|
|
28
|
+
|
|
29
|
+
- **`smartmemory_app.launch_metrics`** module — best-effort `emit(event_type, props)` POSTing to the daemon HTTP API at `/launch/event`. Honors `SMARTMEMORY_DISABLE_LAUNCH_METRICS=1` opt-out. Failures log WARNING; never raises into a CLI command path.
|
|
30
|
+
- Funnel emission wired into the three Stream A entry points:
|
|
31
|
+
- `sm setup` / `sm init` -> `setup.complete` with `{mode: local|remote}`
|
|
32
|
+
- `sm code index` -> `index.start` and `index.complete` with `{repo, files, entities, edges, elapsed_s}`
|
|
33
|
+
- `sm mcp install <client>` -> `mcp.install` with `{client, dry_run}`
|
|
34
|
+
- **Daemon-side ingest** at `POST /launch/event` in `local_api.py`. Local mode appends to `launch_events.jsonl` in the data dir.
|
|
35
|
+
|
|
36
|
+
Contract: `smart-memory-docs/docs/features/LAUNCH-METRICS-1/launch-event-contract.json`.
|
|
37
|
+
|
|
38
|
+
## [1.4.2] — 2026-05-10
|
|
39
|
+
|
|
40
|
+
### Added (Launch Sprint Wave 1, Stream A)
|
|
41
|
+
|
|
42
|
+
- **`sm init`** — alias for `sm setup`. Same Click command registered under a second name; option/flag parity is automatic.
|
|
43
|
+
- **`sm code index <path>`** — wraps `SmartMemory.ingest_code()` from the core library to index Python (and optionally TypeScript) repos into the local knowledge graph. Streams structured progress to stdout (`[code:index] phase=start|done …`). Default exclusions cover `node_modules`, virtualenvs, build artifacts, and VCS dirs. Every entity is tagged with `origin = code:index` (Tier 1 user content per `smartmemory/origin_policy.py`) — set by the indexer itself, not the wrapper. Flags: `--repo`, `--language` (repeatable, `python|typescript`), `--exclude` (repeatable), `--commit-hash`.
|
|
44
|
+
- **`sm mcp install <client>`** — writes MCP config for `claude-code` (→ `~/.claude.json`), `cursor` (→ `~/.cursor/mcp.json`), or `codex` (→ `~/.codex/config.toml`). Pointer-only wrapper around the already-shipped `smartmemory-mcp` PyPI package — no new server logic. Flags: `--dry-run` prints the would-be config without touching disk; `--path` overrides the destination. Existing config files are merged (JSON) or section-replaced (TOML); malformed configs raise loudly rather than being clobbered.
|
|
45
|
+
|
|
46
|
+
### Changed (CORE-EXPERTISE-1 Phase 4b, 2026-05-08)
|
|
47
|
+
|
|
48
|
+
- **README "Memory Types" listing extended** with `Constraint Memory` and `Learned Memory` entries plus a new "Expertise vs knowledge" callout that explains the knowledge / expertise cohort split, points to the canonical 1-pager at `docs.smartmemory.ai/smartmemory/concepts/expertise-vs-knowledge`, and surfaces `mem.search(query, expertise=True)` for partitioned recall. No code change.
|
|
49
|
+
|
|
3
50
|
## [1.1.5] — 2026-03-24
|
|
4
51
|
|
|
5
52
|
### Fixed
|
|
@@ -14,6 +61,12 @@
|
|
|
14
61
|
|
|
15
62
|
## [Unreleased]
|
|
16
63
|
|
|
64
|
+
## [1.4.1] — 2026-05-07
|
|
65
|
+
|
|
66
|
+
### Changed
|
|
67
|
+
|
|
68
|
+
- **Track core 0.9.1.** Pin updated from `smartmemory-core[lite]==0.9.0` to `==0.9.1`. Wrapper itself unchanged; bump pairs with the core release that lands the bounded `EventStream.read_recent(count)` API plus `read_all` deprecation (INSIGHTS-PROGRESS-MIGRATE-1 prep), CORE-EXPERTISE-1 Phase 1 (Decision schema extension), and ONTO-READER-MIGRATE-1 (`OntologyGraph` `:EntityType` → `:OntologyType` reader migration). Per `feedback_bump_wrapper_with_core` policy.
|
|
69
|
+
|
|
17
70
|
## [1.4.0] — 2026-05-04
|
|
18
71
|
|
|
19
72
|
### Changed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: smartmemory
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.3
|
|
4
4
|
License-File: LICENSE
|
|
5
5
|
License-File: LICENSE.agpl-v3
|
|
6
6
|
License-File: LICENSE.header
|
|
@@ -10,7 +10,7 @@ Requires-Dist: fastapi>=0.110
|
|
|
10
10
|
Requires-Dist: filelock>=3.12
|
|
11
11
|
Requires-Dist: httpx>=0.27
|
|
12
12
|
Requires-Dist: keyring>=24.0
|
|
13
|
-
Requires-Dist: smartmemory-core[lite]==0.9.
|
|
13
|
+
Requires-Dist: smartmemory-core[lite]==0.9.8
|
|
14
14
|
Requires-Dist: smartmemory-mcp>=0.2.0
|
|
15
15
|
Requires-Dist: textual>=8.0
|
|
16
16
|
Requires-Dist: tomli-w>=1.0
|
|
@@ -162,7 +162,11 @@ SmartMemory implements a multi-layered memory architecture:
|
|
|
162
162
|
- **Reasoning Memory**: Chain-of-thought traces capturing "why" decisions were made (System 2)
|
|
163
163
|
- **Opinion Memory**: Beliefs with confidence scores, reinforced or contradicted over time
|
|
164
164
|
- **Observation Memory**: Synthesized entity summaries from scattered facts
|
|
165
|
-
- **Decision Memory**: First-class decisions with confidence tracking, provenance chains, and lifecycle management
|
|
165
|
+
- **Decision Memory**: First-class decisions with confidence tracking, provenance chains, and lifecycle management. Now structured: `rejected_alternatives`, `rationale`, `constraints`. Capture: `mem.add_decision(...)`.
|
|
166
|
+
- **Constraint Memory**: Hard rules — discovered or imposed. Capture: `mem.add_constraint(...)`.
|
|
167
|
+
- **Learned Memory**: Lessons learned the hard way. Capture: `mem.add_learning(...)`.
|
|
168
|
+
|
|
169
|
+
> **Expertise vs knowledge.** The five core types above (Pending/Semantic/Episodic/Procedural/Zettelkasten) plus Reasoning are best read as the **knowledge layer** — what's *true*. Decision/Constraint/Learned/Opinion/Observation form the **expertise layer** — what to *do*, and what *not* to do. The expertise layer is what makes an agent's memory useful for *acting*: captured choices, rejected alternatives, hard constraints, lessons learned. Recall partitioned by expertise type via `mem.search(query, expertise=True)`. See [Expertise vs Knowledge](https://docs.smartmemory.ai/smartmemory/concepts/expertise-vs-knowledge) for the full mapping.
|
|
166
170
|
|
|
167
171
|
### Storage Backends
|
|
168
172
|
|
|
@@ -4,10 +4,10 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "smartmemory"
|
|
7
|
-
version = "1.4.
|
|
7
|
+
version = "1.4.3"
|
|
8
8
|
requires-python = ">=3.11"
|
|
9
9
|
dependencies = [
|
|
10
|
-
"smartmemory-core[lite]==0.9.
|
|
10
|
+
"smartmemory-core[lite]==0.9.8", # EXACT pin — wrapper and core versions move in lockstep
|
|
11
11
|
"filelock>=3.12", # cross-process SQLite write locking
|
|
12
12
|
"smartmemory-mcp>=0.2.0", # unified MCP server (PLAT-MCP-UNIFY-1)
|
|
13
13
|
"httpx>=0.27", # remote API calls (DIST-LITE-5)
|
|
@@ -18,7 +18,11 @@ def _parse_extra_props(args: list[str]) -> dict[str, str]:
|
|
|
18
18
|
props = {}
|
|
19
19
|
i = 0
|
|
20
20
|
while i < len(args):
|
|
21
|
-
if
|
|
21
|
+
if (
|
|
22
|
+
args[i].startswith("--")
|
|
23
|
+
and i + 1 < len(args)
|
|
24
|
+
and not args[i + 1].startswith("--")
|
|
25
|
+
):
|
|
22
26
|
props[args[i][2:]] = args[i + 1]
|
|
23
27
|
i += 2
|
|
24
28
|
else:
|
|
@@ -44,7 +48,9 @@ def _daemon_request(method: str, path: str, timeout: int = 120, **kwargs):
|
|
|
44
48
|
|
|
45
49
|
for attempt in range(2):
|
|
46
50
|
try:
|
|
47
|
-
r = httpx.request(
|
|
51
|
+
r = httpx.request(
|
|
52
|
+
method, f"{_daemon_url()}{path}", timeout=timeout, **kwargs
|
|
53
|
+
)
|
|
48
54
|
r.raise_for_status()
|
|
49
55
|
return r.json() if r.status_code != 204 else {}
|
|
50
56
|
except (httpx.ConnectError, httpx.ConnectTimeout, httpx.RemoteProtocolError):
|
|
@@ -72,14 +78,29 @@ def cli() -> None:
|
|
|
72
78
|
from smartmemory_app.setup import setup as _setup_cmd, uninstall as _uninstall_cmd # noqa: E402
|
|
73
79
|
|
|
74
80
|
cli.add_command(_setup_cmd, name="setup")
|
|
81
|
+
# `sm init` — alias for `sm setup`. Same Click command registered under a
|
|
82
|
+
# second name so option/flag parity is automatic.
|
|
83
|
+
cli.add_command(_setup_cmd, name="init")
|
|
75
84
|
cli.add_command(_uninstall_cmd, name="uninstall")
|
|
76
85
|
|
|
86
|
+
# `sm code …` and `sm mcp …` subgroups
|
|
87
|
+
from smartmemory_app.cli_code import code_group as _code_group # noqa: E402
|
|
88
|
+
from smartmemory_app.cli_mcp import mcp_group as _mcp_group # noqa: E402
|
|
89
|
+
|
|
90
|
+
cli.add_command(_code_group, name="code")
|
|
91
|
+
cli.add_command(_mcp_group, name="mcp")
|
|
92
|
+
|
|
77
93
|
|
|
78
94
|
# ── Daemon lifecycle ────────────────────────────────────────────────────────
|
|
79
95
|
|
|
80
96
|
|
|
81
97
|
@cli.command("start")
|
|
82
|
-
@click.option(
|
|
98
|
+
@click.option(
|
|
99
|
+
"--num-workers",
|
|
100
|
+
default=1,
|
|
101
|
+
show_default=True,
|
|
102
|
+
help="Number of enrichment worker processes.",
|
|
103
|
+
)
|
|
83
104
|
def start_cmd(num_workers: int) -> None:
|
|
84
105
|
"""Start the SmartMemory daemon and enrichment workers."""
|
|
85
106
|
from smartmemory_app.daemon import start_daemon, is_running
|
|
@@ -109,7 +130,12 @@ def stop_cmd() -> None:
|
|
|
109
130
|
|
|
110
131
|
|
|
111
132
|
@cli.command("restart")
|
|
112
|
-
@click.option(
|
|
133
|
+
@click.option(
|
|
134
|
+
"--num-workers",
|
|
135
|
+
default=1,
|
|
136
|
+
show_default=True,
|
|
137
|
+
help="Number of enrichment worker processes.",
|
|
138
|
+
)
|
|
113
139
|
def restart_cmd(num_workers: int) -> None:
|
|
114
140
|
"""Restart the SmartMemory daemon and enrichment workers."""
|
|
115
141
|
from smartmemory_app.daemon import stop_daemon, start_daemon, is_running
|
|
@@ -162,7 +188,9 @@ def viewer_cmd(port: int | None) -> None:
|
|
|
162
188
|
|
|
163
189
|
|
|
164
190
|
@cli.command("worker")
|
|
165
|
-
@click.option(
|
|
191
|
+
@click.option(
|
|
192
|
+
"--loop", is_flag=True, help="Poll continuously instead of drain-and-exit."
|
|
193
|
+
)
|
|
166
194
|
def worker_cmd(loop: bool) -> None:
|
|
167
195
|
"""Run the enrichment worker (Tier 2 LLM extraction).
|
|
168
196
|
|
|
@@ -180,8 +208,15 @@ def worker_cmd(loop: bool) -> None:
|
|
|
180
208
|
# ── Memory operations ───────────────────────────────────────────────────────
|
|
181
209
|
|
|
182
210
|
_VALID_MEMORY_TYPES = {
|
|
183
|
-
"pending",
|
|
184
|
-
"
|
|
211
|
+
"pending",
|
|
212
|
+
"semantic",
|
|
213
|
+
"episodic",
|
|
214
|
+
"procedural",
|
|
215
|
+
"zettel",
|
|
216
|
+
"reasoning",
|
|
217
|
+
"opinion",
|
|
218
|
+
"observation",
|
|
219
|
+
"decision",
|
|
185
220
|
}
|
|
186
221
|
|
|
187
222
|
|
|
@@ -194,12 +229,27 @@ def _validate_memory_type(ctx, param, value: str) -> str:
|
|
|
194
229
|
return value
|
|
195
230
|
|
|
196
231
|
|
|
197
|
-
@cli.command(
|
|
198
|
-
|
|
199
|
-
|
|
232
|
+
@cli.command(
|
|
233
|
+
"add",
|
|
234
|
+
context_settings=dict(
|
|
235
|
+
ignore_unknown_options=True,
|
|
236
|
+
allow_extra_args=True,
|
|
237
|
+
),
|
|
238
|
+
)
|
|
200
239
|
@click.argument("text", default="-")
|
|
201
|
-
@click.option(
|
|
202
|
-
|
|
240
|
+
@click.option(
|
|
241
|
+
"--type",
|
|
242
|
+
"memory_type",
|
|
243
|
+
default="episodic",
|
|
244
|
+
show_default=True,
|
|
245
|
+
callback=_validate_memory_type,
|
|
246
|
+
)
|
|
247
|
+
@click.option(
|
|
248
|
+
"--all",
|
|
249
|
+
"as_whole",
|
|
250
|
+
is_flag=True,
|
|
251
|
+
help="Add stdin as one memory instead of line-by-line.",
|
|
252
|
+
)
|
|
203
253
|
@click.pass_context
|
|
204
254
|
def add_cmd(ctx, text: str, memory_type: str, as_whole: bool) -> None:
|
|
205
255
|
"""Add text as a memory. Use - or pipe stdin to read from a file.
|
|
@@ -219,11 +269,17 @@ def add_cmd(ctx, text: str, memory_type: str, as_whole: bool) -> None:
|
|
|
219
269
|
|
|
220
270
|
if text == "-":
|
|
221
271
|
if sys.stdin.isatty():
|
|
222
|
-
raise click.ClickException(
|
|
272
|
+
raise click.ClickException(
|
|
273
|
+
'No input. Pipe text or use: smartmemory add "text"'
|
|
274
|
+
)
|
|
223
275
|
raw = sys.stdin.read()
|
|
224
276
|
if not raw.strip():
|
|
225
277
|
raise click.ClickException("Content cannot be empty.")
|
|
226
|
-
chunks =
|
|
278
|
+
chunks = (
|
|
279
|
+
[raw.strip()]
|
|
280
|
+
if as_whole
|
|
281
|
+
else [l.strip() for l in raw.splitlines() if l.strip()]
|
|
282
|
+
)
|
|
227
283
|
if not chunks:
|
|
228
284
|
raise click.ClickException("Content cannot be empty.")
|
|
229
285
|
props = _parse_extra_props(ctx.args)
|
|
@@ -261,14 +317,27 @@ def add_cmd(ctx, text: str, memory_type: str, as_whole: bool) -> None:
|
|
|
261
317
|
@cli.command("recall")
|
|
262
318
|
@click.option("--cwd", default=None, help="Current working directory for context.")
|
|
263
319
|
@click.option("--top-k", default=10, show_default=True)
|
|
264
|
-
@click.option(
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
320
|
+
@click.option(
|
|
321
|
+
"--query", default=None, help="Optional prompt query (UserPromptSubmit hook)."
|
|
322
|
+
)
|
|
323
|
+
@click.option(
|
|
324
|
+
"--workspace", "workspace_id", default=None, help="Workspace ID override."
|
|
325
|
+
)
|
|
326
|
+
@click.option(
|
|
327
|
+
"--strict/--no-strict",
|
|
328
|
+
default=False,
|
|
329
|
+
help="Drop legacy items without workspace_id (kills cross-workspace leak).",
|
|
330
|
+
)
|
|
331
|
+
@click.option(
|
|
332
|
+
"--no-snapshot",
|
|
333
|
+
"no_snapshot",
|
|
334
|
+
is_flag=True,
|
|
335
|
+
default=False,
|
|
336
|
+
help="Skip snapshot frame.",
|
|
337
|
+
)
|
|
338
|
+
def recall_cmd(
|
|
339
|
+
cwd: str, top_k: int, query: str, workspace_id: str, strict: bool, no_snapshot: bool
|
|
340
|
+
) -> None:
|
|
272
341
|
"""Recall memories (SessionStart / UserPromptSubmit hook)."""
|
|
273
342
|
params = {
|
|
274
343
|
"cwd": cwd or "",
|
|
@@ -285,24 +354,42 @@ def recall_cmd(cwd: str, top_k: int, query: str, workspace_id: str,
|
|
|
285
354
|
from smartmemory_app.storage import recall
|
|
286
355
|
|
|
287
356
|
context = recall(
|
|
288
|
-
cwd,
|
|
289
|
-
|
|
290
|
-
|
|
357
|
+
cwd,
|
|
358
|
+
top_k,
|
|
359
|
+
query=query,
|
|
360
|
+
workspace_id=workspace_id,
|
|
361
|
+
include_snapshot=not no_snapshot,
|
|
362
|
+
strict=strict,
|
|
291
363
|
)
|
|
292
364
|
if context:
|
|
293
365
|
click.echo(context)
|
|
294
366
|
|
|
295
367
|
|
|
296
368
|
@cli.command("retag")
|
|
297
|
-
@click.option(
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
@click.option(
|
|
304
|
-
|
|
305
|
-
|
|
369
|
+
@click.option(
|
|
370
|
+
"--content",
|
|
371
|
+
"content_substring",
|
|
372
|
+
required=True,
|
|
373
|
+
help="Substring to match in item content (case-insensitive).",
|
|
374
|
+
)
|
|
375
|
+
@click.option(
|
|
376
|
+
"--origin",
|
|
377
|
+
"new_origin",
|
|
378
|
+
required=True,
|
|
379
|
+
help='New origin value, e.g. "seed:demo" (tier 4, hidden from recall).',
|
|
380
|
+
)
|
|
381
|
+
@click.option(
|
|
382
|
+
"--dry-run",
|
|
383
|
+
is_flag=True,
|
|
384
|
+
default=False,
|
|
385
|
+
help="Show items that would be retagged without modifying.",
|
|
386
|
+
)
|
|
387
|
+
@click.option(
|
|
388
|
+
"--limit", default=100, show_default=True, help="Max items to retag in one run."
|
|
389
|
+
)
|
|
390
|
+
def retag_cmd(
|
|
391
|
+
content_substring: str, new_origin: str, dry_run: bool, limit: int
|
|
392
|
+
) -> None:
|
|
306
393
|
"""Retag items by content match (HOOK-RECALL-RELEVANCE-1 G3.C).
|
|
307
394
|
|
|
308
395
|
Use to clean up legacy seed/fixture data that leaks into recall:
|
|
@@ -327,7 +414,11 @@ def retag_cmd(content_substring: str, new_origin: str, dry_run: bool, limit: int
|
|
|
327
414
|
# Content lives in `properties.content` for SQLite/FalkorDB serialize shape;
|
|
328
415
|
# fall back to top-level `content` for forward-compat.
|
|
329
416
|
props = raw.get("properties") or {}
|
|
330
|
-
content = (
|
|
417
|
+
content = (
|
|
418
|
+
(props.get("content") if isinstance(props, dict) else None)
|
|
419
|
+
or raw.get("content")
|
|
420
|
+
or ""
|
|
421
|
+
)
|
|
331
422
|
if needle not in content.lower():
|
|
332
423
|
continue
|
|
333
424
|
item_id = raw.get("item_id") or raw.get("id")
|
|
@@ -358,12 +449,21 @@ def retag_cmd(content_substring: str, new_origin: str, dry_run: bool, limit: int
|
|
|
358
449
|
click.echo(f"Retagged {updated}/{len(matched)} items with origin={new_origin!r}.")
|
|
359
450
|
|
|
360
451
|
|
|
361
|
-
@cli.command(
|
|
362
|
-
|
|
363
|
-
|
|
452
|
+
@cli.command(
|
|
453
|
+
"search",
|
|
454
|
+
context_settings=dict(
|
|
455
|
+
ignore_unknown_options=True,
|
|
456
|
+
allow_extra_args=True,
|
|
457
|
+
),
|
|
458
|
+
)
|
|
364
459
|
@click.argument("query")
|
|
365
460
|
@click.option("--top-k", default=5, show_default=True)
|
|
366
|
-
@click.option(
|
|
461
|
+
@click.option(
|
|
462
|
+
"--include-reference",
|
|
463
|
+
is_flag=True,
|
|
464
|
+
default=False,
|
|
465
|
+
help="Include reference data in results",
|
|
466
|
+
)
|
|
367
467
|
@click.pass_context
|
|
368
468
|
def search_cmd(ctx, query: str, top_k: int, include_reference: bool) -> None:
|
|
369
469
|
"""Search memories by semantic similarity. Use '*' to list all.
|
|
@@ -381,13 +481,23 @@ def search_cmd(ctx, query: str, top_k: int, include_reference: bool) -> None:
|
|
|
381
481
|
from smartmemory_app.storage import search
|
|
382
482
|
|
|
383
483
|
try:
|
|
384
|
-
results = search(
|
|
484
|
+
results = search(
|
|
485
|
+
query, top_k, filters=props, include_reference=include_reference
|
|
486
|
+
)
|
|
385
487
|
except NotImplementedError as e:
|
|
386
488
|
raise click.ClickException(str(e))
|
|
489
|
+
# The daemon returns the CORE-CRUD-LIST contract shape {"items": [...]};
|
|
490
|
+
# the storage fallback returns a bare list. Unwrap so we always iterate
|
|
491
|
+
# result dicts (iterating the dict directly yielded its keys -> "items"
|
|
492
|
+
# string -> AttributeError: 'str' object has no attribute 'get').
|
|
493
|
+
if isinstance(results, dict):
|
|
494
|
+
results = results.get("items", [])
|
|
387
495
|
if not results:
|
|
388
496
|
click.echo("No results.")
|
|
389
497
|
return
|
|
390
498
|
for r in results:
|
|
499
|
+
if not isinstance(r, dict):
|
|
500
|
+
continue
|
|
391
501
|
content = r.get("content", "")[:200]
|
|
392
502
|
mem_type = r.get("memory_type", "?")
|
|
393
503
|
item_id = r.get("item_id", "?")
|
|
@@ -434,6 +544,7 @@ def lifecycle_group() -> None:
|
|
|
434
544
|
def lifecycle_orient() -> None:
|
|
435
545
|
"""Orient phase: recall context at session start."""
|
|
436
546
|
import sys
|
|
547
|
+
|
|
437
548
|
body = json.loads(sys.stdin.read()) if not sys.stdin.isatty() else {}
|
|
438
549
|
session_id = body.get("session_id", "unknown")
|
|
439
550
|
cwd = body.get("cwd")
|
|
@@ -441,7 +552,9 @@ def lifecycle_orient() -> None:
|
|
|
441
552
|
from smartmemory_app.lifecycle import MemoryLifecycle
|
|
442
553
|
from smartmemory_app.lifecycle_config import LifecycleConfig
|
|
443
554
|
|
|
444
|
-
lc = MemoryLifecycle(
|
|
555
|
+
lc = MemoryLifecycle(
|
|
556
|
+
session_id, LifecycleConfig.from_config(_load_lifecycle_toml())
|
|
557
|
+
)
|
|
445
558
|
result = lc.orient(cwd=cwd)
|
|
446
559
|
if result:
|
|
447
560
|
click.echo(result)
|
|
@@ -451,6 +564,7 @@ def lifecycle_orient() -> None:
|
|
|
451
564
|
def lifecycle_recall() -> None:
|
|
452
565
|
"""Recall phase: inject prompt-relevant context."""
|
|
453
566
|
import sys
|
|
567
|
+
|
|
454
568
|
body = json.loads(sys.stdin.read()) if not sys.stdin.isatty() else {}
|
|
455
569
|
session_id = body.get("session_id", "unknown")
|
|
456
570
|
prompt = body.get("prompt", "")
|
|
@@ -458,7 +572,9 @@ def lifecycle_recall() -> None:
|
|
|
458
572
|
from smartmemory_app.lifecycle import MemoryLifecycle
|
|
459
573
|
from smartmemory_app.lifecycle_config import LifecycleConfig
|
|
460
574
|
|
|
461
|
-
lc = MemoryLifecycle(
|
|
575
|
+
lc = MemoryLifecycle(
|
|
576
|
+
session_id, LifecycleConfig.from_config(_load_lifecycle_toml())
|
|
577
|
+
)
|
|
462
578
|
result = lc.recall(prompt)
|
|
463
579
|
if result:
|
|
464
580
|
click.echo(result)
|
|
@@ -468,13 +584,16 @@ def lifecycle_recall() -> None:
|
|
|
468
584
|
def lifecycle_observe() -> None:
|
|
469
585
|
"""Observe phase: capture tool call."""
|
|
470
586
|
import sys
|
|
587
|
+
|
|
471
588
|
body = json.loads(sys.stdin.read()) if not sys.stdin.isatty() else {}
|
|
472
589
|
session_id = body.get("session_id", "unknown")
|
|
473
590
|
|
|
474
591
|
from smartmemory_app.lifecycle import MemoryLifecycle
|
|
475
592
|
from smartmemory_app.lifecycle_config import LifecycleConfig
|
|
476
593
|
|
|
477
|
-
lc = MemoryLifecycle(
|
|
594
|
+
lc = MemoryLifecycle(
|
|
595
|
+
session_id, LifecycleConfig.from_config(_load_lifecycle_toml())
|
|
596
|
+
)
|
|
478
597
|
lc.observe(
|
|
479
598
|
tool_name=body.get("tool_name", "unknown"),
|
|
480
599
|
tool_input=body.get("tool_input", {}),
|
|
@@ -486,13 +605,16 @@ def lifecycle_observe() -> None:
|
|
|
486
605
|
def lifecycle_distill() -> None:
|
|
487
606
|
"""Distill phase: pair response with stored prompt."""
|
|
488
607
|
import sys
|
|
608
|
+
|
|
489
609
|
body = json.loads(sys.stdin.read()) if not sys.stdin.isatty() else {}
|
|
490
610
|
session_id = body.get("session_id", "unknown")
|
|
491
611
|
|
|
492
612
|
from smartmemory_app.lifecycle import MemoryLifecycle
|
|
493
613
|
from smartmemory_app.lifecycle_config import LifecycleConfig
|
|
494
614
|
|
|
495
|
-
lc = MemoryLifecycle(
|
|
615
|
+
lc = MemoryLifecycle(
|
|
616
|
+
session_id, LifecycleConfig.from_config(_load_lifecycle_toml())
|
|
617
|
+
)
|
|
496
618
|
lc.distill(response=body.get("last_assistant_message", ""))
|
|
497
619
|
|
|
498
620
|
|
|
@@ -500,13 +622,16 @@ def lifecycle_distill() -> None:
|
|
|
500
622
|
def lifecycle_learn() -> None:
|
|
501
623
|
"""Learn phase: capture error pattern."""
|
|
502
624
|
import sys
|
|
625
|
+
|
|
503
626
|
body = json.loads(sys.stdin.read()) if not sys.stdin.isatty() else {}
|
|
504
627
|
session_id = body.get("session_id", "unknown")
|
|
505
628
|
|
|
506
629
|
from smartmemory_app.lifecycle import MemoryLifecycle
|
|
507
630
|
from smartmemory_app.lifecycle_config import LifecycleConfig
|
|
508
631
|
|
|
509
|
-
lc = MemoryLifecycle(
|
|
632
|
+
lc = MemoryLifecycle(
|
|
633
|
+
session_id, LifecycleConfig.from_config(_load_lifecycle_toml())
|
|
634
|
+
)
|
|
510
635
|
lc.learn(
|
|
511
636
|
tool_name=body.get("tool_name", "unknown"),
|
|
512
637
|
error=body.get("error", body.get("tool_response", "")),
|
|
@@ -517,13 +642,16 @@ def lifecycle_learn() -> None:
|
|
|
517
642
|
def lifecycle_persist() -> None:
|
|
518
643
|
"""Persist phase: save session summary."""
|
|
519
644
|
import sys
|
|
645
|
+
|
|
520
646
|
body = json.loads(sys.stdin.read()) if not sys.stdin.isatty() else {}
|
|
521
647
|
session_id = body.get("session_id", "unknown")
|
|
522
648
|
|
|
523
649
|
from smartmemory_app.lifecycle import MemoryLifecycle
|
|
524
650
|
from smartmemory_app.lifecycle_config import LifecycleConfig
|
|
525
651
|
|
|
526
|
-
lc = MemoryLifecycle(
|
|
652
|
+
lc = MemoryLifecycle(
|
|
653
|
+
session_id, LifecycleConfig.from_config(_load_lifecycle_toml())
|
|
654
|
+
)
|
|
527
655
|
lc.persist()
|
|
528
656
|
|
|
529
657
|
|
|
@@ -531,12 +659,15 @@ def lifecycle_persist() -> None:
|
|
|
531
659
|
def lifecycle_status() -> None:
|
|
532
660
|
"""Show lifecycle configuration and session stats."""
|
|
533
661
|
from smartmemory_app.lifecycle_config import LifecycleConfig
|
|
662
|
+
|
|
534
663
|
cfg = LifecycleConfig.from_config(_load_lifecycle_toml())
|
|
535
664
|
click.echo(f"Lifecycle enabled: {cfg.enabled}")
|
|
536
665
|
click.echo(f"Recall strategy: {cfg.recall_strategy.value}")
|
|
537
666
|
click.echo(f"Orient budget: {cfg.orient_budget} tokens")
|
|
538
667
|
click.echo(f"Recall budget: {cfg.recall_budget} tokens")
|
|
539
|
-
click.echo(
|
|
668
|
+
click.echo(
|
|
669
|
+
f"Observe: {cfg.observe_tool_calls}, Distill: {cfg.distill_turns}, Learn: {cfg.learn_from_errors}"
|
|
670
|
+
)
|
|
540
671
|
|
|
541
672
|
|
|
542
673
|
def _load_lifecycle_toml() -> dict:
|
|
@@ -544,6 +675,7 @@ def _load_lifecycle_toml() -> dict:
|
|
|
544
675
|
try:
|
|
545
676
|
import tomllib
|
|
546
677
|
from smartmemory_app.config import config_path
|
|
678
|
+
|
|
547
679
|
path = config_path()
|
|
548
680
|
if path.exists():
|
|
549
681
|
with open(path, "rb") as f:
|
|
@@ -684,8 +816,10 @@ def config_cmd(key: str | None, value: str | None) -> None:
|
|
|
684
816
|
|
|
685
817
|
if key is None:
|
|
686
818
|
from smartmemory_app import __version__ as wrapper_version
|
|
819
|
+
|
|
687
820
|
try:
|
|
688
821
|
from importlib.metadata import version as _pkg_version
|
|
822
|
+
|
|
689
823
|
core_version = _pkg_version("smartmemory-core")
|
|
690
824
|
except Exception:
|
|
691
825
|
core_version = "?"
|
|
@@ -1183,7 +1317,9 @@ def reextract_cmd() -> None:
|
|
|
1183
1317
|
@cli.command("server", hidden=True)
|
|
1184
1318
|
def server_cmd() -> None:
|
|
1185
1319
|
"""Start the SmartMemory MCP server (called by MCP clients, not users)."""
|
|
1186
|
-
click.echo(
|
|
1320
|
+
click.echo(
|
|
1321
|
+
"MCP server is now 'smartmemory-mcp'. It's included in your installation."
|
|
1322
|
+
)
|
|
1187
1323
|
click.echo("Run: smartmemory-mcp")
|
|
1188
1324
|
|
|
1189
1325
|
|