agent-bios 0.1.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.
Files changed (35) hide show
  1. package/DEPENDENCIES.md +89 -0
  2. package/LICENSE +21 -0
  3. package/README.md +86 -0
  4. package/claude/CLAUDE.md +138 -0
  5. package/claude/guides/cli-multi-model-workflow.md +194 -0
  6. package/claude/guides/coding-staged-workflow.md +70 -0
  7. package/claude/guides/implementation-map.md +34 -0
  8. package/claude/guides/llm-capability-boundary-examples.md +123 -0
  9. package/claude/guides/llm-capability-boundary-patterns.md +339 -0
  10. package/claude/guides/llm-capability-boundary.md +255 -0
  11. package/claude/guides/mock-realization-boundary.md +275 -0
  12. package/claude/guides/svg-visualization-guide.md +321 -0
  13. package/codex/AGENTS.md +139 -0
  14. package/codex/agents/frontier.toml +8 -0
  15. package/codex/agents/reviewer.toml +9 -0
  16. package/codex/agents/sweep.toml +9 -0
  17. package/codex/agents/workhorse.toml +8 -0
  18. package/codex/guides/cli-multi-model-workflow.md +194 -0
  19. package/codex/guides/coding-staged-workflow.md +70 -0
  20. package/codex/guides/implementation-map.md +34 -0
  21. package/codex/guides/llm-capability-boundary-examples.md +123 -0
  22. package/codex/guides/llm-capability-boundary-patterns.md +339 -0
  23. package/codex/guides/llm-capability-boundary.md +255 -0
  24. package/codex/guides/mock-realization-boundary.md +275 -0
  25. package/codex/guides/svg-visualization-guide.md +321 -0
  26. package/config/agent-launch.toml +94 -0
  27. package/package.json +54 -0
  28. package/scripts/agent-launch.py +1742 -0
  29. package/scripts/check-parity.sh +1703 -0
  30. package/scripts/codex-helm.sh +370 -0
  31. package/scripts/codex-run.sh +176 -0
  32. package/scripts/install.sh +310 -0
  33. package/scripts/provision-venv.sh +28 -0
  34. package/scripts/session-cost.py +106 -0
  35. package/shell/agent-launch.zsh +38 -0
