aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-07-13 23:21:32 -0500
committerCraig Jennings <c@cjennings.net>2026-07-13 23:21:32 -0500
commit3eed2e185773451574c51a4bad591a2753dac662 (patch)
tree571c83e0089484962f2e941801787710d5c9c0d8
parent1b98a9ed2acff1605895700302ff276ba88baa5b (diff)
downloadrulesets-3eed2e185773451574c51a4bad591a2753dac662.tar.gz
rulesets-3eed2e185773451574c51a4bad591a2753dac662.zip
feat(launcher): bare ai picks the agent before the projects
The interactive flow now opens with a runtime picker: claude first (Enter-Enter preserves the old two-keystroke habit), codex labeled as ChatGPT, and one local:<model> line per ollama model, queried live with a short timeout so a dead server just drops those lines. The pick feeds the existing runtime map, then the annotated project multi-select runs as before. --runtime and AI_RUNTIME skip the question, single-directory mode is unchanged, and --print-runtimes exposes the choice list for the two new bats tests.
-rwxr-xr-xclaude-templates/bin/ai59
-rw-r--r--scripts/tests/ai-launcher-runtime.bats34
-rw-r--r--todo.org3
3 files changed, 88 insertions, 8 deletions
diff --git a/claude-templates/bin/ai b/claude-templates/bin/ai
index c805e8b..cf17875 100755
--- a/claude-templates/bin/ai
+++ b/claude-templates/bin/ai
@@ -2,10 +2,12 @@
# ai — agent session launcher (unified aix + hey)
#
# Usage:
-# ai Select one or more projects via fzf and open each in
-# an 'ai' tmux session window (creates session if needed).
+# ai Pick the agent first (claude, codex/ChatGPT, or any
+# local ollama model), then select one or more projects
+# via fzf; each opens in an 'ai' tmux session window.
# Git-aware: fetches, annotates with ↑/↓/dirty, auto-pulls
-# clean-and-behind repos before opening.
+# clean-and-behind repos before opening. --runtime or
+# AI_RUNTIME skips the agent pick.
#
# ai <dir>... Single-project mode. Opens each given directory directly
# in the 'ai' session (new window or switch to existing).
@@ -51,6 +53,36 @@ resolve_agent_cmd() {
esac
}
+# One line per launchable agent, claude first (Enter-Enter keeps the old
+# muscle memory). Local models appear only when both codex (the CLI that
+# drives them) and a live ollama answer; a dead server just drops the lines.
+build_runtime_choices() {
+ command -v claude >/dev/null 2>&1 && echo "claude — Claude Code"
+ command -v codex >/dev/null 2>&1 && echo "codex — ChatGPT (Codex CLI)"
+ if command -v codex >/dev/null 2>&1 && command -v ollama >/dev/null 2>&1; then
+ timeout 3 ollama list 2>/dev/null | tail -n +2 | awk 'NF {print "local:" $1 " — ollama"}'
+ fi
+}
+
+# Interactive runtime pick for the bare-`ai` flow. Sets RUNTIME (and
+# LOCAL_MODEL for a local pick) and re-resolves the agent command.
+# Returns 1 when the pick is cancelled.
+pick_runtime() {
+ local choice
+ choice=$(build_runtime_choices | fzf --height=30% --reverse --prompt='agent> ') || return 1
+ [ -z "$choice" ] && return 1
+ case "$choice" in
+ claude*) RUNTIME="claude" ;;
+ codex*) RUNTIME="codex" ;;
+ local:*)
+ RUNTIME="local"
+ LOCAL_MODEL="${choice#local:}"
+ LOCAL_MODEL="${LOCAL_MODEL%% *}"
+ ;;
+ esac
+ resolve_agent_cmd
+}
+
# 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
# passed through to Claude as input rather than suspending the session to the
@@ -70,7 +102,7 @@ build_instructions() {
}
usage() {
- sed -n '2,20p' "$0" | sed 's|^# \?||'
+ sed -n '2,23p' "$0" | sed 's|^# \?||'
exit 0
}
@@ -447,24 +479,31 @@ print_launch_mode() {
# ---------- dispatch ----------
print_launch=""
+runtime_explicit="${AI_RUNTIME:+1}"
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; }
+ [ -z "${2:-}" ] && { echo "ai: --runtime needs a value — valid runtimes: claude, codex, local" >&2; exit 2; }
RUNTIME="$2"
+ runtime_explicit=1
shift 2
;;
--runtime=*)
RUNTIME="${1#--runtime=}"
+ runtime_explicit=1
shift
;;
--print-launch)
print_launch=1
shift
;;
+ --print-runtimes)
+ build_runtime_choices
+ exit 0
+ ;;
*)
break
;;
@@ -478,16 +517,22 @@ if [ -n "$print_launch" ]; then
print_launch_mode "$1"
fi
-check_deps
-
case "${1:-}" in
--attach)
+ check_deps
attach_mode
;;
"")
+ # Bare `ai`: pick the agent first (skipped when --runtime or AI_RUNTIME
+ # already chose), then the familiar project multi-select.
+ if [ -z "$runtime_explicit" ]; then
+ pick_runtime || exit 0
+ fi
+ check_deps
multi_mode
;;
*)
+ check_deps
for arg in "$@"; do
single_mode "$arg"
done
diff --git a/scripts/tests/ai-launcher-runtime.bats b/scripts/tests/ai-launcher-runtime.bats
index b93f778..3f0ad69 100644
--- a/scripts/tests/ai-launcher-runtime.bats
+++ b/scripts/tests/ai-launcher-runtime.bats
@@ -11,10 +11,11 @@ setup() {
PROJ="$(mktemp -d)"
mkdir -p "$PROJ/.ai"
touch "$PROJ/.ai/protocols.org"
+ STUB_BIN="$(mktemp -d)"
}
teardown() {
- rm -rf "$PROJ"
+ rm -rf "$PROJ" "$STUB_BIN"
}
@test "default runtime launches claude" {
@@ -63,3 +64,34 @@ teardown() {
[ "$status" -eq 1 ]
[[ "$output" == *"protocols.org"* ]]
}
+
+@test "--print-runtimes lists claude, codex, and one line per ollama model" {
+ cat > "$STUB_BIN/ollama" <<'STUB'
+#!/bin/bash
+if [ "$1" = "list" ]; then
+ printf 'NAME ID SIZE MODIFIED\n'
+ printf 'gpt-oss:120b aaa 65 GB 1 hour ago\n'
+ printf 'qwen3-coder:30b bbb 18 GB 1 hour ago\n'
+fi
+STUB
+ chmod +x "$STUB_BIN/ollama"
+ PATH="$STUB_BIN:$PATH" run bash "$AI" --print-runtimes
+ [ "$status" -eq 0 ]
+ [ "${lines[0]%% *}" = "claude" ]
+ [[ "$output" == *"codex"* ]]
+ [[ "$output" == *"local:gpt-oss:120b"* ]]
+ [[ "$output" == *"local:qwen3-coder:30b"* ]]
+}
+
+@test "a dead ollama server just drops the local lines" {
+ cat > "$STUB_BIN/ollama" <<'STUB'
+#!/bin/bash
+echo "Error: could not connect to a running Ollama instance" >&2
+exit 1
+STUB
+ chmod +x "$STUB_BIN/ollama"
+ PATH="$STUB_BIN:$PATH" run bash "$AI" --print-runtimes
+ [ "$status" -eq 0 ]
+ [[ "$output" == *"claude"* ]]
+ [[ "$output" != *"local:"* ]]
+}
diff --git a/todo.org b/todo.org
index 42dd8f0..00cb024 100644
--- a/todo.org
+++ b/todo.org
@@ -293,6 +293,9 @@ Analysis in [[file:docs/design/2026-07-13-runtime-portability-inventories.org]]
*** 2026-07-13 Mon @ 13:34:17 -0500 Hook parity inventoried — only two hooks carry real porting work
Full mapping in [[file:docs/design/2026-07-13-runtime-portability-inventories.org]]. AskUserQuestion deny is moot off Claude (no popup tool to deny); PostToolUse validators survive via the bundles' git pre-commit hooks; clear-resume folds into the session-plumbing child; session-title is cosmetic. Real gaps: PreCompact priority-save (prose downgrade) and Stop wrap-teardown (Codex notify / manual elsewhere) — decisions in the VERIFY below.
+*** 2026-07-13 Mon @ 23:21:08 -0500 Interactive agent picker — bare =ai= asks which brain first
+Craig's ask: the fzf flow should offer the LLMs before the projects. Bare =ai= now runs a runtime picker (claude first so Enter-Enter keeps the old muscle memory; codex labeled ChatGPT; one =local:<model>= line per ollama model, live-queried with a 3s timeout — a dead server just drops the local lines), then the familiar annotated project multi-select. =--runtime= / =AI_RUNTIME= skip the pick; single-dir mode unchanged. New =--print-runtimes= test seam; 9/9 launcher bats, suite 441/0. This substantially delivers the "easy lightweight way to change agents / agentically democratic" roam ask — the launcher-hardening task keeps the deeper refactor.
+
*** 2026-07-13 Mon @ 23:04:21 -0500 Local runtime wired — ai --runtime local runs codex --oss over ollama
The reserved lane went live the same day: =local= maps to =codex --oss --local-provider=ollama -m $AI_LOCAL_MODEL= (default gpt-oss:120b; qwen3-coder:30b via the env var). Verified end to end: =codex exec --oss --local-provider=ollama -m gpt-oss:120b= returned a correct completion through the local model (~7K tokens). The explicit provider flag avoids config.toml dependence (a root-level =oss_provider= key would also work; appending to the file lands in the last TOML table and silently does nothing — learned live). Dependency check keys on AGENT_BIN (codex) now that AGENT_CMD carries flags. This partially answers the spec's "first supported local editing CLI" blocker: codex-over-ollama is the de-facto v1. The model-floor child's live trial (a takuzu session under =ai --runtime local=) is the next step.