mirror of
https://github.com/garrytan/gstack.git
synced 2026-05-02 11:45:20 +02:00
feat(setup-gbrain): add list-orphans + delete-project subcommands (D20)
Powers /setup-gbrain --cleanup-orphans. list-orphans filters the
authenticated user's Supabase projects by name prefix (default
"gbrain") and excludes the project the local ~/.gbrain/config.json
currently points at, so only unclaimed gbrain-shaped projects come
back. Active-ref detection parses the pooler URL's user portion
(postgres.<ref>:<pw>@...).
delete-project is a thin DELETE /v1/projects/{ref} wrapper with no
confirmation of its own — the skill's UI layer owns the per-project
confirm AskUserQuestion loop. Keeps responsibilities clean: the bin
manages HTTP; the skill manages user intent.
Both subcommands reuse the existing api_call retry+backoff and the
same PAT discipline (env only, never argv).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -27,6 +27,20 @@
|
||||
# real value — we build from db_user/db_host/db_port/db_name instead).
|
||||
# Output: {"ref","pooler_url"}.
|
||||
#
|
||||
# list-orphans [--name-prefix <str>]
|
||||
# GET /v1/projects. Filter to projects whose name starts with --name-prefix
|
||||
# (default "gbrain") AND whose ref does NOT match the one in the local
|
||||
# active ~/.gbrain/config.json pooler URL. Those are the gbrain-shaped
|
||||
# projects that aren't pointed at by a working local config — candidates
|
||||
# for /setup-gbrain --cleanup-orphans.
|
||||
# Output: {"active_ref","orphans":[{"ref","name","created_at","region"}, ...]}.
|
||||
#
|
||||
# delete-project <ref>
|
||||
# DELETE /v1/projects/{ref}. Destructive, one-way — callers must
|
||||
# double-confirm before invoking. This bin performs NO confirmation
|
||||
# prompt; the skill's UI layer owns that responsibility.
|
||||
# Output: {"deleted_ref"}.
|
||||
#
|
||||
# Secrets discipline (D8, D10, D11):
|
||||
# - SUPABASE_ACCESS_TOKEN is read from env; never accepted as argv.
|
||||
# - DB_PASS (for `create` and `pooler-url`) is read from env; never argv.
|
||||
@@ -353,12 +367,81 @@ cmd_pooler_url() {
|
||||
fi
|
||||
}
|
||||
|
||||
cmd_list_orphans() {
|
||||
local name_prefix="gbrain"
|
||||
local json_mode=false
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--name-prefix) name_prefix="$2"; shift 2 ;;
|
||||
--json) json_mode=true; shift ;;
|
||||
--*) die "list-orphans: unknown flag: $1" ;;
|
||||
*) die "list-orphans: unexpected arg: $1" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
require_jq; require_curl; require_pat
|
||||
local all
|
||||
all=$(api_call GET projects)
|
||||
|
||||
# Extract the active brain's ref from ~/.gbrain/config.json if present.
|
||||
# Pooler URL format: postgresql://postgres.<ref>:<pw>@...
|
||||
local active_ref="null"
|
||||
local gbrain_cfg="$HOME/.gbrain/config.json"
|
||||
if [ -f "$gbrain_cfg" ]; then
|
||||
local url
|
||||
url=$(jq -r '.database_url // empty' "$gbrain_cfg" 2>/dev/null || true)
|
||||
if [ -n "$url" ]; then
|
||||
# Extract user portion before the colon: postgresql://USER:pw@...
|
||||
local user
|
||||
user=$(printf '%s' "$url" | sed -E 's|^[a-z]+://([^:]+):.*$|\1|')
|
||||
# User format: postgres.<ref> — pull ref suffix
|
||||
case "$user" in
|
||||
postgres.*)
|
||||
local ref="${user#postgres.}"
|
||||
active_ref=$(jq -Rn --arg r "$ref" '$r')
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
fi
|
||||
|
||||
local orphans
|
||||
orphans=$(printf '%s' "$all" | jq \
|
||||
--arg prefix "$name_prefix" \
|
||||
--argjson active "$active_ref" \
|
||||
'[.[]
|
||||
| select(.name | startswith($prefix))
|
||||
| select(.ref != $active)
|
||||
| {ref: .ref, name: .name, created_at: .created_at, region: .region}]')
|
||||
|
||||
jq -n --argjson active "$active_ref" --argjson orphans "$orphans" \
|
||||
'{active_ref: $active, orphans: $orphans}'
|
||||
}
|
||||
|
||||
cmd_delete_project() {
|
||||
local ref=""
|
||||
local json_mode=false
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--json) json_mode=true; shift ;;
|
||||
--*) die "delete-project: unknown flag: $1" ;;
|
||||
*) ref="$1"; shift ;;
|
||||
esac
|
||||
done
|
||||
[ -z "$ref" ] && die "delete-project: missing <ref>"
|
||||
|
||||
require_jq; require_curl; require_pat
|
||||
api_call DELETE "projects/$ref" >/dev/null
|
||||
jq -n --arg ref "$ref" '{deleted_ref: $ref}'
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
list-orgs) shift; cmd_list_orgs "$@" ;;
|
||||
create) shift; cmd_create "$@" ;;
|
||||
wait) shift; cmd_wait "$@" ;;
|
||||
pooler-url) shift; cmd_pooler_url "$@" ;;
|
||||
--help|-h|help) sed -n '2,60p' "$0" | sed 's/^# \{0,1\}//' ;;
|
||||
"") die "usage: gstack-gbrain-supabase-provision {list-orgs|create|wait|pooler-url|--help}" ;;
|
||||
list-orgs) shift; cmd_list_orgs "$@" ;;
|
||||
create) shift; cmd_create "$@" ;;
|
||||
wait) shift; cmd_wait "$@" ;;
|
||||
pooler-url) shift; cmd_pooler_url "$@" ;;
|
||||
list-orphans) shift; cmd_list_orphans "$@" ;;
|
||||
delete-project) shift; cmd_delete_project "$@" ;;
|
||||
--help|-h|help) sed -n '2,80p' "$0" | sed 's/^# \{0,1\}//' ;;
|
||||
"") die "usage: gstack-gbrain-supabase-provision {list-orgs|create|wait|pooler-url|list-orphans|delete-project|--help}" ;;
|
||||
*) die "unknown subcommand: $1" ;;
|
||||
esac
|
||||
|
||||
Reference in New Issue
Block a user