231 lines
10 KiB
Bash
Executable File
231 lines
10 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
# Hand a prompt to whichever coding agent this machine has chosen.
|
|
#
|
|
# Every rung of the escalation ladder ends here: a crash toast, a failed shell
|
|
# reload, a red health check, `panama diagnose`. They gather facts; this decides
|
|
# which binary runs them and puts it in a terminal you can watch and interrupt.
|
|
#
|
|
# Two settings decide everything, and both are read at press time rather than at
|
|
# start time, so choosing an agent in Settings takes effect on the next crash
|
|
# without restarting anything:
|
|
#
|
|
# preferredAgent none | claude | codex | t3 ("none" is the default: silence)
|
|
# agentAutoApprove true -> the agent starts in its own "don't stop to ask"
|
|
# mode; false -> its normal prompting mode, untouched.
|
|
# T3 Code ignores it: a T3 thread runs in the mode its
|
|
# project is set to, which is a choice made inside T3.
|
|
#
|
|
# claude and codex open in a kitty window. t3 starts a thread in the Panama
|
|
# project on the running T3 Code server and brings it up in the desktop app,
|
|
# so the investigation lives beside every other thread instead of in a
|
|
# terminal of its own.
|
|
#
|
|
# "none" exits 0 without a word. It is not an error to have no agent; it is the
|
|
# shipped state, and a rung that shouted about it would be a rung that gets
|
|
# turned off.
|
|
#
|
|
# panama-agent open the agent on the repo
|
|
# panama-agent --prompt "text" open it with something to work on
|
|
#
|
|
# Environment seams, for the contract and for a second checkout:
|
|
#
|
|
# PANAMA_PATH the repository; also the agent's working directory
|
|
# PANAMA_AGENT_SETTINGS the settings file to read (default: the real one)
|
|
#
|
|
# ---------------------------------------------------------------------------
|
|
# Adapted from Omarchy's bin/omarchy-agent (https://github.com/basecamp/omarchy)
|
|
#
|
|
# Copyright (c) David Heinemeier Hansson
|
|
#
|
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
# of this software and associated documentation files (the "Software"), to deal
|
|
# in the Software without restriction, including without limitation the rights
|
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
# copies of the Software, and to permit persons to whom the Software is
|
|
# furnished to do so, subject to the following conditions:
|
|
#
|
|
# The above copyright notice and this permission notice shall be included in all
|
|
# copies or substantial portions of the Software.
|
|
#
|
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
# SOFTWARE.
|
|
# ---------------------------------------------------------------------------
|
|
|
|
set -euo pipefail
|
|
|
|
PANAMA_PATH="${PANAMA_PATH:-$(cd "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")/.." && pwd)}"
|
|
SETTINGS="${PANAMA_AGENT_SETTINGS:-${XDG_CONFIG_HOME:-$HOME/.config}/panama/settings.json}"
|
|
|
|
# A fixed window class rather than the per-binary default, so one Hyprland rule
|
|
# can catch every agent window regardless of which agent is chosen.
|
|
readonly WINDOW_CLASS="panama-agent"
|
|
|
|
# THE INHERITED PATH IS NOT THE USER'S PATH. Every rung except `panama diagnose`
|
|
# reaches this script from the Quickshell shell, which systemd starts with
|
|
# neither PANAMA_PATH nor ~/.local/bin -- and ~/.local/bin is where both agents
|
|
# install themselves. Trusting PATH here meant a perfectly well installed agent
|
|
# reporting itself as missing, into the stderr of a detached process nobody will
|
|
# ever read: the whole ladder failing silently, which is the exact failure it
|
|
# exists to prevent.
|
|
#
|
|
# So the binary is resolved rather than named. PATH first, because a user who
|
|
# put an agent somewhere else meant it; then the XDG user bin directory, which
|
|
# is where the installers actually put them.
|
|
resolve_agent() {
|
|
local name="$1" found
|
|
found="$(command -v "$name" 2>/dev/null)" && { printf '%s' "$found"; return 0; }
|
|
[[ -x "$HOME/.local/bin/$name" ]] && { printf '%s' "$HOME/.local/bin/$name"; return 0; }
|
|
return 1
|
|
}
|
|
|
|
# The same repair, for the agent's own sake rather than this script's: an agent
|
|
# launched from a notification click would otherwise run every shell command it
|
|
# is asked to with a PATH unlike the one the user gets in a terminal. Applied
|
|
# just before the spawn rather than here, so resolve_agent above is answering
|
|
# the question the caller actually asked -- "can this be found from where I was
|
|
# started" -- instead of one this script has already fixed for itself.
|
|
repair_path() {
|
|
case ":$PATH:" in
|
|
*":$HOME/.local/bin:"*) ;;
|
|
*) PATH="$PATH:$HOME/.local/bin" ;;
|
|
esac
|
|
export PATH
|
|
}
|
|
|
|
usage() {
|
|
cat <<'EOF'
|
|
Usage: panama-agent [--prompt "text"]
|
|
|
|
Opens the agent named by preferredAgent in the Panama checkout: a terminal for
|
|
claude and codex, a thread in the T3 Code app for t3. With no agent chosen,
|
|
exits silently: choose one on Settings > System > Agents.
|
|
EOF
|
|
}
|
|
|
|
prompt=""
|
|
while (($#)); do
|
|
case "$1" in
|
|
--prompt)
|
|
prompt="${2:?--prompt needs a value}"
|
|
shift 2
|
|
;;
|
|
-h | --help)
|
|
usage
|
|
exit 0
|
|
;;
|
|
*)
|
|
printf 'panama-agent: unexpected argument: %s\n' "$1" >&2
|
|
usage >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
|
|
# Same shape as panama-idle's reader: a missing file, a missing key and an
|
|
# explicit null all mean "the default", because all three describe a machine
|
|
# that has never been asked the question.
|
|
read_setting() {
|
|
local key="$1" fallback="$2"
|
|
[[ -r "$SETTINGS" ]] || { printf '%s' "$fallback"; return; }
|
|
command -v jq >/dev/null 2>&1 || { printf '%s' "$fallback"; return; }
|
|
jq -r --arg k "$key" --arg d "$fallback" \
|
|
'if has($k) and (.[$k] != null) then (.[$k] | tostring) else $d end' \
|
|
"$SETTINGS" 2>/dev/null || printf '%s' "$fallback"
|
|
}
|
|
|
|
agent="$(read_setting preferredAgent none)"
|
|
|
|
# The shipped state. Nothing to launch, nothing to say.
|
|
[[ -n "$agent" && "$agent" != "none" ]] || exit 0
|
|
|
|
auto_approve="$(read_setting agentAutoApprove true)"
|
|
|
|
case "$agent" in
|
|
claude | codex | t3) ;;
|
|
*)
|
|
printf 'panama-agent: unsupported preferredAgent: %s\n' "$agent" >&2
|
|
printf 'Choose one on Settings > System > Agents.\n' >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
# Resolved to a path before argv is built, so kitty is never asked to repeat a
|
|
# PATH lookup this script has already done more carefully than kitty could. T3
|
|
# Code's CLI is `t3`, which the desktop app writes into ~/.local/bin.
|
|
if ! agent_bin="$(resolve_agent "$agent")"; then
|
|
printf 'panama-agent: %s is not installed.\n' "$agent" >&2
|
|
printf 'Looked on PATH and in %s.\n' "$HOME/.local/bin" >&2
|
|
printf 'Install it, or choose another agent on Settings > System > Agents.\n' >&2
|
|
exit 1
|
|
fi
|
|
|
|
# T3 Code: no terminal. The thread starts on the running server in the
|
|
# project whose root is the checkout, with the project's default model, and
|
|
# the t3code:// link opens it in the app. `project add` refuses a root that is
|
|
# already a project, which is the common case, so its failure is not one. The
|
|
# prompt goes over stdin ("-"), so a prompt that begins with a dash is never
|
|
# read as a flag. With no prompt there is nothing to start, so the app just
|
|
# opens on the project.
|
|
if [[ "$agent" == "t3" ]]; then
|
|
repair_path
|
|
"$agent_bin" project add "$PANAMA_PATH" >/dev/null 2>&1 || true
|
|
if [[ -z "$prompt" ]]; then
|
|
exec setsid "$agent_bin" app "$PANAMA_PATH"
|
|
fi
|
|
if ! thread="$(printf '%s' "$prompt" | "$agent_bin" thread start "$PANAMA_PATH" -)"; then
|
|
printf 'panama-agent: T3 Code could not start a thread.\n' >&2
|
|
printf 'Is the T3 Code server running? Check: systemctl --user status t3code\n' >&2
|
|
exit 1
|
|
fi
|
|
environment_file="${T3CODE_HOME:-$HOME/.t3}/userdata/environment-id"
|
|
environment="$(cat "$environment_file" 2>/dev/null || true)"
|
|
if [[ -n "$environment" ]]; then
|
|
exec setsid xdg-open "t3code://threads/$environment/$thread"
|
|
fi
|
|
exec setsid "$agent_bin" app "$PANAMA_PATH"
|
|
fi
|
|
|
|
# The per-agent launch table. VERIFIED against the installed binaries' --help on
|
|
# 2026-08-25 (claude 2.1.245, codex-cli 0.149.1) -- these flags are not the same
|
|
# from release to release, so re-run --help before changing them.
|
|
#
|
|
# claude --permission-mode auto "auto" is one of acceptEdits/auto/
|
|
# bypassPermissions/manual/dontAsk/plan
|
|
# codex --approve-for-me routes approvals through automatic review
|
|
# inside the workspace-write sandbox
|
|
#
|
|
# With agentAutoApprove off, no mode flag is passed at all: the agent's own
|
|
# configured default is a choice the user already made, and overriding it with
|
|
# an explicit "prompt me" would be this script having an opinion it was told not
|
|
# to have.
|
|
declare -a argv=("$agent_bin")
|
|
case "$agent" in
|
|
claude) [[ "$auto_approve" == "true" ]] && argv+=(--permission-mode auto) ;;
|
|
codex) [[ "$auto_approve" == "true" ]] && argv+=(--approve-for-me) ;;
|
|
esac
|
|
|
|
# One argv element, after the option terminator. Both CLIs take the prompt as a
|
|
# trailing positional, and `--` is what stops a prompt beginning with a dash --
|
|
# or one that happens to read like a subcommand -- from being parsed as flags.
|
|
[[ -n "$prompt" ]] && argv+=(-- "$prompt")
|
|
|
|
# The checkout, not $HOME: the skills the prompts point at, the repository the
|
|
# agent is being asked about, and .claude/settings.json's pre-approved read-only
|
|
# diagnostics all live here. An agent started anywhere else finds none of them.
|
|
cd "$PANAMA_PATH"
|
|
|
|
repair_path
|
|
|
|
# setsid so the agent outlives whatever spawned it -- a notification handler, a
|
|
# crash watcher, a terminal that is about to close.
|
|
exec setsid kitty \
|
|
--directory "$PANAMA_PATH" \
|
|
--class "$WINDOW_CLASS" \
|
|
-e "${argv[@]}"
|