chaoscypher-cli 0.1.0__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.
- chaoscypher_cli/__init__.py +32 -0
- chaoscypher_cli/__main__.py +483 -0
- chaoscypher_cli/benchmark/__init__.py +90 -0
- chaoscypher_cli/benchmark/chat_dataset.py +242 -0
- chaoscypher_cli/benchmark/config.py +302 -0
- chaoscypher_cli/benchmark/data/config/extraction.yaml +54 -0
- chaoscypher_cli/benchmark/data/config/full.yaml +45 -0
- chaoscypher_cli/benchmark/data/config/quick.yaml +26 -0
- chaoscypher_cli/benchmark/data/datasets/scientific_methods_tiny/manifest.yaml +8 -0
- chaoscypher_cli/benchmark/data/datasets/scientific_methods_tiny/scientific_methods_tiny.txt +106 -0
- chaoscypher_cli/benchmark/data/datasets/tech_encyclopedia_tiny/manifest.yaml +9 -0
- chaoscypher_cli/benchmark/data/datasets/tech_encyclopedia_tiny/queries.yaml +428 -0
- chaoscypher_cli/benchmark/data/datasets/tech_encyclopedia_tiny/tech_encyclopedia_tiny.txt +98 -0
- chaoscypher_cli/benchmark/data/datasets/war_and_peace_tiny/manifest.yaml +8 -0
- chaoscypher_cli/benchmark/data/datasets/war_and_peace_tiny/war_and_peace_tiny.txt +212 -0
- chaoscypher_cli/benchmark/dataset.py +86 -0
- chaoscypher_cli/benchmark/discovery.py +269 -0
- chaoscypher_cli/benchmark/embedding_dataset.py +197 -0
- chaoscypher_cli/benchmark/extraction_dataset.py +349 -0
- chaoscypher_cli/benchmark/graph_cache.py +147 -0
- chaoscypher_cli/benchmark/graph_provider.py +107 -0
- chaoscypher_cli/benchmark/judge_prompts.py +75 -0
- chaoscypher_cli/benchmark/leaderboard.py +293 -0
- chaoscypher_cli/benchmark/models.py +95 -0
- chaoscypher_cli/benchmark/orchestrator.py +407 -0
- chaoscypher_cli/benchmark/queries.py +141 -0
- chaoscypher_cli/benchmark/results.py +118 -0
- chaoscypher_cli/benchmark/runner.py +191 -0
- chaoscypher_cli/benchmark/scorers/__init__.py +11 -0
- chaoscypher_cli/benchmark/scorers/chat.py +107 -0
- chaoscypher_cli/benchmark/scorers/embedding.py +101 -0
- chaoscypher_cli/benchmark/scorers/v7.py +70 -0
- chaoscypher_cli/commands/__init__.py +36 -0
- chaoscypher_cli/commands/benchmark/__init__.py +29 -0
- chaoscypher_cli/commands/benchmark/fixture.py +95 -0
- chaoscypher_cli/commands/benchmark/init.py +73 -0
- chaoscypher_cli/commands/benchmark/list.py +65 -0
- chaoscypher_cli/commands/benchmark/run.py +261 -0
- chaoscypher_cli/commands/benchmark/show.py +37 -0
- chaoscypher_cli/commands/chat.py +805 -0
- chaoscypher_cli/commands/completions.py +251 -0
- chaoscypher_cli/commands/compose/__init__.py +45 -0
- chaoscypher_cli/commands/compose/build.py +123 -0
- chaoscypher_cli/commands/compose/down.py +67 -0
- chaoscypher_cli/commands/compose/run.py +76 -0
- chaoscypher_cli/commands/compose/up.py +142 -0
- chaoscypher_cli/commands/config_cmd.py +387 -0
- chaoscypher_cli/commands/db/__init__.py +52 -0
- chaoscypher_cli/commands/db/create.py +77 -0
- chaoscypher_cli/commands/db/current.py +51 -0
- chaoscypher_cli/commands/db/delete.py +74 -0
- chaoscypher_cli/commands/db/info.py +86 -0
- chaoscypher_cli/commands/db/list.py +128 -0
- chaoscypher_cli/commands/db/migrate.py +202 -0
- chaoscypher_cli/commands/db/switch.py +47 -0
- chaoscypher_cli/commands/diagnostics.py +91 -0
- chaoscypher_cli/commands/doctor.py +303 -0
- chaoscypher_cli/commands/graph/__init__.py +33 -0
- chaoscypher_cli/commands/health.py +214 -0
- chaoscypher_cli/commands/lexicon/__init__.py +37 -0
- chaoscypher_cli/commands/lexicon/info.py +167 -0
- chaoscypher_cli/commands/lexicon/list.py +96 -0
- chaoscypher_cli/commands/lexicon/login.py +312 -0
- chaoscypher_cli/commands/lexicon/pull.py +142 -0
- chaoscypher_cli/commands/lexicon/push.py +153 -0
- chaoscypher_cli/commands/lexicon/remove.py +121 -0
- chaoscypher_cli/commands/lexicon/search.py +137 -0
- chaoscypher_cli/commands/link/__init__.py +48 -0
- chaoscypher_cli/commands/link/create.py +102 -0
- chaoscypher_cli/commands/link/delete.py +108 -0
- chaoscypher_cli/commands/link/get.py +117 -0
- chaoscypher_cli/commands/link/list.py +144 -0
- chaoscypher_cli/commands/link/update.py +113 -0
- chaoscypher_cli/commands/node/__init__.py +45 -0
- chaoscypher_cli/commands/node/create.py +147 -0
- chaoscypher_cli/commands/node/delete.py +130 -0
- chaoscypher_cli/commands/node/get.py +153 -0
- chaoscypher_cli/commands/node/list.py +146 -0
- chaoscypher_cli/commands/node/update.py +106 -0
- chaoscypher_cli/commands/package/__init__.py +33 -0
- chaoscypher_cli/commands/package/export.py +175 -0
- chaoscypher_cli/commands/package/load.py +161 -0
- chaoscypher_cli/commands/quality/__init__.py +41 -0
- chaoscypher_cli/commands/quality/analyze.py +224 -0
- chaoscypher_cli/commands/quality/recalculate.py +159 -0
- chaoscypher_cli/commands/quality/report.py +285 -0
- chaoscypher_cli/commands/quality/score.py +323 -0
- chaoscypher_cli/commands/quality/utils.py +58 -0
- chaoscypher_cli/commands/render_orchestration.py +64 -0
- chaoscypher_cli/commands/runtime/__init__.py +13 -0
- chaoscypher_cli/commands/runtime/serve.py +247 -0
- chaoscypher_cli/commands/setup.py +1068 -0
- chaoscypher_cli/commands/source/__init__.py +122 -0
- chaoscypher_cli/commands/source/add.py +592 -0
- chaoscypher_cli/commands/source/confirm.py +238 -0
- chaoscypher_cli/commands/source/delete.py +59 -0
- chaoscypher_cli/commands/source/extract.py +314 -0
- chaoscypher_cli/commands/source/get.py +503 -0
- chaoscypher_cli/commands/source/list.py +160 -0
- chaoscypher_cli/commands/source/rebuild_search.py +96 -0
- chaoscypher_cli/commands/source/search.py +270 -0
- chaoscypher_cli/commands/template/__init__.py +48 -0
- chaoscypher_cli/commands/template/create.py +154 -0
- chaoscypher_cli/commands/template/delete.py +74 -0
- chaoscypher_cli/commands/template/get.py +135 -0
- chaoscypher_cli/commands/template/list.py +127 -0
- chaoscypher_cli/commands/template/update.py +144 -0
- chaoscypher_cli/commands/template/utils.py +50 -0
- chaoscypher_cli/commands/upgrade.py +58 -0
- chaoscypher_cli/commands/workflow/__init__.py +31 -0
- chaoscypher_cli/commands/workflow/get.py +148 -0
- chaoscypher_cli/commands/workflow/list.py +120 -0
- chaoscypher_cli/context.py +678 -0
- chaoscypher_cli/engine_config.py +96 -0
- chaoscypher_cli/lazy.py +195 -0
- chaoscypher_cli/mcp/__init__.py +9 -0
- chaoscypher_cli/mcp/command.py +143 -0
- chaoscypher_cli/py.typed +0 -0
- chaoscypher_cli/sources/__init__.py +36 -0
- chaoscypher_cli/sources/domains.py +39 -0
- chaoscypher_cli/sources/pipeline.py +1337 -0
- chaoscypher_cli/sources/service.py +1734 -0
- chaoscypher_cli/utils/__init__.py +92 -0
- chaoscypher_cli/utils/console.py +88 -0
- chaoscypher_cli/utils/display.py +50 -0
- chaoscypher_cli/utils/files.py +123 -0
- chaoscypher_cli/utils/llm_check.py +165 -0
- chaoscypher_cli/utils/paths.py +120 -0
- chaoscypher_cli-0.1.0.dist-info/METADATA +174 -0
- chaoscypher_cli-0.1.0.dist-info/RECORD +133 -0
- chaoscypher_cli-0.1.0.dist-info/WHEEL +5 -0
- chaoscypher_cli-0.1.0.dist-info/entry_points.txt +2 -0
- chaoscypher_cli-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Copyright (C) 2024-2026 Chaos Cypher, Inc.
|
|
2
|
+
# SPDX-License-Identifier: AGPL-3.0-only
|
|
3
|
+
|
|
4
|
+
"""Chaos Cypher CLI - Command-line tools for Chaos Cypher knowledge graph library.
|
|
5
|
+
|
|
6
|
+
This package provides a user-friendly command-line interface for:
|
|
7
|
+
- Managing knowledge graphs and databases
|
|
8
|
+
- Importing and exporting data
|
|
9
|
+
- Searching and querying graphs
|
|
10
|
+
- Viewing statistics and analytics
|
|
11
|
+
|
|
12
|
+
Example:
|
|
13
|
+
chaoscypher db create my-graph
|
|
14
|
+
chaoscypher source add documents/
|
|
15
|
+
chaoscypher source search "artificial intelligence"
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
try:
|
|
22
|
+
__version__ = version("chaoscypher-cli")
|
|
23
|
+
except PackageNotFoundError:
|
|
24
|
+
# Source-tree run without an installed dist (e.g. `python -m chaoscypher_cli`
|
|
25
|
+
# from a checkout that wasn't `uv sync`'d). Fall back to a sentinel so the
|
|
26
|
+
# CLI still launches.
|
|
27
|
+
__version__ = "0.0.0+unknown"
|
|
28
|
+
|
|
29
|
+
__author__ = "Denis MacPherson"
|
|
30
|
+
__license__ = "AGPL-3.0-only"
|
|
31
|
+
|
|
32
|
+
__all__ = ["__version__"]
|
|
@@ -0,0 +1,483 @@
|
|
|
1
|
+
# Copyright (C) 2024-2026 Chaos Cypher, Inc.
|
|
2
|
+
# SPDX-License-Identifier: AGPL-3.0-only
|
|
3
|
+
|
|
4
|
+
"""Chaos Cypher CLI - Knowledge Graph Platform.
|
|
5
|
+
|
|
6
|
+
Organized by user intent:
|
|
7
|
+
|
|
8
|
+
1. Package Manager (docker-like):
|
|
9
|
+
- pull, push (root level)
|
|
10
|
+
- lexicon/ (login, logout, whoami, search, list, info, remove)
|
|
11
|
+
|
|
12
|
+
2. Runtime (like docker run):
|
|
13
|
+
- serve, run, compose
|
|
14
|
+
|
|
15
|
+
3. Builder:
|
|
16
|
+
- init, source/, graph/
|
|
17
|
+
|
|
18
|
+
4. Graph building:
|
|
19
|
+
- graph/ (node, link, template, workflow, package)
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
import os
|
|
23
|
+
import sys
|
|
24
|
+
import warnings
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _configure_console_encoding() -> None:
|
|
28
|
+
"""Force UTF-8 on Windows stdout/stderr before any Rich Console exists.
|
|
29
|
+
|
|
30
|
+
The default Windows console code page is cp1252, which has no mapping
|
|
31
|
+
for routine Rich glyphs (``…`` ``✓`` ``─``) or content we put in
|
|
32
|
+
migration descriptions (``→`` U+2192). Rich's LegacyWindowsTerm
|
|
33
|
+
writes through ``sys.stdout``, so when ``sys.stdout.encoding`` is
|
|
34
|
+
cp1252 and a non-cp1252 char comes through, Python raises
|
|
35
|
+
``UnicodeEncodeError`` and the whole command aborts mid-render —
|
|
36
|
+
that's the failure mode that crashed ``chaoscypher db migrate status``
|
|
37
|
+
on Windows before this hook existed.
|
|
38
|
+
|
|
39
|
+
``errors="replace"`` makes any future unmappable character degrade to
|
|
40
|
+
a ``?`` glyph instead of aborting. POSIX shells default to UTF-8
|
|
41
|
+
already, so this is a Windows-only concern.
|
|
42
|
+
|
|
43
|
+
Best-effort: ``sys.stdout`` / ``sys.stderr`` may have been replaced
|
|
44
|
+
with a non-``TextIOWrapper`` (pytest capture, embedders, redirection
|
|
45
|
+
to a binary pipe). We swallow ``AttributeError`` / ``OSError`` rather
|
|
46
|
+
than refuse to start the CLI over an encoding tweak.
|
|
47
|
+
"""
|
|
48
|
+
if sys.platform != "win32":
|
|
49
|
+
return
|
|
50
|
+
for stream in (sys.stdout, sys.stderr): # type: ignore[unreachable]
|
|
51
|
+
try:
|
|
52
|
+
stream.reconfigure(encoding="utf-8", errors="replace")
|
|
53
|
+
except AttributeError, OSError:
|
|
54
|
+
continue
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
_configure_console_encoding()
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
# Suppress noisy third-party warnings before any chaoscypher_core /
|
|
61
|
+
# langchain import runs (configure_logging in _preconfigure_logging will
|
|
62
|
+
# transitively import 184 langchain/langgraph modules — one of which fires
|
|
63
|
+
# a PendingDeprecationWarning at import time). The langchain filter has to
|
|
64
|
+
# be applied AFTER langchain_core's __init__ runs because
|
|
65
|
+
# surface_langchain_deprecation_warnings() forces a "default" action on
|
|
66
|
+
# LangChain* categories; we therefore import langchain_core eagerly here
|
|
67
|
+
# so we control the order.
|
|
68
|
+
warnings.filterwarnings("ignore", message=".*Pydantic V1.*", category=UserWarning)
|
|
69
|
+
try:
|
|
70
|
+
import langchain_core # noqa: F401 — triggers surface_langchain_deprecation_warnings()
|
|
71
|
+
from langchain_core._api.deprecation import (
|
|
72
|
+
LangChainDeprecationWarning,
|
|
73
|
+
LangChainPendingDeprecationWarning,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
warnings.filterwarnings("ignore", category=LangChainDeprecationWarning)
|
|
77
|
+
warnings.filterwarnings("ignore", category=LangChainPendingDeprecationWarning)
|
|
78
|
+
except Exception:
|
|
79
|
+
# langchain_core not installed (fresh dev env, test harness) — skip.
|
|
80
|
+
pass
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _preconfigure_logging() -> None:
|
|
84
|
+
"""Configure structlog with a quiet WARNING default before any submodule import.
|
|
85
|
+
|
|
86
|
+
Why this runs unconditionally:
|
|
87
|
+
Before this hook, only the ``mcp`` subcommand pre-configured
|
|
88
|
+
logging; every other command relied on ``context.py:190`` to
|
|
89
|
+
call ``configure_logging`` lazily during ``connect()``. Any
|
|
90
|
+
log line emitted before that call (``settings_file_not_found``,
|
|
91
|
+
``settings_loaded``, etc.) went through structlog's default
|
|
92
|
+
config, which dumps INFO/DEBUG to stdout — leaking debug lines
|
|
93
|
+
on every ``chaoscypher --help``, ``chaoscypher source list``, etc.
|
|
94
|
+
|
|
95
|
+
Why it works:
|
|
96
|
+
``configure_logging`` has a process-wide idempotency guard
|
|
97
|
+
(``logging._chaoscypher_logging_configured``). The very first
|
|
98
|
+
call wins; ``context.py:190`` becomes a no-op. Operators who
|
|
99
|
+
want verbose logs set ``LOG_LEVEL=INFO`` (or ``DEBUG``) in the
|
|
100
|
+
environment — both paths honour the env var, so the level the
|
|
101
|
+
operator picks is the level they get.
|
|
102
|
+
|
|
103
|
+
MCP stdio transport caveat (Bug 15, May 2026):
|
|
104
|
+
The MCP server reserves stdout for JSON-RPC; structlog noise on
|
|
105
|
+
stdout breaks stricter clients. For ``chaoscypher mcp …``, route
|
|
106
|
+
to ``sys.stderr``.
|
|
107
|
+
|
|
108
|
+
Best-effort: if the chaoscypher_core import fails (broken install,
|
|
109
|
+
test harness), swallow and let the CLI start normally.
|
|
110
|
+
"""
|
|
111
|
+
try:
|
|
112
|
+
from chaoscypher_core.utils.logging import configure_logging
|
|
113
|
+
|
|
114
|
+
configure_logging(
|
|
115
|
+
log_level=os.getenv("LOG_LEVEL", "WARNING"),
|
|
116
|
+
stream=sys.stderr if "mcp" in sys.argv[1:] else None,
|
|
117
|
+
)
|
|
118
|
+
except Exception:
|
|
119
|
+
pass
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
_preconfigure_logging()
|
|
123
|
+
|
|
124
|
+
import click
|
|
125
|
+
|
|
126
|
+
from chaoscypher_cli import __version__
|
|
127
|
+
from chaoscypher_cli.lazy import LazyGroup
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
# =============================================================================
|
|
131
|
+
# Lazy-loaded commands (heavy imports - only load when executed)
|
|
132
|
+
# =============================================================================
|
|
133
|
+
# Format: "cmd": ("import.path:attr", "Short help text")
|
|
134
|
+
# Help text enables fast --help without loading the command module
|
|
135
|
+
LAZY_COMMANDS = {
|
|
136
|
+
# Setup wizard
|
|
137
|
+
"setup": (
|
|
138
|
+
"chaoscypher_cli.commands.setup:setup",
|
|
139
|
+
"Configure LLM provider for extraction and chat",
|
|
140
|
+
),
|
|
141
|
+
# Package management (docker-like UX)
|
|
142
|
+
"pull": ("chaoscypher_cli.commands.lexicon.pull:pull", "Download a package from Lexicon Hub"),
|
|
143
|
+
"push": ("chaoscypher_cli.commands.lexicon.push:push", "Upload a package to Lexicon Hub"),
|
|
144
|
+
# Runtime commands
|
|
145
|
+
"serve": (
|
|
146
|
+
"chaoscypher_cli.commands.runtime.serve:serve",
|
|
147
|
+
"Start the local API server",
|
|
148
|
+
),
|
|
149
|
+
"compose": (
|
|
150
|
+
"chaoscypher_cli.commands.compose:compose",
|
|
151
|
+
"Multi-package orchestration and composition",
|
|
152
|
+
),
|
|
153
|
+
# Builder commands
|
|
154
|
+
"source": (
|
|
155
|
+
"chaoscypher_cli.commands.source:source",
|
|
156
|
+
"Add, list, search, and manage document sources",
|
|
157
|
+
),
|
|
158
|
+
# Groups
|
|
159
|
+
"graph": ("chaoscypher_cli.commands.graph:graph", "Build and manage knowledge graphs"),
|
|
160
|
+
"lexicon": (
|
|
161
|
+
"chaoscypher_cli.commands.lexicon:lexicon",
|
|
162
|
+
"Lexicon Hub - login, search, manage packages",
|
|
163
|
+
),
|
|
164
|
+
# AI chat
|
|
165
|
+
"chat": ("chaoscypher_cli.commands.chat:chat", "Chat with AI using your knowledge graph"),
|
|
166
|
+
# Database management
|
|
167
|
+
"db": ("chaoscypher_cli.commands.db:db", "Manage databases (create, list, delete, reset)"),
|
|
168
|
+
# Configuration
|
|
169
|
+
"config": ("chaoscypher_cli.commands.config_cmd:config", "View and manage CLI configuration"),
|
|
170
|
+
"completions": (
|
|
171
|
+
"chaoscypher_cli.commands.completions:completions",
|
|
172
|
+
"Generate shell completion script (bash, zsh, fish)",
|
|
173
|
+
),
|
|
174
|
+
# MCP server
|
|
175
|
+
"mcp": ("chaoscypher_cli.mcp.command:mcp", "Start MCP server over stdio"),
|
|
176
|
+
# System health
|
|
177
|
+
"health": ("chaoscypher_cli.commands.health:health", "Check system health status"),
|
|
178
|
+
"doctor": (
|
|
179
|
+
"chaoscypher_cli.commands.doctor:doctor",
|
|
180
|
+
"Run a comprehensive system diagnostic sweep",
|
|
181
|
+
),
|
|
182
|
+
"diagnostics": (
|
|
183
|
+
"chaoscypher_cli.commands.diagnostics:diagnostics",
|
|
184
|
+
"Export diagnostic bundle for bug reports",
|
|
185
|
+
),
|
|
186
|
+
# Benchmarking
|
|
187
|
+
"benchmark": (
|
|
188
|
+
"chaoscypher_cli.commands.benchmark:benchmark",
|
|
189
|
+
"Run and inspect the extraction benchmark",
|
|
190
|
+
),
|
|
191
|
+
# Schema migrations
|
|
192
|
+
"upgrade": (
|
|
193
|
+
"chaoscypher_cli.commands.upgrade:upgrade_command",
|
|
194
|
+
"Apply pending Alembic migrations (alembic upgrade head)",
|
|
195
|
+
),
|
|
196
|
+
# Orchestration template renderer (called by entrypoint.sh)
|
|
197
|
+
"render-orchestration": (
|
|
198
|
+
"chaoscypher_cli.commands.render_orchestration:render_orchestration_command",
|
|
199
|
+
"Render nginx/supervisord/valkey configs from current Pydantic settings",
|
|
200
|
+
),
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
# Subcommands that bypass the first-run setup gate. These are either
|
|
205
|
+
# bootstrap commands (setup, completions), DB-free read-only diagnostics
|
|
206
|
+
# (health, doctor, diagnostics, config), or schema-housekeeping that
|
|
207
|
+
# must run before any feature command can ever succeed (db, upgrade,
|
|
208
|
+
# render-orchestration). `--help` / `--version` are handled separately
|
|
209
|
+
# via sys.argv inspection.
|
|
210
|
+
_FIRST_RUN_SAFE_SUBCOMMANDS = frozenset(
|
|
211
|
+
{
|
|
212
|
+
"setup",
|
|
213
|
+
"health",
|
|
214
|
+
"doctor",
|
|
215
|
+
"diagnostics",
|
|
216
|
+
"config",
|
|
217
|
+
"db",
|
|
218
|
+
"upgrade",
|
|
219
|
+
"completions",
|
|
220
|
+
"render-orchestration",
|
|
221
|
+
}
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
# Subcommands that are allowed to run while the DB is blocked on a
|
|
226
|
+
# tier-2 migration. The migrate subcommand is obviously needed so the
|
|
227
|
+
# user can resolve the block; setup/health/diagnostics are read-only
|
|
228
|
+
# or don't touch the live schema.
|
|
229
|
+
_UPGRADE_SAFE_SUBCOMMANDS = frozenset(
|
|
230
|
+
{
|
|
231
|
+
"db",
|
|
232
|
+
"setup",
|
|
233
|
+
"health",
|
|
234
|
+
"doctor",
|
|
235
|
+
"diagnostics",
|
|
236
|
+
"config",
|
|
237
|
+
"benchmark",
|
|
238
|
+
"upgrade",
|
|
239
|
+
"render-orchestration",
|
|
240
|
+
# `mcp` starts its own degraded maintenance-mode server when the DB is
|
|
241
|
+
# blocked (see chaoscypher_cli/mcp/command.py), so it must NOT be
|
|
242
|
+
# exited(2) here — that drop is exactly the opaque -32000 we're fixing.
|
|
243
|
+
"mcp",
|
|
244
|
+
}
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def _extract_database_override(argv: list[str]) -> str | None:
|
|
249
|
+
"""Find the ``--database``/``-d`` override in a raw arg list.
|
|
250
|
+
|
|
251
|
+
Returns the database name the user supplied via a subcommand option,
|
|
252
|
+
or ``None`` if no override is present (in which case callers should
|
|
253
|
+
fall back to ``settings.yaml``'s ``current_database``). Accepts both
|
|
254
|
+
space-separated forms (``--database foo``, ``-d foo``) and
|
|
255
|
+
equals-joined (``--database=foo``, ``-d=foo``); the equals form is
|
|
256
|
+
the only one Click actually generates internally but operators type
|
|
257
|
+
both.
|
|
258
|
+
|
|
259
|
+
The guard runs at parent-group time, before Click has parsed the
|
|
260
|
+
subcommand's option schema, so we cannot ask Click "what was
|
|
261
|
+
--database resolved to" — we inspect ``sys.argv`` directly. False
|
|
262
|
+
positives are bounded: a value of literally ``"--database"`` inside
|
|
263
|
+
an unrelated string argument would not match because we look for
|
|
264
|
+
the flag as a token, not a substring.
|
|
265
|
+
"""
|
|
266
|
+
for i, tok in enumerate(argv):
|
|
267
|
+
if tok in ("--database", "-d") and i + 1 < len(argv):
|
|
268
|
+
return argv[i + 1]
|
|
269
|
+
if tok.startswith("--database="):
|
|
270
|
+
return tok.split("=", 1)[1]
|
|
271
|
+
if tok.startswith("-d="):
|
|
272
|
+
return tok.split("=", 1)[1]
|
|
273
|
+
return None
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def _upgrade_guard(ctx: click.Context) -> None:
|
|
277
|
+
"""Refuse to run DB-touching commands while the upgrade state is blocked.
|
|
278
|
+
|
|
279
|
+
Surfaces a clear, actionable message pointing at
|
|
280
|
+
``chaoscypher db migrate`` instead of letting the downstream
|
|
281
|
+
command crash with a schema mismatch. Tolerant of missing
|
|
282
|
+
infrastructure (no DB yet, migration helpers not importable, etc.)
|
|
283
|
+
so fresh installs and limited environments aren't blocked by a
|
|
284
|
+
gate that can't read its own state.
|
|
285
|
+
"""
|
|
286
|
+
# `--help` / `-h` are DB-free no-ops — Click will print help and exit
|
|
287
|
+
# before any handler body runs, so gating them just hides command
|
|
288
|
+
# discovery behind a migration the user can't make sense of yet. Same
|
|
289
|
+
# for shell-completion parses, which Click flags via resilient_parsing.
|
|
290
|
+
if ctx.resilient_parsing or any(arg in ("--help", "-h") for arg in sys.argv[1:]):
|
|
291
|
+
return
|
|
292
|
+
|
|
293
|
+
invoked = ctx.invoked_subcommand
|
|
294
|
+
if invoked is None or invoked in _UPGRADE_SAFE_SUBCOMMANDS:
|
|
295
|
+
return
|
|
296
|
+
try:
|
|
297
|
+
from chaoscypher_cli.engine_config import read_current_database
|
|
298
|
+
from chaoscypher_core.database.engine import get_db_path
|
|
299
|
+
from chaoscypher_core.database.migrations.state import get_upgrade_state
|
|
300
|
+
except Exception:
|
|
301
|
+
return # Can't import — fresh install or dev context. Let the command run.
|
|
302
|
+
|
|
303
|
+
try:
|
|
304
|
+
# If the invocation overrides the database via `--database <name>` /
|
|
305
|
+
# `-d <name>`, gate against THAT database — not whatever
|
|
306
|
+
# settings.yaml calls "current". Otherwise `chaoscypher source list
|
|
307
|
+
# --database fresh_db` would refuse to run whenever the default DB
|
|
308
|
+
# is mid-migration, which is exactly the situation a fresh
|
|
309
|
+
# workspace is meant to escape.
|
|
310
|
+
db_name = (
|
|
311
|
+
_extract_database_override(sys.argv[1:])
|
|
312
|
+
or os.environ.get("CHAOSCYPHER_DATABASE")
|
|
313
|
+
or read_current_database()
|
|
314
|
+
or "default"
|
|
315
|
+
)
|
|
316
|
+
db_path = get_db_path(db_name)
|
|
317
|
+
state = get_upgrade_state(db_path)
|
|
318
|
+
except Exception:
|
|
319
|
+
return # No DB yet or unreadable state; don't block the command.
|
|
320
|
+
|
|
321
|
+
if state.ready:
|
|
322
|
+
return
|
|
323
|
+
|
|
324
|
+
click.echo(
|
|
325
|
+
click.style(
|
|
326
|
+
"This database is waiting on a schema upgrade before it can be used.",
|
|
327
|
+
fg="yellow",
|
|
328
|
+
bold=True,
|
|
329
|
+
),
|
|
330
|
+
err=True,
|
|
331
|
+
)
|
|
332
|
+
if state.message:
|
|
333
|
+
click.echo(f" {state.message}", err=True)
|
|
334
|
+
click.echo(
|
|
335
|
+
"\nRun one of:\n"
|
|
336
|
+
" chaoscypher db migrate status — see what's pending\n"
|
|
337
|
+
" chaoscypher db migrate apply — apply the pending migrations\n"
|
|
338
|
+
" chaoscypher db migrate rollback — restore from pre-upgrade backup",
|
|
339
|
+
err=True,
|
|
340
|
+
)
|
|
341
|
+
ctx.exit(2)
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def _stale_cli_yaml_notice(ctx: click.Context) -> None:
|
|
345
|
+
"""Warn once (per invocation) about a leftover, no-longer-read cli.yaml.
|
|
346
|
+
|
|
347
|
+
The 2026-06 config unification retired ``cli.yaml`` — all configuration
|
|
348
|
+
now lives in ``settings.yaml``. A cli.yaml left behind by an older install
|
|
349
|
+
is silently ignored; this prints a single dim stderr line so the operator
|
|
350
|
+
knows the file is dead and can delete it. Stateless: the note prints while
|
|
351
|
+
the file exists and disappears once it's removed.
|
|
352
|
+
|
|
353
|
+
Bypassed for ``--help`` / shell-completion parses (mirroring the other
|
|
354
|
+
guards) so command discovery isn't cluttered.
|
|
355
|
+
"""
|
|
356
|
+
if ctx.resilient_parsing or any(arg in ("--help", "-h", "--version") for arg in sys.argv[1:]):
|
|
357
|
+
return
|
|
358
|
+
try:
|
|
359
|
+
from chaoscypher_cli.utils.paths import get_config_dir
|
|
360
|
+
|
|
361
|
+
stale = get_config_dir() / "cli.yaml"
|
|
362
|
+
if not stale.exists():
|
|
363
|
+
return
|
|
364
|
+
except Exception:
|
|
365
|
+
return # best-effort UX, never block the CLI
|
|
366
|
+
|
|
367
|
+
click.echo(
|
|
368
|
+
click.style(
|
|
369
|
+
f"chaoscypher: note: {stale} is no longer read and is ignored "
|
|
370
|
+
"(config unification); your settings live in settings.yaml — the "
|
|
371
|
+
"old file can be deleted.",
|
|
372
|
+
dim=True,
|
|
373
|
+
),
|
|
374
|
+
err=True,
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
def _first_run_gate(ctx: click.Context) -> None:
|
|
379
|
+
"""Auto-route a fresh `pipx install` user into the setup wizard.
|
|
380
|
+
|
|
381
|
+
A user with no engine configuration in ``settings.yaml`` (and no
|
|
382
|
+
``CHAOSCYPHER_LLM_PROVIDER`` env override) will otherwise
|
|
383
|
+
hit "LLM Required" the moment they run ``chaoscypher source add
|
|
384
|
+
doc.pdf`` — confusing first-run UX. When we detect that signature,
|
|
385
|
+
interactively offer to run ``chaoscypher setup`` first; in
|
|
386
|
+
non-interactive mode, print an actionable message and exit 2.
|
|
387
|
+
|
|
388
|
+
Bypassed for:
|
|
389
|
+
* ``--help`` / ``-h`` / ``--version`` (let Click render those).
|
|
390
|
+
* Shell-completion parses (``ctx.resilient_parsing``).
|
|
391
|
+
* Bootstrap / read-only subcommands (see
|
|
392
|
+
``_FIRST_RUN_SAFE_SUBCOMMANDS``).
|
|
393
|
+
* Any invocation where setup is already complete (``settings.yaml``
|
|
394
|
+
records ``setup_completed`` / an ``llm.chat_provider``, or the
|
|
395
|
+
``CHAOSCYPHER_LLM_PROVIDER`` env var is set).
|
|
396
|
+
|
|
397
|
+
Best-effort: if the config helpers can't import (broken install,
|
|
398
|
+
test harness), let the CLI continue — the gate is friendly UX, not
|
|
399
|
+
a correctness invariant.
|
|
400
|
+
"""
|
|
401
|
+
if ctx.resilient_parsing or any(arg in ("--help", "-h", "--version") for arg in sys.argv[1:]):
|
|
402
|
+
return
|
|
403
|
+
|
|
404
|
+
invoked = ctx.invoked_subcommand
|
|
405
|
+
if invoked is None or invoked in _FIRST_RUN_SAFE_SUBCOMMANDS:
|
|
406
|
+
return
|
|
407
|
+
|
|
408
|
+
try:
|
|
409
|
+
from chaoscypher_cli.engine_config import is_setup_completed, settings_yaml_path
|
|
410
|
+
except Exception:
|
|
411
|
+
return
|
|
412
|
+
|
|
413
|
+
try:
|
|
414
|
+
if is_setup_completed():
|
|
415
|
+
return
|
|
416
|
+
settings_path = settings_yaml_path()
|
|
417
|
+
except Exception:
|
|
418
|
+
return
|
|
419
|
+
|
|
420
|
+
# First run signature confirmed.
|
|
421
|
+
is_tty = sys.stdin.isatty() and sys.stderr.isatty()
|
|
422
|
+
|
|
423
|
+
click.echo(
|
|
424
|
+
click.style(
|
|
425
|
+
"It looks like this is your first time running Chaos Cypher.",
|
|
426
|
+
fg="cyan",
|
|
427
|
+
bold=True,
|
|
428
|
+
),
|
|
429
|
+
err=True,
|
|
430
|
+
)
|
|
431
|
+
click.echo(
|
|
432
|
+
"No engine configuration was found at "
|
|
433
|
+
f"{settings_path}, and no LLM provider has been set up yet.",
|
|
434
|
+
err=True,
|
|
435
|
+
)
|
|
436
|
+
click.echo("", err=True)
|
|
437
|
+
|
|
438
|
+
if not is_tty:
|
|
439
|
+
click.echo(
|
|
440
|
+
"Run `chaoscypher setup` to configure an LLM provider before "
|
|
441
|
+
f"using `chaoscypher {invoked}`.",
|
|
442
|
+
err=True,
|
|
443
|
+
)
|
|
444
|
+
ctx.exit(2)
|
|
445
|
+
|
|
446
|
+
if not click.confirm(
|
|
447
|
+
click.style("Run `chaoscypher setup` now?", fg="cyan"),
|
|
448
|
+
default=True,
|
|
449
|
+
err=True,
|
|
450
|
+
):
|
|
451
|
+
click.echo(
|
|
452
|
+
f"Skipped. Run `chaoscypher setup` when you're ready, then re-run `chaoscypher {invoked}`.",
|
|
453
|
+
err=True,
|
|
454
|
+
)
|
|
455
|
+
ctx.exit(2)
|
|
456
|
+
|
|
457
|
+
# Hand control to the setup wizard. We exit afterwards rather than
|
|
458
|
+
# continue with the original subcommand — the user's original argv
|
|
459
|
+
# may reference a file that doesn't exist yet, a DB that hasn't been
|
|
460
|
+
# created, etc., and the setup wizard's "Next steps" block already
|
|
461
|
+
# shows them what to run next.
|
|
462
|
+
from chaoscypher_cli.commands.setup import setup as setup_cmd
|
|
463
|
+
|
|
464
|
+
ctx.invoke(setup_cmd)
|
|
465
|
+
ctx.exit(0)
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
@click.group(
|
|
469
|
+
cls=LazyGroup,
|
|
470
|
+
lazy_subcommands=LAZY_COMMANDS,
|
|
471
|
+
context_settings={"max_content_width": 120},
|
|
472
|
+
)
|
|
473
|
+
@click.version_option(version=__version__, prog_name="chaoscypher")
|
|
474
|
+
@click.pass_context
|
|
475
|
+
def main(ctx: click.Context) -> None:
|
|
476
|
+
"""Chaos Cypher CLI - Knowledge Graph Platform."""
|
|
477
|
+
_upgrade_guard(ctx)
|
|
478
|
+
_first_run_gate(ctx)
|
|
479
|
+
_stale_cli_yaml_notice(ctx)
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
if __name__ == "__main__":
|
|
483
|
+
main()
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Copyright (C) 2024-2026 Chaos Cypher, Inc.
|
|
2
|
+
# SPDX-License-Identifier: AGPL-3.0-only
|
|
3
|
+
|
|
4
|
+
"""ChaosCypher Extraction Benchmark engine.
|
|
5
|
+
|
|
6
|
+
Public API: dataset/scorer protocols, the ExtractionDataset and
|
|
7
|
+
V7ExtractionScorer v1 implementations, model config + price registry,
|
|
8
|
+
the sequential runner, named-config loader, dataset discovery (built-in
|
|
9
|
+
+ user overlay), and leaderboard rendering. Result types and JSON I/O
|
|
10
|
+
live in ``results``.
|
|
11
|
+
|
|
12
|
+
Vocabulary:
|
|
13
|
+
dataset - the test unit (corpus + metadata + how to evaluate it).
|
|
14
|
+
corpus - the body of text inside a dataset.
|
|
15
|
+
config - a runnable benchmark recipe (name, params, dataset ids,
|
|
16
|
+
models). Loaded by ``bench run [NAME]``.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
from chaoscypher_cli.benchmark.config import (
|
|
22
|
+
DEFAULT_CONFIG_NAME,
|
|
23
|
+
BenchmarkConfig,
|
|
24
|
+
list_configs,
|
|
25
|
+
load_config,
|
|
26
|
+
)
|
|
27
|
+
from chaoscypher_cli.benchmark.dataset import (
|
|
28
|
+
BenchmarkDataset,
|
|
29
|
+
DatasetScorer,
|
|
30
|
+
DatasetSource,
|
|
31
|
+
RawOutput,
|
|
32
|
+
)
|
|
33
|
+
from chaoscypher_cli.benchmark.discovery import (
|
|
34
|
+
builtin_dataset_root,
|
|
35
|
+
discover_datasets,
|
|
36
|
+
user_benchmark_root,
|
|
37
|
+
user_dataset_root,
|
|
38
|
+
)
|
|
39
|
+
from chaoscypher_cli.benchmark.extraction_dataset import ExtractionDataset
|
|
40
|
+
from chaoscypher_cli.benchmark.leaderboard import (
|
|
41
|
+
ModelAggregate,
|
|
42
|
+
aggregate_by_model,
|
|
43
|
+
render_leaderboard,
|
|
44
|
+
)
|
|
45
|
+
from chaoscypher_cli.benchmark.models import (
|
|
46
|
+
ModelConfig,
|
|
47
|
+
compute_cost,
|
|
48
|
+
filter_models,
|
|
49
|
+
)
|
|
50
|
+
from chaoscypher_cli.benchmark.results import (
|
|
51
|
+
BenchmarkResult,
|
|
52
|
+
ScoreResult,
|
|
53
|
+
dump_results,
|
|
54
|
+
load_results,
|
|
55
|
+
)
|
|
56
|
+
from chaoscypher_cli.benchmark.runner import BENCHMARK_VERSION, run_benchmark
|
|
57
|
+
from chaoscypher_cli.benchmark.scorers.v7 import V7ExtractionScorer
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
__version__ = "0.1.0"
|
|
61
|
+
|
|
62
|
+
__all__ = [
|
|
63
|
+
"BENCHMARK_VERSION",
|
|
64
|
+
"DEFAULT_CONFIG_NAME",
|
|
65
|
+
"BenchmarkConfig",
|
|
66
|
+
"BenchmarkDataset",
|
|
67
|
+
"BenchmarkResult",
|
|
68
|
+
"DatasetScorer",
|
|
69
|
+
"DatasetSource",
|
|
70
|
+
"ExtractionDataset",
|
|
71
|
+
"ModelAggregate",
|
|
72
|
+
"ModelConfig",
|
|
73
|
+
"RawOutput",
|
|
74
|
+
"ScoreResult",
|
|
75
|
+
"V7ExtractionScorer",
|
|
76
|
+
"__version__",
|
|
77
|
+
"aggregate_by_model",
|
|
78
|
+
"builtin_dataset_root",
|
|
79
|
+
"compute_cost",
|
|
80
|
+
"discover_datasets",
|
|
81
|
+
"dump_results",
|
|
82
|
+
"filter_models",
|
|
83
|
+
"list_configs",
|
|
84
|
+
"load_config",
|
|
85
|
+
"load_results",
|
|
86
|
+
"render_leaderboard",
|
|
87
|
+
"run_benchmark",
|
|
88
|
+
"user_benchmark_root",
|
|
89
|
+
"user_dataset_root",
|
|
90
|
+
]
|