sibyl-memory-cli 0.1.0__tar.gz → 0.1.2__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.1.0 → sibyl_memory_cli-0.1.2}/PKG-INFO +4 -4
- {sibyl_memory_cli-0.1.0 → sibyl_memory_cli-0.1.2}/pyproject.toml +4 -4
- {sibyl_memory_cli-0.1.0 → sibyl_memory_cli-0.1.2}/src/sibyl_memory_cli/__init__.py +7 -1
- sibyl_memory_cli-0.1.2/src/sibyl_memory_cli/_banner.py +117 -0
- {sibyl_memory_cli-0.1.0 → sibyl_memory_cli-0.1.2}/src/sibyl_memory_cli/cli.py +93 -19
- {sibyl_memory_cli-0.1.0 → sibyl_memory_cli-0.1.2}/src/sibyl_memory_cli.egg-info/PKG-INFO +4 -4
- {sibyl_memory_cli-0.1.0 → sibyl_memory_cli-0.1.2}/src/sibyl_memory_cli.egg-info/SOURCES.txt +1 -0
- sibyl_memory_cli-0.1.2/src/sibyl_memory_cli.egg-info/requires.txt +5 -0
- sibyl_memory_cli-0.1.0/src/sibyl_memory_cli.egg-info/requires.txt +0 -5
- {sibyl_memory_cli-0.1.0 → sibyl_memory_cli-0.1.2}/README.md +0 -0
- {sibyl_memory_cli-0.1.0 → sibyl_memory_cli-0.1.2}/setup.cfg +0 -0
- {sibyl_memory_cli-0.1.0 → sibyl_memory_cli-0.1.2}/src/sibyl_memory_cli.egg-info/dependency_links.txt +0 -0
- {sibyl_memory_cli-0.1.0 → sibyl_memory_cli-0.1.2}/src/sibyl_memory_cli.egg-info/entry_points.txt +0 -0
- {sibyl_memory_cli-0.1.0 → sibyl_memory_cli-0.1.2}/src/sibyl_memory_cli.egg-info/top_level.txt +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sibyl-memory-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
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.
|
|
5
|
-
Author-email: Sibyl Labs <sibyl@sibyllabs.org>
|
|
5
|
+
Author-email: "SIBYL, Sibyl Labs LLC" <sibyl@sibyllabs.org>
|
|
6
6
|
License: MIT
|
|
7
7
|
Project-URL: Homepage, https://sibyllabs.org/plugin
|
|
8
8
|
Project-URL: Documentation, https://docs.sibyllabs.org/memory/
|
|
@@ -18,8 +18,8 @@ Classifier: Environment :: Console
|
|
|
18
18
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
19
19
|
Requires-Python: >=3.10
|
|
20
20
|
Description-Content-Type: text/markdown
|
|
21
|
-
Requires-Dist: sibyl-memory-client>=0.3.
|
|
22
|
-
Requires-Dist: sibyl-memory-hermes>=0.
|
|
21
|
+
Requires-Dist: sibyl-memory-client>=0.3.3
|
|
22
|
+
Requires-Dist: sibyl-memory-hermes>=0.3.1
|
|
23
23
|
Provides-Extra: dev
|
|
24
24
|
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
25
25
|
|
|
@@ -4,9 +4,9 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "sibyl-memory-cli"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.2"
|
|
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."
|
|
9
|
-
authors = [{ name = "Sibyl Labs", email = "sibyl@sibyllabs.org" }]
|
|
9
|
+
authors = [{ name = "SIBYL, Sibyl Labs LLC", email = "sibyl@sibyllabs.org" }]
|
|
10
10
|
license = { text = "MIT" }
|
|
11
11
|
readme = "README.md"
|
|
12
12
|
requires-python = ">=3.10"
|
|
@@ -23,8 +23,8 @@ classifiers = [
|
|
|
23
23
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
24
24
|
]
|
|
25
25
|
dependencies = [
|
|
26
|
-
"sibyl-memory-client>=0.3.
|
|
27
|
-
"sibyl-memory-hermes>=0.
|
|
26
|
+
"sibyl-memory-client>=0.3.3",
|
|
27
|
+
"sibyl-memory-hermes>=0.3.1",
|
|
28
28
|
]
|
|
29
29
|
|
|
30
30
|
[project.optional-dependencies]
|
|
@@ -13,6 +13,12 @@ All HTTP calls target https://api.sibyllabs.org/api/plugin/*.
|
|
|
13
13
|
"""
|
|
14
14
|
from .cli import main
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
# Single-sourced from installed metadata so wheel + code can't drift
|
|
17
|
+
# (C3 audit fix v0.1.2). Same pattern as sibyl-memory-hermes v0.3.0+.
|
|
18
|
+
from importlib.metadata import PackageNotFoundError, version as _pkg_version
|
|
19
|
+
try:
|
|
20
|
+
__version__ = _pkg_version("sibyl-memory-cli")
|
|
21
|
+
except PackageNotFoundError: # pragma: no cover - source-tree dev only
|
|
22
|
+
__version__ = "0.0.0+source"
|
|
17
23
|
|
|
18
24
|
__all__ = ["main", "__version__"]
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""ASCII banner for sibyl-memory-cli.
|
|
2
|
+
|
|
3
|
+
Prints the SIBYL wordmark in ANSI Shadow boxchars with a 24-bit truecolor
|
|
4
|
+
vertical gradient flowing from cream/white at the top through warm gold
|
|
5
|
+
to deep ochre at the bottom — aligned with the lab visual identity per
|
|
6
|
+
the operator's brand-discipline rule (creme palette, deep-ochre accent).
|
|
7
|
+
|
|
8
|
+
Gracefully degrades:
|
|
9
|
+
- NO_COLOR env var set → plain text fallback
|
|
10
|
+
- stdout is not a TTY → plain text fallback (or skip entirely)
|
|
11
|
+
- TERM=dumb → plain text fallback
|
|
12
|
+
|
|
13
|
+
Truecolor support is detected via $COLORTERM (truecolor / 24bit) — most
|
|
14
|
+
modern terminals (iTerm2, Alacritty, Kitty, wezterm, Windows Terminal,
|
|
15
|
+
modern xterm builds, Ghostty) advertise it. Falls back to 256-color
|
|
16
|
+
gradient when not available.
|
|
17
|
+
"""
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import os
|
|
21
|
+
import sys
|
|
22
|
+
|
|
23
|
+
# ANSI Shadow rendering of "SIBYL" — 6 rows, 41 cols. Each row gets its
|
|
24
|
+
# own gradient color (top = pale cream/white, bottom = deep ochre).
|
|
25
|
+
_LINES = (
|
|
26
|
+
"███████╗██╗██████╗ ██╗ ██╗██╗ ",
|
|
27
|
+
"██╔════╝██║██╔══██╗╚██╗ ██╔╝██║ ",
|
|
28
|
+
"███████╗██║██████╔╝ ╚████╔╝ ██║ ",
|
|
29
|
+
"╚════██║██║██╔══██╗ ╚██╔╝ ██║ ",
|
|
30
|
+
"███████║██║██████╔╝ ██║ ███████╗",
|
|
31
|
+
"╚══════╝╚═╝╚═════╝ ╚═╝ ╚══════╝",
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
# Vertical gradient · cream → gold → deep ochre. One RGB tuple per row.
|
|
35
|
+
# Tuned against the SIBYL palette: --paper #f5f1e6 (top blend),
|
|
36
|
+
# --accent #8a6a2a (mid-bottom), with extra highlight + shadow stops
|
|
37
|
+
# to give the wordmark visible dimension.
|
|
38
|
+
_GRADIENT = (
|
|
39
|
+
(253, 251, 245), # almost white, slight cream (top highlight)
|
|
40
|
+
(244, 229, 184), # pale gold (upper)
|
|
41
|
+
(224, 194, 119), # mid gold (upper-mid)
|
|
42
|
+
(184, 146, 73), # rich ochre gold (mid)
|
|
43
|
+
(138, 106, 42), # deep ochre · brand --accent (lower)
|
|
44
|
+
(106, 79, 31), # deepest (bottom shadow)
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
_TAGLINE = "memory you can hold in your hand"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _supports_truecolor() -> bool:
|
|
51
|
+
"""Detect 24-bit color support. Conservative — fall back gracefully."""
|
|
52
|
+
if os.environ.get("NO_COLOR"):
|
|
53
|
+
return False
|
|
54
|
+
if os.environ.get("TERM", "").lower() == "dumb":
|
|
55
|
+
return False
|
|
56
|
+
if not sys.stdout.isatty():
|
|
57
|
+
return False
|
|
58
|
+
colorterm = os.environ.get("COLORTERM", "").lower()
|
|
59
|
+
if "truecolor" in colorterm or "24bit" in colorterm:
|
|
60
|
+
return True
|
|
61
|
+
# Many modern terminals don't set COLORTERM but do support truecolor.
|
|
62
|
+
# Recognize the well-behaved emitters.
|
|
63
|
+
term_program = os.environ.get("TERM_PROGRAM", "").lower()
|
|
64
|
+
if term_program in {"iterm.app", "wezterm", "ghostty", "vscode", "tabby"}:
|
|
65
|
+
return True
|
|
66
|
+
term = os.environ.get("TERM", "").lower()
|
|
67
|
+
if any(k in term for k in ("256color", "kitty", "alacritty", "xterm-direct")):
|
|
68
|
+
return True
|
|
69
|
+
return False
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _color_supported() -> bool:
|
|
73
|
+
"""Plain ANSI color (3/4-bit). Stricter than truecolor."""
|
|
74
|
+
if os.environ.get("NO_COLOR"):
|
|
75
|
+
return False
|
|
76
|
+
if os.environ.get("TERM", "").lower() == "dumb":
|
|
77
|
+
return False
|
|
78
|
+
return sys.stdout.isatty()
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _rgb(r: int, g: int, b: int) -> str:
|
|
82
|
+
return f"\033[38;2;{r};{g};{b}m"
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
_RESET = "\033[0m"
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def render_banner(*, force_color: bool | None = None) -> str:
|
|
89
|
+
"""Return the banner as a string ready to print.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
force_color: Override auto-detection. None = auto, True = force
|
|
93
|
+
truecolor, False = force plain text. Useful for testing.
|
|
94
|
+
"""
|
|
95
|
+
use_truecolor = force_color if force_color is not None else _supports_truecolor()
|
|
96
|
+
|
|
97
|
+
if not use_truecolor:
|
|
98
|
+
# Plain text — still visually clean, just no color.
|
|
99
|
+
body = "\n".join(" " + line for line in _LINES)
|
|
100
|
+
tagline = f"\n {_TAGLINE}\n"
|
|
101
|
+
return body + tagline
|
|
102
|
+
|
|
103
|
+
# Colored — apply per-row gradient.
|
|
104
|
+
colored_lines = []
|
|
105
|
+
for line, (r, g, b) in zip(_LINES, _GRADIENT):
|
|
106
|
+
colored_lines.append(f" {_rgb(r, g, b)}{line}{_RESET}")
|
|
107
|
+
|
|
108
|
+
body = "\n".join(colored_lines)
|
|
109
|
+
# Tagline in dim italic-feeling color (the deepest gold)
|
|
110
|
+
r, g, b = _GRADIENT[-1]
|
|
111
|
+
tagline = f"\n {_rgb(r, g, b)}{_TAGLINE}{_RESET}\n"
|
|
112
|
+
return body + tagline
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def print_banner(*, force_color: bool | None = None) -> None:
|
|
116
|
+
"""Print the banner. Safe to call unconditionally; honors NO_COLOR + TTY checks."""
|
|
117
|
+
print(render_banner(force_color=force_color))
|
|
@@ -5,7 +5,11 @@ https://api.sibyllabs.org/api/plugin/* and the local SibylMemoryProvider.
|
|
|
5
5
|
|
|
6
6
|
Design pillars:
|
|
7
7
|
- Zero non-stdlib deps in this file. urllib is enough.
|
|
8
|
-
- Credentials are written atomically
|
|
8
|
+
- Credentials are written atomically at mode 0600, set at file-creation
|
|
9
|
+
time via O_CREAT|O_EXCL|O_NOFOLLOW (no chmod-after-write race).
|
|
10
|
+
- The URL parameter handed to the browser is an opaque session identifier,
|
|
11
|
+
not the long-lived bearer (audit SEC-1 — server-side pairing handoff
|
|
12
|
+
issues a separate bearer at activation completion if available).
|
|
9
13
|
- session_token is never printed in full — display short slice only.
|
|
10
14
|
- Polling has explicit timeouts; no infinite loops.
|
|
11
15
|
- Every command exits with a clear status code (0 ok, 1 user error, 2 server error).
|
|
@@ -27,6 +31,18 @@ import webbrowser
|
|
|
27
31
|
from pathlib import Path
|
|
28
32
|
from typing import Any
|
|
29
33
|
|
|
34
|
+
|
|
35
|
+
def _client_version() -> str:
|
|
36
|
+
"""Return the installed package version from metadata, never hardcoded."""
|
|
37
|
+
try:
|
|
38
|
+
from importlib.metadata import PackageNotFoundError, version as _v
|
|
39
|
+
try:
|
|
40
|
+
return _v("sibyl-memory-cli")
|
|
41
|
+
except PackageNotFoundError:
|
|
42
|
+
return "0.0.0+source"
|
|
43
|
+
except Exception:
|
|
44
|
+
return "0.0.0+source"
|
|
45
|
+
|
|
30
46
|
# ---- Defaults ----------------------------------------------------------
|
|
31
47
|
|
|
32
48
|
API_BASE = os.environ.get("SIBYL_API_BASE", "https://api.sibyllabs.org")
|
|
@@ -101,7 +117,7 @@ def http_request(
|
|
|
101
117
|
"""Single source of truth for HTTP calls. Returns parsed JSON or raises HttpError."""
|
|
102
118
|
url = f"{API_BASE}{path}"
|
|
103
119
|
data = None
|
|
104
|
-
full_headers = {"Accept": "application/json", "User-Agent": "sibyl-memory-cli/
|
|
120
|
+
full_headers = {"Accept": "application/json", "User-Agent": f"sibyl-memory-cli/{_client_version()}"}
|
|
105
121
|
if body is not None:
|
|
106
122
|
data = json.dumps(body).encode("utf-8")
|
|
107
123
|
full_headers["Content-Type"] = "application/json"
|
|
@@ -124,20 +140,45 @@ def http_request(
|
|
|
124
140
|
# ---- Credentials I/O ---------------------------------------------------
|
|
125
141
|
|
|
126
142
|
def write_credentials_atomic(creds: dict, path: Path = DEFAULT_CRED_PATH) -> Path:
|
|
127
|
-
"""Write credentials.json atomically at mode 0600.
|
|
143
|
+
"""Write credentials.json atomically at mode 0600.
|
|
144
|
+
|
|
145
|
+
v0.1.2 hardening (audit SEC-2): mode 0600 is set by the kernel at
|
|
146
|
+
file-creation time via O_CREAT|O_EXCL|O_NOFOLLOW. Previously used
|
|
147
|
+
write_text() followed by os.chmod(), leaving a world-readable window
|
|
148
|
+
between syscalls every credential save.
|
|
149
|
+
"""
|
|
128
150
|
path = path.expanduser()
|
|
129
151
|
path.parent.mkdir(parents=True, exist_ok=True, mode=0o700)
|
|
152
|
+
data = json.dumps(creds, indent=2).encode("utf-8")
|
|
130
153
|
tmp = path.with_suffix(path.suffix + ".tmp")
|
|
131
|
-
tmp
|
|
132
|
-
|
|
133
|
-
|
|
154
|
+
# Clean any leftover .tmp from a crashed prior write so O_EXCL can succeed.
|
|
155
|
+
try:
|
|
156
|
+
os.unlink(tmp)
|
|
157
|
+
except FileNotFoundError:
|
|
158
|
+
pass
|
|
159
|
+
flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL
|
|
160
|
+
if hasattr(os, "O_NOFOLLOW"):
|
|
161
|
+
flags |= os.O_NOFOLLOW
|
|
162
|
+
fd = os.open(str(tmp), flags, 0o600)
|
|
163
|
+
try:
|
|
164
|
+
os.write(fd, data)
|
|
165
|
+
os.fsync(fd)
|
|
166
|
+
finally:
|
|
167
|
+
os.close(fd)
|
|
168
|
+
os.replace(str(tmp), str(path))
|
|
134
169
|
return path
|
|
135
170
|
|
|
136
171
|
|
|
137
172
|
def read_credentials(path: Path = DEFAULT_CRED_PATH) -> dict | None:
|
|
173
|
+
"""Read credentials.json.
|
|
174
|
+
|
|
175
|
+
v0.1.2 hardening (audit SEC-11): refuses to follow symlinks.
|
|
176
|
+
Returns None if the file is a symlink or doesn't exist."""
|
|
138
177
|
path = path.expanduser()
|
|
139
178
|
if not path.exists():
|
|
140
179
|
return None
|
|
180
|
+
if path.is_symlink():
|
|
181
|
+
return None
|
|
141
182
|
return json.loads(path.read_text(encoding="utf-8"))
|
|
142
183
|
|
|
143
184
|
|
|
@@ -166,6 +207,11 @@ def cmd_init(args: argparse.Namespace) -> int:
|
|
|
166
207
|
The pairing code is printed in the terminal. If the user picks the
|
|
167
208
|
email path in the browser, they type both their email and this code.
|
|
168
209
|
No external email service is required."""
|
|
210
|
+
# Brand moment — gold/white gradient SIBYL wordmark.
|
|
211
|
+
# Honors NO_COLOR + TTY detection automatically; safe to always call.
|
|
212
|
+
from ._banner import print_banner
|
|
213
|
+
print_banner()
|
|
214
|
+
|
|
169
215
|
cred_path = Path(args.credentials).expanduser()
|
|
170
216
|
if cred_path.exists() and not args.force:
|
|
171
217
|
existing = read_credentials(cred_path) or {}
|
|
@@ -175,10 +221,18 @@ def cmd_init(args: argparse.Namespace) -> int:
|
|
|
175
221
|
print_status("Credentials", str(cred_path))
|
|
176
222
|
return 0
|
|
177
223
|
|
|
178
|
-
|
|
224
|
+
# SEC-1 mitigation (v0.1.2): the URL parameter is an opaque pairing
|
|
225
|
+
# session identifier, NOT the long-lived bearer used by /access and
|
|
226
|
+
# /check-write. The CLI generates it locally and the server treats
|
|
227
|
+
# it as the activation rendezvous key only. The persistent bearer
|
|
228
|
+
# is issued by the server in the /check response (`bearer_token`
|
|
229
|
+
# field) after activation completes. Servers running pre-SEC-1
|
|
230
|
+
# firmware that echo the URL identifier as the bearer still work —
|
|
231
|
+
# we use whichever the server returns in the bound credentials.
|
|
232
|
+
session_id = str(uuid.uuid4())
|
|
179
233
|
pairing_code = _gen_pairing_code()
|
|
180
|
-
code_hash = _hash_pairing_code(pairing_code,
|
|
181
|
-
activate_url = f"{ACTIVATE_BASE}?session={
|
|
234
|
+
code_hash = _hash_pairing_code(pairing_code, session_id)
|
|
235
|
+
activate_url = f"{ACTIVATE_BASE}?session={session_id}"
|
|
182
236
|
|
|
183
237
|
# Pre-register the session + pairing code hash with the server.
|
|
184
238
|
# The code itself never leaves the user's machine until they type it
|
|
@@ -188,12 +242,12 @@ def cmd_init(args: argparse.Namespace) -> int:
|
|
|
188
242
|
"POST",
|
|
189
243
|
"/api/plugin/session-init",
|
|
190
244
|
body={
|
|
191
|
-
"session":
|
|
245
|
+
"session": session_id,
|
|
192
246
|
"pairing_code_hash": code_hash,
|
|
193
247
|
"env": {
|
|
194
248
|
"os_family": _detect_os_family(),
|
|
195
249
|
"install_method": "cli",
|
|
196
|
-
"client_version":
|
|
250
|
+
"client_version": _client_version(),
|
|
197
251
|
},
|
|
198
252
|
},
|
|
199
253
|
timeout=10.0,
|
|
@@ -206,7 +260,7 @@ def cmd_init(args: argparse.Namespace) -> int:
|
|
|
206
260
|
print()
|
|
207
261
|
print(bold("Sibyl Memory Plugin · activation"))
|
|
208
262
|
print()
|
|
209
|
-
print(f" {dim('Session:')} {short(
|
|
263
|
+
print(f" {dim('Session:')} {short(session_id)}")
|
|
210
264
|
# Format the code with a visual break to make it easier to read off the screen
|
|
211
265
|
print(f" {dim('Code:')} {bold(pairing_code[:3] + ' ' + pairing_code[3:])} {dim('(use this in the email panel)')}")
|
|
212
266
|
print(f" {dim('Opening:')} {activate_url}")
|
|
@@ -227,7 +281,7 @@ def cmd_init(args: argparse.Namespace) -> int:
|
|
|
227
281
|
|
|
228
282
|
while time.time() < deadline:
|
|
229
283
|
try:
|
|
230
|
-
resp = http_request("GET", f"/api/plugin/check?session={urllib.parse.quote(
|
|
284
|
+
resp = http_request("GET", f"/api/plugin/check?session={urllib.parse.quote(session_id)}", timeout=10.0)
|
|
231
285
|
except HttpError as e:
|
|
232
286
|
if e.status in (404, 503, 0):
|
|
233
287
|
# Session not yet created server-side, or transient — keep polling
|
|
@@ -239,12 +293,23 @@ def cmd_init(args: argparse.Namespace) -> int:
|
|
|
239
293
|
|
|
240
294
|
if resp.get("bound") and resp.get("credentials"):
|
|
241
295
|
creds = resp["credentials"]
|
|
242
|
-
#
|
|
243
|
-
|
|
296
|
+
# SEC-1: prefer the server-issued bearer_token (post-fix) over
|
|
297
|
+
# echoing the URL pairing-session id. Servers running pre-SEC-1
|
|
298
|
+
# firmware echo `session_token` back as the bearer — we use
|
|
299
|
+
# whichever the server returns. The CLI's session_id (URL
|
|
300
|
+
# identifier) is the rendezvous key, not the persistent bearer.
|
|
301
|
+
bearer = creds.get("bearer_token") or creds.get("session_token")
|
|
302
|
+
if not bearer:
|
|
303
|
+
# Fallback: pre-SEC-1 server flow where neither field is
|
|
304
|
+
# echoed back — inject the pairing session id so subsequent
|
|
305
|
+
# /access and /check-write calls have something to send.
|
|
306
|
+
bearer = session_id
|
|
307
|
+
# Sanity check on echoed session_token (pre-SEC-1 flow only)
|
|
308
|
+
if creds.get("session_token") and creds["session_token"] != session_id \
|
|
309
|
+
and not creds.get("bearer_token"):
|
|
244
310
|
print(red("\nSession token mismatch — refusing to write credentials."))
|
|
245
311
|
return 2
|
|
246
|
-
|
|
247
|
-
creds.setdefault("session_token", session_token)
|
|
312
|
+
creds["session_token"] = bearer
|
|
248
313
|
path = write_credentials_atomic(creds, cred_path)
|
|
249
314
|
print(f"\r{' ' * 80}\r", end="") # clear spinner line
|
|
250
315
|
print(green("✓ Activated."))
|
|
@@ -254,9 +319,18 @@ def cmd_init(args: argparse.Namespace) -> int:
|
|
|
254
319
|
print_status("Email", creds.get("email") or "—")
|
|
255
320
|
print_status("Credentials", str(path))
|
|
256
321
|
print()
|
|
257
|
-
print(dim("
|
|
322
|
+
print(dim("Activate Sibyl in Hermes:"))
|
|
323
|
+
print(dim(" sibyl-memory-hermes install-plugin"))
|
|
324
|
+
print(dim(" # then edit ~/.hermes/config.yaml:"))
|
|
325
|
+
print(dim(" # memory:"))
|
|
326
|
+
print(dim(" # provider: sibyl"))
|
|
327
|
+
print()
|
|
328
|
+
print(dim("Or use the MCP server (Claude Code / Codex / Cursor / Continue):"))
|
|
329
|
+
print(dim(" pip install sibyl-memory-mcp"))
|
|
330
|
+
print()
|
|
331
|
+
print(dim("Or call the SDK directly from any Python orchestration:"))
|
|
258
332
|
print(dim(" from sibyl_memory_hermes import SibylMemoryProvider"))
|
|
259
|
-
print(dim("
|
|
333
|
+
print(dim(" provider = SibylMemoryProvider()"))
|
|
260
334
|
return 0
|
|
261
335
|
|
|
262
336
|
# Spinner tick
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sibyl-memory-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
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.
|
|
5
|
-
Author-email: Sibyl Labs <sibyl@sibyllabs.org>
|
|
5
|
+
Author-email: "SIBYL, Sibyl Labs LLC" <sibyl@sibyllabs.org>
|
|
6
6
|
License: MIT
|
|
7
7
|
Project-URL: Homepage, https://sibyllabs.org/plugin
|
|
8
8
|
Project-URL: Documentation, https://docs.sibyllabs.org/memory/
|
|
@@ -18,8 +18,8 @@ Classifier: Environment :: Console
|
|
|
18
18
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
19
19
|
Requires-Python: >=3.10
|
|
20
20
|
Description-Content-Type: text/markdown
|
|
21
|
-
Requires-Dist: sibyl-memory-client>=0.3.
|
|
22
|
-
Requires-Dist: sibyl-memory-hermes>=0.
|
|
21
|
+
Requires-Dist: sibyl-memory-client>=0.3.3
|
|
22
|
+
Requires-Dist: sibyl-memory-hermes>=0.3.1
|
|
23
23
|
Provides-Extra: dev
|
|
24
24
|
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
25
25
|
|
|
File without changes
|
|
File without changes
|
{sibyl_memory_cli-0.1.0 → sibyl_memory_cli-0.1.2}/src/sibyl_memory_cli.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{sibyl_memory_cli-0.1.0 → sibyl_memory_cli-0.1.2}/src/sibyl_memory_cli.egg-info/entry_points.txt
RENAMED
|
File without changes
|
{sibyl_memory_cli-0.1.0 → sibyl_memory_cli-0.1.2}/src/sibyl_memory_cli.egg-info/top_level.txt
RENAMED
|
File without changes
|