@@ -0,0 +1,310 @@
1
+ #!/usr/bin/env bash
2
+ # agent-bios installer.
3
+ #
4
+ # Deploys the single-source-of-truth (globals, scoped guides, Codex agent
5
+ # templates, Codex wrappers, the launch profile/shell/bin, a managed Textual
6
+ # venv, and the zsh hook) into $HOME by COPY — idempotent, backed up before
7
+ # overwrite, and reversible. Distributed as an npm bin; the actual $HOME
8
+ # deployment is this explicit command (never a postinstall side effect).
9
+ #
10
+ # Usage:
11
+ # agent-bios install deploy into this environment (backs up + verifies)
12
+ # agent-bios verify check the deployed state matches the source
13
+ # agent-bios status show what is installed and where
14
+ # agent-bios update git pull + reinstall (clone), or print the npm update line
15
+ # agent-bios uninstall remove deployed files and the zsh hook
16
+ # agent-bios help
17
+ #
18
+ # Flags: --dry-run (print actions, change nothing).
19
+ # Env overrides: CLAUDE_CONFIG_DIR, CODEX_HOME, AGENT_LAUNCH_VENV, ZDOTDIR.
20
+ set -euo pipefail
21
+
22
+ # This installer is non-interactive: every input arrives as a subcommand, flag,
23
+ # or env var. Detach stdin so no child (the codex-helm dry-run, pip, git) can
24
+ # block forever on an inherited idle stdin — that is what hangs an install under
25
+ # CI, pipes, and background runs, where stdin stays open but never delivers.
26
+ exec </dev/null
27
+
28
+ # Resolve this script through symlinks before locating the package: npm links the
29
+ # bin into node_modules/.bin and the global bin dir, so $0 is a link and its
30
+ # dirname is the link's directory, not the package. Without this the source tree
31
+ # resolves to the bin dir's parent (e.g. /opt/homebrew) and every deploy fails.
32
+ SOURCE="${BASH_SOURCE[0]}"
33
+ while [ -L "$SOURCE" ]; do
34
+ LINKDIR="$(cd -P "$(dirname "$SOURCE")" && pwd)"
35
+ SOURCE="$(readlink "$SOURCE")"
36
+ case "$SOURCE" in
37
+ /*) ;;
38
+ *) SOURCE="$LINKDIR/$SOURCE" ;;
39
+ esac
40
+ done
41
+ SELF="$(cd -P "$(dirname "$SOURCE")" && pwd)"
42
+ REPO="$(cd "$SELF/.." && pwd)"
43
+
44
+ CLAUDE_DIR="${CLAUDE_CONFIG_DIR:-$HOME/.claude}"
45
+ CODEX_DIR="${CODEX_HOME:-$HOME/.codex}"
46
+ LAUNCH_DIR="$HOME/.config/agent-launch"
47
+ BIN_DIR="$HOME/.local/bin"
48
+ STATE_DIR="$HOME/.local/share/agent-bios"
49
+ LEGACY_STATE_DIR="$HOME/.local/share/agent-dotfiles" # pre-rename state; migrated on first run
50
+ MANIFEST="$STATE_DIR/manifest.txt"
51
+ ZSHRC="${ZDOTDIR:-$HOME}/.zshrc"
52
+ ZSH_HOOK='[ -r "$HOME/.config/agent-launch/shell.zsh" ] && source "$HOME/.config/agent-launch/shell.zsh"'
53
+ HOOK_MARK='agent-launch/shell.zsh'
54
+
55
+ DRY_RUN=0
56
+ BACKUP_DIR=""
57
+
58
+ log() { printf '%s\n' "$*"; }
59
+ info() { printf ' %s\n' "$*"; }
60
+ run() { if [ "$DRY_RUN" = 1 ]; then printf ' [dry-run] %s\n' "$*"; else "$@"; fi; }
61
+
62
+ # ---- prerequisites -------------------------------------------------------
63
+ check_prereqs() {
64
+ local ok=0
65
+ command -v git >/dev/null 2>&1 || { log "missing prerequisite: git"; ok=1; }
66
+ command -v python3 >/dev/null 2>&1 || { log "missing prerequisite: python3"; ok=1; }
67
+ if command -v python3 >/dev/null 2>&1; then
68
+ python3 -c 'import sys; sys.exit(0 if sys.version_info>=(3,11) else 1)' 2>/dev/null \
69
+ || { log "python3 >= 3.11 required (tomllib)"; ok=1; }
70
+ fi
71
+ command -v zsh >/dev/null 2>&1 || log "note: zsh not found; the shell hook targets zsh"
72
+ command -v codex >/dev/null 2>&1 || log "note: codex CLI not found; Codex-side steps will be skipped"
73
+ command -v claude >/dev/null 2>&1 || log "note: claude CLI not found"
74
+ return $ok
75
+ }
76
+
77
+ # ---- copy with backup + manifest ----------------------------------------
78
+ deploy_file() {
79
+ local src="$1" dst="$2" mode="${3:-}"
80
+ [ -f "$src" ] || { log "source missing: $src"; return 1; }
81
+ if [ -f "$dst" ] && cmp -s "$src" "$dst"; then
82
+ info "unchanged $dst"
83
+ else
84
+ if [ -f "$dst" ] && [ -n "$BACKUP_DIR" ]; then
85
+ run mkdir -p "$(dirname "$BACKUP_DIR$dst")"
86
+ run cp "$dst" "$BACKUP_DIR$dst"
87
+ fi
88
+ run mkdir -p "$(dirname "$dst")"
89
+ run cp "$src" "$dst"
90
+ if [ -n "$mode" ]; then run chmod "$mode" "$dst"; fi
91
+ [ "$DRY_RUN" = 1 ] || info "installed $dst"
92
+ fi
93
+ [ "$DRY_RUN" = 1 ] || printf '%s\n' "$dst" >> "$MANIFEST"
94
+ }
95
+
96
+ deploy_glob() {
97
+ local srcdir="$1" pat="$2" dstdir="$3" mode="${4:-}" f
98
+ for f in "$srcdir"/$pat; do
99
+ [ -f "$f" ] || continue
100
+ deploy_file "$f" "$dstdir/$(basename "$f")" "$mode"
101
+ done
102
+ }
103
+
104
+ add_zsh_hook() {
105
+ if [ -f "$ZSHRC" ] && grep -qF "$HOOK_MARK" "$ZSHRC"; then
106
+ info "zsh hook present $ZSHRC"
107
+ return
108
+ fi
109
+ if [ "$DRY_RUN" = 1 ]; then info "[dry-run] append zsh hook to $ZSHRC"; return; fi
110
+ printf '%s\n' "$ZSH_HOOK" >> "$ZSHRC"
111
+ info "added zsh hook $ZSHRC"
112
+ }
113
+
114
+ # Carry state written under the pre-rename directory so an existing install keeps
115
+ # its manifest and backups instead of stranding them.
116
+ migrate_state() {
117
+ if [ -d "$STATE_DIR" ] || [ ! -d "$LEGACY_STATE_DIR" ]; then
118
+ return
119
+ fi
120
+ if [ "$DRY_RUN" = 1 ]; then
121
+ info "[dry-run] migrate state $LEGACY_STATE_DIR -> $STATE_DIR"
122
+ return
123
+ fi
124
+ mkdir -p "$(dirname "$STATE_DIR")"
125
+ mv "$LEGACY_STATE_DIR" "$STATE_DIR" && info "migrated state $LEGACY_STATE_DIR -> $STATE_DIR"
126
+ }
127
+
128
+ remove_zsh_hook() {
129
+ if [ ! -f "$ZSHRC" ] || ! grep -qF "$HOOK_MARK" "$ZSHRC"; then
130
+ info "no zsh hook to remove"
131
+ return
132
+ fi
133
+ if [ "$DRY_RUN" = 1 ]; then info "[dry-run] remove zsh hook from $ZSHRC"; return; fi
134
+ grep -vF "$HOOK_MARK" "$ZSHRC" > "$ZSHRC.agent-tmp" && mv "$ZSHRC.agent-tmp" "$ZSHRC"
135
+ info "removed zsh hook $ZSHRC"
136
+ }
137
+
138
+ # ---- subcommands ---------------------------------------------------------
139
+ cmd_install() {
140
+ check_prereqs || { log "resolve the prerequisites above and retry"; exit 1; }
141
+ migrate_state
142
+ if [ "$DRY_RUN" != 1 ]; then
143
+ mkdir -p "$STATE_DIR"
144
+ BACKUP_DIR="$STATE_DIR/backups/$(date +%Y%m%d-%H%M%S)"
145
+ : > "$MANIFEST"
146
+ fi
147
+ log "Deploying agent-bios from $REPO"
148
+ deploy_file "$REPO/claude/CLAUDE.md" "$CLAUDE_DIR/CLAUDE.md"
149
+ deploy_glob "$REPO/claude/guides" "*.md" "$CLAUDE_DIR/guides"
150
+ deploy_file "$REPO/codex/AGENTS.md" "$CODEX_DIR/AGENTS.md"
151
+ deploy_glob "$REPO/codex/guides" "*.md" "$CODEX_DIR/guides"
152
+ deploy_glob "$REPO/codex/agents" "*.toml" "$CODEX_DIR/agents"
153
+ deploy_file "$REPO/scripts/codex-run.sh" "$CODEX_DIR/bin/codex-run" "+x"
154
+ deploy_file "$REPO/scripts/codex-helm.sh" "$CODEX_DIR/bin/codex-helm" "+x"
155
+ deploy_file "$REPO/config/agent-launch.toml" "$LAUNCH_DIR/profiles.toml"
156
+ deploy_file "$REPO/shell/agent-launch.zsh" "$LAUNCH_DIR/shell.zsh"
157
+ deploy_file "$REPO/scripts/agent-launch.py" "$BIN_DIR/agent-launch" "+x"
158
+ if [ "$DRY_RUN" = 1 ]; then
159
+ info "[dry-run] provision managed Textual venv"
160
+ else
161
+ bash "$REPO/scripts/provision-venv.sh" && info "managed venv OK" \
162
+ || log "warning: venv provisioning failed (numbered-prompt fallback applies)"
163
+ fi
164
+ add_zsh_hook
165
+ log ""
166
+ log "Verifying deployment..."
167
+ if cmd_verify; then
168
+ log ""
169
+ log "Done. Open a new shell (or: source \"$ZSHRC\") to activate the zero-arg launcher."
170
+ [ -n "$BACKUP_DIR" ] && [ -d "$BACKUP_DIR" ] && log "Replaced files were backed up under $BACKUP_DIR"
171
+ else
172
+ log "VERIFY FAILED after install — see messages above"
173
+ exit 1
174
+ fi
175
+ }
176
+
177
+ verify_match() { if cmp -s "$1" "$2"; then info "match $2"; else log "MISMATCH/absent $2"; return 1; fi; }
178
+ verify_present() { if [ -f "$1" ]; then return 0; else log "missing $1"; return 1; fi; }
179
+
180
+ cmd_verify() {
181
+ local fail=0 gp
182
+ verify_match "$REPO/claude/CLAUDE.md" "$CLAUDE_DIR/CLAUDE.md" || fail=1
183
+ verify_match "$REPO/codex/AGENTS.md" "$CODEX_DIR/AGENTS.md" || fail=1
184
+ verify_match "$REPO/scripts/agent-launch.py" "$BIN_DIR/agent-launch" || fail=1
185
+ verify_match "$REPO/config/agent-launch.toml" "$LAUNCH_DIR/profiles.toml" || fail=1
186
+ verify_match "$REPO/shell/agent-launch.zsh" "$LAUNCH_DIR/shell.zsh" || fail=1
187
+ for gp in "$REPO"/claude/guides/*.md; do verify_present "$CLAUDE_DIR/guides/$(basename "$gp")" || fail=1; done
188
+ for gp in "$REPO"/codex/guides/*.md; do verify_present "$CODEX_DIR/guides/$(basename "$gp")" || fail=1; done
189
+ python3 - "$CODEX_DIR/agents" <<'PY' && info "agent TOMLs OK" || fail=1
190
+ import sys, pathlib, tomllib
191
+ root = pathlib.Path(sys.argv[1])
192
+ required = {"frontier.toml", "workhorse.toml", "sweep.toml", "reviewer.toml"}
193
+ missing = required - {p.name for p in root.glob("*.toml")}
194
+ assert not missing, f"missing agent TOMLs in {root}: {sorted(missing)}"
195
+ for p in sorted(root.glob("*.toml")):
196
+ tomllib.loads(p.read_text())
197
+ PY
198
+ if command -v codex >/dev/null 2>&1 && [ -x "$CODEX_DIR/bin/codex-helm" ]; then
199
+ if "$CODEX_DIR/bin/codex-helm" --dry-run --mode review "probe" >/dev/null 2>&1; then
200
+ info "codex-helm dry-run OK"
201
+ else
202
+ log "codex-helm dry-run FAILED"; fail=1
203
+ fi
204
+ fi
205
+ local vpy="${AGENT_LAUNCH_VENV:-$HOME/.local/share/agent-launch/venv}/bin/python"
206
+ if [ -x "$vpy" ] && "$vpy" -c 'import textual' 2>/dev/null; then
207
+ info "managed venv (textual) OK"
208
+ else
209
+ log "note: managed venv/textual unavailable (numbered-prompt fallback applies)"
210
+ fi
211
+ # Repo-internal mirror parity is a maintainer gate; only meaningful from a clone.
212
+ if [ -d "$REPO/ko" ] && [ -x "$REPO/scripts/check-parity.sh" ]; then
213
+ if "$REPO/scripts/check-parity.sh" >/dev/null 2>&1; then info "repo mirror parity OK"; else log "repo mirror parity FAILED"; fail=1; fi
214
+ fi
215
+ return $fail
216
+ }
217
+
218
+ cmd_uninstall() {
219
+ migrate_state
220
+ if [ -f "$MANIFEST" ]; then
221
+ local f
222
+ while IFS= read -r f; do
223
+ [ -n "$f" ] || continue
224
+ [ -f "$f" ] && { run rm -f "$f"; [ "$DRY_RUN" = 1 ] || info "removed $f"; }
225
+ done < "$MANIFEST"
226
+ [ "$DRY_RUN" = 1 ] || rm -f "$MANIFEST"
227
+ else
228
+ log "no manifest at $MANIFEST; removing known deploy targets"
229
+ local p
230
+ for p in "$CLAUDE_DIR/CLAUDE.md" "$CODEX_DIR/AGENTS.md" \
231
+ "$CODEX_DIR/bin/codex-run" "$CODEX_DIR/bin/codex-helm" \
232
+ "$LAUNCH_DIR/profiles.toml" "$LAUNCH_DIR/shell.zsh" "$BIN_DIR/agent-launch"; do
233
+ [ -f "$p" ] && { run rm -f "$p"; [ "$DRY_RUN" = 1 ] || info "removed $p"; }
234
+ done
235
+ fi
236
+ local d
237
+ for d in "$CLAUDE_DIR/guides" "$CODEX_DIR/guides" "$CODEX_DIR/agents" "$CODEX_DIR/bin" "$LAUNCH_DIR"; do
238
+ [ -d "$d" ] && rmdir "$d" 2>/dev/null && info "removed empty $d" || true
239
+ done
240
+ remove_zsh_hook
241
+ log ""
242
+ log "Uninstalled deployed files and the zsh hook."
243
+ log "Kept: managed venv + backups under $STATE_DIR (rm -rf \"$STATE_DIR\" to purge)."
244
+ }
245
+
246
+ cmd_status() {
247
+ local version p
248
+ if [ -d "$REPO/.git" ]; then
249
+ version="$(git -C "$REPO" describe --tags --always --dirty 2>/dev/null || git -C "$REPO" rev-parse --short HEAD 2>/dev/null || echo '?')"
250
+ log "agent-bios (git clone: $version)"
251
+ elif [ -f "$REPO/package.json" ]; then
252
+ version="$(node -e "try{process.stdout.write(require('$REPO/package.json').version)}catch(e){process.stdout.write('?')}" 2>/dev/null || echo '?')"
253
+ log "agent-bios (npm package: $version)"
254
+ else
255
+ log "agent-bios"
256
+ fi
257
+ log " source: $REPO"
258
+ for p in "$CLAUDE_DIR/CLAUDE.md" "$CODEX_DIR/AGENTS.md" "$BIN_DIR/agent-launch" \
259
+ "$LAUNCH_DIR/profiles.toml" "$LAUNCH_DIR/shell.zsh"; do
260
+ if [ -e "$p" ]; then info "present $p"; else info "MISSING $p"; fi
261
+ done
262
+ if [ -f "$ZSHRC" ] && grep -qF "$HOOK_MARK" "$ZSHRC"; then info "zsh hook present"; else info "zsh hook absent"; fi
263
+ }
264
+
265
+ cmd_update() {
266
+ if [ -d "$REPO/.git" ]; then
267
+ log "Updating from git..."
268
+ run git -C "$REPO" pull --ff-only
269
+ cmd_install
270
+ else
271
+ log "Installed as an npm package. Update with:"
272
+ log " npm install -g agent-bios@latest && agent-bios install"
273
+ fi
274
+ }
275
+
276
+ usage() {
277
+ cat <<'EOF'
278
+ agent-bios — deploy the Claude/Codex instruction SSOT into $HOME (by copy).
279
+
280
+ agent-bios install deploy into this environment (backs up + verifies)
281
+ agent-bios verify check the deployed state matches the source
282
+ agent-bios status show what is installed and where
283
+ agent-bios update git pull + reinstall (clone), or print the npm update line
284
+ agent-bios uninstall remove deployed files and the zsh hook
285
+ agent-bios help
286
+
287
+ Flags: --dry-run print actions without changing anything
288
+ Env: CLAUDE_CONFIG_DIR, CODEX_HOME, AGENT_LAUNCH_VENV, ZDOTDIR
289
+ EOF
290
+ }
291
+
292
+ # ---- dispatch ------------------------------------------------------------
293
+ CMD="${1:-help}"
294
+ if [ $# -gt 0 ]; then shift; fi
295
+ for a in "$@"; do
296
+ case "$a" in
297
+ --dry-run) DRY_RUN=1 ;;
298
+ *) log "unknown flag: $a"; exit 2 ;;
299
+ esac
300
+ done
301
+
302
+ case "$CMD" in
303
+ install) cmd_install ;;
304
+ update) cmd_update ;;
305
+ uninstall) cmd_uninstall ;;
306
+ verify) if cmd_verify; then log "VERIFY OK"; else log "VERIFY FAILED"; exit 1; fi ;;
307
+ status) cmd_status ;;
308
+ help|-h|--help) usage ;;
309
+ *) log "unknown command: $CMD"; usage; exit 2 ;;
310
+ esac
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env bash
2
+ # Provision the managed virtualenv that backs the agent-launch Textual preflight.
3
+ #
4
+ # The interactive launcher re-execs into this venv (scripts/agent-launch.py ->
5
+ # maybe_reexec_into_venv). It is an enhancement, not a hard dependency: when the
6
+ # venv is missing or broken, the launcher falls back to numbered prompts, and
7
+ # every direct / non-TTY / --no-tui path keeps running under the system
8
+ # interpreter untouched. Re-run this after upgrading Python or the pin below.
9
+ #
10
+ # AGENT_LAUNCH_VENV override the venv location (default below)
11
+ # AGENT_LAUNCH_PYTHON python used to create the venv (default: python3)
12
+ set -eu
13
+
14
+ TEXTUAL_PIN="textual==8.2.8"
15
+ VENV="${AGENT_LAUNCH_VENV:-$HOME/.local/share/agent-launch/venv}"
16
+ PYTHON="${AGENT_LAUNCH_PYTHON:-python3}"
17
+
18
+ if [ -x "$VENV/bin/python" ] && "$VENV/bin/python" -c 'import textual' 2>/dev/null; then
19
+ printf 'agent-launch venv already provisioned: %s\n' "$VENV"
20
+ "$VENV/bin/python" -c 'import textual, sys; print(" textual", textual.__version__, "| python", sys.version.split()[0])'
21
+ exit 0
22
+ fi
23
+
24
+ printf 'Provisioning agent-launch venv at %s ...\n' "$VENV"
25
+ "$PYTHON" -m venv "$VENV"
26
+ "$VENV/bin/python" -m pip install --quiet --upgrade pip
27
+ "$VENV/bin/python" -m pip install --quiet "$TEXTUAL_PIN"
28
+ "$VENV/bin/python" -c 'import textual, sys; print("Ready: textual", textual.__version__, "| python", sys.version.split()[0])'
@@ -0,0 +1,106 @@
1
+ #!/usr/bin/env python3
2
+ """Aggregate token usage & cost for a Claude Code session (main + subagents).
3
+
4
+ Usage: session-cost.py <session>.jsonl [...]
5
+ Reads the session transcript and, if present, <session-dir>/subagents/agent-*.jsonl.
6
+ Prints per-source, per-model token sums, modeled cost, and wall-clock span.
7
+ """
8
+ import json, sys, glob, os
9
+ from datetime import datetime
10
+
11
+ # $/MTok: input, output, cache_read, cache_write_5m, cache_write_1h
12
+ PRICES = {
13
+ "claude-fable-5": (10.0, 50.0, 1.00, 12.50, 20.0),
14
+ "claude-mythos-5": (10.0, 50.0, 1.00, 12.50, 20.0),
15
+ "claude-opus-4-8": (5.0, 25.0, 0.50, 6.25, 10.0),
16
+ "claude-opus-4-7": (5.0, 25.0, 0.50, 6.25, 10.0),
17
+ "claude-opus-4-6": (5.0, 25.0, 0.50, 6.25, 10.0),
18
+ "claude-sonnet-5": (3.0, 15.0, 0.30, 3.75, 6.0),
19
+ "claude-sonnet-4-6": (3.0, 15.0, 0.30, 3.75, 6.0),
20
+ "claude-haiku-4-5": (1.0, 5.0, 0.10, 1.25, 2.0),
21
+ }
22
+
23
+ def price_for(model):
24
+ for k, v in PRICES.items():
25
+ if model.startswith(k):
26
+ return v
27
+ return None
28
+
29
+ def scan(path):
30
+ """-> {model: {in,out,cr,cw5,cw1,turns}}, (t_min, t_max)"""
31
+ agg, tmin, tmax, seen = {}, None, None, set()
32
+ for line in open(path):
33
+ try:
34
+ d = json.loads(line)
35
+ except json.JSONDecodeError:
36
+ continue
37
+ ts = d.get("timestamp")
38
+ if ts:
39
+ tmin = min(tmin or ts, ts); tmax = max(tmax or ts, ts)
40
+ m = d.get("message") or {}
41
+ u, model = m.get("usage"), m.get("model")
42
+ if not u or not model or model == "<synthetic>":
43
+ continue
44
+ rid = m.get("id") or d.get("requestId")
45
+ if rid in seen:
46
+ continue # one usage record per API request
47
+ seen.add(rid)
48
+ a = agg.setdefault(model, dict(inp=0, out=0, cr=0, cw5=0, cw1=0, turns=0))
49
+ a["inp"] += u.get("input_tokens", 0)
50
+ a["out"] += u.get("output_tokens", 0)
51
+ a["cr"] += u.get("cache_read_input_tokens", 0)
52
+ cc = u.get("cache_creation") or {}
53
+ if cc:
54
+ a["cw5"] += cc.get("ephemeral_5m_input_tokens", 0)
55
+ a["cw1"] += cc.get("ephemeral_1h_input_tokens", 0)
56
+ else:
57
+ a["cw5"] += u.get("cache_creation_input_tokens", 0)
58
+ a["turns"] += 1
59
+ return agg, (tmin, tmax)
60
+
61
+ def cost(model, a):
62
+ p = price_for(model)
63
+ if not p:
64
+ return None
65
+ i, o, cr, cw5, cw1 = p
66
+ return (a["inp"]*i + a["out"]*o + a["cr"]*cr + a["cw5"]*cw5 + a["cw1"]*cw1) / 1e6
67
+
68
+ def fmt(n):
69
+ return f"{n/1000:,.0f}k" if n >= 1000 else str(n)
70
+
71
+ def report(session_path):
72
+ base = session_path[:-6] # strip .jsonl
73
+ sources = [("main", session_path)]
74
+ sources += [(os.path.basename(f)[:-6], f)
75
+ for f in sorted(glob.glob(os.path.join(base, "subagents", "*.jsonl")))]
76
+ print(f"\n=== {os.path.basename(session_path)} ===")
77
+ grand, unpriced = 0.0, []
78
+ hdr = f"{'source':<38}{'model':<22}{'turns':>6}{'input':>9}{'output':>9}{'cache_rd':>10}{'cache_wr':>10}{'cost$':>9}"
79
+ print(hdr); print("-" * len(hdr))
80
+ span_min = span_max = None
81
+ for name, path in sources:
82
+ agg, (tmin, tmax) = scan(path)
83
+ if tmin:
84
+ span_min = min(span_min or tmin, tmin); span_max = max(span_max or tmax, tmax)
85
+ for model, a in agg.items():
86
+ c = cost(model, a)
87
+ cs = f"{c:9.2f}" if c is not None else " ?"
88
+ if c is None:
89
+ unpriced.append(model)
90
+ else:
91
+ grand += c
92
+ print(f"{name:<38}{model:<22}{a['turns']:>6}{fmt(a['inp']):>9}{fmt(a['out']):>9}"
93
+ f"{fmt(a['cr']):>10}{fmt(a['cw5']+a['cw1']):>10}{cs}")
94
+ if span_min:
95
+ t0 = datetime.fromisoformat(span_min.replace("Z", "+00:00"))
96
+ t1 = datetime.fromisoformat(span_max.replace("Z", "+00:00"))
97
+ mins = (t1 - t0).total_seconds() / 60
98
+ print(f"\nwall clock: {mins:,.1f} min | modeled total cost: ${grand:,.2f}")
99
+ if unpriced:
100
+ print(f"WARN unpriced models: {set(unpriced)}")
101
+
102
+ if __name__ == "__main__":
103
+ if len(sys.argv) < 2:
104
+ sys.exit(__doc__)
105
+ for p in sys.argv[1:]:
106
+ report(p)
@@ -0,0 +1,38 @@
1
+ # Interactive zero-argument entrypoints use agent-launch. Every argument-bearing
2
+ # or non-TTY call skips profile projection; Claude retains its direct-path
3
+ # permission-bypass default.
4
+ _agent_launch_direct() {
5
+ local host="$1"
6
+ shift
7
+ if [[ "$host" == "claude" ]]; then
8
+ command claude --dangerously-skip-permissions "$@"
9
+ else
10
+ command codex "$@"
11
+ fi
12
+ }
13
+
14
+ _agent_launch_dispatch() {
15
+ local host="$1"
16
+ shift
17
+ local launcher="${AGENT_LAUNCH_BIN:-$HOME/.local/bin/agent-launch}"
18
+
19
+ if [[ $# -gt 0 && "$1" == "--no-tui" ]]; then
20
+ shift
21
+ _agent_launch_direct "$host" "$@"
22
+ elif [[ $# -eq 0 && -t 0 && -t 1 && "${AGENT_LAUNCH_TUI:-1}" != "0" ]]; then
23
+ "$launcher" "$host"
24
+ else
25
+ _agent_launch_direct "$host" "$@"
26
+ fi
27
+ }
28
+
29
+ unalias codex 2>/dev/null
30
+ unalias claude 2>/dev/null
31
+
32
+ codex() {
33
+ _agent_launch_dispatch codex "$@"
34
+ }
35
+
36
+ claude() {
37
+ _agent_launch_dispatch claude "$@"
38
+ }