agent-bios 0.16.0 → 0.17.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 +3 -2
- package/README.md +98 -24
- package/claude/CLAUDE.md +1 -1
- package/claude/guides/tooling-gotchas.md +1 -1
- package/claude/hooks/tooling-gotchas-hook.py +9 -16
- package/claude/skills/understand/SKILL.md +83 -0
- package/codex/AGENTS.md +1 -1
- package/codex/guides/tooling-gotchas.md +1 -1
- package/compose/bootstrap/SKILL.md +12 -2
- package/compose/corpus.py +6 -6
- package/compose/corpus_catalog.py +74 -25
- package/compose/corpus_install.py +66 -13
- package/compose/corpus_session.py +105 -6
- package/compose/corpus_store.py +11 -2
- package/compose/corpus_transaction.py +20 -0
- package/compose/corpus_ui.py +23 -12
- package/compose/corpus_understand.py +522 -0
- package/compose/domains.json +1 -0
- package/compose/register-hooks.py +6 -8
- package/install.sh +21 -0
- package/launch/agent-launch.py +201 -18
- package/launch/agent-launch.zsh +16 -2
- package/launch/i18n/en.toml +23 -0
- package/launch/i18n/ja.toml +23 -0
- package/launch/i18n/ko.toml +23 -0
- package/launch/shell_integration.py +267 -0
- package/package.json +4 -2
- package/provenance.json +1 -1
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
"""Opt-in zsh entrypoints, independent of native instruction files and corpus content."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import contextlib
|
|
6
|
+
import hashlib
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
import re
|
|
11
|
+
import shlex
|
|
12
|
+
import tempfile
|
|
13
|
+
import time
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
START = b"# >>> agent-bios shell connection >>>\n"
|
|
17
|
+
END = b"# <<< agent-bios shell connection <<<\n"
|
|
18
|
+
SCRIPT_HEADER = b"# agent-bios optional shell connection; managed by agent-bios shell\n"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ShellIntegrationError(RuntimeError):
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ShellIntegration:
|
|
26
|
+
def __init__(self, environ=None, source_root=None):
|
|
27
|
+
self.env = dict(os.environ if environ is None else environ)
|
|
28
|
+
self.home = Path(self.env.get("HOME", str(Path.home()))).expanduser().absolute()
|
|
29
|
+
self.zdotdir = Path(self.env.get("ZDOTDIR") or str(self.home)).expanduser().absolute()
|
|
30
|
+
self.state = Path(self.env.get("AGENT_BIOS_STATE_DIR", str(self.home / ".local/share/agent-bios"))).expanduser().absolute()
|
|
31
|
+
self.startup = self.zdotdir / ".zshrc"
|
|
32
|
+
self.script = self.home / ".config/agent-launch/shell.zsh"
|
|
33
|
+
self.receipt = self.state / "runtime/shell-connection.json"
|
|
34
|
+
self.source = Path(source_root) if source_root else Path(__file__).resolve().parent.parent
|
|
35
|
+
|
|
36
|
+
@staticmethod
|
|
37
|
+
def _safe_ancestors(path):
|
|
38
|
+
import sys
|
|
39
|
+
compose = str(Path(__file__).resolve().parent.parent / "compose")
|
|
40
|
+
if compose not in sys.path:
|
|
41
|
+
sys.path.insert(0, compose)
|
|
42
|
+
from corpus_transaction import reject_symlink_ancestors, TransactionError
|
|
43
|
+
try:
|
|
44
|
+
reject_symlink_ancestors(path)
|
|
45
|
+
except TransactionError as exc:
|
|
46
|
+
raise ShellIntegrationError(str(exc)) from exc
|
|
47
|
+
|
|
48
|
+
@staticmethod
|
|
49
|
+
def _safe(path):
|
|
50
|
+
ShellIntegration._safe_ancestors(path)
|
|
51
|
+
if path.exists() and not path.is_file():
|
|
52
|
+
raise ShellIntegrationError(f"shell connection target is not a regular file: {path}")
|
|
53
|
+
|
|
54
|
+
def _read(self, path):
|
|
55
|
+
self._safe(path)
|
|
56
|
+
return path.read_bytes() if path.exists() else None
|
|
57
|
+
|
|
58
|
+
def _record(self, raw):
|
|
59
|
+
if raw is None:
|
|
60
|
+
return None
|
|
61
|
+
try:
|
|
62
|
+
value = json.loads(raw)
|
|
63
|
+
except (ValueError, UnicodeError) as exc:
|
|
64
|
+
raise ShellIntegrationError(f"invalid shell connection record: {self.receipt}") from exc
|
|
65
|
+
if (not isinstance(value, dict) or value.get("schema_version") != 1
|
|
66
|
+
or value.get("startup") != str(self.startup)
|
|
67
|
+
or value.get("script") != str(self.script)
|
|
68
|
+
or not isinstance(value.get("startup_existed"), bool)
|
|
69
|
+
or not re.fullmatch(r"[0-9a-f]{64}", str(value.get("sha256", "")))):
|
|
70
|
+
raise ShellIntegrationError(f"shell connection record does not match HOME/ZDOTDIR: {self.receipt}")
|
|
71
|
+
return value
|
|
72
|
+
|
|
73
|
+
def _block(self):
|
|
74
|
+
path = shlex.quote(str(self.script))
|
|
75
|
+
return START + f"[ -r {path} ] && source {path}\n".encode() + END
|
|
76
|
+
|
|
77
|
+
def _strip(self, raw):
|
|
78
|
+
body = raw or b""
|
|
79
|
+
starts, ends = body.count(START.rstrip(b"\n")), body.count(END.rstrip(b"\n"))
|
|
80
|
+
if not starts and not ends:
|
|
81
|
+
return body, False
|
|
82
|
+
block = self._block()
|
|
83
|
+
if starts != 1 or ends != 1 or body.count(block) != 1:
|
|
84
|
+
raise ShellIntegrationError(f"shell connection block was edited; preserve and reconcile it: {self.startup}")
|
|
85
|
+
return body.replace(block, b"", 1), True
|
|
86
|
+
|
|
87
|
+
def _script_body(self):
|
|
88
|
+
source = self.source / "launch/agent-launch.zsh"
|
|
89
|
+
body = self._read(source)
|
|
90
|
+
if body is None:
|
|
91
|
+
raise ShellIntegrationError(f"shell connection source missing: {source}")
|
|
92
|
+
return SCRIPT_HEADER + (
|
|
93
|
+
f"typeset -g _agent_launch_private_connection={shlex.quote(str(self.script))}\n").encode() + body
|
|
94
|
+
|
|
95
|
+
def has_connection(self):
|
|
96
|
+
"""Discover interrupted opt-ins without inspecting an ordinary user's rc.
|
|
97
|
+
|
|
98
|
+
A header is only a reason to inspect: plan still requires exact managed
|
|
99
|
+
bytes or a valid receipt hash before modifying any discovered script.
|
|
100
|
+
"""
|
|
101
|
+
if self.receipt.exists() or self.receipt.is_symlink():
|
|
102
|
+
return True
|
|
103
|
+
if not self.script.is_file():
|
|
104
|
+
return False
|
|
105
|
+
try:
|
|
106
|
+
self._safe(self.script)
|
|
107
|
+
except ShellIntegrationError:
|
|
108
|
+
# Without a receipt this is not evidence of our ownership. Leave a
|
|
109
|
+
# foreign symlink/directory alone; explicit shell actions still refuse it.
|
|
110
|
+
return False
|
|
111
|
+
current = self._read(self.script)
|
|
112
|
+
return current is not None and current.startswith(SCRIPT_HEADER)
|
|
113
|
+
|
|
114
|
+
def plan(self, action, *, installing=False):
|
|
115
|
+
if action not in {"restore", "remove", "status"}:
|
|
116
|
+
raise ShellIntegrationError(f"unknown shell connection action: {action}")
|
|
117
|
+
before = {path: self._read(path) for path in (self.startup, self.script, self.receipt)}
|
|
118
|
+
record = self._record(before[self.receipt])
|
|
119
|
+
remaining, connected = self._strip(before[self.startup])
|
|
120
|
+
expected = self._script_body()
|
|
121
|
+
current = before[self.script]
|
|
122
|
+
if current is not None and current != expected and (
|
|
123
|
+
record is None or hashlib.sha256(current).hexdigest() != record["sha256"]):
|
|
124
|
+
raise ShellIntegrationError(f"shell connection will not replace an unowned or edited file: {self.script}")
|
|
125
|
+
launcher = self.home / ".local/bin/agent-launch"
|
|
126
|
+
try:
|
|
127
|
+
self._safe(launcher)
|
|
128
|
+
launcher_ready = launcher.is_file() and os.access(launcher, os.X_OK)
|
|
129
|
+
except ShellIntegrationError:
|
|
130
|
+
launcher_ready = False
|
|
131
|
+
active = connected and current is not None and record is not None and launcher_ready
|
|
132
|
+
needs_action = []
|
|
133
|
+
if not active and (connected or current is not None or record is not None):
|
|
134
|
+
needs_action.append("shell connection is incomplete; run agent-bios shell restore or remove")
|
|
135
|
+
if action == "restore":
|
|
136
|
+
installed = self._read(self.state / "runtime/private-install.json")
|
|
137
|
+
try:
|
|
138
|
+
private = json.loads(installed) if installed is not None else {}
|
|
139
|
+
except (ValueError, UnicodeError) as exc:
|
|
140
|
+
raise ShellIntegrationError("private installation record is invalid; run agent-bios verify") from exc
|
|
141
|
+
if not installing and (not launcher_ready
|
|
142
|
+
or not isinstance(private, dict) or private.get("mode") != "private-session-scoped"
|
|
143
|
+
or not isinstance(private.get("launcher"), dict)
|
|
144
|
+
or private["launcher"].get("path") != str(launcher)):
|
|
145
|
+
raise ShellIntegrationError("restore needs a private installation; run agent-bios install or migrate first")
|
|
146
|
+
receipt = {"schema_version": 1, "startup": str(self.startup), "script": str(self.script),
|
|
147
|
+
"sha256": hashlib.sha256(expected).hexdigest(),
|
|
148
|
+
"startup_existed": record["startup_existed"] if record else before[self.startup] is not None}
|
|
149
|
+
receipt_body = (json.dumps(receipt, sort_keys=True) + "\n").encode()
|
|
150
|
+
# Persist first-opt-in ownership before publishing the script/hook.
|
|
151
|
+
# On updates, retain the old hash until the new script is published:
|
|
152
|
+
# either old recorded bytes or current source bytes remain recoverable.
|
|
153
|
+
after = {self.receipt: receipt_body} if record is None else {}
|
|
154
|
+
after.update({self.script: expected,
|
|
155
|
+
self.startup: before[self.startup] if connected else self._block() + remaining,
|
|
156
|
+
self.receipt: receipt_body})
|
|
157
|
+
elif action == "remove":
|
|
158
|
+
after = {self.startup: remaining if before[self.startup] is not None else None,
|
|
159
|
+
self.script: None, self.receipt: None}
|
|
160
|
+
if record and not record["startup_existed"] and not remaining:
|
|
161
|
+
after[self.startup] = None
|
|
162
|
+
else:
|
|
163
|
+
after = {}
|
|
164
|
+
changes = [{"path": path, "before": before[path], "after": body,
|
|
165
|
+
"mode": path.stat().st_mode & 0o777 if before[path] is not None else 0o600}
|
|
166
|
+
for path, body in after.items() if before[path] != body]
|
|
167
|
+
return {"enabled": active, "startup_path": str(self.startup), "shell_path": str(self.script),
|
|
168
|
+
"needs_action": needs_action, "changes": changes}
|
|
169
|
+
|
|
170
|
+
def status(self):
|
|
171
|
+
try:
|
|
172
|
+
result = self.plan("status")
|
|
173
|
+
result.pop("changes")
|
|
174
|
+
return result
|
|
175
|
+
except (ShellIntegrationError, OSError) as exc:
|
|
176
|
+
return {"enabled": False, "startup_path": str(self.startup), "shell_path": str(self.script),
|
|
177
|
+
"needs_action": [str(exc)]}
|
|
178
|
+
|
|
179
|
+
@staticmethod
|
|
180
|
+
def _write(path, body, mode):
|
|
181
|
+
if body is None:
|
|
182
|
+
path.unlink(missing_ok=True)
|
|
183
|
+
return
|
|
184
|
+
path.parent.mkdir(parents=True, exist_ok=True, mode=0o700)
|
|
185
|
+
descriptor, name = tempfile.mkstemp(prefix=f".{path.name}.", dir=path.parent)
|
|
186
|
+
try:
|
|
187
|
+
with os.fdopen(descriptor, "wb") as output:
|
|
188
|
+
output.write(body)
|
|
189
|
+
output.flush()
|
|
190
|
+
os.fsync(output.fileno())
|
|
191
|
+
os.chmod(name, mode)
|
|
192
|
+
os.replace(name, path)
|
|
193
|
+
finally:
|
|
194
|
+
Path(name).unlink(missing_ok=True)
|
|
195
|
+
|
|
196
|
+
def apply(self, action, dry_run=False):
|
|
197
|
+
# One writer lock is shared with install/reset/migrate. Imports stay lazy so
|
|
198
|
+
# status and help do not create state or require an installed corpus.
|
|
199
|
+
import sys
|
|
200
|
+
compose = str(Path(__file__).resolve().parent.parent / "compose")
|
|
201
|
+
if compose not in sys.path:
|
|
202
|
+
sys.path.insert(0, compose)
|
|
203
|
+
from corpus_transaction import transaction_lock, guard_pending
|
|
204
|
+
lock = contextlib.nullcontext() if dry_run else transaction_lock(self.state)
|
|
205
|
+
with lock:
|
|
206
|
+
guard_pending(self.state)
|
|
207
|
+
plan = self.plan(action)
|
|
208
|
+
changes = plan.pop("changes")
|
|
209
|
+
result = {**plan, "preview": dry_run, "action": action,
|
|
210
|
+
"changed_paths": [str(row["path"]) for row in changes]}
|
|
211
|
+
if dry_run or not changes:
|
|
212
|
+
return result
|
|
213
|
+
for row in changes:
|
|
214
|
+
if self._read(row["path"]) != row["before"]:
|
|
215
|
+
raise ShellIntegrationError(f"shell file changed during planning: {row['path']}")
|
|
216
|
+
backups = self.state / "runtime/shell-backups"
|
|
217
|
+
self._safe_ancestors(backups)
|
|
218
|
+
backups.mkdir(parents=True, exist_ok=True, mode=0o700)
|
|
219
|
+
backup = Path(tempfile.mkdtemp(prefix=f"{int(time.time())}-", dir=backups))
|
|
220
|
+
metadata = []
|
|
221
|
+
for index, row in enumerate(changes):
|
|
222
|
+
saved = backup / str(index)
|
|
223
|
+
if row["before"] is not None:
|
|
224
|
+
self._write(saved, row["before"], 0o600)
|
|
225
|
+
if saved.read_bytes() != row["before"]:
|
|
226
|
+
raise ShellIntegrationError(f"shell backup did not verify: {saved}")
|
|
227
|
+
metadata.append({"path": str(row["path"]), "existed": row["before"] is not None,
|
|
228
|
+
"file": str(index), "mode": row["mode"]})
|
|
229
|
+
self._write(backup / "paths.json", (json.dumps(metadata) + "\n").encode(), 0o600)
|
|
230
|
+
completed = []
|
|
231
|
+
try:
|
|
232
|
+
for row in changes:
|
|
233
|
+
if self._read(row["path"]) != row["before"]:
|
|
234
|
+
raise ShellIntegrationError(f"shell file changed during apply: {row['path']}")
|
|
235
|
+
completed.append(row)
|
|
236
|
+
self._write(row["path"], row["after"], row["mode"])
|
|
237
|
+
for row in changes:
|
|
238
|
+
if self._read(row["path"]) != row["after"]:
|
|
239
|
+
raise ShellIntegrationError(f"shell change did not verify: {row['path']}")
|
|
240
|
+
except BaseException:
|
|
241
|
+
# Roll back only our exact writes; retain concurrent user edits and
|
|
242
|
+
# the durable originals for recovery rather than overwriting them.
|
|
243
|
+
for row in reversed(completed):
|
|
244
|
+
with contextlib.suppress(OSError, ShellIntegrationError):
|
|
245
|
+
if self._read(row["path"]) == row["after"]:
|
|
246
|
+
self._write(row["path"], row["before"], row["mode"])
|
|
247
|
+
raise ShellIntegrationError(f"shell connection apply interrupted; originals saved at {backup}")
|
|
248
|
+
return {**result, **self.status(), "backup_root": str(backup)}
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def main(argv=None):
|
|
252
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
253
|
+
parser.add_argument("action", choices=("status", "restore", "remove"), nargs="?", default="status")
|
|
254
|
+
parser.add_argument("--dry-run", action="store_true", help="preview without editing shell files")
|
|
255
|
+
args = parser.parse_args(argv)
|
|
256
|
+
manager = ShellIntegration()
|
|
257
|
+
try:
|
|
258
|
+
result = manager.status() if args.action == "status" else manager.apply(args.action, args.dry_run)
|
|
259
|
+
print(json.dumps(result, ensure_ascii=False, indent=2))
|
|
260
|
+
return 1 if result.get("needs_action") else 0
|
|
261
|
+
except (ShellIntegrationError, OSError, RuntimeError) as exc:
|
|
262
|
+
print(f"shell connection: {exc}", file=__import__("sys").stderr)
|
|
263
|
+
return 1
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
if __name__ == "__main__":
|
|
267
|
+
raise SystemExit(main())
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-bios",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"releaseDate": "2026-09-
|
|
3
|
+
"version": "0.17.0",
|
|
4
|
+
"releaseDate": "2026-09-11",
|
|
5
5
|
"description": "A thin, low-level instruction layer for LLM CLI agents: one set of principles and behavior whichever model you run. Stores a private, editable corpus for explicitly activated sessions.",
|
|
6
6
|
"bin": {
|
|
7
7
|
"agent-bios": "install.sh"
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"learn/promotions.json",
|
|
25
25
|
"launch/agent-launch.zsh",
|
|
26
26
|
"launch/agent-launch.py",
|
|
27
|
+
"launch/shell_integration.py",
|
|
27
28
|
"compose/pkgid.py",
|
|
28
29
|
"compose/register-hooks.py",
|
|
29
30
|
"compose/assemble.py",
|
|
@@ -39,6 +40,7 @@
|
|
|
39
40
|
"compose/corpus_session.py",
|
|
40
41
|
"compose/corpus_install.py",
|
|
41
42
|
"compose/corpus_transaction.py",
|
|
43
|
+
"compose/corpus_understand.py",
|
|
42
44
|
"compose/bootstrap/",
|
|
43
45
|
"launch/check-prompting-targets.sh",
|
|
44
46
|
"learn/check-learning.py",
|
package/provenance.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"commit":"
|
|
1
|
+
{"commit":"7b616f4ae5366b1395560b3a88556287ac36122d","committedAt":"2026-09-11T15:52:49+09:00","dirty":false}
|