Adds a skill for delegating code-writing, document generation, and analysis tasks to Claude Code and OpenAI Codex as sub-processes. Includes: - SKILL.md with delegation routing, prompt composition, and security docs - scripts/delegate.sh — unified delegation launcher with API key stripping - scripts/tmux-session.sh — tmux session manager for long-running tasks - references/delegation-policy.md — example policy (adoptable, not prescriptive) Security: strips 23 known AI provider API keys from sub-process environment, forcing subscription-based auth and preventing key leakage.
201 lines
6.2 KiB
Bash
Executable File
201 lines
6.2 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# delegate.sh — Launch Claude Code or Codex as a sub-process.
|
|
#
|
|
# Runs a delegation with API key stripping for security.
|
|
# Supports prompt strings, prompt files, foreground and background
|
|
# execution.
|
|
#
|
|
# Usage:
|
|
# delegate.sh --prompt "Your task" [options]
|
|
# delegate.sh --file /path/to/prompt.md [options]
|
|
#
|
|
# Options:
|
|
# --prompt TEXT Inline prompt string
|
|
# --file PATH Read prompt from file
|
|
# --agent AGENT "claude" (default) or "codex"
|
|
# --workdir DIR Working directory for the sub-process
|
|
# --log PATH Log file path (default: /tmp/delegation-<timestamp>.log)
|
|
# --background Run in background, return immediately
|
|
# --full-auto Codex: enable full-auto mode (default for codex)
|
|
# --timeout SECS Kill sub-process after N seconds (default: 3600)
|
|
# -h, --help Show this help message
|
|
|
|
set -euo pipefail
|
|
|
|
# --- Defaults ---
|
|
AGENT="claude"
|
|
PROMPT=""
|
|
PROMPT_FILE=""
|
|
WORKDIR="${PWD}"
|
|
LOG_FILE=""
|
|
BACKGROUND=false
|
|
FULL_AUTO=true
|
|
TIMEOUT=3600
|
|
|
|
# --- Parse arguments ---
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--prompt|--file|--agent|--workdir|--log|--timeout)
|
|
if [[ $# -lt 2 ]]; then
|
|
echo "Error: $1 requires a value" >&2
|
|
exit 1
|
|
fi
|
|
;;&
|
|
--prompt) PROMPT="$2"; shift 2 ;;
|
|
--file) PROMPT_FILE="$2"; shift 2 ;;
|
|
--agent) AGENT="$2"; shift 2 ;;
|
|
--workdir) WORKDIR="$2"; shift 2 ;;
|
|
--log) LOG_FILE="$2"; shift 2 ;;
|
|
--background) BACKGROUND=true; shift ;;
|
|
--full-auto) FULL_AUTO=true; shift ;;
|
|
--no-full-auto) FULL_AUTO=false; shift ;;
|
|
--timeout) TIMEOUT="$2"; shift 2 ;;
|
|
-h|--help)
|
|
sed -n '2,/^$/p' "$0" | sed 's/^# \?//'
|
|
exit 0
|
|
;;
|
|
*) echo "Unknown option: $1" >&2; exit 1 ;;
|
|
esac
|
|
done
|
|
|
|
# --- Validate ---
|
|
if [[ -z "$PROMPT" && -z "$PROMPT_FILE" ]]; then
|
|
echo "Error: --prompt or --file is required" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [[ -n "$PROMPT_FILE" ]]; then
|
|
if [[ ! -f "$PROMPT_FILE" ]]; then
|
|
echo "Error: prompt file not found: $PROMPT_FILE" >&2
|
|
exit 1
|
|
fi
|
|
PROMPT="$(cat "$PROMPT_FILE")"
|
|
fi
|
|
|
|
if [[ -z "$PROMPT" ]]; then
|
|
echo "Error: prompt is empty" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [[ "$AGENT" != "claude" && "$AGENT" != "codex" ]]; then
|
|
echo "Error: --agent must be 'claude' or 'codex'" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Check agent binary exists
|
|
if ! command -v "$AGENT" &> /dev/null; then
|
|
if [[ "$AGENT" == "claude" ]]; then
|
|
echo "Error: 'claude' is not installed. Install with: npm install -g @anthropic-ai/claude-code" >&2
|
|
else
|
|
echo "Error: 'codex' is not installed. Install with: npm install -g @openai/codex" >&2
|
|
fi
|
|
exit 1
|
|
fi
|
|
|
|
# Check required utilities
|
|
if ! command -v timeout &> /dev/null; then
|
|
echo "Error: 'timeout' is required but not found" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [[ "$AGENT" == "codex" ]] && ! command -v script &> /dev/null; then
|
|
echo "Error: 'script' (from util-linux) is required for Codex PTY support" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [[ ! -d "$WORKDIR" ]]; then
|
|
echo "Error: working directory not found: $WORKDIR" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Codex requires a git repository — fail fast instead of silently mutating
|
|
if [[ "$AGENT" == "codex" ]]; then
|
|
cd "$WORKDIR"
|
|
if [[ ! -d .git ]] && ! git rev-parse --git-dir > /dev/null 2>&1; then
|
|
echo "Error: Codex requires a git repository but $WORKDIR is not one." >&2
|
|
echo "Initialize one with: cd $WORKDIR && git init" >&2
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
# --- Set up log file ---
|
|
if [[ -z "$LOG_FILE" ]]; then
|
|
LOG_FILE="/tmp/delegation-$(date +%s)-$$.log"
|
|
fi
|
|
mkdir -p "$(dirname "$LOG_FILE")"
|
|
|
|
# --- Strip AI provider credentials from environment ---
|
|
# Prevents key leakage to child processes. Forces subscription/OAuth auth.
|
|
for var in ANTHROPIC_API_KEY OPENAI_API_KEY GOOGLE_API_KEY GOOGLE_GENERATIVE_AI_API_KEY \
|
|
AZURE_OPENAI_API_KEY COHERE_API_KEY MISTRAL_API_KEY OPENROUTER_API_KEY \
|
|
DEEPSEEK_API_KEY TOGETHER_API_KEY FIREWORKS_API_KEY GROQ_API_KEY \
|
|
GEMINI_API_KEY PERPLEXITY_API_KEY BRAVE_API_KEY BRAVE_SEARCH_API_KEY \
|
|
REPLICATE_API_TOKEN AI21_API_KEY HUGGINGFACE_API_KEY HF_TOKEN \
|
|
VOYAGE_API_KEY ANYSCALE_API_KEY XAI_API_KEY; do
|
|
unset "$var" 2>/dev/null || true
|
|
done
|
|
|
|
# --- Build command ---
|
|
build_claude_cmd() {
|
|
echo "claude --permission-mode bypassPermissions --print"
|
|
}
|
|
|
|
build_codex_cmd() {
|
|
local cmd="codex exec"
|
|
if $FULL_AUTO; then
|
|
cmd="codex exec --full-auto"
|
|
fi
|
|
echo "$cmd"
|
|
}
|
|
|
|
run_delegation() {
|
|
local exit_code=0
|
|
|
|
cd "$WORKDIR" || exit 1
|
|
|
|
case "$AGENT" in
|
|
claude)
|
|
$(build_claude_cmd) "$PROMPT" > "$LOG_FILE" 2>&1 || exit_code=$?
|
|
;;
|
|
codex)
|
|
# Codex requires a PTY — use script(1) to provide one
|
|
script -q -c "$(build_codex_cmd) $(printf '%q' "$PROMPT")" "$LOG_FILE" || exit_code=$?
|
|
;;
|
|
esac
|
|
|
|
return $exit_code
|
|
}
|
|
|
|
# --- Execute ---
|
|
echo "Delegating to $AGENT in $WORKDIR (timeout: ${TIMEOUT}s)"
|
|
echo "Log: $LOG_FILE"
|
|
|
|
if $BACKGROUND; then
|
|
(
|
|
timeout "$TIMEOUT" bash -c "$(declare -f run_delegation build_claude_cmd build_codex_cmd); \
|
|
AGENT='$AGENT' PROMPT='$(printf '%s' "$PROMPT" | sed "s/'/'\\\\''/g")' \
|
|
WORKDIR='$WORKDIR' LOG_FILE='$LOG_FILE' FULL_AUTO=$FULL_AUTO \
|
|
run_delegation"
|
|
EXIT_CODE=$?
|
|
echo ""
|
|
echo "--- Delegation complete (exit code: $EXIT_CODE) ---"
|
|
) >> "$LOG_FILE" 2>&1 &
|
|
BG_PID=$!
|
|
echo "Background PID: $BG_PID"
|
|
echo "Monitor: tail -f $LOG_FILE"
|
|
else
|
|
timeout "$TIMEOUT" bash -c "$(declare -f run_delegation build_claude_cmd build_codex_cmd); \
|
|
AGENT='$AGENT' PROMPT='$(printf '%s' "$PROMPT" | sed "s/'/'\\\\''/g")' \
|
|
WORKDIR='$WORKDIR' LOG_FILE='$LOG_FILE' FULL_AUTO=$FULL_AUTO \
|
|
run_delegation"
|
|
EXIT_CODE=$?
|
|
if [[ $EXIT_CODE -eq 0 ]]; then
|
|
echo "Delegation complete."
|
|
elif [[ $EXIT_CODE -eq 124 ]]; then
|
|
echo "Delegation timed out after ${TIMEOUT}s. Check log: $LOG_FILE" >&2
|
|
else
|
|
echo "Delegation failed (exit code: $EXIT_CODE). Check log: $LOG_FILE" >&2
|
|
fi
|
|
exit $EXIT_CODE
|
|
fi
|