agent-bios 0.3.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 +3 -1
- package/claude/guides/cli-multi-model-workflow.md +6 -0
- package/claude/guides/{session-learning-workflow.md → session-distill-workflow.md} +13 -13
- package/claude/hooks/tooling-gotchas-hook.py +1 -1
- package/codex/AGENTS.md +3 -1
- package/codex/guides/cli-multi-model-workflow.md +6 -0
- package/codex/guides/{session-learning-workflow.md → session-distill-workflow.md} +13 -13
- package/config/agent-launch.toml +34 -8
- package/package.json +1 -1
- package/scripts/agent-launch.py +248 -159
- package/scripts/check-parity.sh +99 -14
- package/scripts/install.sh +116 -15
package/scripts/check-parity.sh
CHANGED
|
@@ -113,8 +113,39 @@ real Microsoft Excel engine|claude/CLAUDE.md|claude/guides/coding-staged-workflo
|
|
|
113
113
|
severity contract|README.md|claude/guides/coding-staged-workflow.md
|
|
114
114
|
Ambient state|claude/CLAUDE.md|claude/guides/tooling-gotchas.md
|
|
115
115
|
the full lifecycle of what you create|claude/CLAUDE.md|claude/guides/tooling-gotchas.md
|
|
116
|
+
dual-provider frontier design drafts|claude/CLAUDE.md|claude/guides/cli-multi-model-workflow.md
|
|
116
117
|
ANCHORS
|
|
117
118
|
|
|
119
|
+
# Domain-manifest gate: bullet<->anchor bijection, file coverage, router
|
|
120
|
+
# co-packaging (config/domains.json vs the monolith); its --self-test proves
|
|
121
|
+
# every negative control still fails, so a green gate is falsifiable.
|
|
122
|
+
if [ -f config/domains.json ]; then
|
|
123
|
+
python3 scripts/check-domains.py >/dev/null \
|
|
124
|
+
|| { echo "FAIL: domains manifest gate (run scripts/check-domains.py)"; fail=1; }
|
|
125
|
+
python3 scripts/check-domains.py --self-test >/dev/null \
|
|
126
|
+
|| { echo "FAIL: domains gate self-test missed a negative control"; fail=1; }
|
|
127
|
+
bash scripts/test-assemble.sh >/dev/null \
|
|
128
|
+
|| { echo "FAIL: assembler scenario suite (run scripts/test-assemble.sh)"; fail=1; }
|
|
129
|
+
fi
|
|
130
|
+
|
|
131
|
+
# Learning record gate: config/learning.schema.json (collection-loop
|
|
132
|
+
# SSOT) vs its fixtures; --self-test proves every mutation is still caught.
|
|
133
|
+
if [ -f config/learning.schema.json ]; then
|
|
134
|
+
python3 scripts/check-learning.py >/dev/null \
|
|
135
|
+
|| { echo "FAIL: learning record gate (run scripts/check-learning.py)"; fail=1; }
|
|
136
|
+
python3 scripts/check-learning.py --self-test >/dev/null \
|
|
137
|
+
|| { echo "FAIL: learning gate self-test missed a negative control"; fail=1; }
|
|
138
|
+
fi
|
|
139
|
+
|
|
140
|
+
# Lexicon gate: forbid deprecated terminology tokens in live files
|
|
141
|
+
# (LEXICON.md is the SSOT); --self-test proves the detector can fire.
|
|
142
|
+
if [ -f LEXICON.md ]; then
|
|
143
|
+
python3 scripts/check-lexicon.py >/dev/null \
|
|
144
|
+
|| { echo "FAIL: lexicon gate (run scripts/check-lexicon.py)"; fail=1; }
|
|
145
|
+
python3 scripts/check-lexicon.py --self-test >/dev/null \
|
|
146
|
+
|| { echo "FAIL: lexicon gate self-test failed"; fail=1; }
|
|
147
|
+
fi
|
|
148
|
+
|
|
118
149
|
# The launcher's Textual preflight UI tests need the managed venv (textual).
|
|
119
150
|
# Provision it if missing; every non-UI check above runs under system python.
|
|
120
151
|
VENV="${AGENT_LAUNCH_VENV:-$HOME/.local/share/agent-launch/venv}"
|
|
@@ -551,7 +582,9 @@ if launcher.is_file() and launch_profile:
|
|
|
551
582
|
def set_plan(self, plan):
|
|
552
583
|
self.plan = plan
|
|
553
584
|
|
|
554
|
-
def choose(self, title, options, default, allow_back, preview=None,
|
|
585
|
+
def choose(self, title, options, default, allow_back, preview=None, corpus_lines=None):
|
|
586
|
+
if title == "Mode":
|
|
587
|
+
return "builder"
|
|
555
588
|
if title == "Preset":
|
|
556
589
|
if preview is not None:
|
|
557
590
|
balanced = launcher_module.setup_summary_lines(preview("balanced"))
|
|
@@ -772,12 +805,17 @@ if launcher.is_file() and launch_profile:
|
|
|
772
805
|
os.close(master)
|
|
773
806
|
return bytes(transcript), picker_status
|
|
774
807
|
|
|
808
|
+
# Root menu is now a Mode picker (General user / Builder / Session
|
|
809
|
+
# distill); Builder is the default highlight and lists Custom. Reaching
|
|
810
|
+
# Custom means: Enter the root to open Builder's preset submenu, then
|
|
811
|
+
# step down through its fixed presets (Balanced default-highlighted).
|
|
775
812
|
open_custom_steps = (
|
|
813
|
+
(b"Esc cancel | q cancel", b"\r"),
|
|
776
814
|
(b"native multi-perspective review", b""),
|
|
777
|
-
(b"Esc
|
|
815
|
+
(b"Esc back | q cancel", b"\x1b[B"),
|
|
778
816
|
(b"hybrid onto", b"\x1b[B"),
|
|
779
817
|
(b"high-volume", b"\x1b[B"),
|
|
780
|
-
(b"
|
|
818
|
+
(b"the standing spawn policy is lifted", b"\x1b[B"),
|
|
781
819
|
(b"Open a settings hub", b"\r"),
|
|
782
820
|
)
|
|
783
821
|
down = b"\x1b[B"
|
|
@@ -798,7 +836,7 @@ if launcher.is_file() and launch_profile:
|
|
|
798
836
|
b"WORKHORSE",
|
|
799
837
|
b"SWEEP",
|
|
800
838
|
b"About highlighted option",
|
|
801
|
-
b"
|
|
839
|
+
b"Tune tiers, review routes, and permissions",
|
|
802
840
|
b"Options (",
|
|
803
841
|
)
|
|
804
842
|
positions = {token: transcript.find(token) for token in layout_tokens}
|
|
@@ -809,7 +847,7 @@ if launcher.is_file() and launch_profile:
|
|
|
809
847
|
positions[b"Current setup"]
|
|
810
848
|
< min(positions[token] for token in (b"FRONTIER", b"HELM", b"WORKHORSE", b"SWEEP"))
|
|
811
849
|
< positions[b"About highlighted option"]
|
|
812
|
-
< positions[b"
|
|
850
|
+
< positions[b"Tune tiers, review routes, and permissions"]
|
|
813
851
|
< positions[b"Options ("]
|
|
814
852
|
):
|
|
815
853
|
mark_fail("agent-launch layout is not setup then description then options")
|
|
@@ -821,12 +859,27 @@ if launcher.is_file() and launch_profile:
|
|
|
821
859
|
if picker_status != 130:
|
|
822
860
|
mark_fail(f"agent-launch root Esc returned {picker_status}, want 130")
|
|
823
861
|
|
|
862
|
+
# Root Mode picker navigation: Esc from the Builder preset submenu
|
|
863
|
+
# returns to a freshly re-rendered Mode picker (Builder's own
|
|
864
|
+
# description reappears) rather than cancelling the launcher outright.
|
|
865
|
+
_, picker_status = run_picker_scenario(
|
|
866
|
+
"mode picker navigation",
|
|
867
|
+
(
|
|
868
|
+
(b"Esc cancel | q cancel", b"\r"),
|
|
869
|
+
(b"native multi-perspective review", b"\x1b"),
|
|
870
|
+
(b"Tune tiers, review routes, and permissions", b"q"),
|
|
871
|
+
),
|
|
872
|
+
)
|
|
873
|
+
if picker_status != 130:
|
|
874
|
+
mark_fail(f"agent-launch mode picker Esc-back returned {picker_status}, want 130")
|
|
875
|
+
|
|
824
876
|
if argv_log.exists():
|
|
825
877
|
argv_log.unlink()
|
|
826
878
|
_, picker_status = run_picker_scenario(
|
|
827
879
|
"launch confirmation q cancellation",
|
|
828
880
|
(
|
|
829
881
|
(b"Esc cancel | q cancel", b"\r"),
|
|
882
|
+
(b"HELM default for everyday work", b"\r"),
|
|
830
883
|
(b"Launch? [Y/n/q]:", b"q\r"),
|
|
831
884
|
),
|
|
832
885
|
dry_run=False,
|
|
@@ -1128,10 +1181,19 @@ if launcher.is_file() and launch_profile:
|
|
|
1128
1181
|
# TERM=dumb routes to numbered prompts (textual renders on any usable
|
|
1129
1182
|
# terminal, so the numbered fallback is gated on TERM/non-TTY/textual
|
|
1130
1183
|
# availability, not a terminfo probe). 'b' is the numbered back command.
|
|
1131
|
-
|
|
1184
|
+
# The root Mode menu is fixed (General=1, Builder=2, Session distill=3)
|
|
1185
|
+
# regardless of preset count; only Custom's position within Builder's
|
|
1186
|
+
# own preset submenu depends on how many builder-mode presets exist.
|
|
1187
|
+
builder_preset_count = sum(
|
|
1188
|
+
1
|
|
1189
|
+
for data in fake_data["presets"].values()
|
|
1190
|
+
if data.get("mode", "builder") == "builder"
|
|
1191
|
+
)
|
|
1192
|
+
custom_number = str(builder_preset_count + 1).encode()
|
|
1132
1193
|
transcript, picker_status = run_picker_scenario(
|
|
1133
1194
|
"numbered fallback (TERM=dumb)",
|
|
1134
1195
|
(
|
|
1196
|
+
(b"Tune tiers, review routes, and permissions", b"2\n"),
|
|
1135
1197
|
(b"Open a settings hub", custom_number + b"\n"),
|
|
1136
1198
|
(b"Custom settings", b"b\n"),
|
|
1137
1199
|
(b"HELM default for everyday work", b"1\n"),
|
|
@@ -1145,27 +1207,27 @@ if launcher.is_file() and launch_profile:
|
|
|
1145
1207
|
):
|
|
1146
1208
|
mark_fail("agent-launch numbered fallback (TERM=dumb) did not preserve numbered back")
|
|
1147
1209
|
|
|
1148
|
-
# Session
|
|
1149
|
-
learning_number = str(len(fake_data["presets"]) + 2).encode()
|
|
1210
|
+
# Session Distill hub: enter, render status, back out, launch balanced.
|
|
1150
1211
|
transcript, picker_status = run_picker_scenario(
|
|
1151
|
-
"numbered
|
|
1212
|
+
"numbered distill hub (TERM=dumb)",
|
|
1152
1213
|
(
|
|
1153
|
-
(b"Session
|
|
1214
|
+
(b"Session distill", b"3\n"),
|
|
1154
1215
|
(b"Versions & rollback", b"4\n"),
|
|
1216
|
+
(b"Tune tiers, review routes, and permissions", b"2\n"),
|
|
1155
1217
|
(b"HELM default for everyday work", b"1\n"),
|
|
1156
1218
|
),
|
|
1157
1219
|
term="dumb",
|
|
1158
1220
|
)
|
|
1159
|
-
|
|
1221
|
+
corpus_panel_rendered = (
|
|
1160
1222
|
b"Applied version" in transcript or b"not projected yet" in transcript
|
|
1161
1223
|
)
|
|
1162
1224
|
if (
|
|
1163
1225
|
picker_status != 0
|
|
1164
1226
|
or b"Traceback" in transcript
|
|
1165
|
-
or not
|
|
1227
|
+
or not corpus_panel_rendered
|
|
1166
1228
|
or b"Preset Balanced" not in transcript
|
|
1167
1229
|
):
|
|
1168
|
-
mark_fail("agent-launch numbered
|
|
1230
|
+
mark_fail("agent-launch numbered distill hub did not render or return")
|
|
1169
1231
|
|
|
1170
1232
|
passed = invoke([
|
|
1171
1233
|
sys.executable, str(launcher), "--no-tui", "codex", "--", "exec", "--json", "probe"
|
|
@@ -1492,6 +1554,29 @@ if launcher.is_file() and launch_profile:
|
|
|
1492
1554
|
"agent-launch Claude expert overrides were not appended last"
|
|
1493
1555
|
)
|
|
1494
1556
|
|
|
1557
|
+
# Vanilla (mode=general): the raw backend with nothing applied — no
|
|
1558
|
+
# launch contract, no agents, no permission-bypass flag.
|
|
1559
|
+
vanilla_configured = invoke([
|
|
1560
|
+
sys.executable, str(launcher), "--preset", "vanilla", "--yes", "--dry-run",
|
|
1561
|
+
"claude",
|
|
1562
|
+
], env=env)
|
|
1563
|
+
if vanilla_configured.returncode != 0:
|
|
1564
|
+
mark_fail(
|
|
1565
|
+
f"agent-launch vanilla dry-run failed: {vanilla_configured.stderr.strip()}"
|
|
1566
|
+
)
|
|
1567
|
+
else:
|
|
1568
|
+
vanilla_argv = json.loads(vanilla_configured.stdout.splitlines()[-1])
|
|
1569
|
+
for forbidden in (
|
|
1570
|
+
"--append-system-prompt", "--agents", "--dangerously-skip-permissions",
|
|
1571
|
+
):
|
|
1572
|
+
if forbidden in vanilla_argv:
|
|
1573
|
+
mark_fail(f"agent-launch vanilla preset unexpectedly carried {forbidden}")
|
|
1574
|
+
if vanilla_argv != [str(backend)]:
|
|
1575
|
+
mark_fail(
|
|
1576
|
+
"agent-launch vanilla preset must project the bare backend with "
|
|
1577
|
+
f"nothing applied: {vanilla_argv!r}"
|
|
1578
|
+
)
|
|
1579
|
+
|
|
1495
1580
|
# Cross-family default: each main routes every review route to the OPPOSITE
|
|
1496
1581
|
# model family, with concrete tools/paths/bindings named in the contract.
|
|
1497
1582
|
# The cross CODEX_HOME needs the reviewer wrappers (bin) and the same-family
|
|
@@ -1766,5 +1851,5 @@ then
|
|
|
1766
1851
|
fail=1
|
|
1767
1852
|
fi
|
|
1768
1853
|
|
|
1769
|
-
[ "$fail" -eq 0 ] && echo "PARITY OK: mirrors, globals, guides, launch profile, bypass paths, role bindings, and wrapper defaults aligned"
|
|
1854
|
+
[ "$fail" -eq 0 ] && echo "PARITY OK: mirrors, globals, guides, domain manifest, assembler, launch profile, bypass paths, role bindings, and wrapper defaults aligned"
|
|
1770
1855
|
exit "$fail"
|
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"
|
|
@@ -399,12 +422,17 @@ cmd_install() {
|
|
|
399
422
|
: > "$MANIFEST"
|
|
400
423
|
fi
|
|
401
424
|
log "Deploying agent-bios from $REPO"
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
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
|
|
408
436
|
deploy_glob "$REPO/codex/agents" "*.toml" "$CODEX_DIR/agents"
|
|
409
437
|
codex_config_additions merge || exit 1
|
|
410
438
|
deploy_file "$REPO/scripts/codex-run.sh" "$CODEX_DIR/bin/codex-run" "+x"
|
|
@@ -424,10 +452,10 @@ cmd_install() {
|
|
|
424
452
|
|| log "warning: venv provisioning failed (numbered-prompt fallback applies)"
|
|
425
453
|
fi
|
|
426
454
|
add_zsh_hook
|
|
427
|
-
if python3 "$REPO/scripts/session-
|
|
428
|
-
info "
|
|
455
|
+
if python3 "$REPO/scripts/session-distill/corpus-state.py" project --repo "$REPO" >/dev/null 2>&1; then
|
|
456
|
+
info "corpus-status projected"
|
|
429
457
|
else
|
|
430
|
-
log "note:
|
|
458
|
+
log "note: corpus-status projection unavailable (versions.json/ledger missing?)"
|
|
431
459
|
fi
|
|
432
460
|
log ""
|
|
433
461
|
log "Verifying deployment..."
|
|
@@ -448,15 +476,32 @@ verify_present() { if [ -f "$1" ]; then return 0; else log "missing $1"; return
|
|
|
448
476
|
|
|
449
477
|
cmd_verify() {
|
|
450
478
|
local fail=0 gp
|
|
451
|
-
|
|
452
|
-
|
|
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
|
|
453
502
|
verify_match "$REPO/scripts/agent-launch.py" "$BIN_DIR/agent-launch" || fail=1
|
|
454
503
|
verify_match "$REPO/config/agent-launch.toml" "$LAUNCH_DIR/profiles.toml" || fail=1
|
|
455
504
|
verify_match "$REPO/shell/agent-launch.zsh" "$LAUNCH_DIR/shell.zsh" || fail=1
|
|
456
|
-
for gp in "$REPO"/claude/guides/*.md; do verify_present "$CLAUDE_DIR/guides/$(basename "$gp")" || fail=1; done
|
|
457
|
-
for gp in "$REPO"/claude/agents/*.md; do verify_present "$CLAUDE_DIR/agents/$(basename "$gp")" || fail=1; done
|
|
458
|
-
for gp in "$REPO"/claude/hooks/*.py; do verify_present "$CLAUDE_DIR/hooks/$(basename "$gp")" || fail=1; done
|
|
459
|
-
for gp in "$REPO"/codex/guides/*.md; do verify_present "$CODEX_DIR/guides/$(basename "$gp")" || fail=1; done
|
|
460
505
|
python3 - "$CODEX_DIR/agents" <<'PY' && info "agent TOMLs OK" || fail=1
|
|
461
506
|
import sys, pathlib, tomllib
|
|
462
507
|
root = pathlib.Path(sys.argv[1])
|
|
@@ -526,6 +571,49 @@ cmd_uninstall() {
|
|
|
526
571
|
log "Kept: managed venv + backups under $STATE_DIR (rm -rf \"$STATE_DIR\" to purge)."
|
|
527
572
|
}
|
|
528
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
|
+
|
|
529
617
|
cmd_status() {
|
|
530
618
|
local version p
|
|
531
619
|
if [ -d "$REPO/.git" ]; then
|
|
@@ -561,6 +649,7 @@ usage() {
|
|
|
561
649
|
agent-bios — deploy the Claude/Codex instruction SSOT into $HOME (by copy).
|
|
562
650
|
|
|
563
651
|
agent-bios install deploy into this environment (backs up + verifies)
|
|
652
|
+
agent-bios onboard interactive domain selection + packaged install + activation canary
|
|
564
653
|
agent-bios verify check the deployed state matches the source
|
|
565
654
|
agent-bios status show what is installed and where
|
|
566
655
|
agent-bios update git pull + reinstall (clone), or print the npm update line
|
|
@@ -568,6 +657,13 @@ agent-bios — deploy the Claude/Codex instruction SSOT into $HOME (by copy).
|
|
|
568
657
|
agent-bios help
|
|
569
658
|
|
|
570
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.
|
|
571
667
|
--with a,b also install the named optional dependencies (install only).
|
|
572
668
|
Without it, install offers each missing one when the terminal
|
|
573
669
|
is interactive, and otherwise just prints its install line.
|
|
@@ -581,11 +677,15 @@ EOF
|
|
|
581
677
|
CMD="${1:-help}"
|
|
582
678
|
if [ $# -gt 0 ]; then shift; fi
|
|
583
679
|
WITH=""
|
|
680
|
+
DOMAINS_ARG=""
|
|
681
|
+
DOMAINS_SET=0
|
|
584
682
|
while [ $# -gt 0 ]; do
|
|
585
683
|
case "$1" in
|
|
586
684
|
--dry-run) DRY_RUN=1 ;;
|
|
587
685
|
--with) shift; WITH="${1:-}"; [ -n "$WITH" ] || { log "--with needs a comma-separated capability list"; exit 2; } ;;
|
|
588
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; } ;;
|
|
589
689
|
*) log "unknown flag: $1"; exit 2 ;;
|
|
590
690
|
esac
|
|
591
691
|
shift
|
|
@@ -593,6 +693,7 @@ done
|
|
|
593
693
|
|
|
594
694
|
case "$CMD" in
|
|
595
695
|
install) cmd_install ;;
|
|
696
|
+
onboard) cmd_onboard ;;
|
|
596
697
|
update) cmd_update ;;
|
|
597
698
|
uninstall) cmd_uninstall ;;
|
|
598
699
|
verify) if cmd_verify; then log "VERIFY OK"; else log "VERIFY FAILED"; exit 1; fi ;;
|