Files

1 line
31 KiB
JSON

{"repo": "alo-exp/sidekick", "files": {"tests/run_live_codex_e2e.bash": {"32917a2e73967ece869ce4eda04bc531f7a8966e": {"t": 1785596361.397086, "n": 12435, "h": "dc9edad3a7b246ef", "c": "#!/usr/bin/env bash\n# =============================================================================\n# Sidekick Plugin — Live Kay End-to-End Driver\n# =============================================================================\n# Pre-release check that exercises a FULL host→Kay delegation round-trip\n# against the real Kay binary and the real model on the seeded Test Notes App.\n# Gated behind SIDEKICK_LIVE_CODEX=1. Never runs in CI.\n#\n# Flow\n# 1. Copy tests/test-notes-app/ to $TMPDIR so the canonical source never mutates.\n# 2. npm install in the sandbox (host network; Kay sandbox blocks registry.npmjs.org).\n# 3. Run scripts/e2e-smoke.sh — baseline MUST fail (health bug is live).\n# 4. Send Kay a structured task prompt asking it to fix /api/health.\n# 5. Re-run scripts/e2e-smoke.sh — MUST pass.\n# 6. Assert src/server.js returns status ok.\n# 7. Print the path to the sandbox so the maintainer can inspect.\n#\n# Exit codes\n# 0 — E2E passed, OR env var absent (skipped cleanly)\n# 1 — any assertion failed, or Kay returned non-zero, or timeout tripped\n# =============================================================================\n\nset -uo pipefail\n\nSCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\nif [ -f \"${SCRIPT_DIR}/../.env.local\" ]; then\n # shellcheck source=/dev/null\n set -a\n source \"${SCRIPT_DIR}/../.env.local\"\n set +a\nfi\n\ngreen='\\033[0;32m'; red='\\033[0;31m'; yellow='\\033[0;33m'; bold='\\033[1m'; reset='\\033[0m'\n\nif [[ \"${SIDEKICK_LIVE_CODEX:-}\" != \"1\" ]]; then\n echo -e \"${yellow}Live E2E skipped${reset} (set SIDEKICK_LIVE_CODEX=1 to run the full Kay round-trip).\"\n exit 0\nfi\n\necho -e \"${bold}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${reset}\"\necho -e \"${bold}Sidekick live-Kay E2E driver (Test Notes App)${reset}\"\necho -e \"${bold}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${reset}\"\n\nfor tool in node npm curl; do\n if ! command -v \"${tool}\" >/dev/null 2>&1; then\n echo -e \"${red}FAIL${reset}: ${tool} not on PATH\"\n exit 1\n fi\ndone\n\nLIVE_KAY_MODEL_PROVIDER=\"${KAY_LIVE_MODEL_PROVIDER:-${SIDEKICK_KAY_MODEL_PROVIDER:-opencode-go}}\"\nLIVE_KAY_MODEL=\"${KAY_LIVE_MODEL:-${SIDEKICK_KAY_MODEL:-opencode-go/deepseek-v4-flash}}\"\n\nresolve_codex_binary() {\n if [ -n \"${SIDEKICK_KAY_BIN:-}\" ]; then\n if [ -x \"${SIDEKICK_KAY_BIN}\" ] \\\n && \"${SIDEKICK_KAY_BIN}\" --version 2>/dev/null | grep -qiE '^kay([[:space:]]|$)' \\\n && \"${SIDEKICK_KAY_BIN}\" exec --help >/dev/null 2>&1; then\n printf '%s' \"${SIDEKICK_KAY_BIN}\"\n return 0\n fi\n return 1\n fi\n\n for candidate in kay code coder; do\n if command -v \"${candidate}\" >/dev/null 2>&1 \\\n && \"${candidate}\" --version 2>/dev/null | grep -qiE '^kay([[:space:]]|$)' \\\n && \"${candidate}\" exec --help >/dev/null 2>&1; then\n printf '%s' \"${candidate}\"\n return 0\n fi\n done\n return 1\n}\n\nprepare_codex_runner() {\n local bin=\"$1\"\n local help_file\n help_file=\"$(mktemp)\"\n CODEX_RUNNER=()\n\n if \"${bin}\" exec --help >\"${help_file}\" 2>&1; then\n if grep -q -- '--dangerously-bypass-approvals-and-sandbox' \"${help_file}\"; then\n CODEX_RUNNER=( \"${bin}\" exec )\n if grep -q -- '--skip-git-repo-check' \"${help_file}\"; then\n CODEX_RUNNER+=(--skip-git-repo-check)\n fi\n if grep -q -- '--ephemeral' \"${help_file}\"; then\n CODEX_RUNNER+=(--ephemeral)\n fi\n CODEX_RUNNER+=(--dangerously-bypass-approvals-and-sandbox)\n elif grep -q -- '--full-auto' \"${help_file}\"; then\n if grep -q -- '--skip-git-repo-check' \"${help_file}\"; then\n CODEX_RUNNER=( \"${bin}\" exec --skip-git-repo-check --full-auto )\n else\n CODEX_RUNNER=( \"${bin}\" exec --full-auto )\n fi\n else\n if grep -q -- '--skip-git-repo-check' \"${help_file}\"; then\n CODEX_RUNNER=( \"${bin}\" exec --skip-git-repo-check )\n else\n CODEX_RUNNER=( \"${bin}\" exec )\n fi\n fi\n elif \"${bin}\" --help 2>&1 | grep -q -- '--no-approval'; then\n CODEX_RUNNER=( \"${bin}\" --no-approval )\n else\n CODEX_RUNNER=( \"${bin}\" )\n fi\n\n rm -f \"${help_file}\"\n}\n\nrun_with_timeout() {\n local secs=\"$1\"; shift\n if command -v gtimeout >/dev/null 2>&1; then\n gtimeout \"${secs}\" \"$@\"\n elif command -v timeout >/dev/null 2>&1; then\n timeout \"${secs}\" \"$@\"\n else\n perl -e '\n use POSIX \":sys_wait_h\";\n my $secs = shift @ARGV;\n my $pid = fork();\n die \"fork failed: $!\" unless defined $pid;\n if ($pid == 0) {\n setpgrp(0, 0);\n exec @ARGV or die \"exec failed: $!\";\n }\n my $deadline = time + $secs;\n while (time < $deadline) {\n my $res = waitpid($pid, WNOHANG);\n exit($? >> 8) if $res == $pid;\n sleep 1;\n }\n if (kill 0, $pid) {\n kill \"TERM\", -$pid;\n sleep 2;\n kill \"KILL\", -$pid if kill 0, $pid;\n waitpid($pid, 0);\n exit 124;\n }\n waitpid($pid, 0);\n exit($? >> 8);\n ' \"${secs}\" \"$@\"\n fi\n}\n\nCODEX_BIN=\"$(resolve_codex_binary)\"\nif [ -z \"${CODEX_BIN}\" ]; then\n echo -e \"${red}FAIL${reset}: Kay-compatible kay/code/coder not on PATH\"\n exit 1\nfi\nprepare_codex_runner \"${CODEX_BIN}\"\n\nKAY_AUTH_PATH=\"${KAY_AUTH_PATH:-${HOME}/.kay/auth.json}\"\nOPENCODE_GO_API_KEY_VALUE=\"${OPENCODE_GO_API_KEY:-${CUSTOM_OPENCODE_GO_API_KEY:-}}\"\nMINIMAX_API_KEY_VALUE=\"${MINIMAX_API_KEY:-}\"\nOPENCODE_GO_KEY_FROM_ENV=0\nMINIMAX_KEY_FROM_ENV=0\nif [ -n \"${OPENCODE_GO_API_KEY:-${CUSTOM_OPENCODE_GO_API_KEY:-}}\" ]; then\n OPENCODE_GO_KEY_FROM_ENV=1\nfi\nif [ -n \"${MINIMAX_API_KEY:-}\" ]; then\n MINIMAX_KEY_FROM_ENV=1\nfi\n\nif [ \"${LIVE_KAY_MODEL_PROVIDER}\" = \"minimax\" ]; then\n if [ -z \"${MINIMAX_API_KEY_VALUE}\" ] && [ -f \"${KAY_AUTH_PATH}\" ]; then\n MINIMAX_API_KEY_VALUE=\"$(python3 -c 'import json, sys, pathlib\npath = pathlib.Path(sys.argv[1])\ndata = json.loads(path.read_text())\napi_key = data.get(\"provider_credentials\", {}).get(\"minimax\", {}).get(\"api_key\")\nif api_key:\n print(api_key)\n raise SystemExit(0)\nraise SystemExit(1)' \"${KAY_AUTH_PATH}\" 2>/dev/null)\" || true\n fi\n if [ -z \"${MINIMAX_API_KEY_VALUE}\" ]; then\n echo -e \"${red}FAIL${reset}: MINIMAX_API_KEY was not set and no MiniMax key was found in ${KAY_AUTH_PATH}\"\n exit 1\n fi\nelse\n if [ -z \"${OPENCODE_GO_API_KEY_VALUE}\" ] && [ -f \"${KAY_AUTH_PATH}\" ]; then\n OPENCODE_GO_API_KEY_VALUE=\"$(python3 -c 'import json, sys, pathlib\npath = pathlib.Path(sys.argv[1])\ndata = json.loads(path.read_text())\ncreds = data.get(\"provider_credentials\", {})\napi_key = creds.get(\"opencode-go\", {}).get(\"api_key\")\nif api_key:\n print(api_key)\n raise SystemExit(0)\nraise SystemExit(1)' \"${KAY_AUTH_PATH}\")\"\n fi\n if [ -z \"${OPENCODE_GO_API_KEY_VALUE}\" ]; then\n echo -e \"${red}FAIL${reset}: OPENCODE_GO_API_KEY was not set and no OpenCode Go key was found in ${KAY_AUTH_PATH}\"\n exit 1\n fi\nfi\n\n# Kay prefers ~/.kay/auth.json over env API keys when provider credentials exist (alo-labs/kay#43).\nif [[ -z \"${SIDEKICK_KAY_ISOLATED_HOME:-}\" ]]; then\n NEED_ISOLATED=0\n if [ \"${LIVE_KAY_MODEL_PROVIDER}\" = \"minimax\" ] && [[ \"${MINIMAX_KEY_FROM_ENV}\" -eq 1 ]]; then\n NEED_ISOLATED=1\n fi\n if [ \"${LIVE_KAY_MODEL_PROVIDER}\" != \"minimax\" ] && [[ \"${OPENCODE_GO_KEY_FROM_ENV}\" -eq 1 ]]; then\n NEED_ISOLATED=1\n fi\n if [[ \"${NEED_ISOLATED}\" -eq 1 ]]; then\n SIDEKICK_KAY_ISOLATED_HOME=\"$(mktemp -d -t sidekick-kay-home.XXXXXX)\"\n mkdir -p \"${SIDEKICK_KAY_ISOLATED_HOME}/.kay\"\n printf '%s\\n' '{\"auth_mode\":\"chatgpt\",\"provider_credentials\":{}}' > \"${SIDEKICK_KAY_ISOLATED_HOME}/.kay/auth.json\"\n export HOME=\"${SIDEKICK_KAY_ISOLATED_HOME}\"\n KAY_AUTH_PATH=\"${SIDEKICK_KAY_ISOLATED_HOME}/.kay/auth.json\"\n echo \"Using isolated Kay HOME for provider API key override: ${HOME}\"\n fi\nfi\n\nNOTES_APP_SRC=\"${SCRIPT_DIR}/test-notes-app\"\nSMOKE_SCRIPT=\"${NOTES_APP_SRC}/scripts/e2e-smoke.sh\"\n[[ -f \"${NOTES_APP_SRC}/package.json\" && -f \"${NOTES_APP_SRC}/src/server.js\" && -f \"${SMOKE_SCRIPT}\" ]] || {\n echo -e \"${red}FAIL${reset}: test-notes-app source not found at ${NOTES_APP_SRC}\"\n exit 1\n}\n\nSANDBOX=\"$(mktemp -d -t sidekick-notes-e2e.XXXXXX)\"\nif command -v rsync >/dev/null 2>&1; then\n rsync -a --exclude='node_modules' --exclude='data' --exclude='.git' \"${NOTES_APP_SRC}/\" \"${SANDBOX}/\"\nelse\n cp -R \"${NOTES_APP_SRC}/.\" \"${SANDBOX}/\"\n rm -rf \"${SANDBOX}/node_modules\" \"${SANDBOX}/data\" \"${SANDBOX}/.git\"\nfi\nchmod +x \"${SANDBOX}/scripts/e2e-smoke.sh\"\necho \"Sandbox: ${SANDBOX}\"\n\nPASS=0; FAIL=0\npass() { echo -e \"${green}PASS${reset} $1\"; PASS=$((PASS+1)); }\nfail() { echo -e \"${red}FAIL${reset} $1: $2\"; FAIL=$((FAIL+1)); }\n\npick_free_port() {\n python3 -c 'import socket; s=socket.socket(); s.bind((\"\", 0)); print(s.getsockname()[1]); s.close()'\n}\n\nE2E_PORT=\"$(pick_free_port)\"\nexport NOTES_APP_DB_PATH=\"${SANDBOX}/data/notes.db\"\n\necho \"=== e2e_npm_install ===\"\nif ( cd \"${SANDBOX}\" && npm install --silent ); then\n pass \"e2e_npm_install\"\nelse\n fail \"e2e_npm_install\" \"npm install failed in sandbox\"\n exit 1\nfi\n\nrun_smoke() {\n PORT=\"${E2E_PORT}\" NOTES_APP_DB_PATH=\"${NOTES_APP_DB_PATH}\" bash \"${SANDBOX}/scripts/e2e-smoke.sh\" 2>&1\n}\n\necho \"=== e2e_baseline_fails ===\"\n_base=\"$(run_smoke || true)\"\nif printf '%s' \"${_base}\" | grep -q 'health check failed'; then\n pass \"e2e_baseline_fails (seeded health bug is live)\"\nelse\n fail \"e2e_baseline_fails\" \"expected baseline health failure, got:\n${_base}\"\n echo \"Aborting E2E.\"\n exit 1\nfi\n\nread -r -d '' TASK_PROMPT <<EOF || true\nOBJECTIVE: Fix the /api/health bug in src/server.js so the live E2E smoke script passes.\n\nCONTEXT:\n- This is the Test Notes App (Node.js + Express + better-sqlite3).\n- node_modules is already installed; do NOT run npm install.\n- scripts/e2e-smoke.sh starts the server on PORT=${E2E_PORT} and checks:\n - GET /api/health returns JSON with \"status\":\"ok\"\n - POST /api/notes creates a note and GET /api/notes lists it\n- src/server.js currently returns status \"broken\" from /api/health.\n\nDESIRED STATE:\n- /api/health returns { status: \"ok\", timestamp: <iso> }.\n- No other routes or files change unless required for the health fix.\n\nSUCCESS CRITERIA:\n- PORT=${E2E_PORT} NOTES_APP_DB_PATH=<sandbox>/data/notes.db bash scripts/e2e-smoke.sh exits 0.\n- src/server.js contains status: 'ok' in the health handler.\n\nINJECTED SKILLS: quality-gates, testing-strategy\n\nIMPLEMENTATION:\n- Edit src/server.js directly.\n- Run bash scripts/e2e-smoke.sh with PORT=${E2E_PORT} after the fix and confirm it passes.\nEOF\n\necho \"=== e2e_kay_delegation ===\"\necho \"Sending task prompt to Kay (timeout 300s)...\"\nset +e\nif [ \"${LIVE_KAY_MODEL_PROVIDER}\" = \"minimax\" ]; then\n KAY_ENV=( HOME=\"${HOME}\" MINIMAX_API_KEY=\"${MINIMAX_API_KEY_VALUE}\" )\nelse\n KAY_ENV=( HOME=\"${HOME}\" OPENCODE_GO_API_KEY=\"${OPENCODE_GO_API_KEY_VALUE}\" CUSTOM_OPENCODE_GO_API_KEY=\"${OPENCODE_GO_API_KEY_VALUE}\" )\nfi\nKAY_LOG=\"$(mktemp -t sidekick-kay-e2e-out.XXXXXX)\"\nrun_with_timeout 300 env \"${KAY_ENV[@]}\" \\\n \"${CODEX_RUNNER[@]}\" \\\n -C \"${SANDBOX}\" \\\n -c model_provider=\"${LIVE_KAY_MODEL_PROVIDER}\" \\\n -c model=\"${LIVE_KAY_MODEL}\" \\\n -c model_reasoning_effort=low \\\n -c preferred_model_reasoning_effort=low \\\n \"${TASK_PROMPT}\" >\"${KAY_LOG}\" 2>&1\nKAY_RC=$?\nKAY_OUT=\"$(cat \"${KAY_LOG}\")\"\nrm -f \"${KAY_LOG}\"\nset -e\necho \"kay rc=${KAY_RC}\"\necho \"--- Kay output (tail 40 lines) ---\"\nprintf '%s\\n' \"${KAY_OUT}\" | tail -n 40\necho \"--- end kay output ---\"\n\nif [[ \"${KAY_RC}\" -eq 0 ]]; then\n pass \"e2e_kay_delegation (rc=0)\"\nelse\n fail \"e2e_kay_delegation\" \"Kay exited non-zero (rc=${KAY_RC})\"\nfi\n\necho \"=== e2e_health_handler_patched ===\"\nif grep -qE \"status:[[:space:]]*['\\\"]ok['\\\"]\" \"${SANDBOX}/src/server.js\"; then\n pass \"e2e_health_handler_patched (health returns ok)\"\nelse\n fail \"e2e_health_handler_patched\" \"src/server.js still broken:\n$(grep -n 'api/health' -A3 \"${SANDBOX}/src/server.js\" || cat \"${SANDBOX}/src/server.js\")\"\nfi\n\necho \"=== e2e_smoke_passes_after_fix ===\"\n_post=\"$(run_smoke || true)\"\nif printf '%s' \"${_post}\" | grep -q 'e2e-smoke passed'; then\n pass \"e2e_smoke_passes_after_fix\"\nelse\n fail \"e2e_smoke_passes_after_fix\" \"smoke output:\n${_post}\"\nfi\n\necho \"\"\necho -e \"${bold}═══════════════════════════════════════════${reset}\"\nif [[ \"${FAIL}\" -eq 0 ]]; then\n echo -e \"${green}${bold}LIVE E2E PASSED${reset} ($PASS checks)\"\n echo \"Sandbox preserved for inspection: ${SANDBOX}\"\nelse\n echo -e \"${red}${bold}LIVE E2E FAILED${reset} ($FAIL of $((PASS+FAIL)) failed)\"\n echo \"Sandbox preserved for inspection: ${SANDBOX}\"\nfi\necho -e \"${bold}═══════════════════════════════════════════${reset}\"\n\nexit \"${FAIL}\"\n"}}, "tests/run_live_codex_marketplace_install.bash": {"32917a2e73967ece869ce4eda04bc531f7a8966e": {"t": 1785596365.1722999, "n": 16470, "h": "9b2984c96b48f947", "c": "#!/usr/bin/env bash\n# =============================================================================\n# Sidekick Plugin — Live Codex marketplace install test with Kay runtime\n# =============================================================================\n\nset -uo pipefail\n\ngreen='\\033[0;32m'; red='\\033[0;31m'; yellow='\\033[0;33m'; bold='\\033[1m'; reset='\\033[0m'\n\nif [[ \"${SIDEKICK_LIVE_CODEX:-}\" != \"1\" ]]; then\n echo -e \"${yellow}Marketplace install skipped${reset} (set SIDEKICK_LIVE_CODEX=1 to exercise the real Codex marketplace/plugin install path with Kay runtime).\"\n exit 0\nfi\n\necho -e \"${bold}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${reset}\"\necho -e \"${bold}Sidekick live-Codex marketplace install with Kay runtime${reset}\"\necho -e \"${bold}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${reset}\"\n\nPASS=0; FAIL=0\npass() { echo -e \"${green}PASS${reset} $1\"; PASS=$((PASS+1)); }\nfail() { echo -e \"${red}FAIL${reset} $1: $2\"; FAIL=$((FAIL+1)); }\n\nSCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\nSIDEKICK_DIR=\"$(cd \"${SCRIPT_DIR}/..\" && pwd)\"\nHOST_HOME_FOR_TOOLS=\"${SIDEKICK_HOST_HOME:-${HOME}}\"\nCODEX_REPO=\"${SIDEKICK_CODEX_REPO:-${HOST_HOME_FOR_TOOLS}/projects/codex-cli/kay}\"\nCODEX_RUST_REPO=\"${CODEX_REPO}/codex-rs\"\nif [ -d \"${CODEX_REPO}/kay-rs\" ]; then\n CODE_RUST_REPO=\"${CODEX_REPO}/kay-rs\"\nelse\n CODE_RUST_REPO=\"${CODEX_REPO}/code-rs\"\nfi\nPLUGIN_VERSION=\"$(python3 -c \"import json; print(json.load(open('${SIDEKICK_DIR}/.codex-plugin/plugin.json'))['version'])\")\"\nMARKETPLACE_SOURCE=\"${CODEX_MARKETPLACE_SOURCE:-alo-labs/codex-plugins}\"\nMARKETPLACE_NAME=\"${CODEX_MARKETPLACE_NAME:-alo-labs-codex}\"\nEXPECTED_MARKETPLACE_SOURCE=\"${CODEX_MARKETPLACE_EXPECTED_SOURCE:-https://github.com/alo-labs/codex-plugins.git}\"\nEXPECTED_MARKETPLACE_SOURCE_TYPE=\"${CODEX_MARKETPLACE_EXPECTED_SOURCE_TYPE:-git}\"\nLIVE_KAY_MODEL_PROVIDER=\"${KAY_LIVE_MODEL_PROVIDER:-${SIDEKICK_KAY_MODEL_PROVIDER:-opencode-go}}\"\nLIVE_KAY_MODEL=\"${KAY_LIVE_MODEL:-${SIDEKICK_KAY_MODEL:-opencode-go/deepseek-v4-flash}}\"\n\nresolve_codex_runner() {\n if [ -n \"${SIDEKICK_CODEX_BIN:-}\" ]; then\n CODEX_BIN=( \"${SIDEKICK_CODEX_BIN}\" )\n return 0\n fi\n\n local built_codex=\"${CODEX_RUST_REPO}/target/debug/codex\"\n if [[ -x \"${built_codex}\" ]]; then\n CODEX_BIN=( \"${built_codex}\" )\n return 0\n fi\n\n if ! command -v cargo >/dev/null 2>&1; then\n return 1\n fi\n\n CODEX_BIN=( cargo run --manifest-path \"${CODEX_RUST_REPO}/Cargo.toml\" -q -p codex-cli -- )\n return 0\n}\n\nresolve_code_runner() {\n if [ -n \"${SIDEKICK_KAY_BIN:-}\" ]; then\n CODE_BIN=( \"${SIDEKICK_KAY_BIN}\" )\n return 0\n fi\n\n local built_code=\"${CODE_RUST_REPO}/target/debug/kay\"\n if [[ -x \"${built_code}\" ]]; then\n CODE_BIN=( \"${built_code}\" )\n return 0\n fi\n\n if ! command -v cargo >/dev/null 2>&1; then\n return 1\n fi\n\n CODE_BIN=( cargo run --manifest-path \"${CODE_RUST_REPO}/Cargo.toml\" -q -p code-cli --bin kay -- )\n return 0\n}\n\nprepare_code_exec_runner() {\n local help_file\n help_file=\"$(mktemp)\"\n CODE_RUNNER=( \"${CODE_BIN[@]}\" exec )\n\n if \"${CODE_BIN[@]}\" exec --help >\"${help_file}\" 2>&1; then\n CODE_RUNNER=( \"${CODE_BIN[@]}\" exec )\n if grep -q -- '--skip-git-repo-check' \"${help_file}\"; then\n CODE_RUNNER+=(--skip-git-repo-check)\n fi\n if grep -q -- '--dangerously-bypass-approvals-and-sandbox' \"${help_file}\"; then\n CODE_RUNNER+=(--dangerously-bypass-approvals-and-sandbox)\n elif grep -q -- '--full-auto' \"${help_file}\"; then\n CODE_RUNNER+=(--full-auto)\n fi\n fi\n\n rm -f \"${help_file}\"\n}\n\nrun_with_timeout() {\n local secs=\"$1\"; shift\n if command -v gtimeout >/dev/null 2>&1; then\n gtimeout \"${secs}\" \"$@\"\n elif command -v timeout >/dev/null 2>&1; then\n timeout \"${secs}\" \"$@\"\n else\n perl -e '\n use POSIX \":sys_wait_h\";\n my $secs = shift @ARGV;\n my $pid = fork();\n die \"fork failed: $!\" unless defined $pid;\n if ($pid == 0) {\n setpgrp(0, 0);\n exec @ARGV or die \"exec failed: $!\";\n }\n my $deadline = time + $secs;\n while (time < $deadline) {\n my $res = waitpid($pid, WNOHANG);\n exit($? >> 8) if $res == $pid;\n sleep 1;\n }\n if (kill 0, $pid) {\n kill \"TERM\", -$pid;\n sleep 2;\n kill \"KILL\", -$pid if kill 0, $pid;\n waitpid($pid, 0);\n exit 124;\n }\n waitpid($pid, 0);\n exit($? >> 8);\n ' \"${secs}\" \"$@\"\n fi\n}\n\nif ! resolve_codex_runner; then\n fail \"codex runner\" \"could not find a built codex binary or cargo on PATH\"\n exit 1\nfi\n\nif ! resolve_code_runner; then\n fail \"kay runner\" \"could not find a built Kay binary or cargo on PATH\"\n exit 1\nfi\nprepare_code_exec_runner\n\nif ! command -v python3 >/dev/null 2>&1; then\n fail \"python3\" \"python3 not found on PATH\"\n exit 1\nfi\n\nif ! command -v git >/dev/null 2>&1; then\n fail \"git\" \"git not found on PATH\"\n exit 1\nfi\n\nWORKSPACE=\"$(mktemp -d -t sidekick-codex-marketplace.XXXXXX)\"\nCODE_HOME=\"$(mktemp -d -t sidekick-codex-home.XXXXXX)\"\ntrap 'rm -rf \"${WORKSPACE}\" \"${CODE_HOME}\"' EXIT\nmkdir -p \"${WORKSPACE}/workspace\"\nprintf 'sidekick marketplace install smoke\\n' > \"${WORKSPACE}/workspace/README.txt\"\n\necho \"=== marketplace_add ===\"\nset +e\n(cd \"${WORKSPACE}/workspace\" && CODEX_HOME=\"${CODE_HOME}\" CODE_HOME=\"${CODE_HOME}\" \"${CODEX_BIN[@]}\" plugin marketplace add \"${MARKETPLACE_SOURCE}\" >/tmp/sidekick-codex-marketplace-add.log 2>&1)\nADD_RC=$?\nset -e\nif [ \"${ADD_RC}\" -eq 0 ]; then\n pass \"marketplace was added to Codex configuration\"\nelse\n fail \"marketplace_add\" \"Codex exited ${ADD_RC}; output:\n$(cat /tmp/sidekick-codex-marketplace-add.log)\"\n exit 1\nfi\n\nMARKETPLACE_ROOT=\"${CODE_HOME}/.tmp/marketplaces/${MARKETPLACE_NAME}\"\nINSTALLED_ROOT=\"${CODE_HOME}/plugins/cache/${MARKETPLACE_NAME}/sidekick/${PLUGIN_VERSION}\"\n\necho \"=== marketplace_config_entry ===\"\nif grep -Fq \"[marketplaces.${MARKETPLACE_NAME}]\" \"${CODE_HOME}/config.toml\" \\\n && grep -Fq \"source_type = \\\"${EXPECTED_MARKETPLACE_SOURCE_TYPE}\\\"\" \"${CODE_HOME}/config.toml\" \\\n && grep -Fq \"source = \\\"${EXPECTED_MARKETPLACE_SOURCE}\\\"\" \"${CODE_HOME}/config.toml\"\nthen\n pass \"Codex recorded the Sidekick marketplace source in the isolated CODEX_HOME\"\nelse\n fail \"marketplace_config_entry\" \"missing marketplace entry in ${CODE_HOME}/config.toml\"\nfi\n\necho \"=== plugin_add ===\"\nset +e\n(cd \"${WORKSPACE}/workspace\" && CODEX_HOME=\"${CODE_HOME}\" CODE_HOME=\"${CODE_HOME}\" \"${CODEX_BIN[@]}\" plugin add \"sidekick@${MARKETPLACE_NAME}\" >/tmp/sidekick-codex-plugin-add.log 2>&1)\nPLUGIN_ADD_RC=$?\nset -e\nif [ \"${PLUGIN_ADD_RC}\" -eq 0 ]; then\n pass \"Sidekick plugin was added from the configured Codex marketplace\"\nelse\n fail \"plugin_add\" \"Codex exited ${PLUGIN_ADD_RC}; output:\n$(cat /tmp/sidekick-codex-plugin-add.log)\"\n exit 1\nfi\n\necho \"=== marketplace_checkout_root ===\"\nif [ -d \"${MARKETPLACE_ROOT}\" ] \\\n && [ -f \"${MARKETPLACE_ROOT}/.agents/plugins/marketplace.json\" ]; then\n pass \"Codex materialized the Sidekick marketplace checkout in isolated CODEX_HOME\"\nelse\n fail \"marketplace_checkout_root\" \"missing ${MARKETPLACE_ROOT} or marketplace manifest\"\nfi\n\necho \"=== plugin_install_root ===\"\nif [ -d \"${INSTALLED_ROOT}\" ] \\\n && [ -f \"${INSTALLED_ROOT}/.codex-plugin/plugin.json\" ] \\\n && [ \"$(python3 -c \"import json; print(json.load(open('${INSTALLED_ROOT}/.codex-plugin/plugin.json'))['version'])\")\" = \"${PLUGIN_VERSION}\" ]; then\n pass \"Codex materialized the Sidekick plugin cache in isolated CODEX_HOME at the expected version\"\nelse\n fail \"plugin_install_root\" \"missing ${INSTALLED_ROOT} or installed plugin version does not match ${PLUGIN_VERSION}\"\nfi\n\nSKILL_ROOT=\"$(python3 - \"${INSTALLED_ROOT}\" <<'PY'\nimport json\nimport pathlib\nimport sys\n\nroot = pathlib.Path(sys.argv[1]).resolve()\nmanifest = json.loads((root / \".codex-plugin\" / \"plugin.json\").read_text())\nskills = manifest[\"skills\"]\nskill_root = (root / skills).resolve()\ntry:\n skill_root.relative_to(root)\nexcept ValueError:\n raise SystemExit(f\"manifest skills path escapes installed root: {skills}\")\nprint(skill_root)\nPY\n)\"\n\necho \"=== marketplace_skill_surface ===\"\nif [ -f \"${SKILL_ROOT}/codex-delegate/SKILL.md\" ] \\\n && [ -f \"${SKILL_ROOT}/codex-delegate.md\" ] \\\n && [ -f \"${SKILL_ROOT}/codex-stop/SKILL.md\" ] \\\n && [ -f \"${SKILL_ROOT}/kay-delegate/SKILL.md\" ] \\\n && [ -f \"${SKILL_ROOT}/kay-stop/SKILL.md\" ] \\\n && [ ! -f \"${SKILL_ROOT}/kay:delegate/SKILL.md\" ] \\\n && [ ! -f \"${SKILL_ROOT}/codex/SKILL.md\" ] \\\n && [ ! -f \"${SKILL_ROOT}/codex-history/SKILL.md\" ] \\\n && [ ! -f \"${SKILL_ROOT}/forge/SKILL.md\" ] \\\n && [ ! -f \"${SKILL_ROOT}/forge-stop/SKILL.md\" ] \\\n && grep -q '^name: codex$' \"${SKILL_ROOT}/codex-delegate/SKILL.md\" \\\n && grep -q '\\.codex-delegation-active' \"${SKILL_ROOT}/codex-stop/SKILL.md\" \\\n && grep -q '^name: kay$' \"${SKILL_ROOT}/kay-delegate/SKILL.md\" \\\n && grep -q '\\.kay-delegation-active' \"${SKILL_ROOT}/kay-stop/SKILL.md\" \\\n && grep -q '^user-invocable: false' \"${SKILL_ROOT}/codex-delegate.md\"\nthen\n pass \"installed marketplace exposes the canonical Sidekick skill surface\"\nelse\n fail \"marketplace_skill_surface\" \"Codex manifest skill surface is missing or mis-targeted in ${SKILL_ROOT}\"\nfi\n\necho \"=== marketplace_skills_list_surface ===\"\nif python3 - \"${CODE_HOME}\" \"${WORKSPACE}/workspace\" \"${CODEX_BIN[@]}\" <<'PY'\nimport json\nimport os\nimport pathlib\nimport queue\nimport subprocess\nimport sys\nimport threading\nimport time\n\ncode_home = pathlib.Path(sys.argv[1])\nworkspace = pathlib.Path(sys.argv[2])\ncodex_cmd = sys.argv[3:]\n\nenv = os.environ.copy()\nenv[\"CODEX_HOME\"] = str(code_home)\nenv[\"CODE_HOME\"] = str(code_home)\n\nproc = subprocess.Popen(\n [*codex_cmd, \"app-server\", \"--listen\", \"stdio://\"],\n stdin=subprocess.PIPE,\n stdout=subprocess.PIPE,\n stderr=subprocess.PIPE,\n text=True,\n env=env,\n cwd=workspace,\n)\n\nq = queue.Queue()\n\ndef pump(pipe, label):\n for line in iter(pipe.readline, \"\"):\n q.put((label, line))\n q.put((label, None))\n\nthreading.Thread(target=pump, args=(proc.stdout, \"stdout\"), daemon=True).start()\nthreading.Thread(target=pump, args=(proc.stderr, \"stderr\"), daemon=True).start()\n\ndef send(obj):\n proc.stdin.write(json.dumps(obj) + \"\\n\")\n proc.stdin.flush()\n\ndef recv_id(target_id, timeout=30):\n end = time.time() + timeout\n while time.time() < end:\n try:\n _label, line = q.get(timeout=0.2)\n except queue.Empty:\n continue\n if line is None:\n continue\n try:\n obj = json.loads(line)\n except Exception:\n continue\n if obj.get(\"id\") == target_id:\n return obj\n raise TimeoutError(f\"timed out waiting for JSON-RPC response {target_id}\")\n\ntry:\n send(\n {\n \"jsonrpc\": \"2.0\",\n \"id\": 1,\n \"method\": \"initialize\",\n \"params\": {\n \"clientInfo\": {\n \"name\": \"sidekick-marketplace-probe\",\n \"title\": \"Sidekick Marketplace Probe\",\n \"version\": \"0.1.0\",\n },\n \"capabilities\": {\"experimentalApi\": True},\n },\n }\n )\n recv_id(1)\n send({\"jsonrpc\": \"2.0\", \"method\": \"initialized\", \"params\": {}})\n send({\"jsonrpc\": \"2.0\", \"id\": 2, \"method\": \"skills/list\", \"params\": {}})\n resp = recv_id(2)\n names = []\n for entry in resp[\"result\"][\"data\"]:\n for skill in entry.get(\"skills\", []):\n if skill[\"name\"].startswith(\"sidekick:\"):\n names.append(skill[\"name\"])\n print(\"skill_names:\", \", \".join(names))\n expected = [\n \"sidekick:codex\",\n \"sidekick:codex-stop\",\n \"sidekick:kay\",\n \"sidekick:kay-stop\",\n ]\n if names != expected:\n raise SystemExit(\n \"skills/list did not return the unique expected Sidekick surface: \"\n + \", \".join(names)\n )\nfinally:\n proc.terminate()\n try:\n proc.wait(timeout=5)\n except subprocess.TimeoutExpired:\n proc.kill()\nPY\nthen\n pass \"installed marketplace exposes a unique Sidekick skills/list picker surface\"\nelse\n fail \"marketplace_skills_list_surface\" \"Codex skills/list did not match the expected unique Sidekick surface\"\nfi\n\nread -r -d '' TASK_PROMPT <<'EOF' || true\nRespond with exactly one line:\nSTATUS: OK\nDo not edit any files.\nEOF\n\necho \"=== live_kay_exec ===\"\nOPENCODE_GO_API_KEY_VALUE=\"${OPENCODE_GO_API_KEY:-${CUSTOM_OPENCODE_GO_API_KEY:-}}\"\nKAY_AUTH_PATH=\"${KAY_AUTH_PATH:-${HOME}/.kay/auth.json}\"\nif [ -z \"${OPENCODE_GO_API_KEY_VALUE}\" ] && [ -f \"${KAY_AUTH_PATH}\" ]; then\n OPENCODE_GO_API_KEY_VALUE=\"$(python3 -c 'import json, sys, pathlib\npath = pathlib.Path(sys.argv[1])\ndata = json.loads(path.read_text())\ncreds = data.get(\"provider_credentials\", {})\napi_key = creds.get(\"opencode-go\", {}).get(\"api_key\")\nif api_key:\n print(api_key)\n raise SystemExit(0)\nraise SystemExit(1)' \"${KAY_AUTH_PATH}\")\"\nfi\nif [ -z \"${OPENCODE_GO_API_KEY_VALUE}\" ]; then\n fail \"opencode_go_key\" \"OPENCODE_GO_API_KEY was not set and no OpenCode Go key was found in ${KAY_AUTH_PATH}\"\n exit 1\nfi\nset +e\nEXEC_OUT=\"$(cd \"${WORKSPACE}/workspace\" && CODEX_HOME=\"${CODE_HOME}\" CODE_HOME=\"${CODE_HOME}\" OPENCODE_GO_API_KEY=\"${OPENCODE_GO_API_KEY_VALUE}\" CUSTOM_OPENCODE_GO_API_KEY=\"${OPENCODE_GO_API_KEY_VALUE}\" run_with_timeout 180 \"${CODE_RUNNER[@]}\" -c model_provider=\"${LIVE_KAY_MODEL_PROVIDER}\" -c model=\"${LIVE_KAY_MODEL}\" -c model_reasoning_effort=low -c preferred_model_reasoning_effort=low \"${TASK_PROMPT}\" 2>&1)\"\nEXEC_RC=$?\nset -e\necho \"kay rc=${EXEC_RC}\"\necho \"--- Kay output (tail 40 lines) ---\"\nprintf '%s\\n' \"${EXEC_OUT}\" | tail -n 40\necho \"--- end codex output ---\"\n\nif [ \"${EXEC_RC}\" -eq 0 ]; then\n pass \"Kay exec completed with the marketplace installed\"\nelse\n fail \"live_kay_exec\" \"Kay exited non-zero (rc=${EXEC_RC})\"\nfi\n\necho \"=== installed_plugin_cache ===\"\nif [ -d \"${INSTALLED_ROOT}\" ]; then\n if ! HOME=\"${CODE_HOME}\" CODEX_HOME=\"${CODE_HOME}\" CODE_HOME=\"${CODE_HOME}\" CODEX_PLUGIN_ROOT=\"${INSTALLED_ROOT}\" SIDEKICK_INSTALL_KAY=0 bash \"${INSTALLED_ROOT}/install.sh\" >/dev/null 2>&1; then\n fail \"installed cache rewrite\" \"reinstalling the installed tree at ${INSTALLED_ROOT} failed\"\n fi\n if grep -Fq 'CODEX_PLUGIN_ROOT' \"${INSTALLED_ROOT}/hooks/hooks.json\" \\\n && ! grep -Fq 'CLAUDE_PLUGIN_ROOT' \"${INSTALLED_ROOT}/hooks/hooks.json\" \\\n && grep -Fq 'CODEX_PROJECT_DIR' \"${INSTALLED_ROOT}/hooks/lib/sidekick-registry.sh\" \\\n && ! grep -Fq 'CLAUDE_PROJECT_DIR' \"${INSTALLED_ROOT}/hooks/lib/sidekick-registry.sh\" \\\n && grep -Fq '.codex/' \"${INSTALLED_ROOT}/sidekicks/registry.json\" \\\n && ! grep -Fq '.claude/' \"${INSTALLED_ROOT}/sidekicks/registry.json\" \\\n && grep -Fq 'SIDEKICK_HOST_SESSION_ID' \"${SKILL_ROOT}/kay-delegate/SKILL.md\" \\\n && grep -Fq 'CLAUDE_SESSION_ID' \"${SKILL_ROOT}/kay-delegate/SKILL.md\" \\\n && grep -Fq 'CODEX_THREAD_ID' \"${SKILL_ROOT}/kay-delegate/SKILL.md\" \\\n && ! grep -Fq '${HOME}/.codex/sessions/${SIDEKICK_SESSION}' \"${SKILL_ROOT}/kay-delegate/SKILL.md\" \\\n && grep -Fq 'SIDEKICK_HOST_SESSION_ID' \"${SKILL_ROOT}/codex-stop/SKILL.md\" \\\n && grep -Fq 'CLAUDE_SESSION_ID' \"${SKILL_ROOT}/codex-stop/SKILL.md\" \\\n && grep -Fq 'CODEX_THREAD_ID' \"${SKILL_ROOT}/codex-stop/SKILL.md\" \\\n && grep -Fq '.codex-delegation-active' \"${SKILL_ROOT}/codex-stop/SKILL.md\" \\\n && python3 - \"${CODE_HOME}/.Codex\" \"${CODE_HOME}/.codex\" <<'PY' >/dev/null\nimport os\nfrom pathlib import Path\nimport sys\nlegacy = Path(sys.argv[1])\nlower = Path(sys.argv[2])\ntry:\n if not legacy.exists():\n raise SystemExit(0)\n if not os.path.samefile(legacy, lower):\n raise SystemExit(1)\nexcept FileNotFoundError:\n raise SystemExit(0)\nPY\n then\n pass \"installed marketplace cache rewrote runtime surfaces while preserving canonical host-neutral skills\"\n else\n fail \"installed marketplace surface\" \"installed cache contents do not match the expected Codex runtime rewrite and canonical skill surface\"\n fi\nelse\n fail \"installed marketplace surface\" \"could not locate the installed Sidekick marketplace tree\"\nfi\n\necho \"\"\necho -e \"${bold}═══════════════════════════════════════════${reset}\"\nif [ \"${FAIL}\" -eq 0 ]; then\n echo -e \"${green}${bold}LIVE MARKETPLACE INSTALL PASSED${reset} ($PASS checks)\"\n echo \"Temporary workspace was: ${WORKSPACE} (removed on exit)\"\nelse\n echo -e \"${red}${bold}LIVE MARKETPLACE INSTALL FAILED${reset} ($FAIL of $((PASS+FAIL)) failed)\"\n echo \"Temporary workspace was: ${WORKSPACE} (removed on exit)\"\nfi\necho -e \"${bold}═══════════════════════════════════════════${reset}\"\n\nexit \"${FAIL}\"\n"}}}}