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.
- package/DEPENDENCIES.md +89 -0
- package/LICENSE +21 -0
- package/README.md +86 -0
- package/claude/CLAUDE.md +138 -0
- package/claude/guides/cli-multi-model-workflow.md +194 -0
- package/claude/guides/coding-staged-workflow.md +70 -0
- package/claude/guides/implementation-map.md +34 -0
- package/claude/guides/llm-capability-boundary-examples.md +123 -0
- package/claude/guides/llm-capability-boundary-patterns.md +339 -0
- package/claude/guides/llm-capability-boundary.md +255 -0
- package/claude/guides/mock-realization-boundary.md +275 -0
- package/claude/guides/svg-visualization-guide.md +321 -0
- package/codex/AGENTS.md +139 -0
- package/codex/agents/frontier.toml +8 -0
- package/codex/agents/reviewer.toml +9 -0
- package/codex/agents/sweep.toml +9 -0
- package/codex/agents/workhorse.toml +8 -0
- package/codex/guides/cli-multi-model-workflow.md +194 -0
- package/codex/guides/coding-staged-workflow.md +70 -0
- package/codex/guides/implementation-map.md +34 -0
- package/codex/guides/llm-capability-boundary-examples.md +123 -0
- package/codex/guides/llm-capability-boundary-patterns.md +339 -0
- package/codex/guides/llm-capability-boundary.md +255 -0
- package/codex/guides/mock-realization-boundary.md +275 -0
- package/codex/guides/svg-visualization-guide.md +321 -0
- package/config/agent-launch.toml +94 -0
- package/package.json +54 -0
- package/scripts/agent-launch.py +1742 -0
- package/scripts/check-parity.sh +1703 -0
- package/scripts/codex-helm.sh +370 -0
- package/scripts/codex-run.sh +176 -0
- package/scripts/install.sh +310 -0
- package/scripts/provision-venv.sh +28 -0
- package/scripts/session-cost.py +106 -0
- 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
|
+
}
|