superlocalmemory 3.0.25 → 3.0.27
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.
- package/README.md +28 -0
- package/docs/cli-reference.md +52 -1
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/src/superlocalmemory/cli/commands.py +384 -55
- package/src/superlocalmemory/cli/json_output.py +58 -0
- package/src/superlocalmemory/cli/main.py +17 -2
- package/src/superlocalmemory/server/ui.py +12 -10
package/README.md
CHANGED
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-green?style=for-the-badge" alt="MIT License"/></a>
|
|
17
17
|
<a href="#eu-ai-act-compliance"><img src="https://img.shields.io/badge/EU_AI_Act-Compliant-brightgreen?style=for-the-badge" alt="EU AI Act"/></a>
|
|
18
18
|
<a href="https://superlocalmemory.com"><img src="https://img.shields.io/badge/Web-superlocalmemory.com-ff6b35?style=for-the-badge" alt="Website"/></a>
|
|
19
|
+
<a href="#dual-interface-mcp--cli"><img src="https://img.shields.io/badge/MCP-Native-blue?style=for-the-badge" alt="MCP Native"/></a>
|
|
20
|
+
<a href="#dual-interface-mcp--cli"><img src="https://img.shields.io/badge/CLI-Agent--Native-green?style=for-the-badge" alt="CLI Agent-Native"/></a>
|
|
19
21
|
</p>
|
|
20
22
|
|
|
21
23
|
---
|
|
@@ -84,6 +86,32 @@ slm status
|
|
|
84
86
|
|
|
85
87
|
24 MCP tools available. Works with Claude Code, Cursor, Windsurf, VS Code Copilot, Continue, Cody, ChatGPT Desktop, Gemini CLI, JetBrains, Zed, and 17+ AI tools.
|
|
86
88
|
|
|
89
|
+
### Dual Interface: MCP + CLI
|
|
90
|
+
|
|
91
|
+
SLM works everywhere -- from IDEs to CI pipelines to Docker containers. The only AI memory system with both MCP and agent-native CLI.
|
|
92
|
+
|
|
93
|
+
| Need | Use | Example |
|
|
94
|
+
|------|-----|---------|
|
|
95
|
+
| IDE integration | MCP | Auto-configured for 17+ IDEs via `slm connect` |
|
|
96
|
+
| Shell scripts | CLI + `--json` | `slm recall "auth" --json \| jq '.data.results[0]'` |
|
|
97
|
+
| CI/CD pipelines | CLI + `--json` | `slm remember "deployed v2.1" --json` in GitHub Actions |
|
|
98
|
+
| Agent frameworks | CLI + `--json` | OpenClaw, Codex, Goose, nanobot |
|
|
99
|
+
| Human use | CLI | `slm recall "auth"` (readable text output) |
|
|
100
|
+
|
|
101
|
+
**Agent-native JSON output** on every command:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
# Human-readable (default)
|
|
105
|
+
slm recall "database schema"
|
|
106
|
+
# 1. [0.87] Database uses PostgreSQL 16 on port 5432...
|
|
107
|
+
|
|
108
|
+
# Agent-native JSON
|
|
109
|
+
slm recall "database schema" --json
|
|
110
|
+
# {"success": true, "command": "recall", "version": "3.0.22", "data": {"results": [...]}}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
All `--json` responses follow a consistent envelope with `success`, `command`, `version`, `data`, and `next_actions` for agent guidance.
|
|
114
|
+
|
|
87
115
|
---
|
|
88
116
|
|
|
89
117
|
## Three Operating Modes
|
package/docs/cli-reference.md
CHANGED
|
@@ -237,9 +237,60 @@ These options work with any command:
|
|
|
237
237
|
| `--help` | Show help for a command |
|
|
238
238
|
| `--version` | Show SLM version |
|
|
239
239
|
| `--verbose` | Show detailed output |
|
|
240
|
-
| `--json` | Output
|
|
240
|
+
| `--json` | Output structured JSON with agent-native envelope (for AI agents, scripts, CI/CD) |
|
|
241
241
|
| `--profile name` | Override the active profile for this command |
|
|
242
242
|
|
|
243
|
+
## Agent-Native JSON Output
|
|
244
|
+
|
|
245
|
+
All data-returning commands support `--json` for structured output. The envelope follows the 2026 agent-native CLI standard:
|
|
246
|
+
|
|
247
|
+
```json
|
|
248
|
+
{
|
|
249
|
+
"success": true,
|
|
250
|
+
"command": "recall",
|
|
251
|
+
"version": "3.0.22",
|
|
252
|
+
"data": {
|
|
253
|
+
"results": [
|
|
254
|
+
{"fact_id": "abc123", "score": 0.87, "content": "Database uses PostgreSQL 16"}
|
|
255
|
+
],
|
|
256
|
+
"count": 1,
|
|
257
|
+
"query_type": "semantic"
|
|
258
|
+
},
|
|
259
|
+
"next_actions": [
|
|
260
|
+
{"command": "slm list --json", "description": "List recent memories"}
|
|
261
|
+
]
|
|
262
|
+
}
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### Supported Commands
|
|
266
|
+
|
|
267
|
+
`recall`, `remember`, `list`, `status`, `health`, `trace`, `forget`, `delete`, `update`, `mode`, `profile`, `connect`
|
|
268
|
+
|
|
269
|
+
### Usage with jq
|
|
270
|
+
|
|
271
|
+
```bash
|
|
272
|
+
# Get first result content
|
|
273
|
+
slm recall "auth" --json | jq '.data.results[0].content'
|
|
274
|
+
|
|
275
|
+
# Get all fact IDs
|
|
276
|
+
slm list --json | jq '.data.results[].fact_id'
|
|
277
|
+
|
|
278
|
+
# Check current mode
|
|
279
|
+
slm status --json | jq '.data.mode'
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### In CI/CD (GitHub Actions)
|
|
283
|
+
|
|
284
|
+
```yaml
|
|
285
|
+
- name: Store deployment info
|
|
286
|
+
run: slm remember "Deployed ${{ github.sha }} to production" --json
|
|
287
|
+
|
|
288
|
+
- name: Check memory health
|
|
289
|
+
run: slm status --json | jq -e '.success'
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
---
|
|
293
|
+
|
|
243
294
|
## Examples
|
|
244
295
|
|
|
245
296
|
### Daily workflow
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "superlocalmemory",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.27",
|
|
4
4
|
"description": "Information-geometric agent memory with mathematical guarantees. 4-channel retrieval, Fisher-Rao similarity, zero-LLM mode, EU AI Act compliant. Works with Claude, Cursor, Windsurf, and 17+ AI tools.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai-memory",
|
package/pyproject.toml
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
"""CLI command implementations.
|
|
6
6
|
|
|
7
7
|
Each function handles one CLI command. Dispatch routes by name.
|
|
8
|
+
All data-returning commands support --json for agent-native output.
|
|
8
9
|
|
|
9
10
|
Part of Qualixar | Author: Varun Pratap Bhardwaj
|
|
10
11
|
"""
|
|
@@ -45,6 +46,9 @@ def dispatch(args: Namespace) -> None:
|
|
|
45
46
|
sys.exit(1)
|
|
46
47
|
|
|
47
48
|
|
|
49
|
+
# -- Setup & Config (no --json — interactive commands) ---------------------
|
|
50
|
+
|
|
51
|
+
|
|
48
52
|
def cmd_setup(_args: Namespace) -> None:
|
|
49
53
|
"""Run the interactive setup wizard."""
|
|
50
54
|
from superlocalmemory.cli.setup_wizard import run_wizard
|
|
@@ -59,6 +63,31 @@ def cmd_mode(args: Namespace) -> None:
|
|
|
59
63
|
|
|
60
64
|
config = SLMConfig.load()
|
|
61
65
|
|
|
66
|
+
if getattr(args, 'json', False):
|
|
67
|
+
from superlocalmemory.cli.json_output import json_print
|
|
68
|
+
if args.value:
|
|
69
|
+
old_mode = config.mode.value.upper()
|
|
70
|
+
updated = SLMConfig.for_mode(
|
|
71
|
+
Mode(args.value),
|
|
72
|
+
llm_provider=config.llm.provider,
|
|
73
|
+
llm_model=config.llm.model,
|
|
74
|
+
llm_api_key=config.llm.api_key,
|
|
75
|
+
llm_api_base=config.llm.api_base,
|
|
76
|
+
)
|
|
77
|
+
updated.save()
|
|
78
|
+
json_print("mode", data={
|
|
79
|
+
"previous_mode": old_mode, "current_mode": args.value.upper(),
|
|
80
|
+
}, next_actions=[
|
|
81
|
+
{"command": "slm status --json", "description": "Check system status"},
|
|
82
|
+
])
|
|
83
|
+
else:
|
|
84
|
+
json_print("mode", data={"current_mode": config.mode.value.upper()},
|
|
85
|
+
next_actions=[
|
|
86
|
+
{"command": "slm mode a --json", "description": "Switch to zero-cloud mode"},
|
|
87
|
+
{"command": "slm mode c --json", "description": "Switch to full-power mode"},
|
|
88
|
+
])
|
|
89
|
+
return
|
|
90
|
+
|
|
62
91
|
if args.value:
|
|
63
92
|
updated = SLMConfig.for_mode(
|
|
64
93
|
Mode(args.value),
|
|
@@ -94,6 +123,24 @@ def cmd_connect(args: Namespace) -> None:
|
|
|
94
123
|
from superlocalmemory.hooks.ide_connector import IDEConnector
|
|
95
124
|
|
|
96
125
|
connector = IDEConnector()
|
|
126
|
+
|
|
127
|
+
if getattr(args, 'json', False):
|
|
128
|
+
from superlocalmemory.cli.json_output import json_print
|
|
129
|
+
if getattr(args, "list", False):
|
|
130
|
+
json_print("connect", data={"ides": connector.get_status()},
|
|
131
|
+
next_actions=[
|
|
132
|
+
{"command": "slm connect --json", "description": "Auto-configure all IDEs"},
|
|
133
|
+
])
|
|
134
|
+
elif getattr(args, "ide", None):
|
|
135
|
+
success = connector.connect(args.ide)
|
|
136
|
+
json_print("connect", data={"ide": args.ide, "connected": success})
|
|
137
|
+
else:
|
|
138
|
+
json_print("connect", data={"results": connector.connect_all()},
|
|
139
|
+
next_actions=[
|
|
140
|
+
{"command": "slm status --json", "description": "Check system status"},
|
|
141
|
+
])
|
|
142
|
+
return
|
|
143
|
+
|
|
97
144
|
if getattr(args, "list", False):
|
|
98
145
|
status = connector.get_status()
|
|
99
146
|
for s in status:
|
|
@@ -116,20 +163,47 @@ def cmd_migrate(args: Namespace) -> None:
|
|
|
116
163
|
_migrate(args)
|
|
117
164
|
|
|
118
165
|
|
|
166
|
+
# -- Memory Operations (all support --json) --------------------------------
|
|
167
|
+
|
|
168
|
+
|
|
119
169
|
def cmd_list(args: Namespace) -> None:
|
|
120
170
|
"""List recent memories chronologically."""
|
|
121
171
|
from superlocalmemory.core.config import SLMConfig
|
|
122
172
|
from superlocalmemory.core.engine import MemoryEngine
|
|
123
173
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
174
|
+
use_json = getattr(args, 'json', False)
|
|
175
|
+
try:
|
|
176
|
+
config = SLMConfig.load()
|
|
177
|
+
engine = MemoryEngine(config)
|
|
178
|
+
engine.initialize()
|
|
179
|
+
|
|
180
|
+
limit = getattr(args, "limit", 20)
|
|
181
|
+
facts = engine._db.get_all_facts(engine.profile_id)
|
|
182
|
+
facts.sort(key=lambda f: f.created_at or "", reverse=True)
|
|
183
|
+
facts = facts[:limit]
|
|
184
|
+
except Exception as exc:
|
|
185
|
+
if use_json:
|
|
186
|
+
from superlocalmemory.cli.json_output import json_print
|
|
187
|
+
json_print("list", error={"code": "ENGINE_ERROR", "message": str(exc)})
|
|
188
|
+
sys.exit(1)
|
|
189
|
+
raise
|
|
190
|
+
|
|
191
|
+
if use_json:
|
|
192
|
+
from superlocalmemory.cli.json_output import json_print
|
|
193
|
+
items = []
|
|
194
|
+
for f in facts:
|
|
195
|
+
ftype_raw = getattr(f, "fact_type", "")
|
|
196
|
+
ftype = ftype_raw.value if hasattr(ftype_raw, "value") else str(ftype_raw)
|
|
197
|
+
items.append({
|
|
198
|
+
"fact_id": f.fact_id, "content": f.content,
|
|
199
|
+
"fact_type": ftype, "created_at": (f.created_at or "")[:19],
|
|
200
|
+
})
|
|
201
|
+
json_print("list", data={"results": items, "count": len(items)},
|
|
202
|
+
next_actions=[
|
|
203
|
+
{"command": "slm recall '<query>' --json", "description": "Search memories"},
|
|
204
|
+
{"command": "slm delete <fact_id> --json --yes", "description": "Delete a memory"},
|
|
205
|
+
])
|
|
206
|
+
return
|
|
133
207
|
|
|
134
208
|
if not facts:
|
|
135
209
|
print("No memories stored yet.")
|
|
@@ -149,12 +223,30 @@ def cmd_remember(args: Namespace) -> None:
|
|
|
149
223
|
from superlocalmemory.core.config import SLMConfig
|
|
150
224
|
from superlocalmemory.core.engine import MemoryEngine
|
|
151
225
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
226
|
+
use_json = getattr(args, 'json', False)
|
|
227
|
+
try:
|
|
228
|
+
config = SLMConfig.load()
|
|
229
|
+
engine = MemoryEngine(config)
|
|
230
|
+
engine.initialize()
|
|
231
|
+
|
|
232
|
+
metadata = {"tags": args.tags} if args.tags else {}
|
|
233
|
+
fact_ids = engine.store(args.content, metadata=metadata)
|
|
234
|
+
except Exception as exc:
|
|
235
|
+
if use_json:
|
|
236
|
+
from superlocalmemory.cli.json_output import json_print
|
|
237
|
+
json_print("remember", error={"code": "STORE_ERROR", "message": str(exc)})
|
|
238
|
+
sys.exit(1)
|
|
239
|
+
raise
|
|
240
|
+
|
|
241
|
+
if use_json:
|
|
242
|
+
from superlocalmemory.cli.json_output import json_print
|
|
243
|
+
json_print("remember", data={"fact_ids": fact_ids, "count": len(fact_ids)},
|
|
244
|
+
next_actions=[
|
|
245
|
+
{"command": "slm recall '<query>' --json", "description": "Search your memories"},
|
|
246
|
+
{"command": "slm list --json -n 5", "description": "See recent memories"},
|
|
247
|
+
])
|
|
248
|
+
return
|
|
155
249
|
|
|
156
|
-
metadata = {"tags": args.tags} if args.tags else {}
|
|
157
|
-
fact_ids = engine.store(args.content, metadata=metadata)
|
|
158
250
|
print(f"Stored {len(fact_ids)} facts.")
|
|
159
251
|
|
|
160
252
|
|
|
@@ -163,11 +255,39 @@ def cmd_recall(args: Namespace) -> None:
|
|
|
163
255
|
from superlocalmemory.core.config import SLMConfig
|
|
164
256
|
from superlocalmemory.core.engine import MemoryEngine
|
|
165
257
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
258
|
+
use_json = getattr(args, 'json', False)
|
|
259
|
+
try:
|
|
260
|
+
config = SLMConfig.load()
|
|
261
|
+
engine = MemoryEngine(config)
|
|
262
|
+
engine.initialize()
|
|
263
|
+
|
|
264
|
+
response = engine.recall(args.query, limit=args.limit)
|
|
265
|
+
except Exception as exc:
|
|
266
|
+
if use_json:
|
|
267
|
+
from superlocalmemory.cli.json_output import json_print
|
|
268
|
+
json_print("recall", error={"code": "RECALL_ERROR", "message": str(exc)})
|
|
269
|
+
sys.exit(1)
|
|
270
|
+
raise
|
|
271
|
+
|
|
272
|
+
if use_json:
|
|
273
|
+
from superlocalmemory.cli.json_output import json_print
|
|
274
|
+
items = []
|
|
275
|
+
for r in response.results:
|
|
276
|
+
item = {
|
|
277
|
+
"fact_id": r.fact.fact_id, "content": r.fact.content,
|
|
278
|
+
"score": round(r.score, 3),
|
|
279
|
+
}
|
|
280
|
+
if hasattr(r, "channel_scores") and r.channel_scores:
|
|
281
|
+
item["channel_scores"] = {k: round(v, 3) for k, v in r.channel_scores.items()}
|
|
282
|
+
items.append(item)
|
|
283
|
+
json_print("recall", data={
|
|
284
|
+
"results": items, "count": len(items),
|
|
285
|
+
"query_type": getattr(response, "query_type", "unknown"),
|
|
286
|
+
}, next_actions=[
|
|
287
|
+
{"command": "slm list --json", "description": "List recent memories"},
|
|
288
|
+
])
|
|
289
|
+
return
|
|
169
290
|
|
|
170
|
-
response = engine.recall(args.query, limit=args.limit)
|
|
171
291
|
if not response.results:
|
|
172
292
|
print("No memories found.")
|
|
173
293
|
return
|
|
@@ -180,18 +300,57 @@ def cmd_forget(args: Namespace) -> None:
|
|
|
180
300
|
from superlocalmemory.core.engine import MemoryEngine
|
|
181
301
|
from superlocalmemory.core.config import SLMConfig
|
|
182
302
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
303
|
+
use_json = getattr(args, 'json', False)
|
|
304
|
+
try:
|
|
305
|
+
config = SLMConfig.load()
|
|
306
|
+
engine = MemoryEngine(config)
|
|
307
|
+
engine.initialize()
|
|
308
|
+
facts = engine._db.get_all_facts(engine.profile_id)
|
|
309
|
+
query_lower = args.query.lower()
|
|
310
|
+
matches = [f for f in facts if query_lower in f.content.lower()]
|
|
311
|
+
except Exception as exc:
|
|
312
|
+
if use_json:
|
|
313
|
+
from superlocalmemory.cli.json_output import json_print
|
|
314
|
+
json_print("forget", error={"code": "ENGINE_ERROR", "message": str(exc)})
|
|
315
|
+
sys.exit(1)
|
|
316
|
+
raise
|
|
317
|
+
|
|
318
|
+
if use_json:
|
|
319
|
+
from superlocalmemory.cli.json_output import json_print
|
|
320
|
+
if not matches:
|
|
321
|
+
json_print("forget", data={"matched_count": 0, "deleted_count": 0, "matches": []})
|
|
322
|
+
return
|
|
323
|
+
match_items = [{"fact_id": f.fact_id, "content": f.content[:120]} for f in matches[:20]]
|
|
324
|
+
if getattr(args, 'yes', False):
|
|
325
|
+
for f in matches:
|
|
326
|
+
engine._db.delete_fact(f.fact_id)
|
|
327
|
+
json_print("forget", data={
|
|
328
|
+
"matched_count": len(matches), "deleted_count": len(matches),
|
|
329
|
+
"deleted": [f.fact_id for f in matches],
|
|
330
|
+
}, next_actions=[
|
|
331
|
+
{"command": "slm list --json", "description": "Verify remaining memories"},
|
|
332
|
+
])
|
|
333
|
+
else:
|
|
334
|
+
json_print("forget", data={
|
|
335
|
+
"matched_count": len(matches), "deleted_count": 0,
|
|
336
|
+
"matches": match_items,
|
|
337
|
+
"hint": "Add --yes to confirm deletion",
|
|
338
|
+
}, next_actions=[
|
|
339
|
+
{"command": f"slm forget '{args.query}' --json --yes", "description": "Confirm deletion"},
|
|
340
|
+
])
|
|
341
|
+
return
|
|
342
|
+
|
|
189
343
|
if not matches:
|
|
190
344
|
print(f"No memories matching '{args.query}'")
|
|
191
345
|
return
|
|
192
346
|
print(f"Found {len(matches)} matching memories:")
|
|
193
347
|
for f in matches[:10]:
|
|
194
348
|
print(f" - {f.fact_id[:8]}... {f.content[:80]}")
|
|
349
|
+
if getattr(args, 'yes', False):
|
|
350
|
+
for f in matches:
|
|
351
|
+
engine._db.delete_fact(f.fact_id)
|
|
352
|
+
print(f"Deleted {len(matches)} memories.")
|
|
353
|
+
return
|
|
195
354
|
confirm = input(f"Delete {len(matches)} memories? [y/N] ").strip().lower()
|
|
196
355
|
if confirm in ("y", "yes"):
|
|
197
356
|
for f in matches:
|
|
@@ -206,16 +365,47 @@ def cmd_delete(args: Namespace) -> None:
|
|
|
206
365
|
from superlocalmemory.core.config import SLMConfig
|
|
207
366
|
from superlocalmemory.core.engine import MemoryEngine
|
|
208
367
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
368
|
+
use_json = getattr(args, 'json', False)
|
|
369
|
+
try:
|
|
370
|
+
config = SLMConfig.load()
|
|
371
|
+
engine = MemoryEngine(config)
|
|
372
|
+
engine.initialize()
|
|
373
|
+
|
|
374
|
+
fact_id = args.fact_id.strip()
|
|
375
|
+
rows = engine._db.execute(
|
|
376
|
+
"SELECT content FROM atomic_facts WHERE fact_id = ? AND profile_id = ?",
|
|
377
|
+
(fact_id, engine.profile_id),
|
|
378
|
+
)
|
|
379
|
+
except Exception as exc:
|
|
380
|
+
if use_json:
|
|
381
|
+
from superlocalmemory.cli.json_output import json_print
|
|
382
|
+
json_print("delete", error={"code": "ENGINE_ERROR", "message": str(exc)})
|
|
383
|
+
sys.exit(1)
|
|
384
|
+
raise
|
|
385
|
+
|
|
386
|
+
if use_json:
|
|
387
|
+
from superlocalmemory.cli.json_output import json_print
|
|
388
|
+
if not rows:
|
|
389
|
+
json_print("delete", error={
|
|
390
|
+
"code": "NOT_FOUND", "message": f"Memory not found: {fact_id}",
|
|
391
|
+
})
|
|
392
|
+
sys.exit(1)
|
|
393
|
+
content = dict(rows[0]).get("content", "")
|
|
394
|
+
if getattr(args, "yes", False):
|
|
395
|
+
engine._db.delete_fact(fact_id)
|
|
396
|
+
json_print("delete", data={"deleted": fact_id, "content": content[:120]},
|
|
397
|
+
next_actions=[
|
|
398
|
+
{"command": "slm list --json", "description": "Verify remaining memories"},
|
|
399
|
+
])
|
|
400
|
+
else:
|
|
401
|
+
json_print("delete", data={
|
|
402
|
+
"fact_id": fact_id, "content": content[:120], "deleted": False,
|
|
403
|
+
"hint": "Add --yes to confirm deletion",
|
|
404
|
+
}, next_actions=[
|
|
405
|
+
{"command": f"slm delete {fact_id} --json --yes", "description": "Confirm deletion"},
|
|
406
|
+
])
|
|
407
|
+
return
|
|
212
408
|
|
|
213
|
-
fact_id = args.fact_id.strip()
|
|
214
|
-
# Look up the memory first so user can confirm
|
|
215
|
-
rows = engine._db.execute(
|
|
216
|
-
"SELECT content FROM atomic_facts WHERE fact_id = ? AND profile_id = ?",
|
|
217
|
-
(fact_id, engine.profile_id),
|
|
218
|
-
)
|
|
219
409
|
if not rows:
|
|
220
410
|
print(f"Memory not found: {fact_id}")
|
|
221
411
|
return
|
|
@@ -238,40 +428,91 @@ def cmd_update(args: Namespace) -> None:
|
|
|
238
428
|
from superlocalmemory.core.config import SLMConfig
|
|
239
429
|
from superlocalmemory.core.engine import MemoryEngine
|
|
240
430
|
|
|
241
|
-
|
|
242
|
-
engine = MemoryEngine(config)
|
|
243
|
-
engine.initialize()
|
|
244
|
-
|
|
431
|
+
use_json = getattr(args, 'json', False)
|
|
245
432
|
fact_id = args.fact_id.strip()
|
|
246
433
|
new_content = args.content.strip()
|
|
434
|
+
|
|
247
435
|
if not new_content:
|
|
436
|
+
if use_json:
|
|
437
|
+
from superlocalmemory.cli.json_output import json_print
|
|
438
|
+
json_print("update", error={"code": "INVALID_INPUT", "message": "content cannot be empty"})
|
|
439
|
+
sys.exit(1)
|
|
248
440
|
print("Error: content cannot be empty")
|
|
249
441
|
return
|
|
250
442
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
(
|
|
254
|
-
|
|
443
|
+
try:
|
|
444
|
+
config = SLMConfig.load()
|
|
445
|
+
engine = MemoryEngine(config)
|
|
446
|
+
engine.initialize()
|
|
447
|
+
|
|
448
|
+
rows = engine._db.execute(
|
|
449
|
+
"SELECT content FROM atomic_facts WHERE fact_id = ? AND profile_id = ?",
|
|
450
|
+
(fact_id, engine.profile_id),
|
|
451
|
+
)
|
|
452
|
+
except Exception as exc:
|
|
453
|
+
if use_json:
|
|
454
|
+
from superlocalmemory.cli.json_output import json_print
|
|
455
|
+
json_print("update", error={"code": "ENGINE_ERROR", "message": str(exc)})
|
|
456
|
+
sys.exit(1)
|
|
457
|
+
raise
|
|
458
|
+
|
|
255
459
|
if not rows:
|
|
460
|
+
if use_json:
|
|
461
|
+
from superlocalmemory.cli.json_output import json_print
|
|
462
|
+
json_print("update", error={
|
|
463
|
+
"code": "NOT_FOUND", "message": f"Memory not found: {fact_id}",
|
|
464
|
+
})
|
|
465
|
+
sys.exit(1)
|
|
256
466
|
print(f"Memory not found: {fact_id}")
|
|
257
467
|
return
|
|
258
468
|
|
|
259
469
|
old_content = dict(rows[0]).get("content", "")
|
|
260
|
-
print(f"Old: {old_content[:100]}")
|
|
261
|
-
print(f"New: {new_content[:100]}")
|
|
262
|
-
|
|
263
470
|
engine._db.execute(
|
|
264
471
|
"UPDATE atomic_facts SET content = ? WHERE fact_id = ?",
|
|
265
472
|
(new_content, fact_id),
|
|
266
473
|
)
|
|
474
|
+
|
|
475
|
+
if use_json:
|
|
476
|
+
from superlocalmemory.cli.json_output import json_print
|
|
477
|
+
json_print("update", data={
|
|
478
|
+
"fact_id": fact_id,
|
|
479
|
+
"old_content": old_content[:120],
|
|
480
|
+
"new_content": new_content[:120],
|
|
481
|
+
}, next_actions=[
|
|
482
|
+
{"command": "slm list --json", "description": "List recent memories"},
|
|
483
|
+
])
|
|
484
|
+
return
|
|
485
|
+
|
|
486
|
+
print(f"Old: {old_content[:100]}")
|
|
487
|
+
print(f"New: {new_content[:100]}")
|
|
267
488
|
print(f"Updated: {fact_id}")
|
|
268
489
|
|
|
269
490
|
|
|
270
|
-
|
|
491
|
+
# -- Diagnostics (all support --json) -------------------------------------
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
def cmd_status(args: Namespace) -> None:
|
|
271
495
|
"""Show system status."""
|
|
272
496
|
from superlocalmemory.core.config import SLMConfig
|
|
273
497
|
|
|
274
498
|
config = SLMConfig.load()
|
|
499
|
+
|
|
500
|
+
if getattr(args, 'json', False):
|
|
501
|
+
from superlocalmemory.cli.json_output import json_print
|
|
502
|
+
data = {
|
|
503
|
+
"mode": config.mode.value.upper(),
|
|
504
|
+
"provider": config.llm.provider or "none",
|
|
505
|
+
"base_dir": str(config.base_dir),
|
|
506
|
+
"db_path": str(config.db_path),
|
|
507
|
+
}
|
|
508
|
+
if config.db_path.exists():
|
|
509
|
+
data["db_size_mb"] = round(config.db_path.stat().st_size / 1024 / 1024, 2)
|
|
510
|
+
json_print("status", data=data, next_actions=[
|
|
511
|
+
{"command": "slm health --json", "description": "Check math layer health"},
|
|
512
|
+
{"command": "slm list --json", "description": "List recent memories"},
|
|
513
|
+
])
|
|
514
|
+
return
|
|
515
|
+
|
|
275
516
|
print("SuperLocalMemory V3")
|
|
276
517
|
print(f" Mode: {config.mode.value.upper()}")
|
|
277
518
|
print(f" Provider: {config.llm.provider or 'none'}")
|
|
@@ -282,17 +523,39 @@ def cmd_status(_args: Namespace) -> None:
|
|
|
282
523
|
print(f" DB size: {size_mb} MB")
|
|
283
524
|
|
|
284
525
|
|
|
285
|
-
def cmd_health(
|
|
526
|
+
def cmd_health(args: Namespace) -> None:
|
|
286
527
|
"""Show math layer health status."""
|
|
287
528
|
from superlocalmemory.core.engine import MemoryEngine
|
|
288
529
|
from superlocalmemory.core.config import SLMConfig
|
|
289
530
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
531
|
+
use_json = getattr(args, 'json', False)
|
|
532
|
+
try:
|
|
533
|
+
config = SLMConfig.load()
|
|
534
|
+
engine = MemoryEngine(config)
|
|
535
|
+
engine.initialize()
|
|
536
|
+
facts = engine._db.get_all_facts(engine.profile_id)
|
|
537
|
+
fisher_count = sum(1 for f in facts if f.fisher_mean is not None)
|
|
538
|
+
langevin_count = sum(1 for f in facts if f.langevin_position is not None)
|
|
539
|
+
except Exception as exc:
|
|
540
|
+
if use_json:
|
|
541
|
+
from superlocalmemory.cli.json_output import json_print
|
|
542
|
+
json_print("health", error={"code": "ENGINE_ERROR", "message": str(exc)})
|
|
543
|
+
sys.exit(1)
|
|
544
|
+
raise
|
|
545
|
+
|
|
546
|
+
if use_json:
|
|
547
|
+
from superlocalmemory.cli.json_output import json_print
|
|
548
|
+
json_print("health", data={
|
|
549
|
+
"total_facts": len(facts),
|
|
550
|
+
"similarity_indexed": fisher_count,
|
|
551
|
+
"lifecycle_positioned": langevin_count,
|
|
552
|
+
"mode": config.mode.value.upper(),
|
|
553
|
+
}, next_actions=[
|
|
554
|
+
{"command": "slm status --json", "description": "Check system status"},
|
|
555
|
+
{"command": "slm recall '<query>' --json", "description": "Test retrieval"},
|
|
556
|
+
])
|
|
557
|
+
return
|
|
558
|
+
|
|
296
559
|
print("Math Layer Health:")
|
|
297
560
|
print(f" Total facts: {len(facts)}")
|
|
298
561
|
print(f" Fisher-Rao indexed: {fisher_count}/{len(facts)}")
|
|
@@ -305,9 +568,41 @@ def cmd_trace(args: Namespace) -> None:
|
|
|
305
568
|
from superlocalmemory.core.engine import MemoryEngine
|
|
306
569
|
from superlocalmemory.core.config import SLMConfig
|
|
307
570
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
571
|
+
use_json = getattr(args, 'json', False)
|
|
572
|
+
try:
|
|
573
|
+
config = SLMConfig.load()
|
|
574
|
+
engine = MemoryEngine(config)
|
|
575
|
+
response = engine.recall(args.query, limit=5)
|
|
576
|
+
except Exception as exc:
|
|
577
|
+
if use_json:
|
|
578
|
+
from superlocalmemory.cli.json_output import json_print
|
|
579
|
+
json_print("trace", error={"code": "ENGINE_ERROR", "message": str(exc)})
|
|
580
|
+
sys.exit(1)
|
|
581
|
+
raise
|
|
582
|
+
|
|
583
|
+
if use_json:
|
|
584
|
+
from superlocalmemory.cli.json_output import json_print
|
|
585
|
+
items = []
|
|
586
|
+
for r in response.results:
|
|
587
|
+
item = {
|
|
588
|
+
"fact_id": r.fact.fact_id, "content": r.fact.content[:200],
|
|
589
|
+
"score": round(r.score, 3),
|
|
590
|
+
}
|
|
591
|
+
if hasattr(r, "channel_scores") and r.channel_scores:
|
|
592
|
+
item["channel_scores"] = {
|
|
593
|
+
k: round(v, 3) for k, v in r.channel_scores.items()
|
|
594
|
+
}
|
|
595
|
+
items.append(item)
|
|
596
|
+
json_print("trace", data={
|
|
597
|
+
"query": args.query,
|
|
598
|
+
"query_type": getattr(response, "query_type", "unknown"),
|
|
599
|
+
"retrieval_time_ms": round(getattr(response, "retrieval_time_ms", 0), 1),
|
|
600
|
+
"results": items, "count": len(items),
|
|
601
|
+
}, next_actions=[
|
|
602
|
+
{"command": "slm recall '<query>' --json", "description": "Standard recall"},
|
|
603
|
+
])
|
|
604
|
+
return
|
|
605
|
+
|
|
311
606
|
print(f"Query: {args.query}")
|
|
312
607
|
print(f"Type: {response.query_type} | Time: {response.retrieval_time_ms:.0f}ms")
|
|
313
608
|
print(f"Results: {len(response.results)}")
|
|
@@ -318,6 +613,9 @@ def cmd_trace(args: Namespace) -> None:
|
|
|
318
613
|
print(f" {ch}: {sc:.3f}")
|
|
319
614
|
|
|
320
615
|
|
|
616
|
+
# -- Services (no --json — these start long-running processes) -------------
|
|
617
|
+
|
|
618
|
+
|
|
321
619
|
def cmd_mcp(_args: Namespace) -> None:
|
|
322
620
|
"""Start the V3 MCP server (stdio transport for IDE integration)."""
|
|
323
621
|
from superlocalmemory.mcp.server import server
|
|
@@ -397,6 +695,9 @@ def cmd_dashboard(args: Namespace) -> None:
|
|
|
397
695
|
uvicorn.run(app, host="127.0.0.1", port=ui_port, log_level="info")
|
|
398
696
|
|
|
399
697
|
|
|
698
|
+
# -- Profiles (supports --json) -------------------------------------------
|
|
699
|
+
|
|
700
|
+
|
|
400
701
|
def cmd_profile(args: Namespace) -> None:
|
|
401
702
|
"""Profile management (list, switch, create)."""
|
|
402
703
|
from superlocalmemory.core.config import SLMConfig
|
|
@@ -407,6 +708,34 @@ def cmd_profile(args: Namespace) -> None:
|
|
|
407
708
|
db = DatabaseManager(config.db_path)
|
|
408
709
|
db.initialize(schema)
|
|
409
710
|
|
|
711
|
+
if getattr(args, 'json', False):
|
|
712
|
+
from superlocalmemory.cli.json_output import json_print
|
|
713
|
+
if args.action == "list":
|
|
714
|
+
rows = db.execute("SELECT profile_id, name FROM profiles")
|
|
715
|
+
profiles = [
|
|
716
|
+
{"profile_id": dict(r)["profile_id"], "name": dict(r).get("name", "")}
|
|
717
|
+
for r in rows
|
|
718
|
+
]
|
|
719
|
+
json_print("profile", data={"profiles": profiles, "count": len(profiles)},
|
|
720
|
+
next_actions=[
|
|
721
|
+
{"command": "slm profile switch <name> --json", "description": "Switch profile"},
|
|
722
|
+
])
|
|
723
|
+
elif args.action == "switch":
|
|
724
|
+
config.active_profile = args.name
|
|
725
|
+
config.save()
|
|
726
|
+
json_print("profile", data={"action": "switched", "profile": args.name})
|
|
727
|
+
elif args.action == "create":
|
|
728
|
+
db.execute(
|
|
729
|
+
"INSERT OR IGNORE INTO profiles (profile_id, name) VALUES (?, ?)",
|
|
730
|
+
(args.name, args.name),
|
|
731
|
+
)
|
|
732
|
+
json_print("profile", data={"action": "created", "profile": args.name},
|
|
733
|
+
next_actions=[
|
|
734
|
+
{"command": f"slm profile switch {args.name} --json",
|
|
735
|
+
"description": "Switch to new profile"},
|
|
736
|
+
])
|
|
737
|
+
return
|
|
738
|
+
|
|
410
739
|
if args.action == "list":
|
|
411
740
|
rows = db.execute("SELECT profile_id, name FROM profiles")
|
|
412
741
|
print("Profiles:")
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
|
|
2
|
+
# Licensed under the MIT License - see LICENSE file
|
|
3
|
+
# Part of SuperLocalMemory V3 | https://qualixar.com | https://varunpratap.com
|
|
4
|
+
|
|
5
|
+
"""Shared JSON envelope for agent-native CLI output.
|
|
6
|
+
|
|
7
|
+
Follows the 2026 agent-native CLI standard:
|
|
8
|
+
- Consistent envelope: success, command, version, data/error
|
|
9
|
+
- HATEOAS next_actions for agent guidance
|
|
10
|
+
- Metadata for execution context
|
|
11
|
+
|
|
12
|
+
Part of Qualixar | Author: Varun Pratap Bhardwaj
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import json
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _get_version() -> str:
|
|
21
|
+
"""Get installed package version."""
|
|
22
|
+
try:
|
|
23
|
+
from importlib.metadata import version
|
|
24
|
+
return version("superlocalmemory")
|
|
25
|
+
except Exception:
|
|
26
|
+
return "unknown"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def json_print(
|
|
30
|
+
command: str,
|
|
31
|
+
*,
|
|
32
|
+
data: dict | None = None,
|
|
33
|
+
error: dict | None = None,
|
|
34
|
+
next_actions: list[dict] | None = None,
|
|
35
|
+
metadata: dict | None = None,
|
|
36
|
+
) -> None:
|
|
37
|
+
"""Print a standard JSON envelope to stdout.
|
|
38
|
+
|
|
39
|
+
Success envelope:
|
|
40
|
+
{"success": true, "command": "...", "version": "...", "data": {...}}
|
|
41
|
+
|
|
42
|
+
Error envelope:
|
|
43
|
+
{"success": false, "command": "...", "version": "...", "error": {...}}
|
|
44
|
+
"""
|
|
45
|
+
envelope: dict = {
|
|
46
|
+
"success": error is None,
|
|
47
|
+
"command": command,
|
|
48
|
+
"version": _get_version(),
|
|
49
|
+
}
|
|
50
|
+
if error is not None:
|
|
51
|
+
envelope["error"] = error
|
|
52
|
+
else:
|
|
53
|
+
envelope["data"] = data if data is not None else {}
|
|
54
|
+
if metadata:
|
|
55
|
+
envelope["metadata"] = metadata
|
|
56
|
+
if next_actions:
|
|
57
|
+
envelope["next_actions"] = next_actions
|
|
58
|
+
print(json.dumps(envelope, indent=2, default=str))
|
|
@@ -59,6 +59,7 @@ examples:
|
|
|
59
59
|
slm trace "auth flow" Recall with per-channel score breakdown
|
|
60
60
|
slm health Check math layer status
|
|
61
61
|
slm dashboard --port 9000 Dashboard on custom port
|
|
62
|
+
slm recall "query" --json Agent-native JSON output (for scripts, CI/CD)
|
|
62
63
|
|
|
63
64
|
documentation:
|
|
64
65
|
Website: https://superlocalmemory.com
|
|
@@ -93,6 +94,7 @@ def main() -> None:
|
|
|
93
94
|
mode_p.add_argument(
|
|
94
95
|
"value", nargs="?", choices=["a", "b", "c"], help="Mode to set",
|
|
95
96
|
)
|
|
97
|
+
mode_p.add_argument("--json", action="store_true", help="Output structured JSON (agent-native)")
|
|
96
98
|
|
|
97
99
|
provider_p = sub.add_parser("provider", help="Get or set LLM provider for Mode B/C")
|
|
98
100
|
provider_p.add_argument(
|
|
@@ -104,6 +106,7 @@ def main() -> None:
|
|
|
104
106
|
connect_p.add_argument(
|
|
105
107
|
"--list", action="store_true", help="List all supported IDEs",
|
|
106
108
|
)
|
|
109
|
+
connect_p.add_argument("--json", action="store_true", help="Output structured JSON (agent-native)")
|
|
107
110
|
|
|
108
111
|
migrate_p = sub.add_parser("migrate", help="Migrate data from V2 to V3 schema")
|
|
109
112
|
migrate_p.add_argument(
|
|
@@ -114,33 +117,44 @@ def main() -> None:
|
|
|
114
117
|
remember_p = sub.add_parser("remember", help="Store a memory (extracts facts, builds graph)")
|
|
115
118
|
remember_p.add_argument("content", help="Content to remember")
|
|
116
119
|
remember_p.add_argument("--tags", default="", help="Comma-separated tags")
|
|
120
|
+
remember_p.add_argument("--json", action="store_true", help="Output structured JSON (agent-native)")
|
|
117
121
|
|
|
118
122
|
recall_p = sub.add_parser("recall", help="Semantic search with 4-channel retrieval")
|
|
119
123
|
recall_p.add_argument("query", help="Search query")
|
|
120
124
|
recall_p.add_argument("--limit", type=int, default=10, help="Max results (default 10)")
|
|
125
|
+
recall_p.add_argument("--json", action="store_true", help="Output structured JSON (agent-native)")
|
|
121
126
|
|
|
122
127
|
forget_p = sub.add_parser("forget", help="Delete memories matching a query (fuzzy)")
|
|
123
128
|
forget_p.add_argument("query", help="Query to match for deletion")
|
|
129
|
+
forget_p.add_argument("--yes", "-y", action="store_true", help="Skip confirmation prompt")
|
|
130
|
+
forget_p.add_argument("--json", action="store_true", help="Output structured JSON (agent-native)")
|
|
124
131
|
|
|
125
132
|
delete_p = sub.add_parser("delete", help="Delete a specific memory by ID (precise)")
|
|
126
133
|
delete_p.add_argument("fact_id", help="Exact fact ID to delete")
|
|
127
134
|
delete_p.add_argument("--yes", "-y", action="store_true", help="Skip confirmation")
|
|
135
|
+
delete_p.add_argument("--json", action="store_true", help="Output structured JSON (agent-native)")
|
|
128
136
|
|
|
129
137
|
update_p = sub.add_parser("update", help="Edit the content of a specific memory by ID")
|
|
130
138
|
update_p.add_argument("fact_id", help="Exact fact ID to update")
|
|
131
139
|
update_p.add_argument("content", help="New content for the memory")
|
|
140
|
+
update_p.add_argument("--json", action="store_true", help="Output structured JSON (agent-native)")
|
|
132
141
|
|
|
133
142
|
list_p = sub.add_parser("list", help="List recent memories chronologically (shows IDs for delete/update)")
|
|
134
143
|
list_p.add_argument(
|
|
135
144
|
"--limit", "-n", type=int, default=20, help="Number of entries (default 20)",
|
|
136
145
|
)
|
|
146
|
+
list_p.add_argument("--json", action="store_true", help="Output structured JSON (agent-native)")
|
|
137
147
|
|
|
138
148
|
# -- Diagnostics ---------------------------------------------------
|
|
139
|
-
sub.add_parser("status", help="System status (mode, profile, DB size)")
|
|
140
|
-
|
|
149
|
+
status_p = sub.add_parser("status", help="System status (mode, profile, DB size)")
|
|
150
|
+
status_p.add_argument("--json", action="store_true", help="Output structured JSON (agent-native)")
|
|
151
|
+
|
|
152
|
+
health_p = sub.add_parser("health", help="Math layer health (Fisher-Rao, Sheaf, Langevin)")
|
|
153
|
+
health_p.add_argument("--json", action="store_true", help="Output structured JSON (agent-native)")
|
|
141
154
|
|
|
142
155
|
trace_p = sub.add_parser("trace", help="Recall with per-channel score breakdown")
|
|
143
156
|
trace_p.add_argument("query", help="Search query")
|
|
157
|
+
trace_p.add_argument("--json", action="store_true", help="Output structured JSON (agent-native)")
|
|
144
158
|
|
|
145
159
|
# -- Services ------------------------------------------------------
|
|
146
160
|
sub.add_parser("mcp", help="Start MCP server (stdio transport for IDE integration)")
|
|
@@ -157,6 +171,7 @@ def main() -> None:
|
|
|
157
171
|
"action", choices=["list", "switch", "create"], help="Action",
|
|
158
172
|
)
|
|
159
173
|
profile_p.add_argument("name", nargs="?", help="Profile name")
|
|
174
|
+
profile_p.add_argument("--json", action="store_true", help="Output structured JSON (agent-native)")
|
|
160
175
|
|
|
161
176
|
args = parser.parse_args()
|
|
162
177
|
|
|
@@ -26,24 +26,20 @@ logger = logging.getLogger(__name__)
|
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
def _get_version() -> str:
|
|
29
|
-
"""Read version from package
|
|
29
|
+
"""Read version from package.json (npm), pyproject.toml, or metadata."""
|
|
30
30
|
import json as _json
|
|
31
|
-
# 1. Try importlib.metadata (works when pip-installed)
|
|
32
|
-
try:
|
|
33
|
-
from importlib.metadata import version
|
|
34
|
-
return version("superlocalmemory")
|
|
35
|
-
except Exception:
|
|
36
|
-
pass
|
|
37
|
-
# 2. Try package.json (works when npm-installed)
|
|
38
31
|
pkg_root = Path(__file__).resolve().parent.parent.parent.parent
|
|
32
|
+
# 1. Try package.json FIRST (source of truth for npm installs)
|
|
39
33
|
try:
|
|
40
34
|
pkg_json = pkg_root / "package.json"
|
|
41
35
|
if pkg_json.exists():
|
|
42
36
|
with open(pkg_json) as f:
|
|
43
|
-
|
|
37
|
+
v = _json.load(f).get("version", "")
|
|
38
|
+
if v:
|
|
39
|
+
return v
|
|
44
40
|
except Exception:
|
|
45
41
|
pass
|
|
46
|
-
#
|
|
42
|
+
# 2. Try pyproject.toml (source of truth for pip installs)
|
|
47
43
|
try:
|
|
48
44
|
import tomllib
|
|
49
45
|
toml_path = pkg_root / "pyproject.toml"
|
|
@@ -52,6 +48,12 @@ def _get_version() -> str:
|
|
|
52
48
|
return tomllib.load(f)["project"]["version"]
|
|
53
49
|
except Exception:
|
|
54
50
|
pass
|
|
51
|
+
# 3. Fallback to importlib.metadata
|
|
52
|
+
try:
|
|
53
|
+
from importlib.metadata import version
|
|
54
|
+
return version("superlocalmemory")
|
|
55
|
+
except Exception:
|
|
56
|
+
pass
|
|
55
57
|
return "unknown"
|
|
56
58
|
|
|
57
59
|
|