feat(@scripts): ✨ add account-wide env list command
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
a49ea98efd
commit
fa6eda0bbd
4 changed files with 142 additions and 0 deletions
19
README.md
19
README.md
|
|
@ -53,8 +53,27 @@ claude-rc rm <name> # disable + unregister
|
|||
claude-rc sync # reconcile units to the registry
|
||||
claude-rc logs <name> [-f] # journal
|
||||
claude-rc restart|stop|start <name>
|
||||
claude-rc envs # account-wide environment list (all hosts)
|
||||
```
|
||||
|
||||
## Account-wide view — `claude-rc-envs`
|
||||
|
||||
`claude-rc list`/`status` only know about *this host's* units. To see every
|
||||
Remote Control environment across all hosts (exactly what the claude.ai/code
|
||||
picker shows — including orphans and cloud envs), query the API:
|
||||
|
||||
```sh
|
||||
crc status # or: crc envs / claude-rc envs / rc envs
|
||||
claude-rc-envs # state, host, project, env id, dir
|
||||
claude-rc-envs --json
|
||||
claude-rc-envs rm <env_id> # delete an environment (e.g. an orphan)
|
||||
claude-rc-envs archive <env_id>
|
||||
```
|
||||
|
||||
Reads the OAuth token from the macOS Keychain or `~/.claude/.credentials.json`
|
||||
and hits `GET https://api.anthropic.com/v1/environments`
|
||||
(`anthropic-beta: environments-2025-11-01`).
|
||||
|
||||
Defaults (override via env in a unit drop-in):
|
||||
- `CLAUDE_RC_SPAWN=worktree` — isolated git worktree per spawned session.
|
||||
- `CLAUDE_RC_PERM=bypassPermissions` — spawned sessions skip permission prompts.
|
||||
|
|
|
|||
|
|
@ -81,6 +81,11 @@ case "$cmd" in
|
|||
printf '%-16s %-10s %s\n' "$n" "$(uc is-active "$TPL$n" 2>/dev/null || echo -)" "$(reg_dir "$n")"
|
||||
done
|
||||
;;
|
||||
envs|environments)
|
||||
# Account-wide environment list across ALL hosts (the claude.ai/code
|
||||
# picker view) — not just this host's units.
|
||||
exec claude-rc-envs "$@"
|
||||
;;
|
||||
status|st)
|
||||
show() {
|
||||
env=$(url_of "$1")
|
||||
|
|
|
|||
110
bin/claude-rc-envs
Executable file
110
bin/claude-rc-envs
Executable file
|
|
@ -0,0 +1,110 @@
|
|||
#!/usr/bin/env python3
|
||||
"""claude-rc-envs — list / inspect / remove Claude Remote Control environments.
|
||||
|
||||
Account-wide view (the same list the claude.ai/code environment picker shows),
|
||||
across every host. Reads the OAuth token from the macOS Keychain (Claude Code)
|
||||
or ~/.claude/.credentials.json.
|
||||
|
||||
Usage:
|
||||
claude-rc-envs # table: state, host, project, dir
|
||||
claude-rc-envs --json # raw JSON
|
||||
claude-rc-envs rm <env_id> # delete an environment (e.g. an orphan)
|
||||
claude-rc-envs archive <id> # archive instead of delete
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import platform
|
||||
import subprocess
|
||||
import sys
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
|
||||
API = "https://api.anthropic.com/v1/environments"
|
||||
BETA = "environments-2025-11-01"
|
||||
|
||||
|
||||
def token() -> str:
|
||||
if platform.system() == "Darwin":
|
||||
try:
|
||||
out = subprocess.run(
|
||||
["security", "find-generic-password", "-s", "Claude Code-credentials", "-w"],
|
||||
capture_output=True, text=True, timeout=10,
|
||||
)
|
||||
if out.returncode == 0 and out.stdout.strip():
|
||||
return json.loads(out.stdout)["claudeAiOauth"]["accessToken"]
|
||||
except Exception:
|
||||
pass
|
||||
path = os.path.expanduser("~/.claude/.credentials.json")
|
||||
if os.path.exists(path):
|
||||
with open(path) as fh:
|
||||
return json.load(fh)["claudeAiOauth"]["accessToken"]
|
||||
sys.exit("claude-rc-envs: no Claude credentials found (Keychain or ~/.claude/.credentials.json)")
|
||||
|
||||
|
||||
def call(method: str, url: str) -> dict:
|
||||
req = urllib.request.Request(url, method=method, headers={
|
||||
"Authorization": f"Bearer {token()}",
|
||||
"anthropic-beta": BETA,
|
||||
"anthropic-version": "2023-06-01",
|
||||
"content-type": "application/json",
|
||||
})
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=30) as resp:
|
||||
body = resp.read()
|
||||
return json.loads(body) if body else {}
|
||||
except urllib.error.HTTPError as e:
|
||||
sys.exit(f"claude-rc-envs: HTTP {e.code} {e.reason}: {e.read().decode()[:200]}")
|
||||
except urllib.error.URLError as e:
|
||||
sys.exit(f"claude-rc-envs: network error: {e.reason}")
|
||||
|
||||
|
||||
def fetch() -> list:
|
||||
return call("GET", API).get("data", [])
|
||||
|
||||
|
||||
def row(e: dict):
|
||||
c = e.get("config", {})
|
||||
bridge = c.get("type") == "bridge"
|
||||
host = c.get("machine_name") or ("cloud" if not bridge else "?")
|
||||
name = e.get("name", "")
|
||||
project = name.split(":")[1] if bridge and name.count(":") >= 1 else name
|
||||
return e.get("state", "?"), host, project, e.get("id", ""), c.get("directory", "")
|
||||
|
||||
|
||||
def list_envs():
|
||||
rows = [row(e) for e in fetch()]
|
||||
if not rows:
|
||||
print("(no environments)")
|
||||
return
|
||||
wh = max(4, *(len(r[1]) for r in rows))
|
||||
wp = max(7, *(len(r[2]) for r in rows))
|
||||
print(f"{'STATE':8} {'HOST':{wh}} {'PROJECT':{wp}} {'ENV ID':20} DIR")
|
||||
for state, host, project, env_id, directory in rows:
|
||||
print(f"{state:8} {host:{wh}} {project:{wp}} {env_id:20} {directory}")
|
||||
|
||||
|
||||
def main():
|
||||
args = sys.argv[1:]
|
||||
if not args:
|
||||
return list_envs()
|
||||
cmd = args[0]
|
||||
if cmd in ("--json", "json"):
|
||||
print(json.dumps(fetch(), indent=2))
|
||||
elif cmd in ("rm", "delete", "--rm"):
|
||||
if len(args) < 2:
|
||||
sys.exit("usage: claude-rc-envs rm <env_id>")
|
||||
call("DELETE", f"{API}/{args[1]}")
|
||||
print(f"removed {args[1]}")
|
||||
elif cmd in ("archive", "--archive"):
|
||||
if len(args) < 2:
|
||||
sys.exit("usage: claude-rc-envs archive <env_id>")
|
||||
call("POST", f"{API}/{args[1]}/archive")
|
||||
print(f"archived {args[1]}")
|
||||
elif cmd in ("ls", "list", "status", "envs"):
|
||||
list_envs()
|
||||
else:
|
||||
sys.exit(f"claude-rc-envs: unknown command '{cmd}' (try: ls | --json | rm <id> | archive <id>)")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
8
bin/crc
8
bin/crc
|
|
@ -42,6 +42,14 @@ rc_args=''
|
|||
|
||||
usage() { sed -n '2,31p' "$0" | sed 's/^# \{0,1\}//'; }
|
||||
|
||||
# Account-wide environment list (the claude.ai/code picker view, every host).
|
||||
# crc | crc status | crc envs → list
|
||||
# crc envs rm <id> | archive <id> | --json
|
||||
case "${1:-}" in
|
||||
''|status) exec claude-rc-envs ;;
|
||||
envs|ls) shift; exec claude-rc-envs "$@" ;;
|
||||
esac
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
-h|--help) usage; exit 0 ;;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue