loredocs-cli 0.1.0a1__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.
@@ -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,163 @@
1
+ Metadata-Version: 2.4
2
+ Name: loredocs-cli
3
+ Version: 0.1.0a1
4
+ Summary: Command-line interface for LoreDocs knowledge vault management
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/loredocs-cli
101
+ Project-URL: Repository, https://github.com/labyrinth-analytics/loredocs-cli
102
+ Project-URL: Bug Tracker, https://github.com/labyrinth-analytics/loredocs-cli/issues
103
+ Keywords: mcp,claude,knowledge-management,cli,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: loredocs==0.1.7; extra == "server"
121
+ Provides-Extra: dev
122
+ Requires-Dist: pytest==8.3.4; extra == "dev"
123
+ Dynamic: license-file
124
+
125
+ # loredocs-cli v0.1.0
126
+
127
+ Command-line interface for LoreDocs knowledge vaults.
128
+
129
+ List, search, and read your LoreDocs vaults and documents from the terminal --
130
+ no MCP server or Claude session required.
131
+
132
+ ```bash
133
+ pip install loredocs-cli
134
+
135
+ loredocs-cli list
136
+ loredocs-cli info "my vault"
137
+ loredocs-cli search "architecture decision"
138
+ ```
139
+
140
+ ## Commands
141
+
142
+ | Command | Free tier | Description |
143
+ |---------|-----------|-------------|
144
+ | `list` | Yes | List all vaults |
145
+ | `info <vault>` | Yes | Show vault details and document count |
146
+ | `search <query>` | Yes | Search documents by keyword |
147
+ | `add-doc <vault>` | [server] extra | Add a document to a vault |
148
+
149
+ The `add-doc` command requires `pip install "loredocs-cli[server]"` (installs the
150
+ `loredocs` 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,39 @@
1
+ # loredocs-cli v0.1.0
2
+
3
+ Command-line interface for LoreDocs knowledge vaults.
4
+
5
+ List, search, and read your LoreDocs vaults and documents from the terminal --
6
+ no MCP server or Claude session required.
7
+
8
+ ```bash
9
+ pip install loredocs-cli
10
+
11
+ loredocs-cli list
12
+ loredocs-cli info "my vault"
13
+ loredocs-cli search "architecture decision"
14
+ ```
15
+
16
+ ## Commands
17
+
18
+ | Command | Free tier | Description |
19
+ |---------|-----------|-------------|
20
+ | `list` | Yes | List all vaults |
21
+ | `info <vault>` | Yes | Show vault details and document count |
22
+ | `search <query>` | Yes | Search documents by keyword |
23
+ | `add-doc <vault>` | [server] extra | Add a document to a vault |
24
+
25
+ The `add-doc` command requires `pip install "loredocs-cli[server]"` (installs the
26
+ `loredocs` package as a dependency).
27
+
28
+ ## Platform
29
+
30
+ macOS (x86_64 + arm64) and Linux (x86_64). Windows is not supported.
31
+
32
+ ## Full Documentation
33
+
34
+ See [INSTALL.md](INSTALL.md) for complete usage, flags, environment variables,
35
+ and troubleshooting.
36
+
37
+ ## License
38
+
39
+ Business Source License 1.1 -- Labyrinth Analytics Consulting
@@ -0,0 +1,3 @@
1
+ """LoreDocs CLI -- standalone command-line interface for LoreDocs knowledge vaults."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,106 @@
1
+ import os
2
+ import re
3
+ import sqlite3
4
+ import sys
5
+ from pathlib import Path
6
+
7
+ DEFAULT_LOREDOCS_DIR = Path.home() / ".loredocs"
8
+ DEFAULT_DB_PATH = DEFAULT_LOREDOCS_DIR / "loredocs.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."""
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
+ import typer
26
+
27
+ p = Path(path_str).expanduser().resolve()
28
+
29
+ if p.suffix != ".db":
30
+ raise typer.BadParameter(f"DB path must end in .db (got: {p.suffix!r})")
31
+
32
+ home = Path.home().resolve()
33
+ try:
34
+ p.relative_to(home)
35
+ except ValueError:
36
+ if not os.environ.get("LOREDOCS_ALLOW_EXTERNAL_DB"):
37
+ raise typer.BadParameter(
38
+ f"DB path must be under {home}. "
39
+ "Set LOREDOCS_ALLOW_EXTERNAL_DB=1 to override (CI/testing only)."
40
+ )
41
+
42
+ if str(p.parent) not in _KNOWN_DB_DIRS:
43
+ if not os.environ.get("LOREDOCS_ALLOW_EXTERNAL_DB"):
44
+ raise typer.BadParameter(
45
+ f"DB must be in one of: {sorted(_KNOWN_DB_DIRS)}.\n"
46
+ "Set LOREDOCS_ALLOW_EXTERNAL_DB=1 to use a custom directory."
47
+ )
48
+ print(f"[warn] external DB path in use: {p}", file=sys.stderr)
49
+
50
+ if os.environ.get("LOREDOCS_ALLOW_EXTERNAL_DB"):
51
+ print(
52
+ "[warn] LOREDOCS_ALLOW_EXTERNAL_DB is set; external DB path override is active.",
53
+ file=sys.stderr,
54
+ )
55
+
56
+ if p.exists() and hasattr(os, "getuid"):
57
+ if os.stat(p).st_uid != os.getuid():
58
+ raise typer.BadParameter(f"DB at {p} is not owned by current user.")
59
+
60
+ if p.exists() and not p.is_file():
61
+ raise typer.BadParameter(f"{p} exists but is not a file.")
62
+
63
+ return p
64
+
65
+
66
+ def get_db_path(custom_path: str | None = None) -> Path:
67
+ """Resolve DB path: --db-path arg > LOREDOCS_ROOT env > default."""
68
+ if custom_path:
69
+ return validate_db_path(custom_path)
70
+ env_path = os.environ.get("LOREDOCS_ROOT")
71
+ if env_path:
72
+ return Path(env_path).expanduser() / "loredocs.db"
73
+ return DEFAULT_DB_PATH
74
+
75
+
76
+ def open_db(db_path: Path) -> sqlite3.Connection:
77
+ """Open SQLite connection with WAL mode and permissions check."""
78
+ if not db_path.exists():
79
+ print(
80
+ f"\nError: LoreDocs database not found at {db_path}\n\n"
81
+ "The database is created automatically when you first use the LoreDocs MCP server.\n\n"
82
+ "Quick setup:\n"
83
+ " 1. pip install loredocs-cli[server] # install CLI + server\n"
84
+ " 2. Add loredocs to your MCP client config # (see: pip show loredocs)\n"
85
+ " 3. Start a session to initialize the DB # MCP creates DB on first use\n"
86
+ " 4. Then run: loredocs-cli list\n\n"
87
+ "Set LOREDOCS_ROOT=/path/to/loredocs-dir to use a custom data location.",
88
+ file=sys.stderr,
89
+ )
90
+ raise SystemExit(1)
91
+
92
+ if hasattr(os, "stat"):
93
+ mode = db_path.stat().st_mode & 0o777
94
+ if mode & 0o177:
95
+ print(
96
+ f"[warn] DB file permissions are broader than 0600: {db_path} "
97
+ f"(current: {oct(mode)}). "
98
+ f"Fix with: chmod 600 {db_path}",
99
+ file=sys.stderr,
100
+ )
101
+
102
+ conn = sqlite3.connect(str(db_path), timeout=30)
103
+ conn.execute("PRAGMA journal_mode=WAL")
104
+ conn.execute("PRAGMA busy_timeout=30000")
105
+ conn.row_factory = sqlite3.Row
106
+ 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: loredocs-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,38 @@
1
+ import sqlite3
2
+ import sys
3
+
4
+ CLI_MIN_SCHEMA_VERSION = 0
5
+ CLI_MAX_WRITE_VERSION = 8
6
+ CLI_MAX_READ_VERSION = 10
7
+
8
+
9
+ def check_schema_compatibility(conn: sqlite3.Connection) -> str:
10
+ """Check PRAGMA user_version against supported range.
11
+
12
+ Returns 'normal' or 'read_only'. Exits on incompatible schema.
13
+ """
14
+ try:
15
+ version = conn.execute("PRAGMA user_version").fetchone()[0]
16
+ except sqlite3.Error:
17
+ version = 0
18
+
19
+ if version > CLI_MAX_READ_VERSION:
20
+ print(
21
+ f"Error: DB schema v{version} is newer than loredocs-cli supports "
22
+ f"(max readable: {CLI_MAX_READ_VERSION}).\n"
23
+ "Upgrade loredocs-cli: pip install -U loredocs-cli",
24
+ file=sys.stderr,
25
+ )
26
+ raise SystemExit(1)
27
+
28
+ if version > CLI_MAX_WRITE_VERSION:
29
+ print(
30
+ f"[warn] DB schema v{version} is newer than CLI write support "
31
+ f"(max writable: {CLI_MAX_WRITE_VERSION}). "
32
+ "Running in read-only mode. "
33
+ "Upgrade loredocs-cli[server] for full write support.",
34
+ file=sys.stderr,
35
+ )
36
+ return "read_only"
37
+
38
+ 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 loredocs server package is installed."""
7
+ return importlib.util.find_spec("loredocs") is not None
8
+
9
+
10
+ def require_write_capability(operation: str) -> None:
11
+ """Exit with a clear error if loredocs 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 loredocs-cli[server]",
19
+ file=sys.stderr,
20
+ )
21
+ raise SystemExit(1)
@@ -0,0 +1,194 @@
1
+ """LoreDocs CLI -- knowledge vault command line interface.
2
+
3
+ Free-tier operations only. Pro features require the LoreDocs MCP server.
4
+ """
5
+
6
+ import json
7
+ import re
8
+ import sys
9
+ from pathlib import Path
10
+ from typing import Optional
11
+
12
+ import typer
13
+
14
+ from ._db_path import get_db_path, open_db, _sanitize_fts5
15
+ from ._platform_check import check_platform
16
+ from ._schema_compat import check_schema_compatibility
17
+ from ._write_guard import require_write_capability
18
+
19
+ app = typer.Typer(
20
+ name="loredocs-cli",
21
+ help=(
22
+ "LoreDocs -- knowledge vault management.\n\n"
23
+ "Free-tier CLI (read operations).\n"
24
+ "Write operations require the MCP server: pip install loredocs-cli[server]"
25
+ ),
26
+ no_args_is_help=True,
27
+ )
28
+
29
+ _DB_PATH_OPTION = typer.Option(
30
+ None, "--db-path", help="Path to loredocs.db (overrides LOREDOCS_ROOT)"
31
+ )
32
+
33
+
34
+ @app.command(name="list")
35
+ def list_vaults(
36
+ db_path: Optional[str] = _DB_PATH_OPTION,
37
+ ) -> None:
38
+ """List all vaults."""
39
+ path = get_db_path(db_path)
40
+ conn = open_db(path)
41
+ check_schema_compatibility(conn)
42
+
43
+ try:
44
+ rows = conn.execute(
45
+ "SELECT id, name, description, created_at FROM vaults "
46
+ "WHERE archived = 0 ORDER BY name"
47
+ ).fetchall()
48
+ finally:
49
+ conn.close()
50
+
51
+ if not rows:
52
+ typer.echo("No vaults found.")
53
+ return
54
+ for row in rows:
55
+ typer.echo(f" {row['name']}")
56
+ if row["description"]:
57
+ typer.echo(f" {row['description'][:80]}")
58
+ typer.echo(f" id: {row['id']}")
59
+ typer.echo(f"\n{len(rows)} vault(s)")
60
+
61
+
62
+ @app.command()
63
+ def info(
64
+ vault: str = typer.Argument(..., help="Vault name"),
65
+ db_path: Optional[str] = _DB_PATH_OPTION,
66
+ ) -> None:
67
+ """Show vault details and document count."""
68
+ if not re.fullmatch(r"[a-zA-Z0-9_\- ]{1,128}", vault):
69
+ typer.echo("Error: vault name contains invalid characters.", err=True)
70
+ raise typer.Exit(1)
71
+
72
+ path = get_db_path(db_path)
73
+ conn = open_db(path)
74
+ check_schema_compatibility(conn)
75
+
76
+ try:
77
+ row = conn.execute(
78
+ "SELECT id, name, description, created_at, updated_at, tags FROM vaults "
79
+ "WHERE name = ? AND archived = 0",
80
+ (vault,),
81
+ ).fetchone()
82
+ if not row:
83
+ typer.echo(f"Vault not found: {vault!r}", err=True)
84
+ raise typer.Exit(1)
85
+ doc_count = conn.execute(
86
+ "SELECT COUNT(*) FROM documents WHERE vault_id = ? AND deleted = 0",
87
+ (row["id"],),
88
+ ).fetchone()[0]
89
+ finally:
90
+ conn.close()
91
+
92
+ typer.echo(f"\n--- {row['name']} ---")
93
+ typer.echo(f" id: {row['id']}")
94
+ typer.echo(f" documents: {doc_count}")
95
+ typer.echo(f" created: {str(row['created_at'])[:10]}")
96
+ typer.echo(f" updated: {str(row['updated_at'])[:10]}")
97
+ if row["description"]:
98
+ typer.echo(f" description: {row['description']}")
99
+ if row["tags"] and row["tags"] != "[]":
100
+ try:
101
+ tags = json.loads(row["tags"])
102
+ if tags:
103
+ typer.echo(f" tags: {', '.join(tags)}")
104
+ except (json.JSONDecodeError, TypeError):
105
+ pass
106
+
107
+
108
+ @app.command()
109
+ def search(
110
+ query: str = typer.Argument(..., help="Search query"),
111
+ vault: Optional[str] = typer.Option(None, "--vault", "-v", help="Restrict to vault name"),
112
+ limit: int = typer.Option(20, "--limit", "-n", help="Max results"),
113
+ db_path: Optional[str] = _DB_PATH_OPTION,
114
+ ) -> None:
115
+ """Search documents by keyword."""
116
+ path = get_db_path(db_path)
117
+ conn = open_db(path)
118
+ check_schema_compatibility(conn)
119
+
120
+ like_q = f"%{query}%"
121
+ try:
122
+ if vault:
123
+ if not re.fullmatch(r"[a-zA-Z0-9_\- ]{1,128}", vault):
124
+ typer.echo("Error: vault name contains invalid characters.", err=True)
125
+ raise typer.Exit(1)
126
+ vault_row = conn.execute(
127
+ "SELECT id FROM vaults WHERE name = ? AND archived = 0", (vault,)
128
+ ).fetchone()
129
+ if not vault_row:
130
+ typer.echo(f"Vault not found: {vault!r}", err=True)
131
+ raise typer.Exit(1)
132
+ vault_id = vault_row["id"]
133
+ rows = conn.execute(
134
+ "SELECT d.id, d.name, d.vault_id, d.category, d.updated_at "
135
+ "FROM documents d "
136
+ "WHERE d.vault_id = ? AND d.deleted = 0 "
137
+ "AND (d.name LIKE ? OR d.notes LIKE ?) "
138
+ "ORDER BY d.updated_at DESC LIMIT ?",
139
+ (vault_id, like_q, like_q, limit),
140
+ ).fetchall()
141
+ else:
142
+ rows = conn.execute(
143
+ "SELECT d.id, d.name, d.vault_id, d.category, d.updated_at "
144
+ "FROM documents d WHERE d.deleted = 0 "
145
+ "AND (d.name LIKE ? OR d.notes LIKE ? OR d.tags LIKE ?) "
146
+ "ORDER BY d.updated_at DESC LIMIT ?",
147
+ (like_q, like_q, like_q, limit),
148
+ ).fetchall()
149
+ finally:
150
+ conn.close()
151
+
152
+ if not rows:
153
+ typer.echo(f"No documents found for: {query!r}")
154
+ return
155
+ for row in rows:
156
+ typer.echo(f" [{row['category']:10s}] {row['name']}")
157
+ typer.echo(f" id: {row['id']}")
158
+ typer.echo(f"\n{len(rows)} result(s)")
159
+
160
+
161
+ @app.command(name="add-doc")
162
+ def add_doc(
163
+ vault: str = typer.Argument(..., help="Vault name"),
164
+ title: str = typer.Option(..., "--title", "-t", help="Document title"),
165
+ body: str = typer.Option(..., "--body", "-b", help="Document content"),
166
+ category: str = typer.Option("general", "--category", "-c", help="Category"),
167
+ db_path: Optional[str] = _DB_PATH_OPTION,
168
+ ) -> None:
169
+ """Add a document to a vault. Requires loredocs-cli[server]."""
170
+ require_write_capability("add-doc")
171
+
172
+ from loredocs.storage import VaultStorage
173
+
174
+ path = get_db_path(db_path)
175
+ import os
176
+ if db_path:
177
+ os.environ["LOREDOCS_ROOT"] = str(path.parent)
178
+
179
+ storage = VaultStorage()
180
+ result = storage.vault_add_note(vault_name=vault, title=title, body=body, category=category)
181
+ if result.get("error"):
182
+ typer.echo(f"Error: {result['error']}", err=True)
183
+ raise typer.Exit(1)
184
+ typer.echo(f"[OK] Added document: {title}")
185
+ typer.echo(f" vault: {vault}")
186
+
187
+
188
+ def main() -> None:
189
+ check_platform()
190
+ app()
191
+
192
+
193
+ if __name__ == "__main__":
194
+ main()
@@ -0,0 +1,163 @@
1
+ Metadata-Version: 2.4
2
+ Name: loredocs-cli
3
+ Version: 0.1.0a1
4
+ Summary: Command-line interface for LoreDocs knowledge vault management
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/loredocs-cli
101
+ Project-URL: Repository, https://github.com/labyrinth-analytics/loredocs-cli
102
+ Project-URL: Bug Tracker, https://github.com/labyrinth-analytics/loredocs-cli/issues
103
+ Keywords: mcp,claude,knowledge-management,cli,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: loredocs==0.1.7; extra == "server"
121
+ Provides-Extra: dev
122
+ Requires-Dist: pytest==8.3.4; extra == "dev"
123
+ Dynamic: license-file
124
+
125
+ # loredocs-cli v0.1.0
126
+
127
+ Command-line interface for LoreDocs knowledge vaults.
128
+
129
+ List, search, and read your LoreDocs vaults and documents from the terminal --
130
+ no MCP server or Claude session required.
131
+
132
+ ```bash
133
+ pip install loredocs-cli
134
+
135
+ loredocs-cli list
136
+ loredocs-cli info "my vault"
137
+ loredocs-cli search "architecture decision"
138
+ ```
139
+
140
+ ## Commands
141
+
142
+ | Command | Free tier | Description |
143
+ |---------|-----------|-------------|
144
+ | `list` | Yes | List all vaults |
145
+ | `info <vault>` | Yes | Show vault details and document count |
146
+ | `search <query>` | Yes | Search documents by keyword |
147
+ | `add-doc <vault>` | [server] extra | Add a document to a vault |
148
+
149
+ The `add-doc` command requires `pip install "loredocs-cli[server]"` (installs the
150
+ `loredocs` 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,16 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ loredocs_cli/__init__.py
5
+ loredocs_cli/_db_path.py
6
+ loredocs_cli/_platform_check.py
7
+ loredocs_cli/_schema_compat.py
8
+ loredocs_cli/_write_guard.py
9
+ loredocs_cli/main.py
10
+ loredocs_cli.egg-info/PKG-INFO
11
+ loredocs_cli.egg-info/SOURCES.txt
12
+ loredocs_cli.egg-info/dependency_links.txt
13
+ loredocs_cli.egg-info/entry_points.txt
14
+ loredocs_cli.egg-info/requires.txt
15
+ loredocs_cli.egg-info/top_level.txt
16
+ tests/test_cli.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ loredocs-cli = loredocs_cli.main:main
@@ -0,0 +1,7 @@
1
+ typer==0.13.1
2
+
3
+ [dev]
4
+ pytest==8.3.4
5
+
6
+ [server]
7
+ loredocs==0.1.7
@@ -0,0 +1 @@
1
+ loredocs_cli
@@ -0,0 +1,48 @@
1
+ [project]
2
+ name = "loredocs-cli"
3
+ version = "0.1.0a1"
4
+ description = "Command-line interface for LoreDocs knowledge vault management"
5
+ readme = "README.md"
6
+ license = { file = "LICENSE" }
7
+ requires-python = ">=3.10"
8
+ authors = [
9
+ { name = "Labyrinth Analytics Consulting" }
10
+ ]
11
+ keywords = ["mcp", "claude", "knowledge-management", "cli", "ai-tools", "claude-plugin"]
12
+ classifiers = [
13
+ "Development Status :: 3 - Alpha",
14
+ "Intended Audience :: End Users/Desktop",
15
+ "License :: Other/Proprietary License",
16
+ "Programming Language :: Python :: 3",
17
+ "Programming Language :: Python :: 3.10",
18
+ "Programming Language :: Python :: 3.11",
19
+ "Programming Language :: Python :: 3.12",
20
+ "Programming Language :: Python :: 3.13",
21
+ "Programming Language :: Python :: 3.14",
22
+ "Operating System :: MacOS :: MacOS X",
23
+ "Operating System :: POSIX :: Linux",
24
+ ]
25
+
26
+ dependencies = [
27
+ "typer==0.13.1",
28
+ ]
29
+
30
+ [project.optional-dependencies]
31
+ server = [
32
+ "loredocs==0.1.7", # allow-range: loredocs -- CLI depends on server for write ops
33
+ ]
34
+ dev = [
35
+ "pytest==8.3.4",
36
+ ]
37
+
38
+ [project.scripts]
39
+ loredocs-cli = "loredocs_cli.main:main"
40
+
41
+ [project.urls]
42
+ Homepage = "https://github.com/labyrinth-analytics/loredocs-cli"
43
+ Repository = "https://github.com/labyrinth-analytics/loredocs-cli"
44
+ "Bug Tracker" = "https://github.com/labyrinth-analytics/loredocs-cli/issues"
45
+
46
+ [build-system]
47
+ requires = ["setuptools>=68.0", "wheel"]
48
+ build-backend = "setuptools.build_meta"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,179 @@
1
+ """Tests for loredocs-cli."""
2
+
3
+ import json
4
+ import os
5
+ import sqlite3
6
+ import sys
7
+ from pathlib import Path
8
+
9
+ import pytest
10
+
11
+ sys.path.insert(0, str(Path(__file__).parent.parent))
12
+
13
+
14
+ @pytest.fixture
15
+ def tmp_db(tmp_path):
16
+ """Create a minimal loredocs.db fixture."""
17
+ db_path = tmp_path / "loredocs.db"
18
+ conn = sqlite3.connect(str(db_path))
19
+ conn.execute("PRAGMA journal_mode=WAL")
20
+ conn.executescript("""
21
+ CREATE TABLE vaults (
22
+ id TEXT PRIMARY KEY,
23
+ name TEXT NOT NULL,
24
+ description TEXT DEFAULT '',
25
+ created_at TEXT NOT NULL,
26
+ updated_at TEXT NOT NULL,
27
+ archived INTEGER DEFAULT 0,
28
+ tags TEXT DEFAULT '[]',
29
+ linked_projects TEXT DEFAULT '[]'
30
+ );
31
+ CREATE TABLE documents (
32
+ id TEXT PRIMARY KEY,
33
+ vault_id TEXT NOT NULL,
34
+ name TEXT NOT NULL,
35
+ original_filename TEXT NOT NULL,
36
+ file_extension TEXT DEFAULT '',
37
+ category TEXT DEFAULT 'general',
38
+ priority TEXT DEFAULT 'normal',
39
+ tags TEXT DEFAULT '[]',
40
+ notes TEXT DEFAULT '',
41
+ created_at TEXT NOT NULL,
42
+ updated_at TEXT NOT NULL,
43
+ file_size_bytes INTEGER DEFAULT 0,
44
+ version_count INTEGER DEFAULT 1,
45
+ deleted INTEGER DEFAULT 0
46
+ );
47
+ CREATE VIRTUAL TABLE doc_fts USING fts5(
48
+ doc_id,
49
+ vault_id,
50
+ name,
51
+ content,
52
+ tags,
53
+ notes
54
+ );
55
+ INSERT INTO vaults VALUES
56
+ ('vault-1', 'side_hustle', 'Side hustle project vault',
57
+ '2026-05-01T00:00:00Z', '2026-05-30T00:00:00Z', 0, '[]', '[]'),
58
+ ('vault-2', 'job_skills', 'Job skills vault',
59
+ '2026-05-01T00:00:00Z', '2026-05-30T00:00:00Z', 0, '["jobs"]', '[]');
60
+ INSERT INTO documents VALUES
61
+ ('doc-1', 'vault-1', 'LoreCap spec', 'lorecap_spec.md', '.md',
62
+ 'reference', 'authoritative', '["spec","lorecap"]', '',
63
+ '2026-05-20T00:00:00Z', '2026-05-28T00:00:00Z', 1024, 1, 0),
64
+ ('doc-2', 'vault-2', 'Python skills gap analysis', 'gap.md', '.md',
65
+ 'report', 'normal', '["python"]', '',
66
+ '2026-05-25T00:00:00Z', '2026-05-29T00:00:00Z', 512, 1, 0);
67
+ """)
68
+ conn.commit()
69
+ conn.close()
70
+ return db_path
71
+
72
+
73
+ def test_list_vaults(tmp_db, monkeypatch):
74
+ """list command shows all non-archived vaults."""
75
+ from typer.testing import CliRunner
76
+ from loredocs_cli.main import app
77
+
78
+ monkeypatch.setenv("LOREDOCS_ALLOW_EXTERNAL_DB", "1")
79
+ monkeypatch.setattr("loredocs_cli.main.check_platform", lambda: None)
80
+
81
+ runner = CliRunner()
82
+ result = runner.invoke(app, ["list", "--db-path", str(tmp_db)])
83
+ assert result.exit_code == 0
84
+ assert "side_hustle" in result.output
85
+ assert "job_skills" in result.output
86
+ assert "2 vault(s)" in result.output
87
+
88
+
89
+ def test_info_vault(tmp_db, monkeypatch):
90
+ """info command shows vault details and doc count."""
91
+ from typer.testing import CliRunner
92
+ from loredocs_cli.main import app
93
+
94
+ monkeypatch.setenv("LOREDOCS_ALLOW_EXTERNAL_DB", "1")
95
+ monkeypatch.setattr("loredocs_cli.main.check_platform", lambda: None)
96
+
97
+ runner = CliRunner()
98
+ result = runner.invoke(app, ["info", "side_hustle", "--db-path", str(tmp_db)])
99
+ assert result.exit_code == 0
100
+ assert "side_hustle" in result.output
101
+ assert "documents: 1" in result.output
102
+
103
+
104
+ def test_info_missing_vault(tmp_db, monkeypatch):
105
+ """info command exits 1 for unknown vault."""
106
+ from typer.testing import CliRunner
107
+ from loredocs_cli.main import app
108
+
109
+ monkeypatch.setenv("LOREDOCS_ALLOW_EXTERNAL_DB", "1")
110
+ monkeypatch.setattr("loredocs_cli.main.check_platform", lambda: None)
111
+
112
+ runner = CliRunner()
113
+ result = runner.invoke(app, ["info", "nonexistent", "--db-path", str(tmp_db)])
114
+ assert result.exit_code == 1
115
+
116
+
117
+ def test_search_fallback(tmp_db, monkeypatch):
118
+ """search falls back to LIKE when FTS fails."""
119
+ from typer.testing import CliRunner
120
+ from loredocs_cli.main import app
121
+
122
+ monkeypatch.setenv("LOREDOCS_ALLOW_EXTERNAL_DB", "1")
123
+ monkeypatch.setattr("loredocs_cli.main.check_platform", lambda: None)
124
+
125
+ runner = CliRunner()
126
+ result = runner.invoke(app, ["search", "LoreCap", "--db-path", str(tmp_db)])
127
+ assert result.exit_code == 0
128
+ assert "LoreCap spec" in result.output
129
+
130
+
131
+ def test_write_guard_blocks_add_doc(tmp_db, monkeypatch):
132
+ """add-doc exits 1 without server package."""
133
+ from typer.testing import CliRunner
134
+ from loredocs_cli.main import app
135
+
136
+ monkeypatch.setenv("LOREDOCS_ALLOW_EXTERNAL_DB", "1")
137
+ monkeypatch.setattr("loredocs_cli.main.check_platform", lambda: None)
138
+ monkeypatch.setattr(
139
+ "loredocs_cli._write_guard._has_server_package", lambda: False
140
+ )
141
+
142
+ runner = CliRunner()
143
+ result = runner.invoke(
144
+ app,
145
+ ["add-doc", "side_hustle", "--title", "t", "--body", "b", "--db-path", str(tmp_db)],
146
+ catch_exceptions=False,
147
+ )
148
+ assert result.exit_code == 1
149
+
150
+
151
+ def test_sanitize_fts5():
152
+ """FTS5 injection chars are stripped."""
153
+ from loredocs_cli._db_path import _sanitize_fts5
154
+
155
+ assert '"' not in _sanitize_fts5('find "this"')
156
+ assert "*" not in _sanitize_fts5("wildcard*")
157
+ assert _sanitize_fts5(" ") == '""'
158
+
159
+
160
+ def test_open_db_missing(tmp_path, capsys):
161
+ """open_db exits with code 1 when DB not found."""
162
+ from loredocs_cli._db_path import open_db
163
+
164
+ missing = tmp_path / "missing.db"
165
+ with pytest.raises(SystemExit) as exc:
166
+ open_db(missing)
167
+ assert exc.value.code == 1
168
+ assert "not found" in capsys.readouterr().err
169
+
170
+
171
+ def test_schema_compat_normal(tmp_db):
172
+ """schema compat returns 'normal' for user_version=0."""
173
+ from loredocs_cli._db_path import open_db
174
+ from loredocs_cli._schema_compat import check_schema_compatibility
175
+
176
+ conn = open_db(tmp_db)
177
+ result = check_schema_compatibility(conn)
178
+ assert result == "normal"
179
+ conn.close()