sibyl-memory-cli 0.1.3__tar.gz → 0.3.1__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.3 → sibyl_memory_cli-0.3.1}/PKG-INFO +3 -2
- {sibyl_memory_cli-0.1.3 → sibyl_memory_cli-0.3.1}/pyproject.toml +3 -2
- sibyl_memory_cli-0.3.1/src/sibyl_memory_cli/_aesthetic.py +279 -0
- {sibyl_memory_cli-0.1.3 → sibyl_memory_cli-0.3.1}/src/sibyl_memory_cli/cli.py +317 -83
- sibyl_memory_cli-0.3.1/src/sibyl_memory_cli/setup.py +493 -0
- {sibyl_memory_cli-0.1.3 → sibyl_memory_cli-0.3.1}/src/sibyl_memory_cli.egg-info/PKG-INFO +3 -2
- {sibyl_memory_cli-0.1.3 → sibyl_memory_cli-0.3.1}/src/sibyl_memory_cli.egg-info/SOURCES.txt +4 -1
- {sibyl_memory_cli-0.1.3 → sibyl_memory_cli-0.3.1}/src/sibyl_memory_cli.egg-info/requires.txt +1 -0
- sibyl_memory_cli-0.3.1/tests/test_setup.py +381 -0
- {sibyl_memory_cli-0.1.3 → sibyl_memory_cli-0.3.1}/README.md +0 -0
- {sibyl_memory_cli-0.1.3 → sibyl_memory_cli-0.3.1}/setup.cfg +0 -0
- {sibyl_memory_cli-0.1.3 → sibyl_memory_cli-0.3.1}/src/sibyl_memory_cli/__init__.py +0 -0
- {sibyl_memory_cli-0.1.3 → sibyl_memory_cli-0.3.1}/src/sibyl_memory_cli/_banner.py +0 -0
- {sibyl_memory_cli-0.1.3 → sibyl_memory_cli-0.3.1}/src/sibyl_memory_cli.egg-info/dependency_links.txt +0 -0
- {sibyl_memory_cli-0.1.3 → sibyl_memory_cli-0.3.1}/src/sibyl_memory_cli.egg-info/entry_points.txt +0 -0
- {sibyl_memory_cli-0.1.3 → sibyl_memory_cli-0.3.1}/src/sibyl_memory_cli.egg-info/top_level.txt +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sibyl-memory-cli
|
|
3
|
-
Version: 0.1
|
|
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.
|
|
3
|
+
Version: 0.3.1
|
|
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
|
|
7
7
|
Project-URL: Homepage, https://sibyllabs.org/plugin
|
|
@@ -20,6 +20,7 @@ Requires-Python: >=3.10
|
|
|
20
20
|
Description-Content-Type: text/markdown
|
|
21
21
|
Requires-Dist: sibyl-memory-client>=0.4.0
|
|
22
22
|
Requires-Dist: sibyl-memory-hermes>=0.3.2
|
|
23
|
+
Requires-Dist: pyyaml>=6.0
|
|
23
24
|
Provides-Extra: dev
|
|
24
25
|
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
25
26
|
|
|
@@ -4,8 +4,8 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "sibyl-memory-cli"
|
|
7
|
-
version = "0.1
|
|
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."
|
|
7
|
+
version = "0.3.1"
|
|
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" }
|
|
11
11
|
readme = "README.md"
|
|
@@ -25,6 +25,7 @@ classifiers = [
|
|
|
25
25
|
dependencies = [
|
|
26
26
|
"sibyl-memory-client>=0.4.0",
|
|
27
27
|
"sibyl-memory-hermes>=0.3.2",
|
|
28
|
+
"pyyaml>=6.0",
|
|
28
29
|
]
|
|
29
30
|
|
|
30
31
|
[project.optional-dependencies]
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
"""Shared visual identity for the sibyl CLI surface.
|
|
2
|
+
|
|
3
|
+
Sister module to `_banner.py`. Where the banner is the identity-reveal
|
|
4
|
+
moment for `sibyl init`, this module supplies the granular building
|
|
5
|
+
blocks every subcommand uses to share one coherent look:
|
|
6
|
+
|
|
7
|
+
- 24-bit-truecolor → 256-color → plain-text degradation cascade
|
|
8
|
+
- Brand palette derived from the lab creme paper face (rule 46)
|
|
9
|
+
- Letter-spaced eyebrow labels, gradient titles, ASCII rule dividers
|
|
10
|
+
- Key/value rows, status chips, success/warn/error glyphs
|
|
11
|
+
- Pulsing accents for live states (activation, upgrade, watching)
|
|
12
|
+
|
|
13
|
+
Voice constraint: precise, editorial, restrained. Gradients flow over
|
|
14
|
+
2–3 stops max. No rainbow. The terminal is paper.
|
|
15
|
+
"""
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import os
|
|
19
|
+
import sys
|
|
20
|
+
from typing import Iterable
|
|
21
|
+
|
|
22
|
+
# ─── Palette (RGB · derived from rule 46 creme-paper tokens) ─────────
|
|
23
|
+
# Names map 1:1 to CSS custom properties on lab artifacts.
|
|
24
|
+
|
|
25
|
+
PAPER = (245, 241, 230) # --paper — foreground accent on dark
|
|
26
|
+
PAPER_DEEP = (237, 230, 211) # --paper-deep — depth on creme
|
|
27
|
+
CARD = (253, 251, 245) # --card — slightly lifted creme
|
|
28
|
+
INK = (21, 17, 10) # --ink — main text on creme
|
|
29
|
+
INK_SOFT = (44, 39, 29) # --ink-soft — body text
|
|
30
|
+
INK_MUTE = (106, 99, 86) # --ink-mute — secondary text
|
|
31
|
+
INK_FAINT = (152, 145, 127) # --ink-faint — tertiary text
|
|
32
|
+
RULE = (216, 208, 187) # --rule — hairline
|
|
33
|
+
RULE_STRONG = (184, 174, 147) # --rule-strong — emphasised hairline
|
|
34
|
+
ACCENT = (138, 106, 42) # --accent — ochre highlight
|
|
35
|
+
ACCENT_WARM = (160, 132, 56) # --accent-warm — softer ochre
|
|
36
|
+
ACCENT_GOLD = (224, 194, 119) # mid gold — gradient bridge
|
|
37
|
+
ACCENT_PALE = (244, 229, 184) # pale gold — gradient top
|
|
38
|
+
JADE = (45, 110, 106) # --jade — cool counterpoint
|
|
39
|
+
PULSE = (29, 138, 130) # --pulse — brighter jade (live signal)
|
|
40
|
+
ERROR = (162, 58, 42) # --error — measured red
|
|
41
|
+
|
|
42
|
+
# Status glyphs (Unicode, terminal-safe in modern fonts)
|
|
43
|
+
GLYPH_OK = "✓"
|
|
44
|
+
GLYPH_WARN = "⚠"
|
|
45
|
+
GLYPH_ERR = "✗"
|
|
46
|
+
GLYPH_DOT = "·"
|
|
47
|
+
GLYPH_ARROW = "→"
|
|
48
|
+
GLYPH_BULLET = "▸"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
# ─── Terminal capability detection ────────────────────────────────────
|
|
52
|
+
|
|
53
|
+
def supports_truecolor() -> bool:
|
|
54
|
+
"""24-bit RGB ANSI. Same heuristic as _banner.py."""
|
|
55
|
+
if os.environ.get("NO_COLOR"):
|
|
56
|
+
return False
|
|
57
|
+
if os.environ.get("TERM", "").lower() == "dumb":
|
|
58
|
+
return False
|
|
59
|
+
# SIBYL_FORCE_COLOR=1 — explicit override for non-tty rendering
|
|
60
|
+
# (CI logs, doc captures, dev inspection inside the Claude harness).
|
|
61
|
+
if os.environ.get("SIBYL_FORCE_COLOR") == "1":
|
|
62
|
+
return True
|
|
63
|
+
if not sys.stdout.isatty():
|
|
64
|
+
return False
|
|
65
|
+
colorterm = os.environ.get("COLORTERM", "").lower()
|
|
66
|
+
if "truecolor" in colorterm or "24bit" in colorterm:
|
|
67
|
+
return True
|
|
68
|
+
term_program = os.environ.get("TERM_PROGRAM", "").lower()
|
|
69
|
+
if term_program in {"iterm.app", "wezterm", "ghostty", "vscode", "tabby"}:
|
|
70
|
+
return True
|
|
71
|
+
term = os.environ.get("TERM", "").lower()
|
|
72
|
+
if any(k in term for k in ("256color", "kitty", "alacritty", "xterm-direct")):
|
|
73
|
+
return True
|
|
74
|
+
return False
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def supports_color() -> bool:
|
|
78
|
+
"""Any color at all (3/4-bit fallback)."""
|
|
79
|
+
if os.environ.get("NO_COLOR"):
|
|
80
|
+
return False
|
|
81
|
+
if os.environ.get("TERM", "").lower() == "dumb":
|
|
82
|
+
return False
|
|
83
|
+
if os.environ.get("SIBYL_FORCE_COLOR") == "1":
|
|
84
|
+
return True
|
|
85
|
+
return sys.stdout.isatty()
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
_TC = supports_truecolor()
|
|
89
|
+
_C = supports_color()
|
|
90
|
+
RESET = "\033[0m" if _C else ""
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def rgb(r: int, g: int, b: int) -> str:
|
|
94
|
+
"""24-bit foreground escape (no-op if color disabled)."""
|
|
95
|
+
if not _TC:
|
|
96
|
+
return ""
|
|
97
|
+
return f"\033[38;2;{r};{g};{b}m"
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def rgb_bg(r: int, g: int, b: int) -> str:
|
|
101
|
+
if not _TC:
|
|
102
|
+
return ""
|
|
103
|
+
return f"\033[48;2;{r};{g};{b}m"
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def color(text: str, c: tuple[int, int, int]) -> str:
|
|
107
|
+
if not _TC:
|
|
108
|
+
return text
|
|
109
|
+
return f"{rgb(*c)}{text}{RESET}"
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
# ─── Gradient · char-by-char RGB interpolation ────────────────────────
|
|
113
|
+
|
|
114
|
+
def _interp(a: int, b: int, t: float) -> int:
|
|
115
|
+
return round(a + (b - a) * t)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def gradient(text: str, *stops: tuple[int, int, int]) -> str:
|
|
119
|
+
"""Color a string with a gradient across N stops, one char at a time.
|
|
120
|
+
|
|
121
|
+
Plain-text fallback: returns the input unchanged when color is off.
|
|
122
|
+
Whitespace is preserved (uncolored to keep terminals consistent).
|
|
123
|
+
"""
|
|
124
|
+
if not _TC or len(stops) < 2 or not text:
|
|
125
|
+
return text
|
|
126
|
+
out = []
|
|
127
|
+
chars = list(text)
|
|
128
|
+
# Distribute char index across stop segments
|
|
129
|
+
n = max(1, len(chars) - 1)
|
|
130
|
+
segs = len(stops) - 1
|
|
131
|
+
for i, ch in enumerate(chars):
|
|
132
|
+
if ch == " ":
|
|
133
|
+
out.append(ch)
|
|
134
|
+
continue
|
|
135
|
+
seg_f = (i / n) * segs
|
|
136
|
+
seg_i = min(int(seg_f), segs - 1)
|
|
137
|
+
t = seg_f - seg_i
|
|
138
|
+
a = stops[seg_i]
|
|
139
|
+
b = stops[seg_i + 1]
|
|
140
|
+
r = _interp(a[0], b[0], t)
|
|
141
|
+
g = _interp(a[1], b[1], t)
|
|
142
|
+
bb = _interp(a[2], b[2], t)
|
|
143
|
+
out.append(f"\033[38;2;{r};{g};{bb}m{ch}")
|
|
144
|
+
return "".join(out) + RESET
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def gradient_gold(text: str) -> str:
|
|
148
|
+
"""Pale-gold → deep-ochre flow. The brand's headline gradient."""
|
|
149
|
+
return gradient(text, ACCENT_PALE, ACCENT_GOLD, ACCENT)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def gradient_jade(text: str) -> str:
|
|
153
|
+
"""Pulse → jade. Used for success states + live indicators."""
|
|
154
|
+
return gradient(text, PULSE, JADE)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
# ─── Style primitives ─────────────────────────────────────────────────
|
|
158
|
+
|
|
159
|
+
def dim(s: str) -> str:
|
|
160
|
+
return color(s, INK_FAINT)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def muted(s: str) -> str:
|
|
164
|
+
return color(s, INK_MUTE)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def soft(s: str) -> str:
|
|
168
|
+
return color(s, INK_SOFT)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def ink(s: str) -> str:
|
|
172
|
+
return color(s, INK)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def ok(s: str) -> str:
|
|
176
|
+
return color(s, PULSE)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def warn(s: str) -> str:
|
|
180
|
+
return color(s, ACCENT_WARM)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def err(s: str) -> str:
|
|
184
|
+
return color(s, ERROR)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def accent(s: str) -> str:
|
|
188
|
+
return color(s, ACCENT)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def bold(s: str) -> str:
|
|
192
|
+
if not _C:
|
|
193
|
+
return s
|
|
194
|
+
return f"\033[1m{s}{RESET}"
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
# ─── Composite primitives ─────────────────────────────────────────────
|
|
198
|
+
|
|
199
|
+
def eyebrow(label: str) -> str:
|
|
200
|
+
"""Uppercase letter-spaced ochre label. Editorial section marker."""
|
|
201
|
+
spaced = " ".join(label.upper())
|
|
202
|
+
return color(spaced, ACCENT)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def divider(width: int = 60, *, glyph: str = "─") -> str:
|
|
206
|
+
"""Creme-paper rule line."""
|
|
207
|
+
return color(glyph * width, RULE)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def section_header(name: str, *, subtitle: str | None = None, width: int = 60) -> str:
|
|
211
|
+
"""The standard subcommand opener.
|
|
212
|
+
|
|
213
|
+
─ <name> ────────────────────────────────────────
|
|
214
|
+
<subtitle, dim>
|
|
215
|
+
"""
|
|
216
|
+
name_part = f" {gradient_gold(name)} "
|
|
217
|
+
# Stripped-color length for visible width calc
|
|
218
|
+
visible_name_len = len(f" {name} ")
|
|
219
|
+
rule_left = "─"
|
|
220
|
+
rule_right = "─" * max(3, width - 1 - visible_name_len)
|
|
221
|
+
head = color(rule_left, RULE) + name_part + color(rule_right, RULE)
|
|
222
|
+
if subtitle:
|
|
223
|
+
return head + "\n" + dim(subtitle)
|
|
224
|
+
return head
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def chip(text: str, *, palette: str = "accent") -> str:
|
|
228
|
+
"""Compact inline label · [text]."""
|
|
229
|
+
palettes = {
|
|
230
|
+
"accent": ACCENT,
|
|
231
|
+
"jade": PULSE,
|
|
232
|
+
"warn": ACCENT_WARM,
|
|
233
|
+
"error": ERROR,
|
|
234
|
+
"mute": INK_MUTE,
|
|
235
|
+
}
|
|
236
|
+
c = palettes.get(palette, ACCENT)
|
|
237
|
+
return color(f"[{text}]", c)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def kv(label: str, value: str, *, label_width: int = 16, value_color: str = "ink") -> str:
|
|
241
|
+
"""One left-aligned label / value row.
|
|
242
|
+
|
|
243
|
+
Used across status / whoami / devices for the LOCAL / SERVER blocks.
|
|
244
|
+
"""
|
|
245
|
+
palettes = {
|
|
246
|
+
"ink": INK, "soft": INK_SOFT, "mute": INK_MUTE, "faint": INK_FAINT,
|
|
247
|
+
"accent": ACCENT, "ok": PULSE, "warn": ACCENT_WARM, "err": ERROR,
|
|
248
|
+
}
|
|
249
|
+
val_color = palettes.get(value_color, INK_SOFT)
|
|
250
|
+
return f" {color(label.ljust(label_width), INK_FAINT)} {color(value, val_color)}"
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def block_title(text: str) -> str:
|
|
254
|
+
"""Sub-section title within a command output. Like 'LOCAL' or 'SERVER'."""
|
|
255
|
+
return "\n" + eyebrow(text)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def success_line(text: str) -> str:
|
|
259
|
+
"""Single-line success marker with gradient + glyph."""
|
|
260
|
+
return f" {ok(GLYPH_OK)} {gradient_jade(text)}"
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def warn_line(text: str) -> str:
|
|
264
|
+
return f" {warn(GLYPH_WARN)} {warn(text)}"
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def err_line(text: str) -> str:
|
|
268
|
+
return f" {err(GLYPH_ERR)} {err(text)}"
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def hr_caption(caption: str, *, width: int = 60) -> str:
|
|
272
|
+
"""Caption line under a divider — small, muted, centered."""
|
|
273
|
+
pad = max(0, (width - len(caption)) // 2)
|
|
274
|
+
return " " * pad + dim(caption)
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def footer_credits(*, width: int = 60) -> str:
|
|
278
|
+
"""Bottom-of-output line. Used at end of long outputs."""
|
|
279
|
+
return color("─" * width, RULE) + "\n" + dim(" sibyl labs · memory you can hold in your hand")
|