sibyl-memory-cli 0.3.22__tar.gz → 0.4.0__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.
- {sibyl_memory_cli-0.3.22/src/sibyl_memory_cli.egg-info → sibyl_memory_cli-0.4.0}/PKG-INFO +4 -4
- {sibyl_memory_cli-0.3.22 → sibyl_memory_cli-0.4.0}/pyproject.toml +4 -4
- {sibyl_memory_cli-0.3.22 → sibyl_memory_cli-0.4.0}/src/sibyl_memory_cli/cli.py +17 -1
- {sibyl_memory_cli-0.3.22 → sibyl_memory_cli-0.4.0/src/sibyl_memory_cli.egg-info}/PKG-INFO +4 -4
- {sibyl_memory_cli-0.3.22 → sibyl_memory_cli-0.4.0}/src/sibyl_memory_cli.egg-info/SOURCES.txt +1 -0
- sibyl_memory_cli-0.4.0/src/sibyl_memory_cli.egg-info/requires.txt +9 -0
- sibyl_memory_cli-0.4.0/tests/test_verdict_contract_2026_08_31.py +126 -0
- sibyl_memory_cli-0.3.22/src/sibyl_memory_cli.egg-info/requires.txt +0 -9
- {sibyl_memory_cli-0.3.22 → sibyl_memory_cli-0.4.0}/LICENSE +0 -0
- {sibyl_memory_cli-0.3.22 → sibyl_memory_cli-0.4.0}/README.md +0 -0
- {sibyl_memory_cli-0.3.22 → sibyl_memory_cli-0.4.0}/setup.cfg +0 -0
- {sibyl_memory_cli-0.3.22 → sibyl_memory_cli-0.4.0}/src/sibyl_memory_cli/__init__.py +0 -0
- {sibyl_memory_cli-0.3.22 → sibyl_memory_cli-0.4.0}/src/sibyl_memory_cli/_aesthetic.py +0 -0
- {sibyl_memory_cli-0.3.22 → sibyl_memory_cli-0.4.0}/src/sibyl_memory_cli/_banner.py +0 -0
- {sibyl_memory_cli-0.3.22 → sibyl_memory_cli-0.4.0}/src/sibyl_memory_cli/migrate.py +0 -0
- {sibyl_memory_cli-0.3.22 → sibyl_memory_cli-0.4.0}/src/sibyl_memory_cli/setup.py +0 -0
- {sibyl_memory_cli-0.3.22 → sibyl_memory_cli-0.4.0}/src/sibyl_memory_cli.egg-info/dependency_links.txt +0 -0
- {sibyl_memory_cli-0.3.22 → sibyl_memory_cli-0.4.0}/src/sibyl_memory_cli.egg-info/entry_points.txt +0 -0
- {sibyl_memory_cli-0.3.22 → sibyl_memory_cli-0.4.0}/src/sibyl_memory_cli.egg-info/top_level.txt +0 -0
- {sibyl_memory_cli-0.3.22 → sibyl_memory_cli-0.4.0}/tests/test_hermes_install_args.py +0 -0
- {sibyl_memory_cli-0.3.22 → sibyl_memory_cli-0.4.0}/tests/test_init_perms_guard.py +0 -0
- {sibyl_memory_cli-0.3.22 → sibyl_memory_cli-0.4.0}/tests/test_memory_cmd_2026_06_16.py +0 -0
- {sibyl_memory_cli-0.3.22 → sibyl_memory_cli-0.4.0}/tests/test_memory_tenant_2026_08_12.py +0 -0
- {sibyl_memory_cli-0.3.22 → sibyl_memory_cli-0.4.0}/tests/test_migrate.py +0 -0
- {sibyl_memory_cli-0.3.22 → sibyl_memory_cli-0.4.0}/tests/test_migrate_adversarial.py +0 -0
- {sibyl_memory_cli-0.3.22 → sibyl_memory_cli-0.4.0}/tests/test_migrate_force_2026_06_05.py +0 -0
- {sibyl_memory_cli-0.3.22 → sibyl_memory_cli-0.4.0}/tests/test_orchestrator.py +0 -0
- {sibyl_memory_cli-0.3.22 → sibyl_memory_cli-0.4.0}/tests/test_prelaunch_fixes_2026_06_25.py +0 -0
- {sibyl_memory_cli-0.3.22 → sibyl_memory_cli-0.4.0}/tests/test_quality_fixes_2026_06_30.py +0 -0
- {sibyl_memory_cli-0.3.22 → sibyl_memory_cli-0.4.0}/tests/test_setup.py +0 -0
- {sibyl_memory_cli-0.3.22 → sibyl_memory_cli-0.4.0}/tests/test_status_stores_2026_06_11.py +0 -0
- {sibyl_memory_cli-0.3.22 → sibyl_memory_cli-0.4.0}/tests/test_superpatch_2026_07_05.py +0 -0
- {sibyl_memory_cli-0.3.22 → sibyl_memory_cli-0.4.0}/tests/test_wiring_fix.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sibyl-memory-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Command-line interface for the Sibyl Memory Plugin. `sibyl init` activates, `sibyl upgrade` runs the staker / subscription flow, `sibyl status` shows current tier and DB stats, `sibyl whoami` gives a one-line account summary, `sibyl devices` lists active devices and supports per-device revoke.
|
|
5
5
|
Author-email: "SIBYL, Sibyl Labs LLC" <sibyl@sibyllabs.org>
|
|
6
6
|
License: MIT
|
|
@@ -20,11 +20,11 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
|
20
20
|
Requires-Python: >=3.10
|
|
21
21
|
Description-Content-Type: text/markdown
|
|
22
22
|
License-File: LICENSE
|
|
23
|
-
Requires-Dist: sibyl-memory-client>=0.
|
|
24
|
-
Requires-Dist: sibyl-memory-hermes>=0.
|
|
23
|
+
Requires-Dist: sibyl-memory-client>=0.8.0
|
|
24
|
+
Requires-Dist: sibyl-memory-hermes>=0.4.0
|
|
25
25
|
Requires-Dist: pyyaml<7,>=6.0
|
|
26
26
|
Provides-Extra: mcp
|
|
27
|
-
Requires-Dist: sibyl-memory-mcp>=0.
|
|
27
|
+
Requires-Dist: sibyl-memory-mcp>=0.2.0; extra == "mcp"
|
|
28
28
|
Provides-Extra: dev
|
|
29
29
|
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
30
30
|
Dynamic: license-file
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "sibyl-memory-cli"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.4.0"
|
|
8
8
|
description = "Command-line interface for the Sibyl Memory Plugin. `sibyl init` activates, `sibyl upgrade` runs the staker / subscription flow, `sibyl status` shows current tier and DB stats, `sibyl whoami` gives a one-line account summary, `sibyl devices` lists active devices and supports per-device revoke."
|
|
9
9
|
authors = [{ name = "SIBYL, Sibyl Labs LLC", email = "sibyl@sibyllabs.org" }]
|
|
10
10
|
license = { text = "MIT" }
|
|
@@ -23,14 +23,14 @@ classifiers = [
|
|
|
23
23
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
24
24
|
]
|
|
25
25
|
dependencies = [
|
|
26
|
-
"sibyl-memory-client>=0.
|
|
27
|
-
"sibyl-memory-hermes>=0.
|
|
26
|
+
"sibyl-memory-client>=0.8.0",
|
|
27
|
+
"sibyl-memory-hermes>=0.4.0",
|
|
28
28
|
"pyyaml>=6.0,<7",
|
|
29
29
|
]
|
|
30
30
|
|
|
31
31
|
[project.optional-dependencies]
|
|
32
32
|
mcp = [
|
|
33
|
-
"sibyl-memory-mcp>=0.
|
|
33
|
+
"sibyl-memory-mcp>=0.2.0",
|
|
34
34
|
]
|
|
35
35
|
dev = [
|
|
36
36
|
"pytest>=7.0",
|
|
@@ -1549,6 +1549,10 @@ def cmd_memory(args: argparse.Namespace) -> int:
|
|
|
1549
1549
|
you can inspect any split-brain store that `sibyl status` surfaces.
|
|
1550
1550
|
"""
|
|
1551
1551
|
from sibyl_memory_client import DEFAULT_TENANT, MemoryClient
|
|
1552
|
+
# THE canonical cause vocabulary (stage 3, 2026-08-31). Imported from the
|
|
1553
|
+
# client, never re-declared here — the CLI prints causes, it does not own
|
|
1554
|
+
# them.
|
|
1555
|
+
from sibyl_memory_client.verdicts import refine_zero
|
|
1552
1556
|
|
|
1553
1557
|
db_path = Path(args.db).expanduser()
|
|
1554
1558
|
print()
|
|
@@ -1582,9 +1586,21 @@ def cmd_memory(args: argparse.Namespace) -> int:
|
|
|
1582
1586
|
print(a.kv(f"{cat}/{name}", r.get("status") or "-"))
|
|
1583
1587
|
return 0
|
|
1584
1588
|
if op == "search":
|
|
1585
|
-
|
|
1589
|
+
# THE VERDICT CONTRACT (stage 3, 2026-08-31). This used to print
|
|
1590
|
+
# "(no matches)" and stop — the terminal-shaped version of the defect
|
|
1591
|
+
# this stage closes: a zero that cannot explain itself. The SDK now
|
|
1592
|
+
# returns a cause on every call, and `refine_zero` pays one COUNT on the
|
|
1593
|
+
# zero path so a brand-new user is told their store is EMPTY rather than
|
|
1594
|
+
# left to conclude their query was wrong.
|
|
1595
|
+
hits = refine_zero(client, client.search(args.query, limit=args.limit))
|
|
1586
1596
|
if not hits:
|
|
1587
1597
|
print(a.dim(f"(no matches for {args.query!r})"))
|
|
1598
|
+
verdict = getattr(hits, "verdict", None)
|
|
1599
|
+
if verdict is not None:
|
|
1600
|
+
# Plain language, generated from the enum and never from stored
|
|
1601
|
+
# text, so it is always safe to print to a terminal.
|
|
1602
|
+
print(a.warn_line(verdict.explain()))
|
|
1603
|
+
print(a.dim(f"cause: {verdict.code.value}"))
|
|
1588
1604
|
return 0
|
|
1589
1605
|
print(a.eyebrow(f"matches ({len(hits)})"))
|
|
1590
1606
|
for h in hits:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sibyl-memory-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Command-line interface for the Sibyl Memory Plugin. `sibyl init` activates, `sibyl upgrade` runs the staker / subscription flow, `sibyl status` shows current tier and DB stats, `sibyl whoami` gives a one-line account summary, `sibyl devices` lists active devices and supports per-device revoke.
|
|
5
5
|
Author-email: "SIBYL, Sibyl Labs LLC" <sibyl@sibyllabs.org>
|
|
6
6
|
License: MIT
|
|
@@ -20,11 +20,11 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
|
20
20
|
Requires-Python: >=3.10
|
|
21
21
|
Description-Content-Type: text/markdown
|
|
22
22
|
License-File: LICENSE
|
|
23
|
-
Requires-Dist: sibyl-memory-client>=0.
|
|
24
|
-
Requires-Dist: sibyl-memory-hermes>=0.
|
|
23
|
+
Requires-Dist: sibyl-memory-client>=0.8.0
|
|
24
|
+
Requires-Dist: sibyl-memory-hermes>=0.4.0
|
|
25
25
|
Requires-Dist: pyyaml<7,>=6.0
|
|
26
26
|
Provides-Extra: mcp
|
|
27
|
-
Requires-Dist: sibyl-memory-mcp>=0.
|
|
27
|
+
Requires-Dist: sibyl-memory-mcp>=0.2.0; extra == "mcp"
|
|
28
28
|
Provides-Extra: dev
|
|
29
29
|
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
30
30
|
Dynamic: license-file
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"""The verdict contract in the terminal (stage 3, 2026-08-31).
|
|
2
|
+
|
|
3
|
+
`sibyl memory search` used to print "(no matches for 'x')" and stop. That is the
|
|
4
|
+
terminal-shaped version of the defect this stage closes: a zero that cannot
|
|
5
|
+
explain itself, and in the CLI's case the person reading it is a new user
|
|
6
|
+
deciding whether the product works. "No matches" against an EMPTY store is the
|
|
7
|
+
single most misleading thing this command could say, and it was what it said.
|
|
8
|
+
|
|
9
|
+
These tests pin that every zero prints a cause in plain language, that the cause
|
|
10
|
+
is the canonical one from `sibyl_memory_client.verdicts` rather than a
|
|
11
|
+
CLI-local string, and that nothing about a successful search changed.
|
|
12
|
+
"""
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import ast
|
|
16
|
+
import inspect
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
from sibyl_memory_client import MemoryClient
|
|
20
|
+
from sibyl_memory_client.verdicts import GateCause, VerdictCode
|
|
21
|
+
from sibyl_memory_cli import cli
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _store(tmp_path: Path) -> Path:
|
|
25
|
+
d = tmp_path / "memory.db"
|
|
26
|
+
c = MemoryClient.local(path=d)
|
|
27
|
+
c.set_entity("partner", "Blocktronics",
|
|
28
|
+
{"stage": "active", "note": "token forensics suite"})
|
|
29
|
+
c.set_entity("partner", "Reppo", {"stage": "negotiation"})
|
|
30
|
+
return d
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _empty_store(tmp_path: Path) -> Path:
|
|
34
|
+
d = tmp_path / "empty.db"
|
|
35
|
+
MemoryClient.local(path=d)
|
|
36
|
+
return d
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_a_zero_result_prints_a_cause_not_just_no_matches(tmp_path, capsys):
|
|
40
|
+
d = _store(tmp_path)
|
|
41
|
+
rc = cli.main(["--db", str(d), "memory", "search", "qwzjvxzzyplm"])
|
|
42
|
+
out = capsys.readouterr().out
|
|
43
|
+
assert rc == 0
|
|
44
|
+
assert "(no matches" in out
|
|
45
|
+
assert f"cause: {VerdictCode.NO_MATCH.value}" in out
|
|
46
|
+
assert "genuine miss" in out
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_an_empty_store_says_so_instead_of_blaming_the_query(tmp_path, capsys):
|
|
50
|
+
"""The one that matters most to a new user: this is a fixable state, and
|
|
51
|
+
'no matches' hid that behind what looks like a failed search."""
|
|
52
|
+
d = _empty_store(tmp_path)
|
|
53
|
+
rc = cli.main(["--db", str(d), "memory", "search", "anything at all"])
|
|
54
|
+
out = capsys.readouterr().out
|
|
55
|
+
assert rc == 0
|
|
56
|
+
assert f"cause: {VerdictCode.EMPTY_STORE.value}" in out
|
|
57
|
+
assert "no rows yet" in out
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def test_the_cause_is_plain_language_a_human_can_act_on(tmp_path, capsys):
|
|
61
|
+
d = _empty_store(tmp_path)
|
|
62
|
+
cli.main(["--db", str(d), "memory", "search", "anything at all"])
|
|
63
|
+
out = capsys.readouterr().out
|
|
64
|
+
# a sentence, not an enum dump
|
|
65
|
+
assert "Nothing was returned" in out
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def test_a_successful_search_is_unchanged(tmp_path, capsys):
|
|
69
|
+
d = _store(tmp_path)
|
|
70
|
+
rc = cli.main(["--db", str(d), "memory", "search", "forensics"])
|
|
71
|
+
out = capsys.readouterr().out
|
|
72
|
+
assert rc == 0 and "Blocktronics" in out
|
|
73
|
+
assert "cause:" not in out # no noise on the success path
|
|
74
|
+
|
|
75
|
+
# --------------------------------------------------------------------------
|
|
76
|
+
# one vocabulary
|
|
77
|
+
# --------------------------------------------------------------------------
|
|
78
|
+
def _code_only(text: str) -> str:
|
|
79
|
+
"""Strip docstrings and comments, leaving code.
|
|
80
|
+
|
|
81
|
+
Parsed, not split on `\"\"\"`. The first version of this helper did
|
|
82
|
+
`text.replace('\"\"\"', chr(0)).split(chr(0))[0::2]`, which silently INVERTS
|
|
83
|
+
which half it scans when the marker count is odd — the test would then check
|
|
84
|
+
the prose and skip the code, and still pass.
|
|
85
|
+
"""
|
|
86
|
+
try:
|
|
87
|
+
tree = ast.parse(text)
|
|
88
|
+
except SyntaxError: # pragma: no cover
|
|
89
|
+
return text
|
|
90
|
+
spans = []
|
|
91
|
+
for node in ast.walk(tree):
|
|
92
|
+
body = getattr(node, "body", None)
|
|
93
|
+
if not isinstance(body, list): # ast.IfExp.body is an expr
|
|
94
|
+
continue
|
|
95
|
+
for child in body:
|
|
96
|
+
if (isinstance(child, ast.Expr)
|
|
97
|
+
and isinstance(child.value, ast.Constant)
|
|
98
|
+
and isinstance(child.value.value, str)):
|
|
99
|
+
spans.append((child.lineno, child.end_lineno))
|
|
100
|
+
drop = {i for a, b in spans for i in range(a, b + 1)}
|
|
101
|
+
return "\n".join(ln.split("#", 1)[0]
|
|
102
|
+
for i, ln in enumerate(text.splitlines(), 1) if i not in drop)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
#: Derived from the enums, so renaming a cause updates the guard automatically.
|
|
106
|
+
CAUSE_LITERALS = tuple([c.value for c in VerdictCode if c is not VerdictCode.OK]
|
|
107
|
+
+ [g.value for g in GateCause])
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _assert_declares_no_vocabulary(*modules):
|
|
111
|
+
"""BOTH quote styles. These tests originally checked only double quotes, and
|
|
112
|
+
an adversarial review slipped `_LOCAL_CAUSE_COPY = 'abstained_on'` into the
|
|
113
|
+
MCP server past a green suite — exactly the drift the guard exists to stop."""
|
|
114
|
+
offenders = []
|
|
115
|
+
for mod in modules:
|
|
116
|
+
code = _code_only(inspect.getsource(mod))
|
|
117
|
+
for lit in CAUSE_LITERALS:
|
|
118
|
+
if f'"{lit}"' in code or f"'{lit}'" in code:
|
|
119
|
+
offenders.append((mod.__name__, lit))
|
|
120
|
+
assert offenders == [], f"cause strings re-declared outside verdicts.py: {offenders}"
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def test_the_cli_imports_the_vocabulary_and_declares_none():
|
|
124
|
+
"""One vocabulary, one module. The CLI prints causes; it does not own them."""
|
|
125
|
+
assert "from sibyl_memory_client.verdicts import refine_zero" in inspect.getsource(cli)
|
|
126
|
+
_assert_declares_no_vocabulary(cli)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sibyl_memory_cli-0.3.22 → sibyl_memory_cli-0.4.0}/src/sibyl_memory_cli.egg-info/entry_points.txt
RENAMED
|
File without changes
|
{sibyl_memory_cli-0.3.22 → sibyl_memory_cli-0.4.0}/src/sibyl_memory_cli.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|