agent-bios 0.2.0 → 0.4.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 +1 -0
- package/README.md +2 -1
- package/claude/CLAUDE.md +14 -5
- package/claude/agents/frontier.md +9 -0
- package/claude/agents/sweep.md +9 -0
- package/claude/agents/workhorse.md +8 -0
- package/claude/guides/cli-multi-model-workflow.md +17 -4
- package/claude/guides/coding-staged-workflow.md +8 -0
- package/claude/guides/llm-capability-boundary-patterns.md +5 -0
- package/claude/guides/mock-realization-boundary.md +9 -0
- package/claude/guides/review-request.md +8 -0
- package/claude/guides/session-distill-workflow.md +100 -0
- package/claude/guides/tooling-gotchas.md +155 -0
- package/claude/hooks/tooling-gotchas-hook.py +79 -0
- package/codex/AGENTS.md +14 -5
- package/codex/config-additions.toml +22 -0
- package/codex/guides/cli-multi-model-workflow.md +17 -4
- package/codex/guides/coding-staged-workflow.md +8 -0
- package/codex/guides/llm-capability-boundary-patterns.md +5 -0
- package/codex/guides/mock-realization-boundary.md +9 -0
- package/codex/guides/review-request.md +8 -0
- package/codex/guides/session-distill-workflow.md +100 -0
- package/codex/guides/tooling-gotchas.md +155 -0
- package/config/agent-launch.toml +43 -0
- package/package.json +4 -1
- package/scripts/agent-launch.py +486 -52
- package/scripts/check-parity.sh +154 -7
- package/scripts/codex-helm.sh +19 -1
- package/scripts/codex-run.sh +19 -0
- package/scripts/install.sh +271 -10
package/scripts/install.sh
CHANGED
|
@@ -102,6 +102,29 @@ deploy_glob() {
|
|
|
102
102
|
done
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
+
# ---- packaged (domain-selected) corpus deploy ----------------------------
|
|
106
|
+
# Active when --domains is passed or a prior selection exists in STATE_DIR.
|
|
107
|
+
# assemble.py owns the corpus surfaces (central tree, entry seeding, codex
|
|
108
|
+
# marker region, settings merge); the entry CLAUDE.md and AGENTS.md are NOT
|
|
109
|
+
# manifested — the entry is user-owned after seeding, AGENTS.md holds a
|
|
110
|
+
# personal region — so uninstall never deletes them.
|
|
111
|
+
packaged_mode() { [ "${DOMAINS_SET:-0}" = 1 ] || [ -f "$STATE_DIR/selection.json" ]; }
|
|
112
|
+
|
|
113
|
+
assemble_packaged() {
|
|
114
|
+
local args=(--claude-dir "$CLAUDE_DIR" --codex-dir "$CODEX_DIR" --state-dir "$STATE_DIR") rc=0
|
|
115
|
+
[ "${DOMAINS_SET:-0}" = 1 ] && args+=(--domains "$DOMAINS_ARG")
|
|
116
|
+
[ "$DRY_RUN" = 1 ] && args+=(--dry-run)
|
|
117
|
+
python3 "$REPO/scripts/assemble.py" "${args[@]}" || rc=$?
|
|
118
|
+
if [ "$rc" = 2 ]; then
|
|
119
|
+
log "packaged: entry file needs user action (import line missing); central content will not load until it is added"
|
|
120
|
+
elif [ "$rc" != 0 ]; then
|
|
121
|
+
return 1
|
|
122
|
+
fi
|
|
123
|
+
if [ "$DRY_RUN" != 1 ]; then
|
|
124
|
+
find "$CLAUDE_DIR/central" "$CODEX_DIR/guides" -type f 2>/dev/null >> "$MANIFEST"
|
|
125
|
+
fi
|
|
126
|
+
}
|
|
127
|
+
|
|
105
128
|
add_zsh_hook() {
|
|
106
129
|
if [ -f "$ZSHRC" ] && grep -qF "$HOOK_MARK" "$ZSHRC"; then
|
|
107
130
|
info "zsh hook present $ZSHRC"
|
|
@@ -126,6 +149,152 @@ migrate_state() {
|
|
|
126
149
|
mv "$LEGACY_STATE_DIR" "$STATE_DIR" && info "migrated state $LEGACY_STATE_DIR -> $STATE_DIR"
|
|
127
150
|
}
|
|
128
151
|
|
|
152
|
+
# ---- codex live-config additions -----------------------------------------
|
|
153
|
+
# The live ~/.codex/config.toml is user/runtime-owned; agent-bios never
|
|
154
|
+
# deploys or overwrites it. codex/config-additions.toml declares the only
|
|
155
|
+
# content agent-bios manages there — one marked [agents.*] block plus a tagged
|
|
156
|
+
# features.multi_agent line — and this helper merges (install), checks
|
|
157
|
+
# (verify), or removes (uninstall) exactly that content, backed up and
|
|
158
|
+
# tomllib-validated before any write. Modes: merge | check | remove.
|
|
159
|
+
codex_config_additions() {
|
|
160
|
+
AB_MODE="$1" AB_CODEX_DIR="$CODEX_DIR" AB_FRAGMENT="$REPO/codex/config-additions.toml" \
|
|
161
|
+
AB_BACKUP="${BACKUP_DIR:-}" AB_DRY="$DRY_RUN" python3 - <<'PY'
|
|
162
|
+
import os, pathlib, sys, tomllib
|
|
163
|
+
|
|
164
|
+
mode = os.environ["AB_MODE"]
|
|
165
|
+
codex_dir = pathlib.Path(os.environ["AB_CODEX_DIR"])
|
|
166
|
+
fragment_path = pathlib.Path(os.environ["AB_FRAGMENT"])
|
|
167
|
+
backup_root = os.environ.get("AB_BACKUP", "")
|
|
168
|
+
dry = os.environ.get("AB_DRY") == "1"
|
|
169
|
+
target = codex_dir / "config.toml"
|
|
170
|
+
BEGIN = "# >>> agent-bios additions >>>"
|
|
171
|
+
END = "# <<< agent-bios additions <<<"
|
|
172
|
+
TAG = "# agent-bios"
|
|
173
|
+
|
|
174
|
+
def info(msg): print(f" {msg}")
|
|
175
|
+
def fail(msg): print(msg); sys.exit(1)
|
|
176
|
+
|
|
177
|
+
frag_text = fragment_path.read_text().replace("${CODEX_HOME}", str(codex_dir))
|
|
178
|
+
want_agents = tomllib.loads(frag_text)["agents"]
|
|
179
|
+
live_text = target.read_text() if target.is_file() else ""
|
|
180
|
+
try:
|
|
181
|
+
live = tomllib.loads(live_text) if live_text else {}
|
|
182
|
+
except Exception as exc:
|
|
183
|
+
fail(f"live codex config does not parse; not touching it: {target} ({exc})")
|
|
184
|
+
|
|
185
|
+
def state_ok():
|
|
186
|
+
if live.get("features", {}).get("multi_agent") is not True:
|
|
187
|
+
return False
|
|
188
|
+
return all(
|
|
189
|
+
live.get("agents", {}).get(name, {}).get(key) == spec[key]
|
|
190
|
+
for name, spec in want_agents.items()
|
|
191
|
+
for key in ("description", "config_file")
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
if mode == "check":
|
|
195
|
+
problems = []
|
|
196
|
+
if live.get("features", {}).get("multi_agent") is not True:
|
|
197
|
+
problems.append("features.multi_agent is not true")
|
|
198
|
+
for name, spec in want_agents.items():
|
|
199
|
+
if live.get("agents", {}).get(name, {}).get("config_file") != spec["config_file"]:
|
|
200
|
+
problems.append(f"agents.{name}.config_file drifted or missing")
|
|
201
|
+
elif not pathlib.Path(spec["config_file"]).is_file():
|
|
202
|
+
problems.append(f"agents.{name} template missing: {spec['config_file']}")
|
|
203
|
+
if problems:
|
|
204
|
+
fail(f"codex config additions: {'; '.join(problems)} ({target})")
|
|
205
|
+
info("codex config additions OK")
|
|
206
|
+
sys.exit(0)
|
|
207
|
+
|
|
208
|
+
def strip_managed(text):
|
|
209
|
+
out, skipping = [], False
|
|
210
|
+
for line in text.splitlines(keepends=True):
|
|
211
|
+
s = line.strip()
|
|
212
|
+
if s == BEGIN: skipping = True; continue
|
|
213
|
+
if s == END: skipping = False; continue
|
|
214
|
+
if skipping or s.endswith(TAG): continue
|
|
215
|
+
out.append(line)
|
|
216
|
+
return "".join(out)
|
|
217
|
+
|
|
218
|
+
if mode == "remove":
|
|
219
|
+
if not target.is_file():
|
|
220
|
+
sys.exit(0)
|
|
221
|
+
stripped = strip_managed(live_text)
|
|
222
|
+
if stripped == live_text:
|
|
223
|
+
info(f"no agent-bios additions in {target}")
|
|
224
|
+
sys.exit(0)
|
|
225
|
+
try:
|
|
226
|
+
tomllib.loads(stripped)
|
|
227
|
+
except Exception as exc:
|
|
228
|
+
fail(f"refusing removal; result would not parse: {exc}")
|
|
229
|
+
if dry:
|
|
230
|
+
info(f"[dry-run] remove agent-bios additions from {target}")
|
|
231
|
+
sys.exit(0)
|
|
232
|
+
backup = target.with_name(target.name + ".bak-agent-bios-uninstall")
|
|
233
|
+
backup.write_text(live_text)
|
|
234
|
+
target.write_text(stripped)
|
|
235
|
+
info(f"removed additions {target} (backup: {backup.name})")
|
|
236
|
+
sys.exit(0)
|
|
237
|
+
|
|
238
|
+
# mode == merge
|
|
239
|
+
if state_ok():
|
|
240
|
+
info(f"unchanged {target} (additions present)")
|
|
241
|
+
sys.exit(0)
|
|
242
|
+
|
|
243
|
+
# A drifted [agents.<tier>] outside our markers would become a duplicate
|
|
244
|
+
# table if we appended ours; that conflict needs the user, not a clobber.
|
|
245
|
+
base = strip_managed(live_text)
|
|
246
|
+
base_data = tomllib.loads(base) if base.strip() else {}
|
|
247
|
+
clash = [name for name in want_agents if name in base_data.get("agents", {})]
|
|
248
|
+
if clash:
|
|
249
|
+
fail(
|
|
250
|
+
f"unmanaged [agents.{'/'.join(clash)}] with drifted content in {target}; "
|
|
251
|
+
"align or remove them, then rerun install"
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
block_lines = [BEGIN]
|
|
255
|
+
if "features" not in base_data:
|
|
256
|
+
block_lines += ["[features]", f"multi_agent = true {TAG}"]
|
|
257
|
+
for name, spec in want_agents.items():
|
|
258
|
+
block_lines += [
|
|
259
|
+
f"[agents.{name}]",
|
|
260
|
+
f'description = "{spec["description"]}"',
|
|
261
|
+
f'config_file = "{spec["config_file"]}"',
|
|
262
|
+
]
|
|
263
|
+
block_lines.append(END)
|
|
264
|
+
block = "\n".join(block_lines) + "\n"
|
|
265
|
+
|
|
266
|
+
new_text = base
|
|
267
|
+
if "features" in base_data:
|
|
268
|
+
if base_data["features"].get("multi_agent") is None:
|
|
269
|
+
lines = new_text.splitlines(keepends=True)
|
|
270
|
+
for i, line in enumerate(lines):
|
|
271
|
+
if line.strip() == "[features]":
|
|
272
|
+
lines.insert(i + 1, f"multi_agent = true {TAG}\n")
|
|
273
|
+
break
|
|
274
|
+
new_text = "".join(lines)
|
|
275
|
+
elif base_data["features"].get("multi_agent") is not True:
|
|
276
|
+
info(f"note: features.multi_agent explicitly set in {target}; leaving it")
|
|
277
|
+
if new_text and not new_text.endswith("\n"):
|
|
278
|
+
new_text += "\n"
|
|
279
|
+
new_text += ("\n" if new_text else "") + block
|
|
280
|
+
|
|
281
|
+
try:
|
|
282
|
+
tomllib.loads(new_text)
|
|
283
|
+
except Exception as exc:
|
|
284
|
+
fail(f"merge result would not parse; live config untouched ({exc})")
|
|
285
|
+
if dry:
|
|
286
|
+
info(f"[dry-run] merge agent-bios additions into {target}")
|
|
287
|
+
sys.exit(0)
|
|
288
|
+
if live_text and backup_root:
|
|
289
|
+
bpath = pathlib.Path(backup_root + str(target))
|
|
290
|
+
bpath.parent.mkdir(parents=True, exist_ok=True)
|
|
291
|
+
bpath.write_text(live_text)
|
|
292
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
293
|
+
target.write_text(new_text)
|
|
294
|
+
info(f"merged additions {target}")
|
|
295
|
+
PY
|
|
296
|
+
}
|
|
297
|
+
|
|
129
298
|
# ---- optional dependencies -----------------------------------------------
|
|
130
299
|
# Capabilities are optional: a missing one only degrades the review routes that
|
|
131
300
|
# need it. config/agent-launch.toml is the single source for both the command
|
|
@@ -253,11 +422,19 @@ cmd_install() {
|
|
|
253
422
|
: > "$MANIFEST"
|
|
254
423
|
fi
|
|
255
424
|
log "Deploying agent-bios from $REPO"
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
425
|
+
if packaged_mode; then
|
|
426
|
+
log "packaged mode: assembling selected domains (state: $STATE_DIR/selection.json)"
|
|
427
|
+
assemble_packaged || exit 1
|
|
428
|
+
else
|
|
429
|
+
deploy_file "$REPO/claude/CLAUDE.md" "$CLAUDE_DIR/CLAUDE.md"
|
|
430
|
+
deploy_glob "$REPO/claude/guides" "*.md" "$CLAUDE_DIR/guides"
|
|
431
|
+
deploy_glob "$REPO/claude/agents" "*.md" "$CLAUDE_DIR/agents"
|
|
432
|
+
deploy_glob "$REPO/claude/hooks" "*.py" "$CLAUDE_DIR/hooks" "+x"
|
|
433
|
+
deploy_file "$REPO/codex/AGENTS.md" "$CODEX_DIR/AGENTS.md"
|
|
434
|
+
deploy_glob "$REPO/codex/guides" "*.md" "$CODEX_DIR/guides"
|
|
435
|
+
fi
|
|
260
436
|
deploy_glob "$REPO/codex/agents" "*.toml" "$CODEX_DIR/agents"
|
|
437
|
+
codex_config_additions merge || exit 1
|
|
261
438
|
deploy_file "$REPO/scripts/codex-run.sh" "$CODEX_DIR/bin/codex-run" "+x"
|
|
262
439
|
deploy_file "$REPO/scripts/codex-helm.sh" "$CODEX_DIR/bin/codex-helm" "+x"
|
|
263
440
|
migrate_user_presets || exit 1 # must precede the deploy below, which overwrites profiles.toml
|
|
@@ -275,12 +452,19 @@ cmd_install() {
|
|
|
275
452
|
|| log "warning: venv provisioning failed (numbered-prompt fallback applies)"
|
|
276
453
|
fi
|
|
277
454
|
add_zsh_hook
|
|
455
|
+
if python3 "$REPO/scripts/session-distill/corpus-state.py" project --repo "$REPO" >/dev/null 2>&1; then
|
|
456
|
+
info "corpus-status projected"
|
|
457
|
+
else
|
|
458
|
+
log "note: corpus-status projection unavailable (versions.json/ledger missing?)"
|
|
459
|
+
fi
|
|
278
460
|
log ""
|
|
279
461
|
log "Verifying deployment..."
|
|
280
462
|
if cmd_verify; then
|
|
281
463
|
log ""
|
|
282
464
|
log "Done. Open a new shell (or: source \"$ZSHRC\") to activate the zero-arg launcher."
|
|
283
|
-
|
|
465
|
+
# An untouched backup dir means nothing was replaced; that healthy state
|
|
466
|
+
# must not become a nonzero exit under set -e.
|
|
467
|
+
{ [ -n "$BACKUP_DIR" ] && [ -d "$BACKUP_DIR" ] && log "Replaced files were backed up under $BACKUP_DIR"; } || true
|
|
284
468
|
else
|
|
285
469
|
log "VERIFY FAILED after install — see messages above"
|
|
286
470
|
exit 1
|
|
@@ -292,13 +476,32 @@ verify_present() { if [ -f "$1" ]; then return 0; else log "missing $1"; return
|
|
|
292
476
|
|
|
293
477
|
cmd_verify() {
|
|
294
478
|
local fail=0 gp
|
|
295
|
-
|
|
296
|
-
|
|
479
|
+
if packaged_mode; then
|
|
480
|
+
# Packaged: corpus surfaces are selection-derived, not repo-identical.
|
|
481
|
+
# The entry file is user-owned — READ-check the import line, never rewrite.
|
|
482
|
+
python3 "$REPO/scripts/check-domains.py" >/dev/null 2>&1 && info "domains gate OK" || { log "domains gate FAILED"; fail=1; }
|
|
483
|
+
verify_present "$CLAUDE_DIR/central/bundle.md" || fail=1
|
|
484
|
+
if grep -qF '@central/bundle.md' "$CLAUDE_DIR/CLAUDE.md" 2>/dev/null; then
|
|
485
|
+
info "entry import line present"
|
|
486
|
+
else
|
|
487
|
+
log "entry $CLAUDE_DIR/CLAUDE.md lacks '@central/bundle.md' — central corpus is NOT loading"; fail=1
|
|
488
|
+
fi
|
|
489
|
+
if grep -qF 'agent-bios:central:start' "$CODEX_DIR/AGENTS.md" 2>/dev/null; then
|
|
490
|
+
info "codex central region present"
|
|
491
|
+
else
|
|
492
|
+
log "codex AGENTS.md central region missing"; fail=1
|
|
493
|
+
fi
|
|
494
|
+
else
|
|
495
|
+
verify_match "$REPO/claude/CLAUDE.md" "$CLAUDE_DIR/CLAUDE.md" || fail=1
|
|
496
|
+
verify_match "$REPO/codex/AGENTS.md" "$CODEX_DIR/AGENTS.md" || fail=1
|
|
497
|
+
for gp in "$REPO"/claude/guides/*.md; do verify_present "$CLAUDE_DIR/guides/$(basename "$gp")" || fail=1; done
|
|
498
|
+
for gp in "$REPO"/claude/agents/*.md; do verify_present "$CLAUDE_DIR/agents/$(basename "$gp")" || fail=1; done
|
|
499
|
+
for gp in "$REPO"/claude/hooks/*.py; do verify_present "$CLAUDE_DIR/hooks/$(basename "$gp")" || fail=1; done
|
|
500
|
+
for gp in "$REPO"/codex/guides/*.md; do verify_present "$CODEX_DIR/guides/$(basename "$gp")" || fail=1; done
|
|
501
|
+
fi
|
|
297
502
|
verify_match "$REPO/scripts/agent-launch.py" "$BIN_DIR/agent-launch" || fail=1
|
|
298
503
|
verify_match "$REPO/config/agent-launch.toml" "$LAUNCH_DIR/profiles.toml" || fail=1
|
|
299
504
|
verify_match "$REPO/shell/agent-launch.zsh" "$LAUNCH_DIR/shell.zsh" || fail=1
|
|
300
|
-
for gp in "$REPO"/claude/guides/*.md; do verify_present "$CLAUDE_DIR/guides/$(basename "$gp")" || fail=1; done
|
|
301
|
-
for gp in "$REPO"/codex/guides/*.md; do verify_present "$CODEX_DIR/guides/$(basename "$gp")" || fail=1; done
|
|
302
505
|
python3 - "$CODEX_DIR/agents" <<'PY' && info "agent TOMLs OK" || fail=1
|
|
303
506
|
import sys, pathlib, tomllib
|
|
304
507
|
root = pathlib.Path(sys.argv[1])
|
|
@@ -308,6 +511,7 @@ assert not missing, f"missing agent TOMLs in {root}: {sorted(missing)}"
|
|
|
308
511
|
for p in sorted(root.glob("*.toml")):
|
|
309
512
|
tomllib.loads(p.read_text())
|
|
310
513
|
PY
|
|
514
|
+
codex_config_additions check || fail=1
|
|
311
515
|
if command -v codex >/dev/null 2>&1 && [ -x "$CODEX_DIR/bin/codex-helm" ]; then
|
|
312
516
|
if "$CODEX_DIR/bin/codex-helm" --dry-run --mode review "probe" >/dev/null 2>&1; then
|
|
313
517
|
info "codex-helm dry-run OK"
|
|
@@ -340,6 +544,7 @@ PY
|
|
|
340
544
|
|
|
341
545
|
cmd_uninstall() {
|
|
342
546
|
migrate_state
|
|
547
|
+
codex_config_additions remove || log "warning: could not remove codex config additions"
|
|
343
548
|
if [ -f "$MANIFEST" ]; then
|
|
344
549
|
local f
|
|
345
550
|
while IFS= read -r f; do
|
|
@@ -357,7 +562,7 @@ cmd_uninstall() {
|
|
|
357
562
|
done
|
|
358
563
|
fi
|
|
359
564
|
local d
|
|
360
|
-
for d in "$CLAUDE_DIR/guides" "$CODEX_DIR/guides" "$CODEX_DIR/agents" "$CODEX_DIR/bin" "$LAUNCH_DIR"; do
|
|
565
|
+
for d in "$CLAUDE_DIR/guides" "$CLAUDE_DIR/agents" "$CODEX_DIR/guides" "$CODEX_DIR/agents" "$CODEX_DIR/bin" "$LAUNCH_DIR"; do
|
|
361
566
|
[ -d "$d" ] && rmdir "$d" 2>/dev/null && info "removed empty $d" || true
|
|
362
567
|
done
|
|
363
568
|
remove_zsh_hook
|
|
@@ -366,6 +571,49 @@ cmd_uninstall() {
|
|
|
366
571
|
log "Kept: managed venv + backups under $STATE_DIR (rm -rf \"$STATE_DIR\" to purge)."
|
|
367
572
|
}
|
|
368
573
|
|
|
574
|
+
cmd_onboard() {
|
|
575
|
+
log "agent-bios onboarding — pick your domain packages (core + infra always install)"
|
|
576
|
+
local names=() line i=1 choice sel="" n picks
|
|
577
|
+
while IFS= read -r line; do names+=("$line"); done \
|
|
578
|
+
< <(python3 -c "import json;print('\n'.join(sorted(json.load(open('$REPO/config/domains.json'))['domains'])))")
|
|
579
|
+
[ "${#names[@]}" -ge 1 ] || { log "no domains found in config/domains.json"; exit 1; }
|
|
580
|
+
if [ "$DOMAINS_SET" = 1 ]; then
|
|
581
|
+
# Non-interactive path, per this installer's input contract (stdin is
|
|
582
|
+
# detached at the top of the script): selection arrives as domain names.
|
|
583
|
+
[ "$DOMAINS_ARG" = none ] && DOMAINS_ARG=""
|
|
584
|
+
sel="$DOMAINS_ARG"
|
|
585
|
+
elif ( : </dev/tty ) 2>/dev/null; then
|
|
586
|
+
for line in "${names[@]}"; do info "$i) $line"; i=$((i+1)); done
|
|
587
|
+
printf 'Select by number, comma-separated (empty = core+infra only): '
|
|
588
|
+
read -r choice </dev/tty || choice=""
|
|
589
|
+
if [ -n "$choice" ]; then
|
|
590
|
+
# bash 3.2 + set -u: expanding an EMPTY array errors, so split only
|
|
591
|
+
# when there is input; empty input means core+infra only.
|
|
592
|
+
IFS=',' read -ra picks <<<"$choice"
|
|
593
|
+
for n in "${picks[@]}"; do
|
|
594
|
+
n="${n// /}"; [ -n "$n" ] || continue
|
|
595
|
+
case "$n" in (*[!0-9]*) log "invalid selection: $n"; exit 2 ;; esac
|
|
596
|
+
[ "$n" -ge 1 ] && [ "$n" -le "${#names[@]}" ] || { log "selection out of range: $n"; exit 2; }
|
|
597
|
+
sel="$sel${sel:+,}${names[$((n-1))]}"
|
|
598
|
+
done
|
|
599
|
+
fi
|
|
600
|
+
else
|
|
601
|
+
log "onboard needs a terminal or an explicit selection — run:"
|
|
602
|
+
log " agent-bios onboard --domains a,b (or --domains none for core+infra only)"
|
|
603
|
+
exit 2
|
|
604
|
+
fi
|
|
605
|
+
DOMAINS_ARG="$sel"; DOMAINS_SET=1
|
|
606
|
+
log "selection: ${sel:-<core+infra only>}"
|
|
607
|
+
cmd_install
|
|
608
|
+
log ""
|
|
609
|
+
log "Activation canary (proves the bundle loads in a live session)..."
|
|
610
|
+
if [ "$DRY_RUN" = 1 ]; then info "[dry-run] skip canary probe"; return; fi
|
|
611
|
+
bash "$REPO/scripts/canary.sh" || {
|
|
612
|
+
log "ONBOARDING INCOMPLETE: the bundle is installed but not loading — fix the cause above and re-run: agent-bios verify"
|
|
613
|
+
exit 1
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
369
617
|
cmd_status() {
|
|
370
618
|
local version p
|
|
371
619
|
if [ -d "$REPO/.git" ]; then
|
|
@@ -401,6 +649,7 @@ usage() {
|
|
|
401
649
|
agent-bios — deploy the Claude/Codex instruction SSOT into $HOME (by copy).
|
|
402
650
|
|
|
403
651
|
agent-bios install deploy into this environment (backs up + verifies)
|
|
652
|
+
agent-bios onboard interactive domain selection + packaged install + activation canary
|
|
404
653
|
agent-bios verify check the deployed state matches the source
|
|
405
654
|
agent-bios status show what is installed and where
|
|
406
655
|
agent-bios update git pull + reinstall (clone), or print the npm update line
|
|
@@ -408,6 +657,13 @@ agent-bios — deploy the Claude/Codex instruction SSOT into $HOME (by copy).
|
|
|
408
657
|
agent-bios help
|
|
409
658
|
|
|
410
659
|
Flags: --dry-run print actions without changing anything
|
|
660
|
+
--domains a,b packaged mode (install/onboard): assemble ONLY the named
|
|
661
|
+
domain packages (plus core+infra) instead of the full
|
|
662
|
+
corpus; with onboard, 'none' means core+infra only. The
|
|
663
|
+
selection persists in the state dir, so later
|
|
664
|
+
installs/updates stay packaged until the selection file
|
|
665
|
+
is removed. Default (no flag, no saved selection) keeps
|
|
666
|
+
today's full-corpus deploy.
|
|
411
667
|
--with a,b also install the named optional dependencies (install only).
|
|
412
668
|
Without it, install offers each missing one when the terminal
|
|
413
669
|
is interactive, and otherwise just prints its install line.
|
|
@@ -421,11 +677,15 @@ EOF
|
|
|
421
677
|
CMD="${1:-help}"
|
|
422
678
|
if [ $# -gt 0 ]; then shift; fi
|
|
423
679
|
WITH=""
|
|
680
|
+
DOMAINS_ARG=""
|
|
681
|
+
DOMAINS_SET=0
|
|
424
682
|
while [ $# -gt 0 ]; do
|
|
425
683
|
case "$1" in
|
|
426
684
|
--dry-run) DRY_RUN=1 ;;
|
|
427
685
|
--with) shift; WITH="${1:-}"; [ -n "$WITH" ] || { log "--with needs a comma-separated capability list"; exit 2; } ;;
|
|
428
686
|
--with=*) WITH="${1#--with=}"; [ -n "$WITH" ] || { log "--with needs a comma-separated capability list"; exit 2; } ;;
|
|
687
|
+
--domains) shift; DOMAINS_ARG="${1:-}"; DOMAINS_SET=1; [ -n "$DOMAINS_ARG" ] || { log "--domains needs a comma-separated domain list (use onboard for core-only)"; exit 2; } ;;
|
|
688
|
+
--domains=*) DOMAINS_ARG="${1#--domains=}"; DOMAINS_SET=1; [ -n "$DOMAINS_ARG" ] || { log "--domains needs a comma-separated domain list (use onboard for core-only)"; exit 2; } ;;
|
|
429
689
|
*) log "unknown flag: $1"; exit 2 ;;
|
|
430
690
|
esac
|
|
431
691
|
shift
|
|
@@ -433,6 +693,7 @@ done
|
|
|
433
693
|
|
|
434
694
|
case "$CMD" in
|
|
435
695
|
install) cmd_install ;;
|
|
696
|
+
onboard) cmd_onboard ;;
|
|
436
697
|
update) cmd_update ;;
|
|
437
698
|
uninstall) cmd_uninstall ;;
|
|
438
699
|
verify) if cmd_verify; then log "VERIFY OK"; else log "VERIFY FAILED"; exit 1; fi ;;
|