Skip to content

Instantly share code, notes, and snippets.

@mikehostetler
Created February 21, 2026 13:57
Show Gist options
  • Select an option

  • Save mikehostetler/14777f2419ca13f68743a237c36b85be to your computer and use it in GitHub Desktop.

Select an option

Save mikehostetler/14777f2419ca13f68743a237c36b85be to your computer and use it in GitHub Desktop.
Jido PrBot Sprite Smoke Test — Codex provider
#!/usr/bin/env bash
#
# test_codex_sprite.sh
#
# Pressure-test the full PrBot flow using ONLY the Sprites CLI + gh + git
# and one provider CLI in stream-JSON mode.
#
# Validates: sprite lifecycle, env injection, gh auth, git clone, branch,
# provider streaming prompt, provider code change, commit, push, PR, comment.
#
# Usage:
# ./test_codex_sprite.sh https://github.com/OWNER/REPO/issues/123
#
# Options:
# --keep-sprite Don't destroy the sprite on exit (for debugging)
# --sprite-name NAME Use a custom sprite name (default: jido-test-<runid>)
# --org ORG Sprites organization
# --dry-run Validate everything but skip mutating GitHub operations
# --verbose Print raw JSON lines in addition to parsed progress
#
# Required env vars (auto-sourced from jido_lib/.env):
# Shared: SPRITES_TOKEN (or sprite login), GH_TOKEN or GITHUB_TOKEN
# Codex: OPENAI_API_KEY
#
set -euo pipefail
PROVIDER="codex"
PROVIDER_LABEL="Codex"
SCRIPT_NAME="test_codex_sprite.sh"
# ─── Source .env if present ─────────────────────────────────────────────────
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
for envfile in "$SCRIPT_DIR/jido_lib/.env" "$SCRIPT_DIR/.env"; do
if [[ -f "$envfile" ]]; then
set -a
# shellcheck disable=SC1090
source "$envfile"
set +a
fi
done
# ─── Color helpers ──────────────────────────────────────────────────────────
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m'
info() { echo -e "${BLUE}[INFO]${NC} $*"; }
ok() { echo -e "${GREEN}[OK]${NC} $*"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
fail() { echo -e "${RED}[FAIL]${NC} $*"; }
step() { echo -e "\n${CYAN}━━━ $* ━━━${NC}"; }
# ─── Defaults ───────────────────────────────────────────────────────────────
KEEP_SPRITE=false
DRY_RUN=false
VERBOSE=false
SPRITE_NAME=""
ORG_FLAG=""
RUN_ID=$(openssl rand -hex 6)
BRANCH_PREFIX="jido/prbot"
WORKSPACE_DIR=""
REPO_DIR=""
ARTIFACTS=()
LAST_STREAM_ARTIFACT=""
LAST_STREAM_RESULT_TEXT=""
LAST_STREAM_EVENT_COUNT="0"
# ─── Parse args ─────────────────────────────────────────────────────────────
ISSUE_URL=""
while [[ $# -gt 0 ]]; do
case "$1" in
--keep-sprite) KEEP_SPRITE=true; shift ;;
--dry-run) DRY_RUN=true; shift ;;
--verbose) VERBOSE=true; shift ;;
--sprite-name) SPRITE_NAME="$2"; shift 2 ;;
--org) ORG_FLAG="-o $2"; shift 2 ;;
-h|--help)
head -26 "$0" | grep '^#' | sed 's/^# \?//'
exit 0
;;
https://*) ISSUE_URL="$1"; shift ;;
*) fail "Unknown argument: $1"; exit 1 ;;
esac
done
if [[ -z "$ISSUE_URL" ]]; then
fail "Usage: $0 <github-issue-url> [options]"
fail "Example: $0 https://github.com/agentjido/jido_chat/issues/20"
exit 1
fi
# ─── Parse issue URL ───────────────────────────────────────────────────────
if [[ "$ISSUE_URL" =~ github\.com/([^/]+)/([^/]+)/issues/([0-9]+) ]]; then
OWNER="${BASH_REMATCH[1]}"
REPO="${BASH_REMATCH[2]}"
ISSUE_NUMBER="${BASH_REMATCH[3]}"
else
fail "Invalid GitHub issue URL: $ISSUE_URL"
exit 1
fi
[[ -z "$SPRITE_NAME" ]] && SPRITE_NAME="jido-test-${RUN_ID}"
info "Provider: ${PROVIDER_LABEL}"
info "Issue: ${OWNER}/${REPO}#${ISSUE_NUMBER}"
info "Run ID: ${RUN_ID}"
info "Sprite: ${SPRITE_NAME}"
# ─── Helpers ────────────────────────────────────────────────────────────────
shorten() {
local text="$1"
local max_len="${2:-160}"
text="$(echo "$text" | tr '\r\n' ' ' | sed -E 's/[[:space:]]+/ /g')"
if [[ ${#text} -le $max_len ]]; then
printf '%s' "$text"
else
printf '%s…' "${text:0:max_len}"
fi
}
shell_quote() {
printf "'%s'" "$(printf "%s" "$1" | sed "s/'/'\\\\''/g")"
}
# ─── Helper: run command in sprite ─────────────────────────────────────────
sprite_exec() {
local cmd="$1"
local dir="${2:-}"
local env_pairs=""
local val=""
for var in \
GH_TOKEN GITHUB_TOKEN \
ANTHROPIC_BASE_URL ANTHROPIC_AUTH_TOKEN ANTHROPIC_API_KEY CLAUDE_CODE_API_KEY \
ANTHROPIC_DEFAULT_HAIKU_MODEL ANTHROPIC_DEFAULT_SONNET_MODEL ANTHROPIC_DEFAULT_OPUS_MODEL \
AMP_API_KEY AMP_URL \
OPENAI_API_KEY \
GEMINI_API_KEY GOOGLE_API_KEY GOOGLE_GENAI_USE_VERTEXAI GOOGLE_GENAI_USE_GCA GOOGLE_CLOUD_PROJECT GOOGLE_CLOUD_LOCATION; do
val="${!var:-}"
if [[ -n "$val" ]]; then
[[ -n "$env_pairs" ]] && env_pairs="${env_pairs},"
env_pairs="${env_pairs}${var}=${val}"
fi
done
[[ -n "$env_pairs" ]] && env_pairs="${env_pairs},"
env_pairs="${env_pairs}GH_PROMPT_DISABLED=1,GIT_TERMINAL_PROMPT=0,CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1,API_TIMEOUT_MS=3000000"
local path_bootstrap='export PATH="$PATH:$HOME/.local/bin:$HOME/.npm/bin:$HOME/.npm-global/bin"; if command -v npm >/dev/null 2>&1; then _npm_prefix="$(npm config get prefix 2>/dev/null || true)"; if [ -n "$_npm_prefix" ] && [ "$_npm_prefix" != "undefined" ]; then PATH="$PATH:${_npm_prefix}/bin"; fi; fi; export PATH'
local wrapped_cmd="${path_bootstrap}; ${cmd}"
local -a exec_args=(sprite exec)
# shellcheck disable=SC2206
[[ -n "$ORG_FLAG" ]] && exec_args+=($ORG_FLAG)
exec_args+=(-s "$SPRITE_NAME")
[[ -n "$dir" ]] && exec_args+=(-dir "$dir")
exec_args+=(-env "$env_pairs" -- sh -c "$wrapped_cmd")
[[ "$VERBOSE" == true ]] && info "EXEC: $cmd" >&2
"${exec_args[@]}" 2>&1
}
provider_binary() {
case "$PROVIDER" in
claude|amp|codex|gemini) echo "$PROVIDER" ;;
*) echo "$PROVIDER" ;;
esac
}
provider_npm_package() {
case "$PROVIDER" in
amp) echo "@sourcegraph/amp" ;;
codex) echo "@openai/codex" ;;
gemini) echo "@google/gemini-cli" ;;
*) echo "" ;;
esac
}
provider_step_9_title() {
case "$PROVIDER" in
claude) echo "Step 9: Provision Claude CLI for stream-JSON (non-interactive)" ;;
amp) echo "Step 9: Provision Amp CLI for stream-JSON (non-interactive)" ;;
codex) echo "Step 9: Provision Codex CLI for JSON streaming (non-interactive)" ;;
gemini) echo "Step 9: Provision Gemini CLI for stream-JSON (non-interactive)" ;;
*) echo "Step 9: Provision Provider CLI" ;;
esac
}
provider_step_10_title() {
case "$PROVIDER" in
claude) echo "Step 10: Claude Code Change (stream-JSON)" ;;
amp) echo "Step 10: Amp Code Change (stream-JSON)" ;;
codex) echo "Step 10: Codex Code Change (JSONL)" ;;
gemini) echo "Step 10: Gemini Code Change (stream-JSON)" ;;
*) echo "Step 10: Provider Code Change" ;;
esac
}
require_provider_env() {
case "$PROVIDER" in
claude)
if [[ -z "${ANTHROPIC_AUTH_TOKEN:-}" && -z "${ANTHROPIC_API_KEY:-}" ]]; then
fail "Claude requires ANTHROPIC_AUTH_TOKEN (or ANTHROPIC_API_KEY)"
exit 1
fi
[[ -n "${ANTHROPIC_BASE_URL:-}" ]] || warn "ANTHROPIC_BASE_URL not set; defaulting to https://api.z.ai/api/anthropic"
;;
amp)
if [[ -z "${AMP_API_KEY:-}" ]]; then
fail "Amp requires AMP_API_KEY"
exit 1
fi
;;
codex)
if [[ -z "${OPENAI_API_KEY:-}" ]]; then
fail "Codex requires OPENAI_API_KEY"
exit 1
fi
;;
gemini)
if [[ -z "${GEMINI_API_KEY:-}" && -n "${GOOGLE_API_KEY:-}" ]]; then
GEMINI_API_KEY="${GOOGLE_API_KEY}"
export GEMINI_API_KEY
warn "GEMINI_API_KEY not set; mirroring GOOGLE_API_KEY for Gemini CLI compatibility"
fi
if [[ -z "${GEMINI_API_KEY:-}" && "${GOOGLE_GENAI_USE_VERTEXAI:-}" != "true" && "${GOOGLE_GENAI_USE_GCA:-}" != "true" ]]; then
fail "Gemini requires GEMINI_API_KEY (recommended), or GOOGLE_GENAI_USE_VERTEXAI=true, or GOOGLE_GENAI_USE_GCA=true"
exit 1
fi
if [[ "${GOOGLE_GENAI_USE_VERTEXAI:-}" == "true" ]]; then
if [[ -z "${GOOGLE_API_KEY:-}" && ( -z "${GOOGLE_CLOUD_PROJECT:-}" || -z "${GOOGLE_CLOUD_LOCATION:-}" ) ]]; then
fail "Vertex mode requires GOOGLE_API_KEY or GOOGLE_CLOUD_PROJECT + GOOGLE_CLOUD_LOCATION"
exit 1
fi
fi
;;
*)
fail "Unknown provider: $PROVIDER"
exit 1
;;
esac
}
ensure_provider_cli_in_sprite() {
local bin
local npm_pkg
local cli_check
bin="$(provider_binary)"
npm_pkg="$(provider_npm_package)"
cli_check=$(sprite_exec "command -v ${bin} >/dev/null 2>&1 && ${bin} --version 2>&1 | head -1 || echo MISSING") || true
if echo "$cli_check" | grep -q "MISSING"; then
if [[ -z "$npm_pkg" ]]; then
fail "${bin} CLI not found inside sprite"
exit 1
fi
warn "${bin} not found inside sprite; attempting install via npm (${npm_pkg})"
sprite_exec "command -v npm >/dev/null 2>&1" || {
fail "npm is required inside sprite to install ${bin} (${npm_pkg})"
exit 1
}
INSTALL_OUTPUT=$(sprite_exec "npm install -g ${npm_pkg} 2>&1") || {
fail "Failed to install ${bin}: $(shorten "$INSTALL_OUTPUT" 300)"
exit 1
}
ok "Installed ${bin} via npm"
fi
cli_check=$(sprite_exec "command -v ${bin} >/dev/null 2>&1 && ${bin} --version 2>&1 | head -1 || echo MISSING")
if echo "$cli_check" | grep -q "MISSING"; then
fail "${bin} CLI not found inside sprite after install attempt"
exit 1
fi
ok "${bin}: $cli_check"
case "$PROVIDER" in
claude)
local help_output
help_output=$(sprite_exec "claude --help 2>&1")
echo "$help_output" | grep -q -- "--output-format" || {
fail "claude --help missing --output-format"; exit 1
}
echo "$help_output" | grep -q -- "stream-json" || {
fail "claude --help missing stream-json support"; exit 1
}
ok "Claude stream-json flags detected"
;;
amp)
local amp_help
amp_help=$(sprite_exec "amp --help 2>&1")
echo "$amp_help" | grep -q -- "--execute" || {
fail "amp --help missing --execute"; exit 1
}
echo "$amp_help" | grep -q -- "--stream-json" || {
fail "amp --help missing --stream-json"; exit 1
}
ok "Amp stream-json flags detected"
;;
codex)
local codex_help
codex_help=$(sprite_exec "codex exec --help 2>&1")
echo "$codex_help" | grep -q -- "--json" || {
fail "codex exec --help missing --json"; exit 1
}
ok "Codex JSONL flag detected"
;;
gemini)
local gemini_help
gemini_help=$(sprite_exec "gemini --help 2>&1")
echo "$gemini_help" | grep -q -- "--output-format" || {
fail "gemini --help missing --output-format"; exit 1
}
echo "$gemini_help" | grep -q -- "stream-json" || {
fail "gemini --help missing stream-json"; exit 1
}
ok "Gemini stream-json flags detected"
;;
esac
}
configure_claude_runtime() {
local zai_check
local provision_check
zai_check=$(sprite_exec 'if [ -n "${ANTHROPIC_BASE_URL:-}" ] && { [ -n "${ANTHROPIC_AUTH_TOKEN:-}" ] || [ -n "${ANTHROPIC_API_KEY:-}" ]; }; then echo present; else echo missing; fi')
if echo "$zai_check" | grep -q "missing"; then
fail "Claude requires ANTHROPIC_BASE_URL and token env visible in sprite"
exit 1
fi
ok "Claude auth config visible inside sprite"
info "Provisioning Claude config files for non-interactive usage..."
sprite_exec 'node -e '\''
const fs = require("fs");
const path = require("path");
const filePath = path.join(process.env.HOME, ".claude.json");
let existing = {};
try { existing = JSON.parse(fs.readFileSync(filePath, "utf-8")); } catch (e) {}
const config = {
...existing,
hasCompletedOnboarding: true,
hasTrustDialogAccepted: true,
hasTrustDialogHooksAccepted: true,
numStartups: (existing.numStartups || 0) + 1
};
fs.writeFileSync(filePath, JSON.stringify(config, null, 2), "utf-8");
console.log("OK");
'\''' || { fail "Failed to write ~/.claude.json"; exit 1; }
ok "~/.claude.json written"
sprite_exec 'mkdir -p ~/.claude && echo "2025-01-01" > ~/.claude/.acceptedTos' || {
fail "Failed to write ~/.claude/.acceptedTos"
exit 1
}
ok "~/.claude/.acceptedTos written"
sprite_exec 'node -e '\''
const fs = require("fs");
const path = require("path");
const settingsPath = path.join(process.env.HOME, ".claude", "settings.json");
let settings = {};
try {
settings = JSON.parse(fs.readFileSync(settingsPath, "utf-8"));
} catch (e) {}
settings.env = {
...(settings.env || {}),
ANTHROPIC_AUTH_TOKEN: process.env.ANTHROPIC_AUTH_TOKEN || process.env.ANTHROPIC_API_KEY || "",
ANTHROPIC_BASE_URL: process.env.ANTHROPIC_BASE_URL || "https://api.z.ai/api/anthropic",
API_TIMEOUT_MS: "3000000",
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1"
};
fs.mkdirSync(path.dirname(settingsPath), { recursive: true });
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), "utf-8");
console.log("OK");
'\''' || { fail "Failed to update ~/.claude/settings.json"; exit 1; }
ok "~/.claude/settings.json updated"
provision_check=$(sprite_exec '
OK=true
test -f ~/.claude.json || { echo "MISSING: ~/.claude.json"; OK=false; }
test -f ~/.claude/.acceptedTos || { echo "MISSING: ~/.claude/.acceptedTos"; OK=false; }
test -f ~/.claude/settings.json || { echo "MISSING: ~/.claude/settings.json"; OK=false; }
$OK && echo "ALL_PRESENT"
')
if ! echo "$provision_check" | grep -q "ALL_PRESENT"; then
fail "Claude provisioning incomplete: $provision_check"
exit 1
fi
ok "All Claude runtime files present"
}
bootstrap_codex_auth() {
if sprite_exec "codex login status >/dev/null 2>&1"; then
ok "Codex auth already configured inside sprite"
return
fi
info "Bootstrapping Codex auth from OPENAI_API_KEY..."
sprite_exec 'if [ -z "${OPENAI_API_KEY:-}" ]; then echo "MISSING_OPENAI_API_KEY"; exit 1; fi; printenv OPENAI_API_KEY | codex login --with-api-key >/dev/null 2>&1' || {
fail "codex login --with-api-key failed"
exit 1
}
sprite_exec "codex login status >/dev/null 2>&1" || {
fail "Codex login status still failing after bootstrap"
exit 1
}
ok "Codex auth configured inside sprite"
}
prepare_provider_runtime() {
case "$PROVIDER" in
claude)
configure_claude_runtime
;;
codex)
bootstrap_codex_auth
;;
amp|gemini)
ok "No additional ${PROVIDER_LABEL} runtime bootstrap required"
;;
esac
}
write_sprite_prompt_file() {
local prompt="$1"
local prompt_file="$2"
local dir="$3"
local prompt_q
prompt_q="$(shell_quote "$prompt_file")"
sprite_exec "cat > ${prompt_q} << 'JIDO_PROMPT_EOF'
${prompt}
JIDO_PROMPT_EOF" "$dir" >/dev/null
}
provider_smoke_command() {
local prompt_file="$1"
local pf_q
pf_q="$(shell_quote "$prompt_file")"
case "$PROVIDER" in
claude)
echo "if command -v timeout >/dev/null 2>&1; then timeout 120 claude -p --output-format stream-json --include-partial-messages --no-session-persistence --verbose --dangerously-skip-permissions --tools '' \\\"\$(cat ${pf_q})\\\"; else claude -p --output-format stream-json --include-partial-messages --no-session-persistence --verbose --dangerously-skip-permissions --tools '' \\\"\$(cat ${pf_q})\\\"; fi"
;;
amp)
echo "if command -v timeout >/dev/null 2>&1; then timeout 120 amp -x --stream-json --dangerously-allow-all --no-color < ${pf_q}; else amp -x --stream-json --dangerously-allow-all --no-color < ${pf_q}; fi"
;;
codex)
echo "if command -v timeout >/dev/null 2>&1; then timeout 120 codex exec --json --full-auto - < ${pf_q}; else codex exec --json --full-auto - < ${pf_q}; fi"
;;
gemini)
echo "if command -v timeout >/dev/null 2>&1; then timeout 120 gemini --output-format stream-json \\\"\$(cat ${pf_q})\\\"; else gemini --output-format stream-json \\\"\$(cat ${pf_q})\\\"; fi"
;;
*)
fail "Unknown provider in smoke command: $PROVIDER"
exit 1
;;
esac
}
provider_change_command() {
local prompt_file="$1"
local pf_q
pf_q="$(shell_quote "$prompt_file")"
case "$PROVIDER" in
claude)
echo "if command -v timeout >/dev/null 2>&1; then timeout 180 claude -p --output-format stream-json --include-partial-messages --no-session-persistence --verbose --dangerously-skip-permissions \\\"\$(cat ${pf_q})\\\"; else claude -p --output-format stream-json --include-partial-messages --no-session-persistence --verbose --dangerously-skip-permissions \\\"\$(cat ${pf_q})\\\"; fi"
;;
amp)
echo "if command -v timeout >/dev/null 2>&1; then timeout 180 amp -x --stream-json --dangerously-allow-all --no-color < ${pf_q}; else amp -x --stream-json --dangerously-allow-all --no-color < ${pf_q}; fi"
;;
codex)
echo "if command -v timeout >/dev/null 2>&1; then timeout 180 codex exec --json --dangerously-bypass-approvals-and-sandbox - < ${pf_q}; else codex exec --json --dangerously-bypass-approvals-and-sandbox - < ${pf_q}; fi"
;;
gemini)
echo "if command -v timeout >/dev/null 2>&1; then timeout 180 gemini --output-format stream-json --approval-mode yolo \\\"\$(cat ${pf_q})\\\"; else gemini --output-format stream-json --approval-mode yolo \\\"\$(cat ${pf_q})\\\"; fi"
;;
*)
fail "Unknown provider in change command: $PROVIDER"
exit 1
;;
esac
}
provider_smoke_prompt() {
case "$PROVIDER" in
claude) echo "Return exactly one line: JIDO_${PROVIDER_LABEL^^}_SMOKE_OK. Do not use tools. Do not ask questions." ;;
amp) echo "Return exactly one line: JIDO_AMP_SMOKE_OK. Do not ask questions." ;;
codex) echo "Return exactly one line: JIDO_CODEX_SMOKE_OK. Do not ask questions." ;;
gemini) echo "Return exactly one line: JIDO_GEMINI_SMOKE_OK. Do not ask questions." ;;
*) echo "Return exactly one line: JIDO_SMOKE_OK. Do not ask questions." ;;
esac
}
provider_change_prompt() {
local timestamp="$1"
cat <<PROMPT
Create a file called .jido-smoke-test.md with exactly this content and nothing else:
# Jido PrBot Smoke Test (${PROVIDER_LABEL})
Automated smoke test confirming the full PrBot pipeline works end-to-end.
- Provider: ${PROVIDER_LABEL}
- Run ID: ${RUN_ID}
- Issue: ${OWNER}/${REPO}#${ISSUE_NUMBER}
- Timestamp: ${timestamp}
This file can be safely deleted.
Do not ask follow-up questions.
Do not wait for confirmation.
If any detail is ambiguous, choose a reasonable default and proceed.
PROMPT
}
extract_result_text_from_artifact() {
local artifact="$1"
jq -Rr '
fromjson? |
if . == null then empty
elif (.type == "result" and (.result | type == "string")) then .result
elif (.type == "assistant" and (.message.content | type == "array")) then ([.message.content[]? | select(.type == "text") | .text] | join(""))
elif (.output_text | type == "string") then .output_text
elif (.text | type == "string") then .text
elif (.delta | type == "string") then .delta
elif (.message | type == "string") then .message
elif (.event.delta.text | type == "string") then .event.delta.text
else empty
end
' "$artifact" 2>/dev/null | awk 'NF {last=$0} END {print last}'
}
stream_artifact_indicates_success() {
local artifact="$1"
case "$PROVIDER" in
amp|claude|gemini)
jq -Rre '
fromjson? |
select(
(.type == "result" and ((.subtype // "") == "success" or (.is_error == false)))
)
' "$artifact" >/dev/null 2>&1
;;
codex)
jq -Rre '
fromjson? |
select(.type == "turn.completed")
' "$artifact" >/dev/null 2>&1
;;
*)
jq -Rre '
fromjson? |
select(.type == "result" and ((.subtype // "") == "success"))
' "$artifact" >/dev/null 2>&1
;;
esac
}
print_stream_progress_line() {
local phase="$1"
local line="$2"
local parsed=""
local type=""
local nested=""
local text=""
parsed=$(printf '%s\n' "$line" | jq -Rr '
fromjson? |
if . == null then empty
else
[
(.type // .event.type // .name // .kind // "event"),
(.event.type // ""),
(
if (.event.delta.text? // "") != "" then .event.delta.text
elif (.message.content? | type == "array") then ([.message.content[]? | select(.type == "text") | .text] | join(""))
elif (.result? | type == "string") then .result
elif (.text? | type == "string") then .text
elif (.delta? | type == "string") then .delta
elif (.message? | type == "string") then .message
elif (.output_text? | type == "string") then .output_text
else ""
end
)
] | @tsv
end
' 2>/dev/null)
if [[ -z "$parsed" ]]; then
if [[ "$VERBOSE" == true ]]; then
info "[${PROVIDER_LABEL}][${phase}] non-json $(shorten "$line" 140)"
fi
return 0
fi
IFS=$'\t' read -r type nested text <<<"$parsed"
text="$(shorten "$text" 160)"
case "$type" in
stream_event|assistant|result|session_started|session_completed|session_failed|error|warning|tool_call|tool_result|output_text_delta|output_text_final|thinking_delta)
if [[ -n "$text" ]]; then
info "[${PROVIDER_LABEL}][${phase}] ${type}${nested:+:${nested}} ${text}"
else
info "[${PROVIDER_LABEL}][${phase}] ${type}${nested:+:${nested}}"
fi
;;
*)
if [[ "$VERBOSE" == true ]]; then
if [[ -n "$text" ]]; then
info "[${PROVIDER_LABEL}][${phase}] ${type}${nested:+:${nested}} ${text}"
else
info "[${PROVIDER_LABEL}][${phase}] ${type}${nested:+:${nested}}"
fi
fi
;;
esac
if [[ "$VERBOSE" == true ]]; then
echo "[RAW][${PROVIDER}/${phase}] $line"
fi
}
run_stream_json() {
local phase="$1"
local cmd="$2"
local dir="${3:-}"
local artifact="/tmp/jido_${PROVIDER}_${RUN_ID}_${phase}.jsonl"
local max_attempts=2
local attempt=1
ARTIFACTS+=("$artifact")
while (( attempt <= max_attempts )); do
: > "$artifact"
if (( attempt == 1 )); then
info "Streaming ${PROVIDER_LABEL} ${phase} JSONL -> ${artifact}"
else
warn "Retrying ${PROVIDER_LABEL} ${phase} stream (${attempt}/${max_attempts})"
fi
local rc
set +e
if [[ -n "$dir" ]]; then
sprite_exec "$cmd" "$dir" | tee "$artifact" | while IFS= read -r line; do
print_stream_progress_line "$phase" "$line"
done
else
sprite_exec "$cmd" | tee "$artifact" | while IFS= read -r line; do
print_stream_progress_line "$phase" "$line"
done
fi
rc=${PIPESTATUS[0]}
set -e
LAST_STREAM_ARTIFACT="$artifact"
LAST_STREAM_EVENT_COUNT="$(wc -l < "$artifact" | tr -d '[:space:]')"
LAST_STREAM_RESULT_TEXT="$(extract_result_text_from_artifact "$artifact")"
if [[ "$rc" -ne 0 ]] && stream_artifact_indicates_success "$artifact"; then
warn "${PROVIDER_LABEL} ${phase} exited rc=${rc} but stream indicates success; continuing"
rc=0
fi
if [[ "$rc" -eq 0 ]]; then
ok "${PROVIDER_LABEL} ${phase} stream completed (${LAST_STREAM_EVENT_COUNT} lines)"
if [[ -n "$LAST_STREAM_RESULT_TEXT" ]]; then
info "${PROVIDER_LABEL} ${phase} result: $(shorten "$LAST_STREAM_RESULT_TEXT" 180)"
fi
return 0
fi
if (( attempt < max_attempts )) && grep -qi "connection closed" "$artifact"; then
warn "${PROVIDER_LABEL} ${phase} stream ended with transient connection error; retrying..."
sleep 2
attempt=$((attempt + 1))
continue
fi
fail "${PROVIDER_LABEL} ${phase} command failed with rc=${rc}"
warn "Artifact: ${artifact}"
tail -n 30 "$artifact" >&2 || true
return "$rc"
done
return 1
}
# ─── Cleanup trap ───────────────────────────────────────────────────────────
SPRITE_CREATED=false
BRANCH_NAME=""
PR_URL=""
TIMESTAMP=""
INTERRUPTED=false
interrupt_handler() {
local sig="${1:-INT}"
trap - INT TERM
INTERRUPTED=true
echo ""
warn "Received ${sig}; stopping current run..."
pkill -TERM -P $$ 2>/dev/null || true
sleep 1
pkill -KILL -P $$ 2>/dev/null || true
exit 130
}
cleanup() {
if [[ "$KEEP_SPRITE" == true ]]; then
warn "Keeping sprite '${SPRITE_NAME}' alive (--keep-sprite)"
return
fi
if [[ "$SPRITE_CREATED" == true ]]; then
step "Teardown"
info "Destroying sprite '${SPRITE_NAME}'..."
local -a destroy_args=(sprite destroy)
# shellcheck disable=SC2206
[[ -n "$ORG_FLAG" ]] && destroy_args+=($ORG_FLAG)
destroy_args+=(-s "$SPRITE_NAME" --force)
"${destroy_args[@]}" 2>&1 || warn "Sprite destroy failed (may already be gone)"
ok "Sprite destroyed"
fi
}
trap 'interrupt_handler INT' INT
trap 'interrupt_handler TERM' TERM
trap cleanup EXIT
# ═══════════════════════════════════════════════════════════════════════════
# STEP 1: Validate host environment
# ═══════════════════════════════════════════════════════════════════════════
step "Step 1: Validate Host Environment"
command -v sprite &>/dev/null || { fail "sprite CLI not found in PATH"; exit 1; }
ok "sprite CLI found: $(which sprite)"
command -v gh &>/dev/null && ok "gh CLI found on host: $(which gh)" \
|| warn "gh CLI not found on host (will check inside sprite)"
command -v git &>/dev/null && ok "git CLI found on host: $(which git)" \
|| warn "git CLI not found on host"
HOST_PROVIDER_BIN="$(provider_binary)"
if command -v "$HOST_PROVIDER_BIN" &>/dev/null; then
ok "${PROVIDER_LABEL} CLI found on host: $(which "$HOST_PROVIDER_BIN")"
else
warn "${PROVIDER_LABEL} CLI not found on host (script only requires it inside sprite)"
fi
[[ -n "${SPRITES_TOKEN:-}" ]] && ok "SPRITES_TOKEN is set" \
|| warn "SPRITES_TOKEN not set — assuming 'sprite login' session exists"
GH_TOKEN_VALUE="${GH_TOKEN:-${GITHUB_TOKEN:-}}"
if [[ -z "$GH_TOKEN_VALUE" ]]; then
fail "Neither GH_TOKEN nor GITHUB_TOKEN is set"
fail "Set one of these to a GitHub PAT with repo + issues scope"
exit 1
fi
ok "GitHub token is set (${#GH_TOKEN_VALUE} chars)"
require_provider_env
ok "${PROVIDER_LABEL} env contract validated"
info "Validating issue exists via gh on host..."
if command -v gh &>/dev/null; then
ISSUE_TITLE=$(GH_TOKEN="$GH_TOKEN_VALUE" gh issue view "$ISSUE_NUMBER" \
--repo "${OWNER}/${REPO}" --json title --jq .title 2>&1) || {
fail "Could not fetch issue from host: $ISSUE_TITLE"; exit 1
}
ok "Issue found: \"${ISSUE_TITLE}\""
else
warn "Skipping host-side issue validation (no gh on host)"
fi
# ═══════════════════════════════════════════════════════════════════════════
# STEP 2: Create Sprite
# ═══════════════════════════════════════════════════════════════════════════
step "Step 2: Provision Sprite"
info "Creating sprite '${SPRITE_NAME}'..."
CREATE_OUTPUT=$(sprite create $ORG_FLAG "$SPRITE_NAME" -skip-console 2>&1) || {
fail "Failed to create sprite: $CREATE_OUTPUT"; exit 1
}
SPRITE_CREATED=true
ok "Sprite '${SPRITE_NAME}' created"
[[ "$VERBOSE" == true ]] && echo "$CREATE_OUTPUT"
info "Verifying sprite appears in list..."
LIST_OUTPUT=$(sprite list $ORG_FLAG 2>&1)
echo "$LIST_OUTPUT" | grep -q "$SPRITE_NAME" && ok "Sprite confirmed in list" \
|| warn "Sprite not found in list output (may be org-scoped)"
WORKSPACE_DIR="/work/jido-test-${RUN_ID}"
info "Creating workspace: ${WORKSPACE_DIR}"
sprite_exec "mkdir -p ${WORKSPACE_DIR}" || { fail "Failed to create workspace directory"; exit 1; }
ok "Workspace created"
# ═══════════════════════════════════════════════════════════════════════════
# STEP 3: Validate runtime inside sprite
# ═══════════════════════════════════════════════════════════════════════════
step "Step 3: Validate Runtime Inside Sprite"
GIT_CHECK=$(sprite_exec "command -v git >/dev/null 2>&1 && git --version || echo MISSING")
echo "$GIT_CHECK" | grep -q "MISSING" && { fail "git not found inside sprite"; exit 1; }
ok "git: $GIT_CHECK"
GH_CHECK=$(sprite_exec "command -v gh >/dev/null 2>&1 && gh --version | head -1 || echo MISSING")
echo "$GH_CHECK" | grep -q "MISSING" && { fail "gh CLI not found inside sprite"; exit 1; }
ok "gh: $GH_CHECK"
# ═══════════════════════════════════════════════════════════════════════════
# STEP 4: Validate GitHub auth inside sprite
# ═══════════════════════════════════════════════════════════════════════════
step "Step 4: Validate GitHub Auth Inside Sprite"
TOKEN_CHECK=$(sprite_exec 'if [ -n "${GH_TOKEN:-}" ] || [ -n "${GITHUB_TOKEN:-}" ]; then echo present; else echo missing; fi')
echo "$TOKEN_CHECK" | grep -q "missing" && {
fail "GitHub token not visible inside sprite"; exit 1
}
ok "GitHub token visible inside sprite"
AUTH_CHECK=$(sprite_exec "gh auth status -h github.com 2>&1 || gh auth status 2>&1" 2>&1)
AUTH_RC=$?
if [[ $AUTH_RC -eq 0 ]] || echo "$AUTH_CHECK" | grep -qi "logged in"; then
ok "gh auth status: authenticated"
else
warn "gh auth status returned non-zero (rc=$AUTH_RC)"
info "Testing direct gh API call..."
API_TEST=$(sprite_exec "gh api user --jq .login 2>&1")
if [[ -n "$API_TEST" ]] && ! echo "$API_TEST" | grep -qi "error\|fail"; then
ok "gh API works — authenticated as: $API_TEST"
else
fail "gh authentication not working inside sprite"; exit 1
fi
fi
# ═══════════════════════════════════════════════════════════════════════════
# STEP 5: Fetch issue details inside sprite
# ═══════════════════════════════════════════════════════════════════════════
step "Step 5: Fetch Issue Details Inside Sprite"
SPRITE_ISSUE_TITLE=$(sprite_exec "gh issue view ${ISSUE_NUMBER} --repo ${OWNER}/${REPO} --json title --jq .title") || {
fail "Failed to fetch issue title inside sprite"; exit 1
}
SPRITE_ISSUE_STATE=$(sprite_exec "gh issue view ${ISSUE_NUMBER} --repo ${OWNER}/${REPO} --json state --jq .state") || {
fail "Failed to fetch issue state inside sprite"; exit 1
}
ok "Issue title: \"${SPRITE_ISSUE_TITLE}\""
ok "Issue state: ${SPRITE_ISSUE_STATE}"
if [[ "$VERBOSE" == true ]]; then
sprite_exec "gh issue view ${ISSUE_NUMBER} --repo ${OWNER}/${REPO} --json title,body,labels,author,state,url" || true
fi
# ═══════════════════════════════════════════════════════════════════════════
# STEP 6: Clone repo inside sprite
# ═══════════════════════════════════════════════════════════════════════════
step "Step 6: Clone Repository Inside Sprite"
REPO_DIR="${WORKSPACE_DIR}/${REPO}"
info "Cloning ${OWNER}/${REPO} into ${REPO_DIR}..."
sprite_exec "git clone https://github.com/${OWNER}/${REPO}.git ${REPO_DIR}" >/dev/null || {
fail "git clone failed"; exit 1
}
ok "Repository cloned"
VERIFY_CLONE=$(sprite_exec "ls ${REPO_DIR}/.git/HEAD && echo CLONE_OK || echo CLONE_FAIL")
echo "$VERIFY_CLONE" | grep -q "CLONE_OK" && ok "Clone verified (.git/HEAD exists)" \
|| { fail "Clone verification failed"; exit 1; }
# ═══════════════════════════════════════════════════════════════════════════
# STEP 7: Configure git identity inside sprite
# ═══════════════════════════════════════════════════════════════════════════
step "Step 7: Configure Git Identity & Credentials"
sprite_exec "git config user.email 'jido-bot@agentjido.com' && git config user.name 'Jido Bot'" "$REPO_DIR" || {
fail "Failed to configure git identity"; exit 1
}
ok "Git identity configured"
sprite_exec "gh auth setup-git" "$REPO_DIR" || {
fail "Failed to configure gh as git credential helper"; exit 1
}
ok "gh auth setup-git configured"
# ═══════════════════════════════════════════════════════════════════════════
# STEP 8: Resolve base branch & create PR branch
# ═══════════════════════════════════════════════════════════════════════════
step "Step 8: Create PR Branch"
BASE_BRANCH=$(sprite_exec "gh repo view ${OWNER}/${REPO} --json defaultBranchRef -q .defaultBranchRef.name" "$REPO_DIR") || {
fail "Failed to resolve default branch"; exit 1
}
BASE_BRANCH=$(echo "$BASE_BRANCH" | tr -d '[:space:]')
ok "Base branch: ${BASE_BRANCH}"
info "Syncing base branch..."
sprite_exec "git fetch origin ${BASE_BRANCH} && git checkout ${BASE_BRANCH} && git pull --ff-only origin ${BASE_BRANCH}" "$REPO_DIR" >/dev/null || {
fail "Failed to sync base branch"; exit 1
}
ok "Base branch synced"
BRANCH_NAME="${BRANCH_PREFIX}/issue-${ISSUE_NUMBER}-${RUN_ID}"
BRANCH_EXISTS=$(sprite_exec "
if git show-ref --verify --quiet refs/heads/${BRANCH_NAME}; then
echo exists
elif git ls-remote --exit-code --heads origin ${BRANCH_NAME} >/dev/null 2>&1; then
echo exists
else
echo missing
fi" "$REPO_DIR")
if echo "$BRANCH_EXISTS" | grep -q "exists"; then
fail "Branch ${BRANCH_NAME} already exists"; exit 1
fi
sprite_exec "git checkout -b ${BRANCH_NAME}" "$REPO_DIR" >/dev/null || {
fail "Failed to create branch ${BRANCH_NAME}"; exit 1
}
ok "Branch created: ${BRANCH_NAME}"
BASE_SHA=$(sprite_exec "git rev-parse ${BASE_BRANCH}" "$REPO_DIR") || {
fail "Failed to get base SHA"; exit 1
}
BASE_SHA=$(echo "$BASE_SHA" | tr -d '[:space:]')
ok "Base SHA: ${BASE_SHA:0:12}"
# ═══════════════════════════════════════════════════════════════════════════
# STEP 9: Provider provisioning + stream-json validation
# ═══════════════════════════════════════════════════════════════════════════
step "$(provider_step_9_title)"
ensure_provider_cli_in_sprite
prepare_provider_runtime
SMOKE_PROMPT_FILE="/tmp/jido_${PROVIDER}_smoke_prompt_${RUN_ID}.txt"
SMOKE_PROMPT="$(provider_smoke_prompt)"
write_sprite_prompt_file "$SMOKE_PROMPT" "$SMOKE_PROMPT_FILE" "$REPO_DIR"
info "Running ${PROVIDER_LABEL} smoke prompt in stream mode..."
SMOKE_CMD="$(provider_smoke_command "$SMOKE_PROMPT_FILE")"
run_stream_json "smoke" "$SMOKE_CMD" "$REPO_DIR" || {
fail "${PROVIDER_LABEL} smoke command failed"
exit 1
}
if [[ -n "$LAST_STREAM_RESULT_TEXT" ]]; then
ok "${PROVIDER_LABEL} smoke returned text"
else
warn "${PROVIDER_LABEL} smoke returned no final text (events captured: ${LAST_STREAM_EVENT_COUNT})"
fi
# ═══════════════════════════════════════════════════════════════════════════
# STEP 10: Provider performs code change
# ═══════════════════════════════════════════════════════════════════════════
step "$(provider_step_10_title)"
TIMESTAMP=$(date -u '+%Y-%m-%dT%H:%M:%SZ')
CHANGE_PROMPT_FILE="/tmp/jido_${PROVIDER}_change_prompt_${RUN_ID}.txt"
CHANGE_PROMPT="$(provider_change_prompt "$TIMESTAMP")"
write_sprite_prompt_file "$CHANGE_PROMPT" "$CHANGE_PROMPT_FILE" "$REPO_DIR"
info "Running ${PROVIDER_LABEL} code-change prompt in stream mode..."
CHANGE_CMD="$(provider_change_command "$CHANGE_PROMPT_FILE")"
run_stream_json "change" "$CHANGE_CMD" "$REPO_DIR" || {
fail "${PROVIDER_LABEL} code-change command failed"
exit 1
}
VERIFY_FILE=$(sprite_exec "test -f .jido-smoke-test.md && wc -c < .jido-smoke-test.md || echo 0" "$REPO_DIR")
VERIFY_FILE=$(echo "$VERIFY_FILE" | tr -d '[:space:]')
if [[ "$VERIFY_FILE" -gt 0 ]] 2>/dev/null; then
ok "${PROVIDER_LABEL} created .jido-smoke-test.md (${VERIFY_FILE} bytes)"
else
fail "${PROVIDER_LABEL} did not create the expected file"
sprite_exec "ls -la" "$REPO_DIR" >&2
exit 1
fi
# ═══════════════════════════════════════════════════════════════════════════
# STEP 11: Commit
# ═══════════════════════════════════════════════════════════════════════════
step "Step 11: Commit Changes"
DIRTY_CHECK=$(sprite_exec "git status --porcelain" "$REPO_DIR")
if [[ -z "$DIRTY_CHECK" ]]; then
fail "Working tree is clean — no changes to commit"; exit 1
fi
ok "Working tree has changes"
[[ "$VERBOSE" == true ]] && echo "$DIRTY_CHECK"
sprite_exec "git add -A && git commit -m 'test(smoke): sprite ${PROVIDER_LABEL} PrBot plumbing test #${ISSUE_NUMBER}'" "$REPO_DIR" >/dev/null || {
fail "git commit failed"; exit 1
}
ok "Changes committed"
COMMIT_COUNT=$(sprite_exec "git rev-list --count ${BASE_SHA}..HEAD" "$REPO_DIR")
COMMIT_COUNT=$(echo "$COMMIT_COUNT" | tr -d '[:space:]')
ok "Commits since base: ${COMMIT_COUNT}"
COMMIT_SHA=$(sprite_exec "git rev-parse HEAD" "$REPO_DIR")
COMMIT_SHA=$(echo "$COMMIT_SHA" | tr -d '[:space:]')
ok "Commit SHA: ${COMMIT_SHA:0:12}"
# ═══════════════════════════════════════════════════════════════════════════
# STEP 12: Push branch
# ═══════════════════════════════════════════════════════════════════════════
step "Step 12: Push Branch"
ORIGIN_URL=$(sprite_exec "git remote get-url origin" "$REPO_DIR")
if ! echo "$ORIGIN_URL" | grep -q "${OWNER}/${REPO}"; then
fail "Remote origin mismatch: $ORIGIN_URL (expected ${OWNER}/${REPO})"; exit 1
fi
ok "Remote origin verified: $ORIGIN_URL"
if [[ "$DRY_RUN" == true ]]; then
warn "DRY RUN: Would push branch ${BRANCH_NAME}"
else
info "Pushing ${BRANCH_NAME}..."
PUSH_OUTPUT=$(sprite_exec "git push -u origin ${BRANCH_NAME}" "$REPO_DIR" 2>&1) || {
fail "git push failed: $PUSH_OUTPUT"; exit 1
}
ok "Branch pushed"
[[ "$VERBOSE" == true ]] && echo "$PUSH_OUTPUT"
fi
# ═══════════════════════════════════════════════════════════════════════════
# STEP 13: Create pull request
# ═══════════════════════════════════════════════════════════════════════════
step "Step 13: Create Pull Request"
PR_TITLE="Fix #${ISSUE_NUMBER}: ${SPRITE_ISSUE_TITLE}"
PR_BODY_FILE="/tmp/jido_pr_body_${RUN_ID}.md"
sprite_exec "cat > ${PR_BODY_FILE} << 'JIDO_PR_BODY_EOF'
## Automated PR from Jido PrBot (smoke test: ${PROVIDER_LABEL})
Resolves issue #${ISSUE_NUMBER}
Issue URL: ${ISSUE_URL}
Run ID: ${RUN_ID}
Branch: ${BRANCH_NAME}
Provider: ${PROVIDER_LABEL}
JIDO_PR_BODY_EOF" "$REPO_DIR" || {
fail "Failed to write PR body file"; exit 1
}
ok "PR body written"
if [[ "$DRY_RUN" == true ]]; then
warn "DRY RUN: Would create PR '${PR_TITLE}'"
warn " base=${BASE_BRANCH} head=${BRANCH_NAME}"
else
info "Creating PR..."
PR_OUTPUT=$(sprite_exec "gh pr create --repo ${OWNER}/${REPO} --base ${BASE_BRANCH} --head ${BRANCH_NAME} --title '${PR_TITLE}' --body-file ${PR_BODY_FILE}" "$REPO_DIR" 2>&1) || {
fail "gh pr create failed: $PR_OUTPUT"; exit 1
}
PR_URL=$(echo "$PR_OUTPUT" | grep -oE 'https://github\.com/[^ ]+/pull/[0-9]+' | head -1)
if [[ -z "$PR_URL" ]]; then
PR_URL=$(sprite_exec "gh pr list --repo ${OWNER}/${REPO} --head ${BRANCH_NAME} --state open --json url --jq '.[0].url'" "$REPO_DIR")
fi
if [[ -n "$PR_URL" ]]; then
ok "PR created: ${PR_URL}"
else
warn "PR created but could not extract URL"
[[ "$VERBOSE" == true ]] && echo "$PR_OUTPUT"
fi
fi
# ═══════════════════════════════════════════════════════════════════════════
# STEP 14: Comment on issue with PR link
# ═══════════════════════════════════════════════════════════════════════════
step "Step 14: Comment Issue With PR Link"
COMMENT_BODY_FILE="/tmp/jido_pr_issue_comment_${RUN_ID}.md"
sprite_exec "cat > ${COMMENT_BODY_FILE} << 'JIDO_ISSUE_PR_EOF'
✅ Automated PR created for this issue.
- PR: ${PR_URL:-[dry-run]}
- Branch: \`${BRANCH_NAME}\`
- Commit: \`${COMMIT_SHA:0:12}\`
- Run ID: \`${RUN_ID}\`
- Sprite: \`${SPRITE_NAME}\`
- Provider: \`${PROVIDER_LABEL}\`
- Timestamp: ${TIMESTAMP}
_Posted by \`${SCRIPT_NAME}\` smoke test._
JIDO_ISSUE_PR_EOF" "$REPO_DIR" || {
fail "Failed to write issue comment body"; exit 1
}
ok "Issue comment body written"
if [[ "$DRY_RUN" == true ]]; then
warn "DRY RUN: Would comment on ${OWNER}/${REPO}#${ISSUE_NUMBER}"
else
info "Posting comment to ${OWNER}/${REPO}#${ISSUE_NUMBER}..."
COMMENT_OUTPUT=$(sprite_exec "gh issue comment ${ISSUE_NUMBER} --repo ${OWNER}/${REPO} --body-file ${COMMENT_BODY_FILE}" "$REPO_DIR" 2>&1)
COMMENT_RC=$?
if [[ $COMMENT_RC -eq 0 ]]; then
ok "Comment posted successfully!"
[[ -n "$COMMENT_OUTPUT" ]] && info "Comment URL: $COMMENT_OUTPUT"
else
fail "gh issue comment failed (rc=$COMMENT_RC): $COMMENT_OUTPUT"
exit 1
fi
fi
# ═══════════════════════════════════════════════════════════════════════════
# Summary
# ═══════════════════════════════════════════════════════════════════════════
step "Summary"
echo ""
ok "All checks passed! ✅"
echo ""
info "What was validated:"
echo " 1. Host env: SPRITES_TOKEN, GH_TOKEN/GITHUB_TOKEN"
echo " 2. Sprite lifecycle: create → exec → (destroy on exit)"
echo " 3. Env var forwarding via sprite exec -env"
echo " 4. Runtime binaries inside sprite: git, gh, $(provider_binary)"
echo " 5. GitHub auth inside sprite: gh auth status"
echo " 6. gh issue view from inside sprite"
echo " 7. git clone from inside sprite"
echo " 8. git config identity + gh auth setup-git"
echo " 9. ${PROVIDER_LABEL} stream-JSON smoke prompt"
echo " 10. ${PROVIDER_LABEL} stream-JSON code change"
echo " 11. git add + git commit"
echo " 12. git push -u origin"
echo " 13. gh pr create"
echo " 14. gh issue comment --body-file (PR link back to issue)"
echo ""
info "Provider: ${PROVIDER_LABEL}"
info "Sprite: ${SPRITE_NAME}"
info "Run ID: ${RUN_ID}"
info "Branch: ${BRANCH_NAME}"
[[ -n "${PR_URL:-}" ]] && info "PR: ${PR_URL}"
if [[ "${#ARTIFACTS[@]}" -gt 0 ]]; then
info "Stream JSON artifacts:"
for artifact in "${ARTIFACTS[@]}"; do
info " ${artifact}"
done
fi
if [[ "$KEEP_SPRITE" == true ]]; then
warn "Sprite kept alive — destroy manually with:"
warn " sprite destroy -s ${SPRITE_NAME} --force"
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment