elephia 0.1.1__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.
- elephia/__init__.py +11 -0
- elephia/__main__.py +4 -0
- elephia/cli.py +416 -0
- elephia/core/__init__.py +1 -0
- elephia/core/compiler.py +1032 -0
- elephia/core/models.py +111 -0
- elephia/core/retrieval.py +93 -0
- elephia/core/store.py +414 -0
- elephia/core/tokens.py +109 -0
- elephia/demo.py +49 -0
- elephia/engine.py +766 -0
- elephia/install.py +198 -0
- elephia/server.py +341 -0
- elephia/ui.py +461 -0
- elephia/usage.py +116 -0
- elephia-0.1.1.dist-info/METADATA +188 -0
- elephia-0.1.1.dist-info/RECORD +21 -0
- elephia-0.1.1.dist-info/WHEEL +5 -0
- elephia-0.1.1.dist-info/entry_points.txt +2 -0
- elephia-0.1.1.dist-info/licenses/LICENSE +190 -0
- elephia-0.1.1.dist-info/top_level.txt +1 -0
elephia/__init__.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""elephia - git for your AI's context.
|
|
2
|
+
|
|
3
|
+
A local-first, deterministic memory engine exposed over MCP (Model Context
|
|
4
|
+
Protocol) and a git-style CLI. Every conversation turn is committed to an
|
|
5
|
+
append-only journal; durable facts merge into a versioned wiki; each prompt
|
|
6
|
+
gets a token-budgeted, salience-ranked context "branch" compiled from history.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
__version__ = "0.1.1"
|
|
10
|
+
|
|
11
|
+
from elephia.engine import Elephia, resolve_store_dir # noqa: F401
|
elephia/__main__.py
ADDED
elephia/cli.py
ADDED
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
"""elephia CLI — git-style commands over your AI's context.
|
|
2
|
+
|
|
3
|
+
elephia init create a context store here (.elephia/)
|
|
4
|
+
elephia ui open the point-and-click dashboard in your browser
|
|
5
|
+
elephia serve run the MCP server on stdio
|
|
6
|
+
elephia install X wire the server into claude-desktop/claude-code/codex/cursor
|
|
7
|
+
elephia status store overview + token counters
|
|
8
|
+
elephia log recent events (newest first)
|
|
9
|
+
elephia show REF full record: event:<id> | wiki:<title> | mut:<id>
|
|
10
|
+
elephia search QUERY BM25 search over events + wiki
|
|
11
|
+
elephia branch PROMPT compile the context branch for a prompt
|
|
12
|
+
elephia merges merge history + pending queue
|
|
13
|
+
elephia pending ... list / approve / reject pending merges
|
|
14
|
+
elephia remember FACT save a durable fact
|
|
15
|
+
elephia stale PAGE mark a wiki page stale
|
|
16
|
+
elephia stats token usage ledger
|
|
17
|
+
elephia demo seed demo data to try things out
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import argparse
|
|
23
|
+
import json
|
|
24
|
+
import os
|
|
25
|
+
import sys
|
|
26
|
+
from typing import Any
|
|
27
|
+
|
|
28
|
+
from elephia import __version__
|
|
29
|
+
from elephia.engine import Elephia, GLOBAL_STORE, STORE_DIRNAME, resolve_store_dir
|
|
30
|
+
from elephia.install import INSTALLERS, snippets
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _print_json(data: Any) -> None:
|
|
34
|
+
print(json.dumps(data, indent=2, ensure_ascii=False))
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _engine(args: argparse.Namespace) -> Elephia:
|
|
38
|
+
return Elephia(getattr(args, "store", None), budget=getattr(args, "budget", None) or 700)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def cmd_init(args: argparse.Namespace) -> int:
|
|
42
|
+
if args.store:
|
|
43
|
+
target = os.path.abspath(os.path.expanduser(args.store))
|
|
44
|
+
elif getattr(args, "global_store", False):
|
|
45
|
+
target = GLOBAL_STORE
|
|
46
|
+
else:
|
|
47
|
+
target = os.path.join(os.getcwd(), STORE_DIRNAME)
|
|
48
|
+
Elephia(target)
|
|
49
|
+
print(f"Initialized empty context store in {target}")
|
|
50
|
+
print("Next steps:")
|
|
51
|
+
print(" elephia demo # optional: seed sample data")
|
|
52
|
+
print(" elephia install claude-code # or claude-desktop / codex / cursor")
|
|
53
|
+
return 0
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def cmd_serve(args: argparse.Namespace) -> int:
|
|
57
|
+
from elephia.server import run_stdio
|
|
58
|
+
|
|
59
|
+
store = args.store or os.environ.get("ELEPHIA_DIR") or os.environ.get("CONTEXTGIT_DIR") or None
|
|
60
|
+
engine = Elephia(
|
|
61
|
+
store,
|
|
62
|
+
budget=args.budget,
|
|
63
|
+
compiler_config={"include_full_history": args.full_history},
|
|
64
|
+
)
|
|
65
|
+
run_stdio(engine)
|
|
66
|
+
return 0
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def cmd_ui(args: argparse.Namespace) -> int:
|
|
70
|
+
from elephia.ui import run_ui
|
|
71
|
+
|
|
72
|
+
return run_ui(
|
|
73
|
+
_engine(args),
|
|
74
|
+
port=args.port,
|
|
75
|
+
open_browser=not args.no_open,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def cmd_status(args: argparse.Namespace) -> int:
|
|
80
|
+
status = _engine(args).status()
|
|
81
|
+
if args.json:
|
|
82
|
+
_print_json(status)
|
|
83
|
+
return 0
|
|
84
|
+
print(f"store: {status['store_dir']}")
|
|
85
|
+
print(f"events: {status['events']} ({status['full_history_tokens']} tokens of raw history)")
|
|
86
|
+
print(
|
|
87
|
+
f"wiki: {status['wiki_pages']} pages "
|
|
88
|
+
f"({status['wiki_pages_active']} active, {status['wiki_pages_stale']} stale)"
|
|
89
|
+
)
|
|
90
|
+
print(f"merges: {status['mutations']} mutations, {status['pending_merges']} pending review")
|
|
91
|
+
print(f"budget: {status['budget_tokens']} tokens/patch (counter: {status['token_counter']})")
|
|
92
|
+
usage = status["usage"]
|
|
93
|
+
print(
|
|
94
|
+
f"usage: {usage['compilations']} compilations, "
|
|
95
|
+
f"{usage['saved_tokens_total']} tokens saved ({usage['savings_pct']}%)"
|
|
96
|
+
)
|
|
97
|
+
return 0
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def cmd_log(args: argparse.Namespace) -> int:
|
|
101
|
+
rows = _engine(args).log(limit=args.number)
|
|
102
|
+
if args.json:
|
|
103
|
+
_print_json(rows)
|
|
104
|
+
return 0
|
|
105
|
+
if not rows:
|
|
106
|
+
print("(no events yet — try `elephia demo` or `elephia remember \"...\"`)")
|
|
107
|
+
return 0
|
|
108
|
+
for row in rows:
|
|
109
|
+
print(f"{row['ref']}")
|
|
110
|
+
print(f" {row['timestamp']} {row['speaker']:<9} {row['tokens']:>4} tok {row['summary']}")
|
|
111
|
+
return 0
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def cmd_show(args: argparse.Namespace) -> int:
|
|
115
|
+
try:
|
|
116
|
+
_print_json(_engine(args).show(args.ref))
|
|
117
|
+
return 0
|
|
118
|
+
except (KeyError, ValueError) as exc:
|
|
119
|
+
print(f"error: {exc}", file=sys.stderr)
|
|
120
|
+
return 1
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def cmd_search(args: argparse.Namespace) -> int:
|
|
124
|
+
results = _engine(args).search(args.query, limit=args.number)
|
|
125
|
+
if args.json:
|
|
126
|
+
_print_json(results)
|
|
127
|
+
return 0
|
|
128
|
+
if not results:
|
|
129
|
+
print("(no matches)")
|
|
130
|
+
return 0
|
|
131
|
+
for row in results:
|
|
132
|
+
print(f"{row['score']:>7.3f} {row['ref']}")
|
|
133
|
+
print(f" {row['summary']}")
|
|
134
|
+
return 0
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def cmd_branch(args: argparse.Namespace) -> int:
|
|
138
|
+
engine = _engine(args)
|
|
139
|
+
if args.explain:
|
|
140
|
+
explanation = engine.explain(args.prompt, budget=args.budget)
|
|
141
|
+
if args.json:
|
|
142
|
+
_print_json(explanation)
|
|
143
|
+
return 0
|
|
144
|
+
print(f"budget: {explanation['budget']} tokens -> patch: {explanation['estimated_tokens']} tokens\n")
|
|
145
|
+
print("SELECTED:")
|
|
146
|
+
for row in explanation["selected"]:
|
|
147
|
+
comps = row["score_components"]
|
|
148
|
+
print(f" {row['score']:+.3f} {row['ref']} ({row['tokens']} tok)")
|
|
149
|
+
print(
|
|
150
|
+
f" rel={comps.get('query_relevance', 0):.2f} "
|
|
151
|
+
f"rec={comps.get('recency', 0):.2f} freq={comps.get('frequency', 0):.2f} "
|
|
152
|
+
f"corr={comps.get('correction_priority', 0):.0f} stale={comps.get('stale_noise_penalty', 0):.1f}"
|
|
153
|
+
)
|
|
154
|
+
print(f"\nEXCLUDED ({explanation['excluded_total']}):")
|
|
155
|
+
for row in explanation["excluded"]:
|
|
156
|
+
reasons = ",".join(row["exclusion_reasons"]) or "ranked_below_cut"
|
|
157
|
+
print(f" {row['score']:+.3f} {row['ref']} [{reasons}]")
|
|
158
|
+
return 0
|
|
159
|
+
result = engine.prepare(args.prompt, budget=args.budget, record_usage=not args.dry_run)
|
|
160
|
+
if args.json:
|
|
161
|
+
_print_json(result)
|
|
162
|
+
return 0
|
|
163
|
+
print(result["context"])
|
|
164
|
+
print(
|
|
165
|
+
f"\n-- {result['estimated_tokens']} tokens (budget {result['budget']}) | "
|
|
166
|
+
f"full history would be {result['full_history_tokens']} tokens | "
|
|
167
|
+
f"saved {result['saved_tokens']} ({result['savings_pct']}%)"
|
|
168
|
+
)
|
|
169
|
+
return 0
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def cmd_merges(args: argparse.Namespace) -> int:
|
|
173
|
+
data = _engine(args).merges(limit=args.number)
|
|
174
|
+
if args.json:
|
|
175
|
+
_print_json(data)
|
|
176
|
+
return 0
|
|
177
|
+
if not data["mutations"]:
|
|
178
|
+
print("(no merges yet)")
|
|
179
|
+
for row in data["mutations"]:
|
|
180
|
+
claim = (row["claim"] or "")[:90]
|
|
181
|
+
target = f" -> {row['target_page']}" if row["target_page"] else ""
|
|
182
|
+
print(f"{row['ref']}")
|
|
183
|
+
print(f" {row['timestamp']} {row['action']:<11} {claim}{target}")
|
|
184
|
+
if data["pending"]:
|
|
185
|
+
print(f"\nPENDING REVIEW ({len(data['pending'])}):")
|
|
186
|
+
for item in data["pending"]:
|
|
187
|
+
print(f" [{item['type']}] {item['content'][:90]}")
|
|
188
|
+
print(f" reason: {item['reason']}")
|
|
189
|
+
return 0
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def cmd_pending(args: argparse.Namespace) -> int:
|
|
193
|
+
engine = _engine(args)
|
|
194
|
+
if args.action == "list":
|
|
195
|
+
items = engine.merges(limit=0)["pending"]
|
|
196
|
+
if args.json:
|
|
197
|
+
_print_json(items)
|
|
198
|
+
elif not items:
|
|
199
|
+
print("(pending queue is empty)")
|
|
200
|
+
else:
|
|
201
|
+
for item in items:
|
|
202
|
+
print(f"[{item['type']}] {item['content']}")
|
|
203
|
+
print(f" reason: {item['reason']}")
|
|
204
|
+
return 0
|
|
205
|
+
if not args.content:
|
|
206
|
+
print("error: approve/reject require the pending item's content", file=sys.stderr)
|
|
207
|
+
return 1
|
|
208
|
+
try:
|
|
209
|
+
result = engine.resolve_pending(args.content, args.action)
|
|
210
|
+
except (KeyError, ValueError) as exc:
|
|
211
|
+
print(f"error: {exc}", file=sys.stderr)
|
|
212
|
+
return 1
|
|
213
|
+
print(f"{result['action']}: {args.content[:90]} ({result['ref']})")
|
|
214
|
+
return 0
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def cmd_remember(args: argparse.Namespace) -> int:
|
|
218
|
+
result = _engine(args).remember(args.fact, page=args.page)
|
|
219
|
+
if result.get("pending_review"):
|
|
220
|
+
print(f"pending review for '{result['target_page']}' ({result['ref']})")
|
|
221
|
+
print(f" {result['claim']}")
|
|
222
|
+
return 0
|
|
223
|
+
print(f"saved to '{result['target_page']}' ({result['ref']})")
|
|
224
|
+
print(f" {result['claim']}")
|
|
225
|
+
return 0
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def cmd_stale(args: argparse.Namespace) -> int:
|
|
229
|
+
result = _engine(args).mark_stale(args.page, superseded_by=args.superseded_by)
|
|
230
|
+
print(f"marked stale: {args.page} ({result['ref']})")
|
|
231
|
+
return 0
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def cmd_stats(args: argparse.Namespace) -> int:
|
|
235
|
+
stats = _engine(args).stats()
|
|
236
|
+
if args.json:
|
|
237
|
+
_print_json(stats)
|
|
238
|
+
return 0
|
|
239
|
+
all_time = stats["all_time"]
|
|
240
|
+
week = stats["last_7_days"]
|
|
241
|
+
print(f"store: {stats['store_dir']} (token counter: {stats['token_counter']})\n")
|
|
242
|
+
print(f"{'':<14}{'compilations':>14}{'patch tokens':>14}{'saved tokens':>14}{'savings':>10}")
|
|
243
|
+
print(
|
|
244
|
+
f"{'all time':<14}{all_time['compilations']:>14}{all_time['patch_tokens_total']:>14}"
|
|
245
|
+
f"{all_time['saved_tokens_total']:>14}{all_time['savings_pct']:>9}%"
|
|
246
|
+
)
|
|
247
|
+
print(
|
|
248
|
+
f"{'last 7 days':<14}{week['compilations']:>14}{week['patch_tokens_total']:>14}"
|
|
249
|
+
f"{week['saved_tokens_total']:>14}{week['savings_pct']:>9}%"
|
|
250
|
+
)
|
|
251
|
+
if all_time["by_day"]:
|
|
252
|
+
print("\nby day:")
|
|
253
|
+
for day, row in all_time["by_day"].items():
|
|
254
|
+
print(f" {day} {row['compilations']:>4} compilations {row['saved_tokens']:>8} tokens saved")
|
|
255
|
+
return 0
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def cmd_demo(args: argparse.Namespace) -> int:
|
|
259
|
+
from elephia.demo import seed_demo
|
|
260
|
+
|
|
261
|
+
engine = _engine(args)
|
|
262
|
+
result = seed_demo(engine)
|
|
263
|
+
print(f"Seeded {result['turns']} demo turns into {engine.store_dir}")
|
|
264
|
+
print(f" events added: {result['events_added']}, wiki pages now: {result['wiki_pages']}")
|
|
265
|
+
print("Try:")
|
|
266
|
+
print(' elephia branch "What database does Atlas use?"')
|
|
267
|
+
print(' elephia branch "What database does Atlas use?" --explain')
|
|
268
|
+
print(" elephia merges")
|
|
269
|
+
print(" elephia stats")
|
|
270
|
+
return 0
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def cmd_install(args: argparse.Namespace) -> int:
|
|
274
|
+
if args.client == "print":
|
|
275
|
+
print(snippets(store=args.store, budget=args.budget))
|
|
276
|
+
return 0
|
|
277
|
+
installer = INSTALLERS[args.client]
|
|
278
|
+
kwargs = {"store": args.store, "budget": args.budget}
|
|
279
|
+
if args.client == "codex":
|
|
280
|
+
kwargs["force"] = args.force
|
|
281
|
+
print(installer(**kwargs))
|
|
282
|
+
return 0
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def cmd_export(args: argparse.Namespace) -> int:
|
|
286
|
+
engine = _engine(args)
|
|
287
|
+
snapshot = engine.runtime.snapshot().model_dump()
|
|
288
|
+
payload = json.dumps(snapshot, indent=2, ensure_ascii=False)
|
|
289
|
+
if args.out:
|
|
290
|
+
with open(args.out, "w", encoding="utf-8") as f:
|
|
291
|
+
f.write(payload)
|
|
292
|
+
print(f"exported snapshot to {args.out}")
|
|
293
|
+
else:
|
|
294
|
+
print(payload)
|
|
295
|
+
return 0
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def _add_common(parser: argparse.ArgumentParser, json_flag: bool = True) -> None:
|
|
299
|
+
parser.add_argument("--store", help="context store path (default: nearest .elephia/, else global)")
|
|
300
|
+
if json_flag:
|
|
301
|
+
parser.add_argument("--json", action="store_true", help="machine-readable output")
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
305
|
+
parser = argparse.ArgumentParser(
|
|
306
|
+
prog="elephia",
|
|
307
|
+
description="git for your AI's context — local-first memory over MCP",
|
|
308
|
+
)
|
|
309
|
+
parser.add_argument("--version", action="version", version=f"elephia {__version__}")
|
|
310
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
311
|
+
|
|
312
|
+
p = sub.add_parser("init", help="create a context store")
|
|
313
|
+
p.add_argument("--global", dest="global_store", action="store_true", help=f"use the global store ({GLOBAL_STORE})")
|
|
314
|
+
p.add_argument("--store", help="explicit store path")
|
|
315
|
+
p.set_defaults(func=cmd_init)
|
|
316
|
+
|
|
317
|
+
p = sub.add_parser("serve", aliases=["mcp"], help="run the MCP server on stdio")
|
|
318
|
+
p.add_argument("--store", help="context store path")
|
|
319
|
+
p.add_argument("--budget", type=int, default=700, help="token budget per context patch (default 700)")
|
|
320
|
+
p.add_argument("--full-history", action="store_true", help="append full history to every patch (debug)")
|
|
321
|
+
p.set_defaults(func=cmd_serve)
|
|
322
|
+
|
|
323
|
+
p = sub.add_parser("ui", help="open the point-and-click dashboard in your browser")
|
|
324
|
+
p.add_argument("--store", help="context store path (default: nearest .elephia/, else global)")
|
|
325
|
+
p.add_argument("--port", type=int, default=0, help="port to listen on (default: random free port)")
|
|
326
|
+
p.add_argument("--no-open", action="store_true", help="don't open the browser automatically")
|
|
327
|
+
p.add_argument("--budget", type=int, default=700, help="token budget for context previews (default 700)")
|
|
328
|
+
p.set_defaults(func=cmd_ui)
|
|
329
|
+
|
|
330
|
+
p = sub.add_parser("status", help="store overview + token counters")
|
|
331
|
+
_add_common(p)
|
|
332
|
+
p.set_defaults(func=cmd_status)
|
|
333
|
+
|
|
334
|
+
p = sub.add_parser("log", help="recent events, newest first")
|
|
335
|
+
_add_common(p)
|
|
336
|
+
p.add_argument("-n", "--number", type=int, default=20)
|
|
337
|
+
p.set_defaults(func=cmd_log)
|
|
338
|
+
|
|
339
|
+
p = sub.add_parser("show", help="show one record by ref")
|
|
340
|
+
_add_common(p, json_flag=False)
|
|
341
|
+
p.add_argument("ref", help="event:<id> | wiki:<title> | mut:<id>")
|
|
342
|
+
p.set_defaults(func=cmd_show)
|
|
343
|
+
|
|
344
|
+
p = sub.add_parser("search", help="BM25 search over events + wiki")
|
|
345
|
+
_add_common(p)
|
|
346
|
+
p.add_argument("query")
|
|
347
|
+
p.add_argument("-n", "--number", type=int, default=8)
|
|
348
|
+
p.set_defaults(func=cmd_search)
|
|
349
|
+
|
|
350
|
+
p = sub.add_parser("branch", help="compile the context branch for a prompt")
|
|
351
|
+
_add_common(p)
|
|
352
|
+
p.add_argument("prompt")
|
|
353
|
+
p.add_argument("--budget", type=int, help="token budget (default 700)")
|
|
354
|
+
p.add_argument("--explain", action="store_true", help="show selected/excluded with score components")
|
|
355
|
+
p.add_argument("--dry-run", action="store_true", help="don't record this compilation in the usage ledger")
|
|
356
|
+
p.set_defaults(func=cmd_branch)
|
|
357
|
+
|
|
358
|
+
p = sub.add_parser("merges", help="merge history + pending queue")
|
|
359
|
+
_add_common(p)
|
|
360
|
+
p.add_argument("-n", "--number", type=int, default=20)
|
|
361
|
+
p.set_defaults(func=cmd_merges)
|
|
362
|
+
|
|
363
|
+
p = sub.add_parser("pending", help="list/approve/reject pending merges")
|
|
364
|
+
_add_common(p)
|
|
365
|
+
p.add_argument("action", choices=["list", "approve", "reject"], nargs="?", default="list")
|
|
366
|
+
p.add_argument("content", nargs="?", help="exact content of the pending item (for approve/reject)")
|
|
367
|
+
p.set_defaults(func=cmd_pending)
|
|
368
|
+
|
|
369
|
+
p = sub.add_parser("remember", help="save a durable fact")
|
|
370
|
+
_add_common(p, json_flag=False)
|
|
371
|
+
p.add_argument("fact")
|
|
372
|
+
p.add_argument("--page", help="target wiki page title")
|
|
373
|
+
p.set_defaults(func=cmd_remember)
|
|
374
|
+
|
|
375
|
+
p = sub.add_parser("stale", help="mark a wiki page stale")
|
|
376
|
+
_add_common(p, json_flag=False)
|
|
377
|
+
p.add_argument("page")
|
|
378
|
+
p.add_argument("--superseded-by", help="what replaces it")
|
|
379
|
+
p.set_defaults(func=cmd_stale)
|
|
380
|
+
|
|
381
|
+
p = sub.add_parser("stats", help="token usage ledger")
|
|
382
|
+
_add_common(p)
|
|
383
|
+
p.set_defaults(func=cmd_stats)
|
|
384
|
+
|
|
385
|
+
p = sub.add_parser("demo", help="seed demo data")
|
|
386
|
+
_add_common(p, json_flag=False)
|
|
387
|
+
p.set_defaults(func=cmd_demo)
|
|
388
|
+
|
|
389
|
+
p = sub.add_parser("install", help="wire the MCP server into a client")
|
|
390
|
+
p.add_argument("client", choices=[*sorted(INSTALLERS), "print"])
|
|
391
|
+
p.add_argument("--store", help="pin the server to a specific store path")
|
|
392
|
+
p.add_argument("--budget", type=int, help="token budget per patch")
|
|
393
|
+
p.add_argument("--force", action="store_true", help="replace an existing elephia block when supported")
|
|
394
|
+
p.set_defaults(func=cmd_install)
|
|
395
|
+
|
|
396
|
+
p = sub.add_parser("export", help="dump a full JSON snapshot of the store")
|
|
397
|
+
_add_common(p, json_flag=False)
|
|
398
|
+
p.add_argument("--out", help="output file (default: stdout)")
|
|
399
|
+
p.set_defaults(func=cmd_export)
|
|
400
|
+
|
|
401
|
+
return parser
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
def main(argv=None) -> int:
|
|
405
|
+
parser = build_parser()
|
|
406
|
+
args = parser.parse_args(argv)
|
|
407
|
+
try:
|
|
408
|
+
return args.func(args)
|
|
409
|
+
except BrokenPipeError:
|
|
410
|
+
return 0
|
|
411
|
+
except KeyboardInterrupt:
|
|
412
|
+
return 130
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
if __name__ == "__main__":
|
|
416
|
+
raise SystemExit(main())
|
elephia/core/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Deterministic context engine internals (vendored from branch-context-lab)."""
|