sibyl-memory-cli 0.3.6__tar.gz → 0.3.7__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.6 → sibyl_memory_cli-0.3.7}/PKG-INFO +1 -1
- {sibyl_memory_cli-0.3.6 → sibyl_memory_cli-0.3.7}/pyproject.toml +1 -1
- {sibyl_memory_cli-0.3.6 → sibyl_memory_cli-0.3.7}/src/sibyl_memory_cli/cli.py +6 -2
- {sibyl_memory_cli-0.3.6 → sibyl_memory_cli-0.3.7}/src/sibyl_memory_cli/setup.py +174 -7
- {sibyl_memory_cli-0.3.6 → sibyl_memory_cli-0.3.7}/src/sibyl_memory_cli.egg-info/PKG-INFO +1 -1
- {sibyl_memory_cli-0.3.6 → sibyl_memory_cli-0.3.7}/README.md +0 -0
- {sibyl_memory_cli-0.3.6 → sibyl_memory_cli-0.3.7}/setup.cfg +0 -0
- {sibyl_memory_cli-0.3.6 → sibyl_memory_cli-0.3.7}/src/sibyl_memory_cli/__init__.py +0 -0
- {sibyl_memory_cli-0.3.6 → sibyl_memory_cli-0.3.7}/src/sibyl_memory_cli/_aesthetic.py +0 -0
- {sibyl_memory_cli-0.3.6 → sibyl_memory_cli-0.3.7}/src/sibyl_memory_cli/_banner.py +0 -0
- {sibyl_memory_cli-0.3.6 → sibyl_memory_cli-0.3.7}/src/sibyl_memory_cli.egg-info/SOURCES.txt +0 -0
- {sibyl_memory_cli-0.3.6 → sibyl_memory_cli-0.3.7}/src/sibyl_memory_cli.egg-info/dependency_links.txt +0 -0
- {sibyl_memory_cli-0.3.6 → sibyl_memory_cli-0.3.7}/src/sibyl_memory_cli.egg-info/entry_points.txt +0 -0
- {sibyl_memory_cli-0.3.6 → sibyl_memory_cli-0.3.7}/src/sibyl_memory_cli.egg-info/requires.txt +0 -0
- {sibyl_memory_cli-0.3.6 → sibyl_memory_cli-0.3.7}/src/sibyl_memory_cli.egg-info/top_level.txt +0 -0
- {sibyl_memory_cli-0.3.6 → sibyl_memory_cli-0.3.7}/tests/test_setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sibyl-memory-cli
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.7
|
|
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
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "sibyl-memory-cli"
|
|
7
|
-
version = "0.3.
|
|
7
|
+
version = "0.3.7"
|
|
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" }
|
|
@@ -1042,7 +1042,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
1042
1042
|
help="Auto-detect Hermes / Claude Code and wire SIBYL as the memory provider",
|
|
1043
1043
|
)
|
|
1044
1044
|
p_setup.add_argument(
|
|
1045
|
-
"target", nargs="?", choices=list(["hermes", "claude-code"]),
|
|
1045
|
+
"target", nargs="?", choices=list(["hermes", "claude-code", "codex"]),
|
|
1046
1046
|
help="Wire just this framework (default: detect all)",
|
|
1047
1047
|
)
|
|
1048
1048
|
p_setup.add_argument(
|
|
@@ -1063,7 +1063,11 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
1063
1063
|
)
|
|
1064
1064
|
p_setup.add_argument(
|
|
1065
1065
|
"--claude-settings", default=None,
|
|
1066
|
-
help="Override ~/.claude
|
|
1066
|
+
help="Override ~/.claude.json autodetection",
|
|
1067
|
+
)
|
|
1068
|
+
p_setup.add_argument(
|
|
1069
|
+
"--codex-config", default=None,
|
|
1070
|
+
help="Override ~/.codex/config.toml autodetection",
|
|
1067
1071
|
)
|
|
1068
1072
|
p_setup.set_defaults(func=cmd_setup)
|
|
1069
1073
|
|
|
@@ -3,11 +3,12 @@
|
|
|
3
3
|
Maximum-efficiency onboarding command. Single-command path for the user:
|
|
4
4
|
|
|
5
5
|
pip install sibyl-memory-cli
|
|
6
|
-
sibyl setup # auto-detects Hermes + Claude Code, prompts per stack, wires
|
|
6
|
+
sibyl setup # auto-detects Hermes + Claude Code + Codex, prompts per stack, wires
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
Three wirers in v0.3.7:
|
|
9
9
|
- HermesWirer: install-plugin + edit $HERMES_HOME/config.yaml (memory.provider)
|
|
10
|
-
- ClaudeCodeWirer: edit ~/.claude
|
|
10
|
+
- ClaudeCodeWirer: edit ~/.claude.json (mcpServers.sibyl-memory)
|
|
11
|
+
- CodexWirer: edit ~/.codex/config.toml ([mcp_servers.sibyl_memory])
|
|
11
12
|
|
|
12
13
|
Each wirer follows the same protocol:
|
|
13
14
|
is_present() -> bool (filesystem + PATH detect)
|
|
@@ -26,6 +27,7 @@ from __future__ import annotations
|
|
|
26
27
|
import argparse
|
|
27
28
|
import json
|
|
28
29
|
import os
|
|
30
|
+
import re
|
|
29
31
|
import shutil
|
|
30
32
|
import sys
|
|
31
33
|
from dataclasses import dataclass
|
|
@@ -229,9 +231,15 @@ class ClaudeCodeWirer:
|
|
|
229
231
|
SIBYL_MCP_BLOCK = {"command": "sibyl-memory-mcp"}
|
|
230
232
|
|
|
231
233
|
def __init__(self, *, settings_path: Optional[Union[str, Path]] = None):
|
|
234
|
+
# v0.3.7 (2026-05-22): default flipped from ~/.claude/settings.json to
|
|
235
|
+
# ~/.claude.json. Recent Claude Code reads MCP server config from the
|
|
236
|
+
# user-level ~/.claude.json — writing to ~/.claude/settings.json was a
|
|
237
|
+
# silent-failure path (wirer reported success but MCP server didn't
|
|
238
|
+
# appear in Claude Code). Override with --claude-settings if your
|
|
239
|
+
# install uses a non-standard location.
|
|
232
240
|
self.settings_path = (
|
|
233
241
|
Path(settings_path).expanduser() if settings_path
|
|
234
|
-
else Path.home() / ".claude
|
|
242
|
+
else Path.home() / ".claude.json"
|
|
235
243
|
)
|
|
236
244
|
|
|
237
245
|
def is_present(self) -> bool:
|
|
@@ -333,6 +341,158 @@ class ClaudeCodeWirer:
|
|
|
333
341
|
os.replace(tmp, self.settings_path)
|
|
334
342
|
|
|
335
343
|
|
|
344
|
+
# ----------------------------------------------------------------------
|
|
345
|
+
# CodexWirer — edits ~/.codex/config.toml
|
|
346
|
+
# ----------------------------------------------------------------------
|
|
347
|
+
|
|
348
|
+
class CodexWirer:
|
|
349
|
+
"""Auto-wire Codex CLI's MCP server registry at ~/.codex/config.toml.
|
|
350
|
+
|
|
351
|
+
Codex reads server defs from a TOML table named `[mcp_servers.<name>]`.
|
|
352
|
+
We use `[mcp_servers.sibyl_memory]` (snake_case is TOML-idiomatic; the
|
|
353
|
+
Claude Code side uses `sibyl-memory` because JSON keys allow hyphens
|
|
354
|
+
and Anthropic docs use that form).
|
|
355
|
+
|
|
356
|
+
Idempotency is text-based rather than full TOML round-trip, so we
|
|
357
|
+
preserve operator hand-edits and comments in the rest of the file
|
|
358
|
+
instead of reformatting on every run. The cost: we won't catch
|
|
359
|
+
pathological TOML edge cases (e.g., comments mid-key). For the
|
|
360
|
+
canonical Codex config layout this is fine.
|
|
361
|
+
"""
|
|
362
|
+
name = "codex"
|
|
363
|
+
display_name = "Codex CLI"
|
|
364
|
+
initial = "x" # 'c' is taken by Claude Code; 'x' from codeX
|
|
365
|
+
|
|
366
|
+
SIBYL_TOML_BLOCK = '[mcp_servers.sibyl_memory]\ncommand = "sibyl-memory-mcp"\n'
|
|
367
|
+
BLOCK_HEADER_RE = re.compile(
|
|
368
|
+
r'^\s*\[\s*mcp_servers\s*\.\s*sibyl_memory\s*\]\s*$', re.MULTILINE
|
|
369
|
+
)
|
|
370
|
+
NEXT_TABLE_RE = re.compile(r'^\s*\[', re.MULTILINE)
|
|
371
|
+
SIBYL_COMMAND_RE = re.compile(
|
|
372
|
+
r'^\s*command\s*=\s*"sibyl-memory-mcp"\s*$', re.MULTILINE
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
def __init__(self, *, config_path: Optional[Union[str, Path]] = None):
|
|
376
|
+
self.config_path = (
|
|
377
|
+
Path(config_path).expanduser() if config_path
|
|
378
|
+
else Path.home() / ".codex" / "config.toml"
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
def is_present(self) -> bool:
|
|
382
|
+
if self.config_path.exists():
|
|
383
|
+
return True
|
|
384
|
+
if shutil.which("codex"):
|
|
385
|
+
return True
|
|
386
|
+
return False
|
|
387
|
+
|
|
388
|
+
def _read(self) -> str:
|
|
389
|
+
if not self.config_path.exists():
|
|
390
|
+
return ""
|
|
391
|
+
try:
|
|
392
|
+
return self.config_path.read_text(encoding="utf-8")
|
|
393
|
+
except Exception:
|
|
394
|
+
return ""
|
|
395
|
+
|
|
396
|
+
def _extract_block(self, content: str) -> Optional[str]:
|
|
397
|
+
m = self.BLOCK_HEADER_RE.search(content)
|
|
398
|
+
if not m:
|
|
399
|
+
return None
|
|
400
|
+
# Find next [...] table header after our match (start-of-line "[").
|
|
401
|
+
next_tbl = self.NEXT_TABLE_RE.search(content, m.end())
|
|
402
|
+
end = next_tbl.start() if next_tbl else len(content)
|
|
403
|
+
return content[m.start():end]
|
|
404
|
+
|
|
405
|
+
def current_state(self) -> dict:
|
|
406
|
+
content = self._read()
|
|
407
|
+
block = self._extract_block(content)
|
|
408
|
+
sibyl_block_present = block is not None
|
|
409
|
+
wired_with_sibyl = bool(block and self.SIBYL_COMMAND_RE.search(block))
|
|
410
|
+
return {
|
|
411
|
+
"config_path": str(self.config_path),
|
|
412
|
+
"config_exists": self.config_path.exists(),
|
|
413
|
+
"sibyl_block_present": sibyl_block_present,
|
|
414
|
+
"wired_with_sibyl": wired_with_sibyl,
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
def wire(self, *, force: bool = False, dry_run: bool = False,
|
|
418
|
+
prompt_fn: Optional[Callable[..., str]] = None) -> WireOutcome:
|
|
419
|
+
state = self.current_state()
|
|
420
|
+
|
|
421
|
+
if state["wired_with_sibyl"]:
|
|
422
|
+
return WireOutcome(
|
|
423
|
+
self.name, "already",
|
|
424
|
+
f"Codex CLI already has [mcp_servers.sibyl_memory] block in {self.config_path}",
|
|
425
|
+
)
|
|
426
|
+
|
|
427
|
+
if state["sibyl_block_present"] and not force:
|
|
428
|
+
if prompt_fn is None:
|
|
429
|
+
return WireOutcome(
|
|
430
|
+
self.name, "skipped",
|
|
431
|
+
"Existing [mcp_servers.sibyl_memory] differs. Use --force to overwrite.",
|
|
432
|
+
)
|
|
433
|
+
ans = prompt_fn(
|
|
434
|
+
"Codex has [mcp_servers.sibyl_memory] but command differs. Update?",
|
|
435
|
+
default="N",
|
|
436
|
+
)
|
|
437
|
+
if ans != "y":
|
|
438
|
+
return WireOutcome(self.name, "skipped", "TOML block update declined.")
|
|
439
|
+
|
|
440
|
+
if dry_run:
|
|
441
|
+
verb = "replace" if state["sibyl_block_present"] else "append"
|
|
442
|
+
return WireOutcome(
|
|
443
|
+
self.name, "dry-run",
|
|
444
|
+
f"Would {verb} [mcp_servers.sibyl_memory] in {self.config_path}",
|
|
445
|
+
)
|
|
446
|
+
|
|
447
|
+
backup = self._backup_config()
|
|
448
|
+
try:
|
|
449
|
+
self._write_config_with_sibyl(replace_existing=state["sibyl_block_present"])
|
|
450
|
+
except Exception as e:
|
|
451
|
+
return WireOutcome(
|
|
452
|
+
self.name, "error",
|
|
453
|
+
f"config write failed: {type(e).__name__}: {e}",
|
|
454
|
+
backup_path=backup,
|
|
455
|
+
)
|
|
456
|
+
return WireOutcome(
|
|
457
|
+
self.name, "wired",
|
|
458
|
+
f"Added [mcp_servers.sibyl_memory] to {self.config_path}",
|
|
459
|
+
backup_path=backup,
|
|
460
|
+
)
|
|
461
|
+
|
|
462
|
+
def _backup_config(self) -> Optional[Path]:
|
|
463
|
+
if not self.config_path.exists():
|
|
464
|
+
return None
|
|
465
|
+
backup = self.config_path.with_suffix(".toml.bak")
|
|
466
|
+
shutil.copy2(self.config_path, backup)
|
|
467
|
+
return backup
|
|
468
|
+
|
|
469
|
+
def _write_config_with_sibyl(self, *, replace_existing: bool) -> None:
|
|
470
|
+
content = self._read()
|
|
471
|
+
|
|
472
|
+
if replace_existing:
|
|
473
|
+
existing = self._extract_block(content) or ""
|
|
474
|
+
# Rstrip the existing block to a single newline before splice so
|
|
475
|
+
# we don't accumulate blank lines on repeat replaces.
|
|
476
|
+
stripped = existing.rstrip("\n")
|
|
477
|
+
new_content = content.replace(stripped, self.SIBYL_TOML_BLOCK.rstrip("\n"), 1)
|
|
478
|
+
# Ensure file ends with a newline
|
|
479
|
+
if not new_content.endswith("\n"):
|
|
480
|
+
new_content += "\n"
|
|
481
|
+
else:
|
|
482
|
+
# Append: one blank line of separator if file is non-empty
|
|
483
|
+
if not content:
|
|
484
|
+
new_content = self.SIBYL_TOML_BLOCK
|
|
485
|
+
else:
|
|
486
|
+
# Normalize trailing whitespace to exactly "\n\n"
|
|
487
|
+
trimmed = content.rstrip("\n") + "\n\n"
|
|
488
|
+
new_content = trimmed + self.SIBYL_TOML_BLOCK
|
|
489
|
+
|
|
490
|
+
self.config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
491
|
+
tmp = self.config_path.with_suffix(".toml.tmp")
|
|
492
|
+
tmp.write_text(new_content, encoding="utf-8")
|
|
493
|
+
os.replace(tmp, self.config_path)
|
|
494
|
+
|
|
495
|
+
|
|
336
496
|
# ----------------------------------------------------------------------
|
|
337
497
|
# Registry + dispatch
|
|
338
498
|
# ----------------------------------------------------------------------
|
|
@@ -340,6 +500,7 @@ class ClaudeCodeWirer:
|
|
|
340
500
|
ALL_WIRERS: dict = {
|
|
341
501
|
"hermes": HermesWirer,
|
|
342
502
|
"claude-code": ClaudeCodeWirer,
|
|
503
|
+
"codex": CodexWirer,
|
|
343
504
|
}
|
|
344
505
|
|
|
345
506
|
|
|
@@ -366,6 +527,8 @@ def _wirer_kwargs(args: argparse.Namespace, name: str) -> dict:
|
|
|
366
527
|
kw["hermes_home"] = args.hermes_home
|
|
367
528
|
if name == "claude-code" and getattr(args, "claude_settings", None):
|
|
368
529
|
kw["settings_path"] = args.claude_settings
|
|
530
|
+
if name == "codex" and getattr(args, "codex_config", None):
|
|
531
|
+
kw["config_path"] = args.codex_config
|
|
369
532
|
return kw
|
|
370
533
|
|
|
371
534
|
|
|
@@ -402,12 +565,13 @@ def cmd_setup(args: argparse.Namespace) -> int:
|
|
|
402
565
|
print(dim("Looked for:"))
|
|
403
566
|
for name, w in wirers.items():
|
|
404
567
|
st = w.current_state()
|
|
405
|
-
loc = st.get("hermes_home") or st.get("settings_path")
|
|
568
|
+
loc = st.get("hermes_home") or st.get("settings_path") or st.get("config_path")
|
|
406
569
|
print(f" {w.display_name}: {loc}")
|
|
407
570
|
print()
|
|
408
571
|
print(dim("To override detection, point setup at a custom path:"))
|
|
409
572
|
print(f" {cyan('sibyl setup --hermes-home /custom/path')}")
|
|
410
|
-
print(f" {cyan('sibyl setup --claude-settings /custom
|
|
573
|
+
print(f" {cyan('sibyl setup --claude-settings /custom/.claude.json')}")
|
|
574
|
+
print(f" {cyan('sibyl setup --codex-config /custom/config.toml')}")
|
|
411
575
|
print()
|
|
412
576
|
return 0
|
|
413
577
|
|
|
@@ -415,7 +579,7 @@ def cmd_setup(args: argparse.Namespace) -> int:
|
|
|
415
579
|
print(dim("Detected:"))
|
|
416
580
|
for name, w in detected.items():
|
|
417
581
|
st = w.current_state()
|
|
418
|
-
loc = st.get("hermes_home") or st.get("settings_path")
|
|
582
|
+
loc = st.get("hermes_home") or st.get("settings_path") or st.get("config_path")
|
|
419
583
|
print(f" {w.display_name} at {loc}")
|
|
420
584
|
print()
|
|
421
585
|
|
|
@@ -449,11 +613,14 @@ def cmd_setup(args: argparse.Namespace) -> int:
|
|
|
449
613
|
|
|
450
614
|
# Pre-prompt for fresh adds (interactive only). Already-wired and
|
|
451
615
|
# existing-other-provider are handled inside wire() itself.
|
|
616
|
+
# State keys per wirer: hermes → memory_provider; claude-code →
|
|
617
|
+
# sibyl_mcp; codex → sibyl_block_present.
|
|
452
618
|
if (
|
|
453
619
|
not args.yes
|
|
454
620
|
and not st.get("wired_with_sibyl")
|
|
455
621
|
and not st.get("memory_provider")
|
|
456
622
|
and not st.get("sibyl_mcp")
|
|
623
|
+
and not st.get("sibyl_block_present")
|
|
457
624
|
):
|
|
458
625
|
if name == "hermes":
|
|
459
626
|
q = f"Set SIBYL as default memory provider in {wirer.display_name}?"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sibyl-memory-cli
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.7
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sibyl_memory_cli-0.3.6 → sibyl_memory_cli-0.3.7}/src/sibyl_memory_cli.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{sibyl_memory_cli-0.3.6 → sibyl_memory_cli-0.3.7}/src/sibyl_memory_cli.egg-info/entry_points.txt
RENAMED
|
File without changes
|
{sibyl_memory_cli-0.3.6 → sibyl_memory_cli-0.3.7}/src/sibyl_memory_cli.egg-info/requires.txt
RENAMED
|
File without changes
|
{sibyl_memory_cli-0.3.6 → sibyl_memory_cli-0.3.7}/src/sibyl_memory_cli.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|