No error is a dead end: crash, click, and your agent is already looking
Claude-Session: https://claude.ai/code/session_01Ms2FbjQy31TVf3CEvQhGM8
This commit is contained in:
Executable
+195
@@ -0,0 +1,195 @@
|
||||
#!/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 ("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.
|
||||
#
|
||||
# "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 a terminal, in the Panama checkout.
|
||||
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) ;;
|
||||
*)
|
||||
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.
|
||||
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
|
||||
|
||||
# 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[@]}"
|
||||
Reference in New Issue
Block a user