agent-bios 0.14.0 → 0.16.0
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.
- package/DEPENDENCIES.md +35 -12
- package/README.md +346 -31
- package/claude/CLAUDE.md +2 -2
- package/claude/agents/frontier.md +1 -1
- package/claude/agents/sweep.md +3 -3
- package/claude/agents/workhorse.md +2 -2
- package/claude/guides/claude-prompting.md +119 -34
- package/claude/guides/cli-multi-model-workflow.md +33 -15
- package/claude/guides/gpt-prompting.md +148 -28
- package/claude/guides/review-request.md +27 -0
- package/claude/guides/session-distill-workflow.md +54 -2
- package/claude/guides/slide-writing/RUNBOOK.md +137 -0
- package/claude/guides/slide-writing/scripts/pair.py +979 -0
- package/claude/guides/slide-writing/scripts/render.mjs +82 -0
- package/claude/guides/slide-writing.md +195 -0
- package/claude/guides/svg-visualization-guide.md +9 -0
- package/claude/guides/verification-discipline.md +5 -1
- package/claude/hooks/tooling-gotchas-hook.py +7 -5
- package/codex/AGENTS.md +2 -2
- package/codex/agents/frontier.toml +2 -1
- package/codex/agents/reviewer.toml +1 -1
- package/codex/agents/sweep.toml +3 -3
- package/codex/agents/workhorse.toml +1 -1
- package/codex/config-additions.toml +1 -1
- package/codex/guides/claude-prompting.md +119 -34
- package/codex/guides/cli-multi-model-workflow.md +33 -15
- package/codex/guides/gpt-prompting.md +148 -28
- package/codex/guides/review-request.md +27 -0
- package/codex/guides/session-distill-workflow.md +54 -2
- package/codex/guides/slide-writing/RUNBOOK.md +137 -0
- package/codex/guides/slide-writing/scripts/pair.py +979 -0
- package/codex/guides/slide-writing/scripts/render.mjs +82 -0
- package/codex/guides/slide-writing.md +195 -0
- package/codex/guides/svg-visualization-guide.md +9 -0
- package/codex/guides/verification-discipline.md +5 -1
- package/compose/assemble.py +290 -14
- package/compose/bootstrap/SKILL.md +119 -0
- package/compose/check-domains.py +102 -9
- package/compose/corpus-state.py +1174 -0
- package/compose/corpus.py +387 -0
- package/compose/corpus_catalog.py +882 -0
- package/compose/corpus_install.py +1617 -0
- package/compose/corpus_session.py +726 -0
- package/compose/corpus_store.py +1414 -0
- package/compose/corpus_transaction.py +236 -0
- package/compose/corpus_ui.py +644 -0
- package/compose/domains.json +101 -100
- package/compose/write-update-cache.py +53 -0
- package/install.sh +174 -24
- package/launch/agent-launch.py +1327 -184
- package/launch/agent-launch.toml +12 -16
- package/launch/i18n/en.toml +113 -7
- package/launch/i18n/ja.toml +113 -7
- package/launch/i18n/ko.toml +113 -7
- package/learn/collect-learning.py +46 -19
- package/learn/migrate-learnings.py +10 -1
- package/package.json +13 -3
- package/provenance.json +1 -1
- package/session-cost.py +22 -2
- package/wrappers/codex-helm.sh +3 -3
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Corpus Studio and machine CLI over the private, revision-checked corpus store.
|
|
3
|
+
|
|
4
|
+
The command is deliberately a client. It renders records and translates user
|
|
5
|
+
input into semantic plan payloads; ``CorpusStore`` remains the only writer and
|
|
6
|
+
owns ids, revisions, paths, serialization, validation, and publication.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import argparse
|
|
11
|
+
import difflib
|
|
12
|
+
import importlib
|
|
13
|
+
import json
|
|
14
|
+
import os
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
import shlex
|
|
17
|
+
import subprocess
|
|
18
|
+
import sys
|
|
19
|
+
import tempfile
|
|
20
|
+
from typing import Any, Sequence
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
from corpus_store import CorpusStore, CorpusStoreError
|
|
24
|
+
except ImportError: # package-style import in tests
|
|
25
|
+
from .corpus_store import CorpusStore, CorpusStoreError
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
SURFACES = ("always", "relevant", "requested", "event", "delegated")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def default_repo() -> Path:
|
|
32
|
+
return Path(os.environ.get("AGENT_BIOS_REPO", Path(__file__).resolve().parents[1]))
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def venv_python() -> Path | None:
|
|
36
|
+
roots: list[Path] = []
|
|
37
|
+
if override := os.environ.get("AGENT_LAUNCH_VENV"):
|
|
38
|
+
roots.append(Path(override).expanduser())
|
|
39
|
+
roots.append(Path.home() / ".local/share/agent-launch/venv")
|
|
40
|
+
for root in roots:
|
|
41
|
+
interpreter = root / "bin" / "python"
|
|
42
|
+
if interpreter.is_file() and os.access(interpreter, os.X_OK):
|
|
43
|
+
return interpreter
|
|
44
|
+
return None
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
48
|
+
parser = argparse.ArgumentParser(
|
|
49
|
+
prog="agent-bios corpus",
|
|
50
|
+
description="Inspect and change the private corpus used by future activated sessions.",
|
|
51
|
+
)
|
|
52
|
+
parser.add_argument("--repo", type=Path, default=default_repo())
|
|
53
|
+
parser.add_argument("--state-dir", type=Path)
|
|
54
|
+
parser.add_argument("--user-dir", type=Path)
|
|
55
|
+
parser.add_argument("--json", action="store_true", dest="as_json")
|
|
56
|
+
sub = parser.add_subparsers(dest="command")
|
|
57
|
+
|
|
58
|
+
list_cmd = sub.add_parser("list", help="list corpus items")
|
|
59
|
+
list_cmd.add_argument("--active-only", action="store_true")
|
|
60
|
+
|
|
61
|
+
search = sub.add_parser("search", help="search item text and metadata")
|
|
62
|
+
search.add_argument("query")
|
|
63
|
+
search.add_argument("--active-only", action="store_true")
|
|
64
|
+
|
|
65
|
+
show = sub.add_parser("show", help="show one corpus item")
|
|
66
|
+
show.add_argument("ref")
|
|
67
|
+
show.add_argument(
|
|
68
|
+
"--view", default="effective",
|
|
69
|
+
choices=("effective", "installed", "change", "diff", "history"),
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
plan = sub.add_parser("plan", help="validate a semantic change without publishing it")
|
|
73
|
+
plan.add_argument("--input", default="-", metavar="FILE")
|
|
74
|
+
|
|
75
|
+
apply_cmd = sub.add_parser("apply", help="publish a previously validated plan")
|
|
76
|
+
apply_cmd.add_argument("plan")
|
|
77
|
+
apply_cmd.add_argument("--expected-revision")
|
|
78
|
+
|
|
79
|
+
history = sub.add_parser("history", help="list recoverable authoring revisions")
|
|
80
|
+
history.add_argument("ref", nargs="?")
|
|
81
|
+
|
|
82
|
+
sub.add_parser("status", help="show private store state")
|
|
83
|
+
|
|
84
|
+
snapshot = sub.add_parser("snapshot", help="compose current authoring or inspect an immutable snapshot")
|
|
85
|
+
snapshot_view = snapshot.add_mutually_exclusive_group(required=True)
|
|
86
|
+
snapshot_view.add_argument("--host", choices=("claude", "codex"))
|
|
87
|
+
snapshot_view.add_argument("--content-ref", help="read this stored snapshot without resolving current authoring")
|
|
88
|
+
snapshot.add_argument("--native", action="store_true", help="opt into selected Claude native hooks and agents for this snapshot")
|
|
89
|
+
|
|
90
|
+
install = sub.add_parser("install", help="record the current package as a private baseline")
|
|
91
|
+
install.add_argument("--domains", help="comma-separated qualified selection values")
|
|
92
|
+
return parser
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _load_json(source: str) -> dict[str, Any]:
|
|
96
|
+
try:
|
|
97
|
+
text = sys.stdin.read() if source == "-" else Path(source).read_text(encoding="utf-8")
|
|
98
|
+
payload = json.loads(text)
|
|
99
|
+
except (OSError, ValueError) as exc:
|
|
100
|
+
raise CorpusStoreError(f"cannot read plan input {source!r}: {exc}") from exc
|
|
101
|
+
if not isinstance(payload, dict):
|
|
102
|
+
raise CorpusStoreError("plan input must be one JSON object")
|
|
103
|
+
return payload
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _matches(item: dict[str, Any], query: str) -> bool:
|
|
107
|
+
needle = query.casefold()
|
|
108
|
+
values: list[str] = []
|
|
109
|
+
for key in ("ref", "title", "body", "surface", "tier", "kind", "state", "package_id"):
|
|
110
|
+
value = item.get(key)
|
|
111
|
+
if isinstance(value, str):
|
|
112
|
+
values.append(value)
|
|
113
|
+
values.extend(str(value) for value in item.get("domains", []) if isinstance(value, str))
|
|
114
|
+
return needle in "\n".join(values).casefold()
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def search_items(store: CorpusStore, query: str, *, include_removed: bool = True) -> list[dict[str, Any]]:
|
|
118
|
+
return [item for item in store.list_items(include_removed=include_removed) if _matches(item, query)]
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _human_item(item: dict[str, Any]) -> str:
|
|
122
|
+
state = item.get("state", "active")
|
|
123
|
+
return f"{item.get('ref', '?')}\t{state}\t{item.get('surface', '?')}\t{item.get('title', '')}"
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _human_show(result: dict[str, Any]) -> str:
|
|
127
|
+
item = result.get("item")
|
|
128
|
+
if isinstance(item, dict):
|
|
129
|
+
metadata = (
|
|
130
|
+
f"`{item.get('ref', result.get('ref', ''))}` · {result.get('state', 'active')} · "
|
|
131
|
+
f"{item.get('surface', '?')} · {item.get('kind', '?')}"
|
|
132
|
+
)
|
|
133
|
+
return f"# {item.get('title', item.get('ref', 'Corpus item'))}\n\n{metadata}\n\n{item.get('body', '')}"
|
|
134
|
+
return json.dumps(result, ensure_ascii=False, indent=2, sort_keys=True)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def emit(value: Any, *, as_json: bool, command: str) -> None:
|
|
138
|
+
if as_json:
|
|
139
|
+
print(json.dumps(value, ensure_ascii=False, indent=2, sort_keys=True))
|
|
140
|
+
return
|
|
141
|
+
if command in {"list", "search"} and isinstance(value, list):
|
|
142
|
+
for item in value:
|
|
143
|
+
print(_human_item(item))
|
|
144
|
+
elif command == "show" and isinstance(value, dict):
|
|
145
|
+
print(_human_show(value))
|
|
146
|
+
elif command == "history" and isinstance(value, list):
|
|
147
|
+
for row in value:
|
|
148
|
+
print(f"{row.get('history_id', '?')}\t{row.get('revision', '?')}\t{row.get('path', '')}")
|
|
149
|
+
elif command == "status" and isinstance(value, dict):
|
|
150
|
+
for key, current in value.items():
|
|
151
|
+
print(f"{key}: {json.dumps(current, ensure_ascii=False)}")
|
|
152
|
+
elif command == "snapshot" and isinstance(value, dict):
|
|
153
|
+
print(f"content_ref: {value.get('content_ref')}\npath: {value.get('path')}\nrevision: {value.get('revision')}")
|
|
154
|
+
if value.get("unavailable"):
|
|
155
|
+
print("activation unverified or unavailable:")
|
|
156
|
+
for row in value["unavailable"]:
|
|
157
|
+
print(f"- {row.get('ref')}: {row.get('reason')}")
|
|
158
|
+
else:
|
|
159
|
+
print(json.dumps(value, ensure_ascii=False, indent=2, sort_keys=True))
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def run_command(args: argparse.Namespace, store: CorpusStore) -> Any:
|
|
163
|
+
command = args.command
|
|
164
|
+
if command == "list":
|
|
165
|
+
return store.list_items(include_removed=not args.active_only)
|
|
166
|
+
if command == "search":
|
|
167
|
+
return search_items(store, args.query, include_removed=not args.active_only)
|
|
168
|
+
if command == "show":
|
|
169
|
+
return store.show(args.ref, view=args.view)
|
|
170
|
+
if command == "plan":
|
|
171
|
+
return store.plan(_load_json(args.input))
|
|
172
|
+
if command == "apply":
|
|
173
|
+
return store.apply(args.plan, expected_revision=args.expected_revision)
|
|
174
|
+
if command == "history":
|
|
175
|
+
return store.history(args.ref)
|
|
176
|
+
if command == "status":
|
|
177
|
+
return store.status()
|
|
178
|
+
if command == "snapshot":
|
|
179
|
+
if args.content_ref:
|
|
180
|
+
if args.native:
|
|
181
|
+
raise CorpusStoreError("a stored snapshot is immutable; --native requires --host")
|
|
182
|
+
return store.snapshot_inventory(args.content_ref)
|
|
183
|
+
return store.snapshot(args.host, native=args.native)
|
|
184
|
+
if command == "install":
|
|
185
|
+
domains = None
|
|
186
|
+
if args.domains is not None:
|
|
187
|
+
domains = [part.strip() for part in args.domains.split(",") if part.strip()]
|
|
188
|
+
if domains == ["none"]:
|
|
189
|
+
domains = []
|
|
190
|
+
return store.install(domains=domains)
|
|
191
|
+
raise CorpusStoreError(f"unknown corpus command: {command}")
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def _draft_text(initial: str = "") -> str:
|
|
195
|
+
editor = os.environ.get("VISUAL") or os.environ.get("EDITOR")
|
|
196
|
+
if editor:
|
|
197
|
+
with tempfile.TemporaryDirectory(prefix="agent-bios-corpus-draft-") as directory:
|
|
198
|
+
path = Path(directory) / "draft.md"
|
|
199
|
+
path.write_text(initial, encoding="utf-8")
|
|
200
|
+
try:
|
|
201
|
+
completed = subprocess.run([*shlex.split(editor), str(path)], check=False)
|
|
202
|
+
except OSError as exc:
|
|
203
|
+
raise CorpusStoreError(f"cannot run editor: {exc}") from exc
|
|
204
|
+
if completed.returncode != 0:
|
|
205
|
+
raise CorpusStoreError(f"editor exited with status {completed.returncode}")
|
|
206
|
+
return path.read_text(encoding="utf-8")
|
|
207
|
+
print("Enter Markdown. Finish with a line containing only '.'")
|
|
208
|
+
if initial:
|
|
209
|
+
print("Current body follows; enter a complete replacement.")
|
|
210
|
+
print(initial)
|
|
211
|
+
lines: list[str] = []
|
|
212
|
+
while True:
|
|
213
|
+
line = input()
|
|
214
|
+
if line == ".":
|
|
215
|
+
return "\n".join(lines) + ("\n" if lines else "")
|
|
216
|
+
lines.append(line)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def _choose_surface(current: str = "requested") -> str:
|
|
220
|
+
print("Consumption surface:")
|
|
221
|
+
for index, surface in enumerate(SURFACES, 1):
|
|
222
|
+
marker = " *" if surface == current else ""
|
|
223
|
+
print(f" {index}. {surface}{marker}")
|
|
224
|
+
raw = input(f"Select [default {SURFACES.index(current) + 1}]: ").strip()
|
|
225
|
+
if not raw:
|
|
226
|
+
return current
|
|
227
|
+
try:
|
|
228
|
+
return SURFACES[int(raw) - 1]
|
|
229
|
+
except (ValueError, IndexError) as exc:
|
|
230
|
+
raise CorpusStoreError("invalid surface selection") from exc
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def _preview_and_apply(store: CorpusStore, payload: dict[str, Any], preview: str) -> None:
|
|
234
|
+
plan = store.plan(payload)
|
|
235
|
+
print(preview)
|
|
236
|
+
print(json.dumps(plan, ensure_ascii=False, indent=2, sort_keys=True))
|
|
237
|
+
if input("Apply this plan? [y/N]: ").strip().casefold() not in {"y", "yes"}:
|
|
238
|
+
print("Plan was not applied.")
|
|
239
|
+
return
|
|
240
|
+
applied = store.apply(plan["plan_id"], expected_revision=plan["expected_revision"])
|
|
241
|
+
print(json.dumps(applied, ensure_ascii=False, indent=2, sort_keys=True))
|
|
242
|
+
print("Future activated sessions use the new authoring state; this running session is unchanged.")
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def run_numbered(store: CorpusStore) -> int:
|
|
246
|
+
"""Useful fallback when Textual is unavailable; canonical writes still use the store."""
|
|
247
|
+
while True:
|
|
248
|
+
items = store.list_items(include_removed=True)
|
|
249
|
+
print("\nCorpus Studio (numbered fallback)")
|
|
250
|
+
for index, item in enumerate(items, 1):
|
|
251
|
+
print(f" {index}. {_human_item(item)}")
|
|
252
|
+
print(" c. create e. edit r. remove s. restore v. recover x. reset /. search q. quit")
|
|
253
|
+
choice = input("Choice: ").strip()
|
|
254
|
+
if choice.casefold() == "q":
|
|
255
|
+
return 0
|
|
256
|
+
if choice == "/":
|
|
257
|
+
query = input("Search: ")
|
|
258
|
+
for item in search_items(store, query):
|
|
259
|
+
print(_human_item(item))
|
|
260
|
+
continue
|
|
261
|
+
if choice.casefold() == "c":
|
|
262
|
+
title = input("Title: ").strip()
|
|
263
|
+
body = _draft_text()
|
|
264
|
+
surface = _choose_surface()
|
|
265
|
+
item = {"title": title, "body": body, "surface": surface,
|
|
266
|
+
"tier": "env-personal", "domains": ["personal"], "kind": "rule",
|
|
267
|
+
"members": {"content.md": body}}
|
|
268
|
+
_preview_and_apply(store, {"operation": "create", "item": item}, f"Create {title!r} on {surface}.")
|
|
269
|
+
continue
|
|
270
|
+
if choice.casefold() == "x":
|
|
271
|
+
_preview_and_apply(store, {"operation": "reset"}, "Reset future corpus authoring to the last successful install tuple.")
|
|
272
|
+
continue
|
|
273
|
+
if choice.casefold() in {"e", "r", "s", "v"}:
|
|
274
|
+
ref = input("CorpusRef: ").strip()
|
|
275
|
+
row = next((item for item in items if item.get("ref") == ref), None)
|
|
276
|
+
if row is None:
|
|
277
|
+
print("Unknown CorpusRef.", file=sys.stderr)
|
|
278
|
+
continue
|
|
279
|
+
if choice.casefold() == "e":
|
|
280
|
+
body = _draft_text(str(row.get("body", "")))
|
|
281
|
+
surface = _choose_surface(str(row.get("surface", "requested")))
|
|
282
|
+
patch = {"body": body, "surface": surface}
|
|
283
|
+
if row.get("kind") == "hook":
|
|
284
|
+
try:
|
|
285
|
+
from corpus_catalog import CLAUDE_HOOK_EVENTS
|
|
286
|
+
except ImportError: # package-style import from repository root
|
|
287
|
+
from .corpus_catalog import CLAUDE_HOOK_EVENTS
|
|
288
|
+
binding = row.get("hook") if isinstance(row.get("hook"), dict) else {}
|
|
289
|
+
default_event = str(binding.get("event", sorted(CLAUDE_HOOK_EVENTS)[0]))
|
|
290
|
+
while True:
|
|
291
|
+
event = input(f"Hook event [{default_event}]: ").strip() or default_event
|
|
292
|
+
if event in CLAUDE_HOOK_EVENTS:
|
|
293
|
+
break
|
|
294
|
+
print("Unsupported Claude hook event.", file=sys.stderr)
|
|
295
|
+
default_matcher = str(binding.get("matcher", ""))
|
|
296
|
+
matcher = input(f"Hook matcher [{default_matcher}]: ")
|
|
297
|
+
matcher = matcher if matcher else default_matcher
|
|
298
|
+
if not matcher or "\n" in matcher or "\r" in matcher:
|
|
299
|
+
print("Hook matcher must be one non-empty line.", file=sys.stderr)
|
|
300
|
+
continue
|
|
301
|
+
patch["hook"] = {"event": event, "matcher": matcher}
|
|
302
|
+
diff = "".join(difflib.unified_diff(
|
|
303
|
+
str(row.get("body", "")).splitlines(True), body.splitlines(True),
|
|
304
|
+
fromfile="current", tofile="planned",
|
|
305
|
+
)) or "(metadata-only change)"
|
|
306
|
+
hook_preview = (f"\nHook binding: {patch['hook']['event']} / {patch['hook']['matcher']}"
|
|
307
|
+
if "hook" in patch else "")
|
|
308
|
+
_preview_and_apply(store, {"operation": "update", "ref": ref,
|
|
309
|
+
"item_digest": row["digest"], "patch": patch}, diff + hook_preview)
|
|
310
|
+
elif choice.casefold() == "r":
|
|
311
|
+
_preview_and_apply(store, {"operation": "remove", "ref": ref,
|
|
312
|
+
"item_digest": row.get("digest")}, f"Remove {ref} from future snapshots.")
|
|
313
|
+
elif choice.casefold() == "s":
|
|
314
|
+
_preview_and_apply(store, {"operation": "restore", "ref": ref},
|
|
315
|
+
f"Restore {ref} from the selected installed baseline.")
|
|
316
|
+
else:
|
|
317
|
+
_preview_and_apply(store, {"operation": "recover", "ref": ref},
|
|
318
|
+
f"Recover personal item {ref} for future snapshots.")
|
|
319
|
+
continue
|
|
320
|
+
try:
|
|
321
|
+
index = int(choice) - 1
|
|
322
|
+
print(_human_show(store.show(items[index]["ref"])))
|
|
323
|
+
except (ValueError, IndexError):
|
|
324
|
+
print("Invalid choice.", file=sys.stderr)
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def _run_tui_or_fallback(args: argparse.Namespace, store: CorpusStore) -> int:
|
|
328
|
+
module_name = f"{__package__}.corpus_ui" if __package__ else "corpus_ui"
|
|
329
|
+
try:
|
|
330
|
+
module = importlib.import_module(module_name)
|
|
331
|
+
except ModuleNotFoundError as exc:
|
|
332
|
+
if exc.name not in {"textual", "corpus_ui", module_name}:
|
|
333
|
+
raise
|
|
334
|
+
interpreter = venv_python()
|
|
335
|
+
if (
|
|
336
|
+
exc.name == "textual"
|
|
337
|
+
and interpreter is not None
|
|
338
|
+
and Path(sys.executable).resolve() != interpreter.resolve()
|
|
339
|
+
and os.environ.get("AGENT_BIOS_CORPUS_TUI_REEXEC") != "1"
|
|
340
|
+
):
|
|
341
|
+
env = os.environ.copy()
|
|
342
|
+
env["AGENT_BIOS_CORPUS_TUI_REEXEC"] = "1"
|
|
343
|
+
try:
|
|
344
|
+
os.execve(
|
|
345
|
+
str(interpreter),
|
|
346
|
+
[str(interpreter), str(Path(__file__).resolve()), *sys.argv[1:]],
|
|
347
|
+
env,
|
|
348
|
+
)
|
|
349
|
+
except OSError as reexec_error:
|
|
350
|
+
print(
|
|
351
|
+
f"agent-bios corpus: managed Textual runtime is unusable ({reexec_error}); "
|
|
352
|
+
"using numbered fallback.",
|
|
353
|
+
file=sys.stderr,
|
|
354
|
+
)
|
|
355
|
+
return run_numbered(store)
|
|
356
|
+
app = module.CorpusStudio(store)
|
|
357
|
+
app.run()
|
|
358
|
+
return 0
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def main(argv: Sequence[str] | None = None) -> int:
|
|
362
|
+
parser = build_parser()
|
|
363
|
+
raw = list(sys.argv[1:] if argv is None else argv)
|
|
364
|
+
# Machine callers naturally put --json after the verb. argparse only accepts
|
|
365
|
+
# a parent option before a subparser, so normalize this one order-independent
|
|
366
|
+
# presentation flag without changing any semantic argument.
|
|
367
|
+
json_requested = "--json" in raw
|
|
368
|
+
raw = [value for value in raw if value != "--json"]
|
|
369
|
+
args = parser.parse_args(raw)
|
|
370
|
+
args.as_json = args.as_json or json_requested
|
|
371
|
+
store = CorpusStore(args.repo, state_root=args.state_dir, user_root=args.user_dir)
|
|
372
|
+
try:
|
|
373
|
+
if args.command is None:
|
|
374
|
+
if sys.stdin.isatty() and sys.stdout.isatty():
|
|
375
|
+
return _run_tui_or_fallback(args, store)
|
|
376
|
+
args.command = "list"
|
|
377
|
+
args.active_only = False
|
|
378
|
+
result = run_command(args, store)
|
|
379
|
+
emit(result, as_json=args.as_json, command=args.command)
|
|
380
|
+
return 0
|
|
381
|
+
except CorpusStoreError as exc:
|
|
382
|
+
print(f"agent-bios corpus: {exc}", file=sys.stderr)
|
|
383
|
+
return 2
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
if __name__ == "__main__":
|
|
387
|
+
raise SystemExit(main())
|