loreconvo-cli 0.1.0a1__py3-none-any.whl
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.
- loreconvo_cli/__init__.py +3 -0
- loreconvo_cli/_db_path.py +114 -0
- loreconvo_cli/_platform_check.py +33 -0
- loreconvo_cli/_schema_compat.py +40 -0
- loreconvo_cli/_write_guard.py +21 -0
- loreconvo_cli/main.py +239 -0
- loreconvo_cli-0.1.0a1.dist-info/METADATA +163 -0
- loreconvo_cli-0.1.0a1.dist-info/RECORD +12 -0
- loreconvo_cli-0.1.0a1.dist-info/WHEEL +5 -0
- loreconvo_cli-0.1.0a1.dist-info/entry_points.txt +2 -0
- loreconvo_cli-0.1.0a1.dist-info/licenses/LICENSE +93 -0
- loreconvo_cli-0.1.0a1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import re
|
|
3
|
+
import sqlite3
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
DEFAULT_LORECONVO_DIR = Path.home() / ".loreconvo"
|
|
8
|
+
DEFAULT_DB_PATH = DEFAULT_LORECONVO_DIR / "sessions.db"
|
|
9
|
+
|
|
10
|
+
_KNOWN_DB_DIRS = frozenset({
|
|
11
|
+
str(Path.home() / ".loreconvo"),
|
|
12
|
+
str(Path.home() / ".loredocs"),
|
|
13
|
+
str(Path.home() / ".lorecap"),
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _sanitize_fts5(query: str) -> str:
|
|
18
|
+
"""Strip FTS5 special chars to prevent injection. Returns sanitized query."""
|
|
19
|
+
sanitized = re.sub(r'["\*\^\(\)\[\]]', ' ', query).strip()
|
|
20
|
+
return sanitized if sanitized else '""'
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def validate_db_path(path_str: str) -> Path:
|
|
24
|
+
"""Validate a --db-path argument.
|
|
25
|
+
|
|
26
|
+
Raises SystemExit(1) with a clear message on violation rather than
|
|
27
|
+
typer.BadParameter so it works outside typer context (tests, direct calls).
|
|
28
|
+
"""
|
|
29
|
+
import typer
|
|
30
|
+
|
|
31
|
+
p = Path(path_str).expanduser().resolve()
|
|
32
|
+
|
|
33
|
+
if p.suffix != ".db":
|
|
34
|
+
raise typer.BadParameter(f"DB path must end in .db (got: {p.suffix!r})")
|
|
35
|
+
|
|
36
|
+
home = Path.home().resolve()
|
|
37
|
+
try:
|
|
38
|
+
p.relative_to(home)
|
|
39
|
+
except ValueError:
|
|
40
|
+
if not os.environ.get("LORECONVO_ALLOW_EXTERNAL_DB"):
|
|
41
|
+
raise typer.BadParameter(
|
|
42
|
+
f"DB path must be under {home}. "
|
|
43
|
+
"Set LORECONVO_ALLOW_EXTERNAL_DB=1 to override (CI/testing only)."
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
if str(p.parent) not in _KNOWN_DB_DIRS:
|
|
47
|
+
if not os.environ.get("LORECONVO_ALLOW_EXTERNAL_DB"):
|
|
48
|
+
raise typer.BadParameter(
|
|
49
|
+
f"DB must be in one of: {sorted(_KNOWN_DB_DIRS)}.\n"
|
|
50
|
+
"Set LORECONVO_ALLOW_EXTERNAL_DB=1 to use a custom directory."
|
|
51
|
+
)
|
|
52
|
+
print(f"[warn] external DB path in use: {p}", file=sys.stderr)
|
|
53
|
+
|
|
54
|
+
if os.environ.get("LORECONVO_ALLOW_EXTERNAL_DB"):
|
|
55
|
+
print(
|
|
56
|
+
"[warn] LORECONVO_ALLOW_EXTERNAL_DB is set; external DB path override is active.",
|
|
57
|
+
file=sys.stderr,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
if p.exists() and hasattr(os, "getuid"):
|
|
61
|
+
if os.stat(p).st_uid != os.getuid():
|
|
62
|
+
raise typer.BadParameter(f"DB at {p} is not owned by current user.")
|
|
63
|
+
|
|
64
|
+
if p.exists() and not p.is_file():
|
|
65
|
+
raise typer.BadParameter(f"{p} exists but is not a file.")
|
|
66
|
+
|
|
67
|
+
return p
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def get_db_path(custom_path: str | None = None) -> Path:
|
|
71
|
+
"""Resolve DB path: --db-path arg > LORECONVO_DB_PATH env > default."""
|
|
72
|
+
if custom_path:
|
|
73
|
+
return validate_db_path(custom_path)
|
|
74
|
+
env_path = os.environ.get("LORECONVO_DB_PATH")
|
|
75
|
+
if env_path:
|
|
76
|
+
return validate_db_path(env_path)
|
|
77
|
+
return DEFAULT_DB_PATH
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def open_db(db_path: Path) -> sqlite3.Connection:
|
|
81
|
+
"""Open a read-only SQLite connection with WAL mode and permissions check.
|
|
82
|
+
|
|
83
|
+
Exits with a helpful error if the DB does not exist.
|
|
84
|
+
"""
|
|
85
|
+
if not db_path.exists():
|
|
86
|
+
print(
|
|
87
|
+
f"\nError: LoreConvo database not found at {db_path}\n\n"
|
|
88
|
+
"The database is created automatically when you first use the LoreConvo MCP server.\n\n"
|
|
89
|
+
"Quick setup:\n"
|
|
90
|
+
" 1. pip install loreconvo-cli[server] # install CLI + server\n"
|
|
91
|
+
" 2. Add loreconvo to your MCP client config # (see: pip show loreconvo)\n"
|
|
92
|
+
" 3. Start a session to initialize the DB # MCP creates DB on first use\n"
|
|
93
|
+
" 4. Then run: loreconvo-cli search 'keyword'\n\n"
|
|
94
|
+
"Or initialize directly: loreconvo-cli server --init\n\n"
|
|
95
|
+
"Set LORECONVO_DB_PATH=/path/to/sessions.db to use a custom DB location.",
|
|
96
|
+
file=sys.stderr,
|
|
97
|
+
)
|
|
98
|
+
raise SystemExit(1)
|
|
99
|
+
|
|
100
|
+
if hasattr(os, "stat"):
|
|
101
|
+
mode = db_path.stat().st_mode & 0o777
|
|
102
|
+
if mode & 0o177:
|
|
103
|
+
print(
|
|
104
|
+
f"[warn] DB file permissions are broader than 0600: {db_path} "
|
|
105
|
+
f"(current: {oct(mode)}). "
|
|
106
|
+
f"Fix with: chmod 600 {db_path}",
|
|
107
|
+
file=sys.stderr,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
conn = sqlite3.connect(str(db_path), timeout=30)
|
|
111
|
+
conn.execute("PRAGMA journal_mode=WAL")
|
|
112
|
+
conn.execute("PRAGMA busy_timeout=30000")
|
|
113
|
+
conn.row_factory = sqlite3.Row
|
|
114
|
+
return conn
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import platform
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
_SUPPORTED = {
|
|
5
|
+
("darwin", "x86_64"),
|
|
6
|
+
("darwin", "arm64"),
|
|
7
|
+
("linux", "x86_64"),
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def check_platform() -> None:
|
|
12
|
+
"""Exit with a clear error on unsupported platforms. Called first in main()."""
|
|
13
|
+
plat = "darwin" if sys.platform == "darwin" else "linux"
|
|
14
|
+
key = (plat, platform.machine())
|
|
15
|
+
if key not in _SUPPORTED:
|
|
16
|
+
print(
|
|
17
|
+
f"ERROR: loreconvo-cli is not supported on {sys.platform}/{platform.machine()}.\n"
|
|
18
|
+
"Supported: macOS (x86_64, arm64), Linux x86_64 (glibc).\n"
|
|
19
|
+
"Windows and musl/Alpine are not supported in v0.1.x.",
|
|
20
|
+
file=sys.stderr,
|
|
21
|
+
)
|
|
22
|
+
raise SystemExit(1)
|
|
23
|
+
if sys.platform == "linux":
|
|
24
|
+
try:
|
|
25
|
+
proc_ver = open("/proc/version").read()
|
|
26
|
+
if "Microsoft" in proc_ver or "microsoft" in proc_ver:
|
|
27
|
+
print(
|
|
28
|
+
"[warn] WSL detected. DB path defaults to WSL filesystem. "
|
|
29
|
+
"Windows-host DB paths are not supported.",
|
|
30
|
+
file=sys.stderr,
|
|
31
|
+
)
|
|
32
|
+
except OSError:
|
|
33
|
+
pass
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import sqlite3
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
# Schema version range this CLI was tested against.
|
|
5
|
+
# user_version 0 = unversioned (pre-migration DBs) -- treated as compatible.
|
|
6
|
+
CLI_MIN_SCHEMA_VERSION = 0
|
|
7
|
+
CLI_MAX_WRITE_VERSION = 8
|
|
8
|
+
CLI_MAX_READ_VERSION = 10
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def check_schema_compatibility(conn: sqlite3.Connection) -> str:
|
|
12
|
+
"""Check PRAGMA user_version against supported range.
|
|
13
|
+
|
|
14
|
+
Returns 'normal' or 'read_only'. Exits on incompatible schema.
|
|
15
|
+
"""
|
|
16
|
+
try:
|
|
17
|
+
version = conn.execute("PRAGMA user_version").fetchone()[0]
|
|
18
|
+
except sqlite3.Error:
|
|
19
|
+
version = 0
|
|
20
|
+
|
|
21
|
+
if version > CLI_MAX_READ_VERSION:
|
|
22
|
+
print(
|
|
23
|
+
f"Error: DB schema v{version} is newer than loreconvo-cli supports "
|
|
24
|
+
f"(max readable: {CLI_MAX_READ_VERSION}).\n"
|
|
25
|
+
"Upgrade loreconvo-cli: pip install -U loreconvo-cli",
|
|
26
|
+
file=sys.stderr,
|
|
27
|
+
)
|
|
28
|
+
raise SystemExit(1)
|
|
29
|
+
|
|
30
|
+
if version > CLI_MAX_WRITE_VERSION:
|
|
31
|
+
print(
|
|
32
|
+
f"[warn] DB schema v{version} is newer than CLI write support "
|
|
33
|
+
f"(max writable: {CLI_MAX_WRITE_VERSION}). "
|
|
34
|
+
"Running in read-only mode. "
|
|
35
|
+
"Upgrade loreconvo-cli[server] for full write support.",
|
|
36
|
+
file=sys.stderr,
|
|
37
|
+
)
|
|
38
|
+
return "read_only"
|
|
39
|
+
|
|
40
|
+
return "normal"
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import importlib.util
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def _has_server_package() -> bool:
|
|
6
|
+
"""Check if the loreconvo server package is installed."""
|
|
7
|
+
return importlib.util.find_spec("loreconvo") is not None
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def require_write_capability(operation: str) -> None:
|
|
11
|
+
"""Exit with a clear error if loreconvo server package is not installed.
|
|
12
|
+
|
|
13
|
+
Call this as the first line of every write command handler before any DB access.
|
|
14
|
+
"""
|
|
15
|
+
if not _has_server_package():
|
|
16
|
+
print(
|
|
17
|
+
f"Error: '{operation}' requires write access.\n"
|
|
18
|
+
"Install the server package: pip install loreconvo-cli[server]",
|
|
19
|
+
file=sys.stderr,
|
|
20
|
+
)
|
|
21
|
+
raise SystemExit(1)
|
loreconvo_cli/main.py
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
"""LoreConvo CLI -- cross-surface session memory command line interface.
|
|
2
|
+
|
|
3
|
+
Free-tier operations only. Pro features require the LoreConvo MCP server.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import enum
|
|
7
|
+
import json
|
|
8
|
+
import re
|
|
9
|
+
import sys
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import List, Optional
|
|
12
|
+
|
|
13
|
+
import typer
|
|
14
|
+
|
|
15
|
+
from ._db_path import get_db_path, open_db, _sanitize_fts5
|
|
16
|
+
from ._platform_check import check_platform
|
|
17
|
+
from ._schema_compat import check_schema_compatibility
|
|
18
|
+
from ._write_guard import require_write_capability
|
|
19
|
+
|
|
20
|
+
app = typer.Typer(
|
|
21
|
+
name="loreconvo-cli",
|
|
22
|
+
help=(
|
|
23
|
+
"LoreConvo -- cross-surface session memory.\n\n"
|
|
24
|
+
"Free-tier CLI (read + save operations).\n"
|
|
25
|
+
"Pro features require the MCP server: pip install loreconvo-cli[server]"
|
|
26
|
+
),
|
|
27
|
+
no_args_is_help=True,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class FeatureSet(str, enum.Enum):
|
|
32
|
+
FREE = "free"
|
|
33
|
+
PRO = "pro"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _get_feature_set() -> FeatureSet:
|
|
37
|
+
"""Return the active feature set. v0.1.x always returns FREE."""
|
|
38
|
+
return FeatureSet.FREE
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
_DB_PATH_OPTION = typer.Option(
|
|
42
|
+
None, "--db-path", help="Path to sessions.db (overrides LORECONVO_DB_PATH)"
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@app.command()
|
|
47
|
+
def search(
|
|
48
|
+
query: str = typer.Argument(..., help="Search query (full-text)"),
|
|
49
|
+
limit: int = typer.Option(20, "--limit", "-n", help="Max results"),
|
|
50
|
+
offset: int = typer.Option(0, "--offset", help="Skip first N results"),
|
|
51
|
+
project: Optional[str] = typer.Option(None, "--project", "-p", help="Filter by project"),
|
|
52
|
+
db_path: Optional[str] = _DB_PATH_OPTION,
|
|
53
|
+
) -> None:
|
|
54
|
+
"""Search sessions by keyword."""
|
|
55
|
+
path = get_db_path(db_path)
|
|
56
|
+
conn = open_db(path)
|
|
57
|
+
check_schema_compatibility(conn)
|
|
58
|
+
|
|
59
|
+
safe_q = _sanitize_fts5(query)
|
|
60
|
+
like_q = f"%{query}%"
|
|
61
|
+
try:
|
|
62
|
+
if project:
|
|
63
|
+
rows = conn.execute(
|
|
64
|
+
"SELECT id, title, surface, project, start_date, summary FROM sessions "
|
|
65
|
+
"WHERE project = ? AND (title LIKE ? OR summary LIKE ?) "
|
|
66
|
+
"ORDER BY start_date DESC LIMIT ? OFFSET ?",
|
|
67
|
+
(project, like_q, like_q, limit, offset),
|
|
68
|
+
).fetchall()
|
|
69
|
+
else:
|
|
70
|
+
rows = conn.execute(
|
|
71
|
+
"SELECT id, title, surface, project, start_date, summary FROM sessions "
|
|
72
|
+
"WHERE title LIKE ? OR summary LIKE ? OR tags LIKE ? "
|
|
73
|
+
"ORDER BY start_date DESC LIMIT ? OFFSET ?",
|
|
74
|
+
(like_q, like_q, like_q, limit, offset),
|
|
75
|
+
).fetchall()
|
|
76
|
+
finally:
|
|
77
|
+
conn.close()
|
|
78
|
+
|
|
79
|
+
if not rows:
|
|
80
|
+
typer.echo(f"No sessions found for: {query!r}")
|
|
81
|
+
return
|
|
82
|
+
for row in rows:
|
|
83
|
+
_print_session_line(dict(row))
|
|
84
|
+
typer.echo(f"\n{len(rows)} result(s)")
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@app.command(name="list")
|
|
88
|
+
def list_sessions(
|
|
89
|
+
limit: int = typer.Option(10, "--limit", "-n", help="Max sessions to show"),
|
|
90
|
+
offset: int = typer.Option(0, "--offset", help="Skip first N sessions"),
|
|
91
|
+
project: Optional[str] = typer.Option(None, "--project", "-p", help="Filter by project"),
|
|
92
|
+
db_path: Optional[str] = _DB_PATH_OPTION,
|
|
93
|
+
) -> None:
|
|
94
|
+
"""List recent sessions."""
|
|
95
|
+
path = get_db_path(db_path)
|
|
96
|
+
conn = open_db(path)
|
|
97
|
+
check_schema_compatibility(conn)
|
|
98
|
+
|
|
99
|
+
try:
|
|
100
|
+
if project:
|
|
101
|
+
rows = conn.execute(
|
|
102
|
+
"SELECT id, title, surface, project, start_date FROM sessions "
|
|
103
|
+
"WHERE project = ? ORDER BY start_date DESC LIMIT ? OFFSET ?",
|
|
104
|
+
(project, limit, offset),
|
|
105
|
+
).fetchall()
|
|
106
|
+
else:
|
|
107
|
+
rows = conn.execute(
|
|
108
|
+
"SELECT id, title, surface, project, start_date FROM sessions "
|
|
109
|
+
"ORDER BY start_date DESC LIMIT ? OFFSET ?",
|
|
110
|
+
(limit, offset),
|
|
111
|
+
).fetchall()
|
|
112
|
+
finally:
|
|
113
|
+
conn.close()
|
|
114
|
+
|
|
115
|
+
if not rows:
|
|
116
|
+
typer.echo("No sessions found.")
|
|
117
|
+
return
|
|
118
|
+
for row in rows:
|
|
119
|
+
_print_session_line(dict(row))
|
|
120
|
+
typer.echo(f"\n{len(rows)} session(s)")
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@app.command()
|
|
124
|
+
def read(
|
|
125
|
+
id: Optional[str] = typer.Option(None, "--id", help="Session UUID"),
|
|
126
|
+
limit: int = typer.Option(5, "--limit", "-n", help="Sessions to show (without --id)"),
|
|
127
|
+
db_path: Optional[str] = _DB_PATH_OPTION,
|
|
128
|
+
) -> None:
|
|
129
|
+
"""Read one session by UUID, or list recent sessions with full detail."""
|
|
130
|
+
path = get_db_path(db_path)
|
|
131
|
+
conn = open_db(path)
|
|
132
|
+
check_schema_compatibility(conn)
|
|
133
|
+
|
|
134
|
+
try:
|
|
135
|
+
if id:
|
|
136
|
+
if not re.fullmatch(r"[0-9a-zA-Z\-]{1,64}", id):
|
|
137
|
+
typer.echo("Error: --id must be a valid session UUID.", err=True)
|
|
138
|
+
raise typer.Exit(1)
|
|
139
|
+
row = conn.execute(
|
|
140
|
+
"SELECT id, title, surface, project, start_date, summary, decisions, "
|
|
141
|
+
"open_questions, tags FROM sessions WHERE id = ?",
|
|
142
|
+
(id,),
|
|
143
|
+
).fetchone()
|
|
144
|
+
if not row:
|
|
145
|
+
typer.echo(f"Session not found: {id!r}", err=True)
|
|
146
|
+
raise typer.Exit(1)
|
|
147
|
+
_print_session_detail(dict(row))
|
|
148
|
+
else:
|
|
149
|
+
rows = conn.execute(
|
|
150
|
+
"SELECT id, title, surface, project, start_date, summary, decisions, "
|
|
151
|
+
"open_questions, tags FROM sessions ORDER BY start_date DESC LIMIT ?",
|
|
152
|
+
(limit,),
|
|
153
|
+
).fetchall()
|
|
154
|
+
for row in rows:
|
|
155
|
+
_print_session_detail(dict(row))
|
|
156
|
+
finally:
|
|
157
|
+
conn.close()
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
@app.command()
|
|
161
|
+
def save(
|
|
162
|
+
title: str = typer.Option(..., "--title", "-t", help="Session title"),
|
|
163
|
+
surface: str = typer.Option(..., "--surface", "-s", help="Surface (code/cowork/chat)"),
|
|
164
|
+
summary: str = typer.Option(..., "--summary", "-m", help="Session summary"),
|
|
165
|
+
project: Optional[str] = typer.Option(None, "--project", "-p", help="Project name"),
|
|
166
|
+
tags: Optional[List[str]] = typer.Option(None, "--tags", help="Tags (repeatable)"),
|
|
167
|
+
db_path: Optional[str] = _DB_PATH_OPTION,
|
|
168
|
+
) -> None:
|
|
169
|
+
"""Save a session. Requires loreconvo-cli[server]."""
|
|
170
|
+
require_write_capability("save")
|
|
171
|
+
|
|
172
|
+
from loreconvo.core.database import SessionDatabase
|
|
173
|
+
from loreconvo.core.models import Session
|
|
174
|
+
from loreconvo.core.config import Config
|
|
175
|
+
|
|
176
|
+
path = get_db_path(db_path)
|
|
177
|
+
if db_path:
|
|
178
|
+
import os
|
|
179
|
+
os.environ["LORECONVO_DB_PATH"] = str(path)
|
|
180
|
+
|
|
181
|
+
cfg = Config()
|
|
182
|
+
db = SessionDatabase(cfg)
|
|
183
|
+
session = Session(
|
|
184
|
+
title=title,
|
|
185
|
+
surface=surface,
|
|
186
|
+
summary=summary,
|
|
187
|
+
project=project,
|
|
188
|
+
tags=list(tags) if tags else [],
|
|
189
|
+
)
|
|
190
|
+
session_id = db.save_session(session)
|
|
191
|
+
typer.echo(f"[OK] Saved session: {session_id}")
|
|
192
|
+
typer.echo(f" Title: {title}")
|
|
193
|
+
if project:
|
|
194
|
+
typer.echo(f" Project: {project}")
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def _print_session_line(row: dict) -> None:
|
|
198
|
+
project_str = f" [{row['project']}]" if row.get("project") else ""
|
|
199
|
+
date_str = str(row.get("start_date", ""))[:10]
|
|
200
|
+
typer.echo(f" {date_str} {str(row.get('surface', '')):8s}{project_str} {row.get('title', '')}")
|
|
201
|
+
typer.echo(f" id: {row.get('id', '')}")
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _print_session_detail(row: dict) -> None:
|
|
205
|
+
typer.echo(f"\n--- {row.get('title', '')} ---")
|
|
206
|
+
typer.echo(f" id: {row.get('id', '')}")
|
|
207
|
+
typer.echo(f" date: {str(row.get('start_date', ''))[:10]}")
|
|
208
|
+
typer.echo(f" surface: {row.get('surface', '')}")
|
|
209
|
+
if row.get("project"):
|
|
210
|
+
typer.echo(f" project: {row['project']}")
|
|
211
|
+
if row.get("summary"):
|
|
212
|
+
typer.echo(f" summary: {row['summary'][:300]}")
|
|
213
|
+
if row.get("decisions"):
|
|
214
|
+
try:
|
|
215
|
+
decisions = json.loads(row["decisions"]) if isinstance(row["decisions"], str) else row["decisions"]
|
|
216
|
+
if decisions:
|
|
217
|
+
typer.echo(" decisions:")
|
|
218
|
+
for d in (decisions or [])[:3]:
|
|
219
|
+
typer.echo(f" - {d}")
|
|
220
|
+
except (json.JSONDecodeError, TypeError):
|
|
221
|
+
pass
|
|
222
|
+
if row.get("open_questions"):
|
|
223
|
+
try:
|
|
224
|
+
qs = json.loads(row["open_questions"]) if isinstance(row["open_questions"], str) else row["open_questions"]
|
|
225
|
+
if qs:
|
|
226
|
+
typer.echo(" open_questions:")
|
|
227
|
+
for q in (qs or [])[:3]:
|
|
228
|
+
typer.echo(f" ? {q}")
|
|
229
|
+
except (json.JSONDecodeError, TypeError):
|
|
230
|
+
pass
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def main() -> None:
|
|
234
|
+
check_platform()
|
|
235
|
+
app()
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
if __name__ == "__main__":
|
|
239
|
+
main()
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: loreconvo-cli
|
|
3
|
+
Version: 0.1.0a1
|
|
4
|
+
Summary: Command-line interface for LoreConvo cross-surface session memory
|
|
5
|
+
Author: Labyrinth Analytics Consulting
|
|
6
|
+
License: Business Source License 1.1
|
|
7
|
+
|
|
8
|
+
Parameters
|
|
9
|
+
|
|
10
|
+
Licensor: Labyrinth Analytics Consulting
|
|
11
|
+
Licensed Work: LoreConvo v0.3.2
|
|
12
|
+
The Licensed Work is (c) 2026 Labyrinth Analytics Consulting
|
|
13
|
+
Additional Use Grant: You may make personal, non-commercial use of the Licensed Work,
|
|
14
|
+
subject to the following limitation: you may store no more than
|
|
15
|
+
50 sessions in total across all databases on your machine. Any
|
|
16
|
+
use that exceeds this limit, or any commercial use, requires a
|
|
17
|
+
paid license from the Licensor.
|
|
18
|
+
Change Date: 2030-03-31
|
|
19
|
+
Change License: Apache License, Version 2.0
|
|
20
|
+
|
|
21
|
+
For information about alternative licensing arrangements for the Licensed Work,
|
|
22
|
+
please contact: info@labyrinthanalyticsconsulting.com
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
Business Source License 1.1
|
|
27
|
+
|
|
28
|
+
License text copyright (c) 2017 MariaDB Corporation Ab, All Rights Reserved.
|
|
29
|
+
"Business Source License" is a trademark of MariaDB Corporation Ab.
|
|
30
|
+
|
|
31
|
+
Terms
|
|
32
|
+
|
|
33
|
+
The Licensor hereby grants you the right to copy, modify, create derivative
|
|
34
|
+
works, redistribute, and make non-production use of the Licensed Work. The
|
|
35
|
+
Licensor may make an Additional Use Grant, above, permitting limited production
|
|
36
|
+
use.
|
|
37
|
+
|
|
38
|
+
Effective on the Change Date, or the fourth anniversary of the first publicly
|
|
39
|
+
available distribution of a specific version of the Licensed Work under this
|
|
40
|
+
License, whichever comes first, the Licensor hereby grants you rights under
|
|
41
|
+
the terms of the Change License, and the rights granted in the paragraph
|
|
42
|
+
above terminate.
|
|
43
|
+
|
|
44
|
+
If your use of the Licensed Work does not comply with the requirements
|
|
45
|
+
currently in effect as described in this License, you must purchase a
|
|
46
|
+
commercial license from the Licensor, its affiliated entities, or authorized
|
|
47
|
+
resellers, or you must refrain from using the Licensed Work.
|
|
48
|
+
|
|
49
|
+
All copies of the original and modified Licensed Work, and derivative works
|
|
50
|
+
of the Licensed Work, are subject to this License. This License applies
|
|
51
|
+
separately for each version of the Licensed Work and the Change Date may vary
|
|
52
|
+
for each version of the Licensed Work released by Licensor.
|
|
53
|
+
|
|
54
|
+
You must conspicuously display this License on each original or modified copy
|
|
55
|
+
of the Licensed Work. If you receive a copy of the Licensed Work in
|
|
56
|
+
combination with other programs, as part of a larger work, or packaged by a
|
|
57
|
+
third party, and you receive the Licensed Work under a different license from
|
|
58
|
+
the one described in this License, you are required to comply with the license
|
|
59
|
+
applicable to you.
|
|
60
|
+
|
|
61
|
+
Any use of the Licensed Work in violation of this License will automatically
|
|
62
|
+
terminate your rights under this License for the current and all other
|
|
63
|
+
versions of the Licensed Work.
|
|
64
|
+
|
|
65
|
+
This License does not grant you any right in any trademark or logo of
|
|
66
|
+
Licensor or its affiliates (provided that you may use a trademark or logo of
|
|
67
|
+
Licensor as expressly required by this License).
|
|
68
|
+
|
|
69
|
+
TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON
|
|
70
|
+
AN "AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS,
|
|
71
|
+
EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF
|
|
72
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND
|
|
73
|
+
TITLE.
|
|
74
|
+
|
|
75
|
+
MariaDB hereby grants you permission to use this License's text to license
|
|
76
|
+
your works, and to refer to it using the trademark "Business Source License",
|
|
77
|
+
as long as you comply with the Covenants of Licensor below.
|
|
78
|
+
|
|
79
|
+
Covenants of Licensor
|
|
80
|
+
|
|
81
|
+
In consideration of the right to use this License's text and the "Business
|
|
82
|
+
Source License" name and trademark, Licensor covenants to MariaDB, and to all
|
|
83
|
+
other recipients of the licensed work to be provided under this License:
|
|
84
|
+
|
|
85
|
+
1. To specify as the Change License the GPL Version 2.0 or any later version,
|
|
86
|
+
or a license that is compatible with GPL Version 2.0 or a later version,
|
|
87
|
+
where "compatible" means that software provided under the Change License can
|
|
88
|
+
be included in a program with software provided under GPL Version 2.0 or a
|
|
89
|
+
later version. Licensor may specify additional Change Licenses without
|
|
90
|
+
limitation.
|
|
91
|
+
|
|
92
|
+
2. To either: (a) specify an additional grant of rights to use that does not
|
|
93
|
+
impose any additional restriction on the right granted in this License, as
|
|
94
|
+
the Additional Use Grant; or (b) insert the text "None".
|
|
95
|
+
|
|
96
|
+
3. To specify a Change Date.
|
|
97
|
+
|
|
98
|
+
4. Not to modify this License in any other way.
|
|
99
|
+
|
|
100
|
+
Project-URL: Homepage, https://github.com/labyrinth-analytics/loreconvo-cli
|
|
101
|
+
Project-URL: Repository, https://github.com/labyrinth-analytics/loreconvo-cli
|
|
102
|
+
Project-URL: Bug Tracker, https://github.com/labyrinth-analytics/loreconvo-cli/issues
|
|
103
|
+
Keywords: mcp,claude,memory,cli,sessions,ai-tools,claude-plugin
|
|
104
|
+
Classifier: Development Status :: 3 - Alpha
|
|
105
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
106
|
+
Classifier: License :: Other/Proprietary License
|
|
107
|
+
Classifier: Programming Language :: Python :: 3
|
|
108
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
109
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
110
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
111
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
112
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
113
|
+
Classifier: Operating System :: MacOS :: MacOS X
|
|
114
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
115
|
+
Requires-Python: >=3.10
|
|
116
|
+
Description-Content-Type: text/markdown
|
|
117
|
+
License-File: LICENSE
|
|
118
|
+
Requires-Dist: typer==0.13.1
|
|
119
|
+
Provides-Extra: server
|
|
120
|
+
Requires-Dist: loreconvo==0.7.1; extra == "server"
|
|
121
|
+
Provides-Extra: dev
|
|
122
|
+
Requires-Dist: pytest==8.3.4; extra == "dev"
|
|
123
|
+
Dynamic: license-file
|
|
124
|
+
|
|
125
|
+
# loreconvo-cli v0.1.0
|
|
126
|
+
|
|
127
|
+
Command-line interface for LoreConvo session memory.
|
|
128
|
+
|
|
129
|
+
Search, list, read, and save your LoreConvo sessions from the terminal -- no MCP
|
|
130
|
+
server or Claude session required.
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
pip install loreconvo-cli
|
|
134
|
+
|
|
135
|
+
loreconvo-cli search "rental property"
|
|
136
|
+
loreconvo-cli list --project side_hustle
|
|
137
|
+
loreconvo-cli read --id <session-uuid>
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Commands
|
|
141
|
+
|
|
142
|
+
| Command | Free tier | Description |
|
|
143
|
+
|---------|-----------|-------------|
|
|
144
|
+
| `search <query>` | Yes | Full-text search across sessions |
|
|
145
|
+
| `list` | Yes | List recent sessions |
|
|
146
|
+
| `read` | Yes | Read session detail (full summary + decisions) |
|
|
147
|
+
| `save` | [server] extra | Save a new session |
|
|
148
|
+
|
|
149
|
+
The `save` command requires `pip install "loreconvo-cli[server]"` (installs the
|
|
150
|
+
`loreconvo` package as a dependency).
|
|
151
|
+
|
|
152
|
+
## Platform
|
|
153
|
+
|
|
154
|
+
macOS (x86_64 + arm64) and Linux (x86_64). Windows is not supported.
|
|
155
|
+
|
|
156
|
+
## Full Documentation
|
|
157
|
+
|
|
158
|
+
See [INSTALL.md](INSTALL.md) for complete usage, flags, environment variables,
|
|
159
|
+
and troubleshooting.
|
|
160
|
+
|
|
161
|
+
## License
|
|
162
|
+
|
|
163
|
+
Business Source License 1.1 -- Labyrinth Analytics Consulting
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
loreconvo_cli/__init__.py,sha256=Xvu0Mv5uMfQCeWf4jAdcttxATQVKz-qtiDqcRUUUfUY,110
|
|
2
|
+
loreconvo_cli/_db_path.py,sha256=cHDW-jYkXjmeUpxFBbE0DH8dfa-Az9bM5INEWlfOzeI,4051
|
|
3
|
+
loreconvo_cli/_platform_check.py,sha256=QHpqphtsUWA_AxAYwmkwAlJWKJA1-2yJzi7v8YmTkqo,1109
|
|
4
|
+
loreconvo_cli/_schema_compat.py,sha256=uDhL58MiG0Bv6i7W5ZRP41ogD-1AThdPqE0_37Qxli4,1273
|
|
5
|
+
loreconvo_cli/_write_guard.py,sha256=DbMZhrEkkXUxUVjsZDT2s45jkcP5f5PS_EUOKWyfC_Q,674
|
|
6
|
+
loreconvo_cli/main.py,sha256=F_z5TPEU87nPdA2POZs4VrlsNkN7N7d0W-k0zFt5zpE,8159
|
|
7
|
+
loreconvo_cli-0.1.0a1.dist-info/licenses/LICENSE,sha256=pB6wEBPx5ySw29_aP-X-f_izOxWbAvD_eCVY5W06ao4,4377
|
|
8
|
+
loreconvo_cli-0.1.0a1.dist-info/METADATA,sha256=xK4Vy4hi0cG-agdMA7Oec160U-HnOF1IFed23Z2rDMQ,7489
|
|
9
|
+
loreconvo_cli-0.1.0a1.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
10
|
+
loreconvo_cli-0.1.0a1.dist-info/entry_points.txt,sha256=bcG70xjZQaCPMQdnFHFq9ZE4huwP4ySYT7qhNSUE0uc,58
|
|
11
|
+
loreconvo_cli-0.1.0a1.dist-info/top_level.txt,sha256=ilMhDcK7iMgsbzfGjF8OV3DIm7Icp-u33nwqxdhKcnY,14
|
|
12
|
+
loreconvo_cli-0.1.0a1.dist-info/RECORD,,
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
Business Source License 1.1
|
|
2
|
+
|
|
3
|
+
Parameters
|
|
4
|
+
|
|
5
|
+
Licensor: Labyrinth Analytics Consulting
|
|
6
|
+
Licensed Work: LoreConvo v0.3.2
|
|
7
|
+
The Licensed Work is (c) 2026 Labyrinth Analytics Consulting
|
|
8
|
+
Additional Use Grant: You may make personal, non-commercial use of the Licensed Work,
|
|
9
|
+
subject to the following limitation: you may store no more than
|
|
10
|
+
50 sessions in total across all databases on your machine. Any
|
|
11
|
+
use that exceeds this limit, or any commercial use, requires a
|
|
12
|
+
paid license from the Licensor.
|
|
13
|
+
Change Date: 2030-03-31
|
|
14
|
+
Change License: Apache License, Version 2.0
|
|
15
|
+
|
|
16
|
+
For information about alternative licensing arrangements for the Licensed Work,
|
|
17
|
+
please contact: info@labyrinthanalyticsconsulting.com
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
Business Source License 1.1
|
|
22
|
+
|
|
23
|
+
License text copyright (c) 2017 MariaDB Corporation Ab, All Rights Reserved.
|
|
24
|
+
"Business Source License" is a trademark of MariaDB Corporation Ab.
|
|
25
|
+
|
|
26
|
+
Terms
|
|
27
|
+
|
|
28
|
+
The Licensor hereby grants you the right to copy, modify, create derivative
|
|
29
|
+
works, redistribute, and make non-production use of the Licensed Work. The
|
|
30
|
+
Licensor may make an Additional Use Grant, above, permitting limited production
|
|
31
|
+
use.
|
|
32
|
+
|
|
33
|
+
Effective on the Change Date, or the fourth anniversary of the first publicly
|
|
34
|
+
available distribution of a specific version of the Licensed Work under this
|
|
35
|
+
License, whichever comes first, the Licensor hereby grants you rights under
|
|
36
|
+
the terms of the Change License, and the rights granted in the paragraph
|
|
37
|
+
above terminate.
|
|
38
|
+
|
|
39
|
+
If your use of the Licensed Work does not comply with the requirements
|
|
40
|
+
currently in effect as described in this License, you must purchase a
|
|
41
|
+
commercial license from the Licensor, its affiliated entities, or authorized
|
|
42
|
+
resellers, or you must refrain from using the Licensed Work.
|
|
43
|
+
|
|
44
|
+
All copies of the original and modified Licensed Work, and derivative works
|
|
45
|
+
of the Licensed Work, are subject to this License. This License applies
|
|
46
|
+
separately for each version of the Licensed Work and the Change Date may vary
|
|
47
|
+
for each version of the Licensed Work released by Licensor.
|
|
48
|
+
|
|
49
|
+
You must conspicuously display this License on each original or modified copy
|
|
50
|
+
of the Licensed Work. If you receive a copy of the Licensed Work in
|
|
51
|
+
combination with other programs, as part of a larger work, or packaged by a
|
|
52
|
+
third party, and you receive the Licensed Work under a different license from
|
|
53
|
+
the one described in this License, you are required to comply with the license
|
|
54
|
+
applicable to you.
|
|
55
|
+
|
|
56
|
+
Any use of the Licensed Work in violation of this License will automatically
|
|
57
|
+
terminate your rights under this License for the current and all other
|
|
58
|
+
versions of the Licensed Work.
|
|
59
|
+
|
|
60
|
+
This License does not grant you any right in any trademark or logo of
|
|
61
|
+
Licensor or its affiliates (provided that you may use a trademark or logo of
|
|
62
|
+
Licensor as expressly required by this License).
|
|
63
|
+
|
|
64
|
+
TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON
|
|
65
|
+
AN "AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS,
|
|
66
|
+
EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF
|
|
67
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND
|
|
68
|
+
TITLE.
|
|
69
|
+
|
|
70
|
+
MariaDB hereby grants you permission to use this License's text to license
|
|
71
|
+
your works, and to refer to it using the trademark "Business Source License",
|
|
72
|
+
as long as you comply with the Covenants of Licensor below.
|
|
73
|
+
|
|
74
|
+
Covenants of Licensor
|
|
75
|
+
|
|
76
|
+
In consideration of the right to use this License's text and the "Business
|
|
77
|
+
Source License" name and trademark, Licensor covenants to MariaDB, and to all
|
|
78
|
+
other recipients of the licensed work to be provided under this License:
|
|
79
|
+
|
|
80
|
+
1. To specify as the Change License the GPL Version 2.0 or any later version,
|
|
81
|
+
or a license that is compatible with GPL Version 2.0 or a later version,
|
|
82
|
+
where "compatible" means that software provided under the Change License can
|
|
83
|
+
be included in a program with software provided under GPL Version 2.0 or a
|
|
84
|
+
later version. Licensor may specify additional Change Licenses without
|
|
85
|
+
limitation.
|
|
86
|
+
|
|
87
|
+
2. To either: (a) specify an additional grant of rights to use that does not
|
|
88
|
+
impose any additional restriction on the right granted in this License, as
|
|
89
|
+
the Additional Use Grant; or (b) insert the text "None".
|
|
90
|
+
|
|
91
|
+
3. To specify a Change Date.
|
|
92
|
+
|
|
93
|
+
4. Not to modify this License in any other way.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
loreconvo_cli
|