From 04c3b29a968d62eac1a4123cf03606fc667c2d14 Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Mon, 13 Jul 2026 16:40:07 -0500 Subject: feat(launcher): add ai --runtime for choosing the agent CLI The ai launcher can now start codex sessions: --runtime claude|codex (or AI_RUNTIME), a runtime-to-CLI map with local reserved behind a clear error until the local-model evaluation picks a CLI, and a runtime-aware dependency check. A new --print-launch mode prints the pane launch command without touching tmux or fzf, and is the seam the six new bats tests drive. Both current CLIs take the opening line as a positional prompt, so only the command name varies. --- claude-templates/bin/ai | 98 ++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 84 insertions(+), 14 deletions(-) (limited to 'claude-templates/bin/ai') diff --git a/claude-templates/bin/ai b/claude-templates/bin/ai index 994dc1f..c09faab 100755 --- a/claude-templates/bin/ai +++ b/claude-templates/bin/ai @@ -1,5 +1,5 @@ #!/bin/bash -# ai — Claude Code session launcher (unified aix + hey) +# ai — agent session launcher (unified aix + hey) # # Usage: # ai Select one or more projects via fzf and open each in @@ -11,6 +11,10 @@ # in the 'ai' session (new window or switch to existing). # Use '.' for current directory. Git prep per dir. # +# ai --runtime Launch with a different agent CLI: claude (default) or +# codex. 'local' is reserved for a local-LLM CLI, pending +# the model evaluation. Also settable via AI_RUNTIME. +# # ai --attach Attach to the existing 'ai' session without changes. # # ai -h | --help Show this help. @@ -25,7 +29,25 @@ # would kill the script. SESSION="ai" -CLAUDE_CMD="claude" +RUNTIME="${AI_RUNTIME:-claude}" + +# Map the runtime name to the agent CLI a pane launches. Both current CLIs +# take the opening instructions as a positional prompt, so only the command +# name varies; a runtime whose CLI needs different plumbing gets its own case. +resolve_agent_cmd() { + case "$RUNTIME" in + claude) AGENT_CMD="claude" ;; + codex) AGENT_CMD="codex" ;; + local) + echo "ai: the 'local' runtime is reserved but not wired yet (pending the local-model evaluation) — valid runtimes: claude, codex" >&2 + exit 2 + ;; + *) + echo "ai: unknown runtime '$RUNTIME' — valid runtimes: claude, codex" >&2 + exit 2 + ;; + esac +} # Run in the pane's shell just before Claude launches. `stty susp undef` clears # the tty's SIGTSTP (C-z) character for this pane only, so an accidental C-z is @@ -50,12 +72,14 @@ usage() { exit 0 } -for cmd in fzf tmux claude; do - if ! command -v "$cmd" &>/dev/null; then - echo "ai: $cmd is not installed" >&2 - exit 1 - fi -done +check_deps() { + for cmd in fzf tmux "$AGENT_CMD"; do + if ! command -v "$cmd" &>/dev/null; then + echo "ai: $cmd is not installed" >&2 + exit 1 + fi + done +} # ---------- shared helpers ---------- @@ -73,7 +97,7 @@ create_window() { wid=$(tmux new-window -a -t "$SESSION:{end}" -n "$name" -c "$dir" -P -F '#{window_id}') sleep 0.1 instructions=$(build_instructions "$name") - tmux send-keys -t "$wid" "${LAUNCH_PREFIX}$CLAUDE_CMD \"$instructions\"" Enter + tmux send-keys -t "$wid" "${LAUNCH_PREFIX}$AGENT_CMD \"$instructions\"" Enter echo "$wid" } @@ -319,7 +343,7 @@ single_mode() { local instructions wid=$(tmux new-session -d -s "$SESSION" -n "$name" -c "$dir" -P -F '#{window_id}') instructions=$(build_instructions "$name") - tmux send-keys -t "$wid" "${LAUNCH_PREFIX}$CLAUDE_CMD \"$instructions\"" Enter + tmux send-keys -t "$wid" "${LAUNCH_PREFIX}$AGENT_CMD \"$instructions\"" Enter fi sort_windows @@ -379,7 +403,7 @@ multi_mode() { local instructions first_wid=$(tmux new-session -d -s "$SESSION" -n "$name" -c "$dir" -P -F '#{window_id}') instructions=$(build_instructions "$name") - tmux send-keys -t "$first_wid" "${LAUNCH_PREFIX}$CLAUDE_CMD \"$instructions\"" Enter + tmux send-keys -t "$first_wid" "${LAUNCH_PREFIX}$AGENT_CMD \"$instructions\"" Enter for entry in "${selected[@]:1}"; do dir="${entry/#\~/$HOME}" name="$(basename "$dir")" @@ -403,12 +427,58 @@ multi_mode() { attach_session } +# Print the launch command a real run would send to the pane, then exit. +# Exists for the launcher's bats tests: exercises runtime resolution and the +# opening line with no tmux or fzf involved. +print_launch_mode() { + local arg="$1" dir name + dir="$(cd "$arg" 2>/dev/null && pwd)" || { echo "ai: cannot access '$arg'" >&2; exit 1; } + if [ ! -f "$dir/.ai/protocols.org" ]; then + echo "ai: $dir has no .ai/protocols.org — not an agent-template project" >&2 + exit 1 + fi + name="$(basename "$dir")" + printf '%s "%s"\n' "$AGENT_CMD" "$(build_instructions "$name")" + exit 0 +} + # ---------- dispatch ---------- +print_launch="" +while [ $# -gt 0 ]; do + case "$1" in + -h|--help) + usage + ;; + --runtime) + [ -z "${2:-}" ] && { echo "ai: --runtime needs a value — valid runtimes: claude, codex" >&2; exit 2; } + RUNTIME="$2" + shift 2 + ;; + --runtime=*) + RUNTIME="${1#--runtime=}" + shift + ;; + --print-launch) + print_launch=1 + shift + ;; + *) + break + ;; + esac +done + +resolve_agent_cmd + +if [ -n "$print_launch" ]; then + [ $# -eq 0 ] && { echo "ai: --print-launch needs a project directory" >&2; exit 2; } + print_launch_mode "$1" +fi + +check_deps + case "${1:-}" in - -h|--help) - usage - ;; --attach) attach_mode ;; -- cgit v1.2.3