Investigation (pi 0.84.4 docs + host auth.json metadata, values never read): provider stacking is native (one auth.json keyed by provider; resolution --api-key > auth.json > env > models.json; OAuth auto-refresh). Multi-account per provider is NOT native -> named-file design: auth.<account>.json + per-launch injection. - scripts/auth.sh: status (provider names, credential types, perms, env-side names informational — never credential material) and accounts (named files, active marker). Exit codes per convention: 3 missing for a read, 2 unparseable, 4 file/environment (symlinks refuse). - scripts/agent.sh --auth <account>: resolves auth.<account>.json and exports PI_AUTH_FILE (the existing compose read-only mount source — no new plumbing); missing/invalid account refuses pre-container. - scripts/test-auth.sh: 13 no-Docker cases; core assertion is the safety property itself — fixture key/token/env VALUES never reach output. - Docs: TOOLS.md Auth section, AGENTS.md command surface + suites. Headless task runs keep the default credential (worker auth selection is a separate policy decision). Real-host smoke: anthropic/openai-codex oauth + zai api_key reported, perms 600, no named accounts yet. Suites 24/90/14/17/13 + verify green. Agreed sequence M16-M19 complete; M20 owner-gated.
89 lines
3.4 KiB
Bash
Executable File
89 lines
3.4 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# auth.sh — pi credential checkpoint (M19): per-provider auth report and
|
|
# named-account listing. Never prints credential material: provider names,
|
|
# credential types, permission bits, and env var NAMES only.
|
|
#
|
|
# Usage:
|
|
# scripts/auth.sh status per-provider report (auth.json + env-side names)
|
|
# scripts/auth.sh accounts list named account files (auth.<account>.json)
|
|
#
|
|
# The credential file is the compose read-only mount source (PI_AUTH_FILE,
|
|
# default ~/.pi/agent/auth.json). Resolution follows pi: auth.json entries
|
|
# take priority over environment variables.
|
|
#
|
|
# Exit codes: 0 report produced · 2 credential file unparseable ·
|
|
# 3 file/dir missing for a read · 4 file/environment problem.
|
|
set -uo pipefail
|
|
cd "$(dirname "$0")/.."
|
|
|
|
AUTH_FILE="${PI_AUTH_FILE:-$HOME/.pi/agent/auth.json}"
|
|
AUTH_DIR="$(dirname "$AUTH_FILE")"
|
|
OP="${1:-}"
|
|
|
|
case "$OP" in
|
|
status)
|
|
if [ ! -e "$AUTH_FILE" ]; then
|
|
echo "auth: credential file not found: $AUTH_FILE" >&2
|
|
exit 3
|
|
fi
|
|
if [ ! -f "$AUTH_FILE" ] || [ -L "$AUTH_FILE" ]; then
|
|
echo "auth: credential file must be a regular, non-symbolic-link file: $AUTH_FILE" >&2
|
|
exit 4
|
|
fi
|
|
if ! node -e '
|
|
const fs = require("fs");
|
|
const file = process.argv[1];
|
|
let doc;
|
|
try { doc = JSON.parse(fs.readFileSync(file, "utf8")); }
|
|
catch (e) { console.error("auth: credential file is not valid JSON: " + e.message); process.exit(2); }
|
|
if (typeof doc !== "object" || doc === null || Array.isArray(doc)) {
|
|
console.error("auth: credential file must be a JSON object keyed by provider"); process.exit(2);
|
|
}
|
|
const st = fs.statSync(file);
|
|
console.log("auth file: " + file + " (perms " + String(st.mode & 0o777).toString(8).padStart(3, "0") + ")");
|
|
const keys = Object.keys(doc).sort();
|
|
if (keys.length === 0) console.log("providers: (none in file)");
|
|
for (const k of keys) {
|
|
const e = doc[k];
|
|
let type = "unknown";
|
|
if (typeof e === "object" && e !== null && typeof e.type === "string" && /^[a-z0-9_-]+$/i.test(e.type)) type = e.type;
|
|
console.log(" " + k + " type=" + type + " source=auth.json");
|
|
}
|
|
console.log("resolution: auth.json entries take priority over environment (pi order: --api-key > auth.json > env > models.json)");
|
|
' "$AUTH_FILE"; then
|
|
exit 2
|
|
fi
|
|
ENV_NAMES="$(env | grep -oE '^[A-Z0-9_]+_API_KEY' | sort -u | paste -sd, -)"
|
|
if [ -n "$ENV_NAMES" ]; then
|
|
echo "env-side credential-like names set (informational; file entries take priority): $ENV_NAMES"
|
|
else
|
|
echo "env-side credential-like names set: (none)"
|
|
fi
|
|
;;
|
|
accounts)
|
|
if [ ! -d "$AUTH_DIR" ]; then
|
|
echo "auth: directory not found: $AUTH_DIR" >&2
|
|
exit 3
|
|
fi
|
|
shopt -s nullglob
|
|
FILES=("$AUTH_DIR"/auth.*.json)
|
|
shopt -u nullglob
|
|
if [ "${#FILES[@]}" -eq 0 ]; then
|
|
echo "accounts: (none) — named accounts are auth.<account>.json beside the credential file"
|
|
exit 0
|
|
fi
|
|
echo "named accounts in $AUTH_DIR:"
|
|
for f in "${FILES[@]}"; do
|
|
name="$(basename "$f" .json)"
|
|
name="${name#auth.}"
|
|
marker=""
|
|
[ "$f" = "$AUTH_FILE" ] && marker=" <- active (PI_AUTH_FILE)"
|
|
echo " $name$marker"
|
|
done
|
|
;;
|
|
*)
|
|
echo "usage: scripts/auth.sh status | accounts" >&2
|
|
exit 4
|
|
;;
|
|
esac
|