feat(bin): add dt, dtach-router, claude-provider CLI scripts
- dt: dtach session manager for claude-in-dtach sessions - dtach-router: SSH-login dashboard to resume sessions (sourced from bashrc) - claude-provider: switch Claude Code between Anthropic and OpenRouter; the OpenRouter API key is read from $OPENROUTER_API_KEY at runtime and is never stored in the repo Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
27d47ae866
commit
6d341f2c9f
63
bin/claude-provider
Executable file
63
bin/claude-provider
Executable file
@ -0,0 +1,63 @@
|
||||
#!/bin/bash
|
||||
# Switch Claude Code between Anthropic (default) and OpenRouter.
|
||||
# Usage: claude-provider [anthropic|openrouter|status]
|
||||
# After switching, re-source your shell or run: cpsource
|
||||
#
|
||||
# OpenRouter mode reads the API key from $OPENROUTER_API_KEY at source time —
|
||||
# the key is NOT stored in this script or in the repo. Export it from a private,
|
||||
# untracked location (e.g. ~/.bashrc.local):
|
||||
# export OPENROUTER_API_KEY="<your-openrouter-key>"
|
||||
|
||||
PROVIDER_FILE="$HOME/.claude-provider-env"
|
||||
|
||||
write_anthropic() {
|
||||
cat > "$PROVIDER_FILE" << 'EOF'
|
||||
# Claude provider: Anthropic (default)
|
||||
unset ANTHROPIC_BASE_URL
|
||||
unset ANTHROPIC_API_KEY
|
||||
unset ANTHROPIC_DEFAULT_SONNET_MODEL
|
||||
export CLAUDE_PROVIDER="anthropic"
|
||||
EOF
|
||||
echo "Switched to Anthropic (default)."
|
||||
echo "Run: cpsource (or restart your shell)"
|
||||
}
|
||||
|
||||
write_openrouter() {
|
||||
cat > "$PROVIDER_FILE" << 'EOF'
|
||||
# Claude provider: OpenRouter
|
||||
# Key comes from $OPENROUTER_API_KEY in your environment (never hardcoded here).
|
||||
export ANTHROPIC_BASE_URL="https://openrouter.ai/api"
|
||||
export ANTHROPIC_API_KEY="${OPENROUTER_API_KEY:?Set OPENROUTER_API_KEY in your environment to use OpenRouter}"
|
||||
export CLAUDE_PROVIDER="openrouter"
|
||||
export ANTHROPIC_DEFAULT_SONNET_MODEL="google/gemma-4-31b-it:free"
|
||||
EOF
|
||||
echo "Switched to OpenRouter."
|
||||
echo "Run: cpsource (or restart your shell)"
|
||||
}
|
||||
|
||||
show_status() {
|
||||
local provider="${CLAUDE_PROVIDER:-anthropic}"
|
||||
echo "Current provider: $provider"
|
||||
if [ -f "$PROVIDER_FILE" ]; then
|
||||
echo "Config file: $PROVIDER_FILE"
|
||||
fi
|
||||
}
|
||||
|
||||
case "${1:-status}" in
|
||||
openrouter|or)
|
||||
write_openrouter
|
||||
;;
|
||||
anthropic|an)
|
||||
write_anthropic
|
||||
;;
|
||||
status|st)
|
||||
show_status
|
||||
;;
|
||||
*)
|
||||
echo "Usage: claude-provider [anthropic|openrouter|status]"
|
||||
echo " anthropic (an) — Use Anthropic directly (default)"
|
||||
echo " openrouter (or) — Use OpenRouter proxy"
|
||||
echo " status (st) — Show current provider"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
104
bin/dt
Executable file
104
bin/dt
Executable file
@ -0,0 +1,104 @@
|
||||
#!/usr/bin/env bash
|
||||
# dt — gestionnaire de sessions claude-dans-dtach (1 session = 1 claude = 1 socket)
|
||||
# Sockets dans ~/.dtach/. La creation se fait via l'alias `cc` (voir plus bas),
|
||||
# pas ici : dt ne gere que listing / rattachement / kill.
|
||||
#
|
||||
# dt ls lister (nom, age, dossier, commande)
|
||||
# dt at <nom> rattacher une session
|
||||
# dt kill <nom> tuer une session
|
||||
# dt --raw listing TSV (pour fzf)
|
||||
# dt sock <nom> imprime le chemin du socket (utilitaire interne)
|
||||
#
|
||||
# Detacher une session attachee : Ctrl-\
|
||||
#
|
||||
# CREATION (a mettre dans ~/.bashrc) :
|
||||
# dtach_claude() { dtach -c "$HOME/.dtach/${1:-claude-$(date +%H%M%S)}" -e '^\' claude; }
|
||||
# alias cc='dtach_claude'
|
||||
# # usage : cd ~/projets/seo && cc seo
|
||||
set -uo pipefail
|
||||
|
||||
DTDIR="${DTACH_DIR:-$HOME/.dtach}"
|
||||
mkdir -p "$DTDIR"
|
||||
now=$(date +%s)
|
||||
|
||||
# PID(s) du process dtach pour ce socket, via la cmdline (ss ne liste pas les
|
||||
# sockets dtach de maniere fiable selon la version). Matche -c/-n/-A/-N.
|
||||
_pids_for_socket() {
|
||||
local sock="$1"
|
||||
pgrep -f -- "dtach -[cnAN] $sock" 2>/dev/null | sort -u
|
||||
}
|
||||
|
||||
_cwd_of() { readlink -f "/proc/$1/cwd" 2>/dev/null; }
|
||||
|
||||
# Heure de demarrage REELLE du process (fiable, immuable), en epoch.
|
||||
_starttime_of() {
|
||||
local ls; ls=$(ps -o lstart= -p "$1" 2>/dev/null) || return
|
||||
[ -n "$ls" ] && date -d "$ls" +%s 2>/dev/null
|
||||
}
|
||||
|
||||
_age() {
|
||||
local epoch="${1:-$now}"
|
||||
# garde-fou : si epoch n'est pas numerique, age=0
|
||||
[[ "$epoch" =~ ^[0-9]+$ ]] || epoch="$now"
|
||||
local age="$(( now - epoch ))"
|
||||
(( age < 0 )) && age=0
|
||||
if (( age<60 )); then echo "${age}s"
|
||||
elif (( age<3600 )); then echo "$(( age/60 ))m"
|
||||
elif (( age<86400));then echo "$(( age/3600 ))h$(( (age%3600)/60 ))m"
|
||||
else echo "$(( age/86400 ))j$(( (age%86400)/3600 ))h"; fi
|
||||
}
|
||||
|
||||
# Une ligne TSV par session vivante. Nettoie les sockets morts au passage.
|
||||
emit_raw() {
|
||||
shopt -s nullglob
|
||||
local sock name pids master cwd start
|
||||
for sock in "$DTDIR"/*; do
|
||||
[ -S "$sock" ] || continue
|
||||
name=$(basename "$sock")
|
||||
pids=$(_pids_for_socket "$sock")
|
||||
if [ -z "$pids" ]; then rm -f "$sock"; continue; fi # orphelin -> menage
|
||||
master=$(echo "$pids" | head -1)
|
||||
cwd=$(_cwd_of "$master"); cwd="${cwd:-?}"
|
||||
start=$(_starttime_of "$master"); start="${start:-$now}"
|
||||
# colonnes : NOM(cache pour fzf) TAB DOSSIER TAB AGE
|
||||
printf '%s\t%s\t%s\n' "$name" "${cwd/#$HOME/\~}" "$(_age "$start")"
|
||||
done
|
||||
}
|
||||
|
||||
cmd_at() {
|
||||
local name="${1:?nom requis}"; local sock="$DTDIR/$name"
|
||||
[ -S "$sock" ] || { echo "Session '$name' introuvable."; return 1; }
|
||||
exec dtach -a "$sock" -e '^\'
|
||||
}
|
||||
|
||||
cmd_kill() {
|
||||
local name="${1:?nom requis}"; local sock="$DTDIR/$name"
|
||||
[ -S "$sock" ] || { echo "Session '$name' introuvable."; return 1; }
|
||||
local master; master=$(_pids_for_socket "$sock" | head -1)
|
||||
[ -n "$master" ] && kill "$master" 2>/dev/null
|
||||
rm -f "$sock"
|
||||
echo "Session '$name' tuee."
|
||||
}
|
||||
|
||||
cmd_sock() { echo "$DTDIR/${1:?nom requis}"; }
|
||||
|
||||
cmd_ls() {
|
||||
local rows; rows=$(emit_raw)
|
||||
if [ -z "$rows" ]; then echo "Aucune session dtach."; return 0; fi
|
||||
printf '\n \033[1;36mSessions claude\033[0m\n\n'
|
||||
printf ' \033[2m%-40s %s\033[0m\n' DOSSIER AGE
|
||||
printf '%s\n' "$rows" | while IFS=$'\t' read -r name cwd age; do
|
||||
printf ' \033[34m%-40s\033[0m \033[33m%s\033[0m\n' "$cwd" "$age"
|
||||
done
|
||||
printf '\n'
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
ls) shift; cmd_ls "$@" ;;
|
||||
at) shift; cmd_at "$@" ;;
|
||||
kill) shift; cmd_kill "$@" ;;
|
||||
sock) shift; cmd_sock "$@" ;;
|
||||
--raw) emit_raw ;;
|
||||
""|-h|--help) sed -n '2,18p' "$0" | sed 's/^# \?//' ;;
|
||||
*) echo "Commande inconnue: $1"; sed -n '2,18p' "$0" | sed 's/^# \?//'; exit 1 ;;
|
||||
esac
|
||||
54
bin/dtach-router
Executable file
54
bin/dtach-router
Executable file
@ -0,0 +1,54 @@
|
||||
#!/usr/bin/env bash
|
||||
# dtach-router — au login SSH : s'il existe des sessions claude/dtach, affiche
|
||||
# le dashboard et propose d'en rejoindre une (fzf). Sinon, shell normal direct.
|
||||
# NE CREE PLUS de session (la creation se fait via l'alias `cc`).
|
||||
# À SOURCER depuis ~/.bashrc. Pas de set -e/-u (fuiraient dans le shell hote).
|
||||
set -o pipefail
|
||||
|
||||
# SSH interactif uniquement ; sinon on ne fait rien.
|
||||
case $- in *i*) ;; *) return 0 2>/dev/null ;; esac
|
||||
|
||||
DT="${DT_BIN:-dt}"
|
||||
|
||||
raw=$("$DT" --raw 2>/dev/null)
|
||||
|
||||
# Aucune session : shell normal, zero friction.
|
||||
[ -z "$raw" ] && return 0 2>/dev/null
|
||||
|
||||
# Des sessions existent : dashboard + menu de selection.
|
||||
"$DT" ls || true
|
||||
|
||||
out=$(printf '%s\n' "$raw" | \
|
||||
fzf --with-nth=2,3 --delimiter='\t' \
|
||||
--header=$'ENTER=rejoindre une session ESC=shell normal' \
|
||||
--preview 'echo "Dossier : {2}"; echo "Age : {3}"' \
|
||||
--preview-window=down,15% )
|
||||
|
||||
# ESC / selection vide : shell normal.
|
||||
[ -z "$out" ] && return 0 2>/dev/null
|
||||
|
||||
name=$(printf '%s' "$out" | cut -f1)
|
||||
dt at "$name" </dev/tty >/dev/tty 2>&1
|
||||
return 0 2>/dev/null
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# INSTALLATION (~/.bashrc serveur), a la fin :
|
||||
#
|
||||
# # creation de session claude-dans-dtach
|
||||
# dtach_claude() { dtach -c "$HOME/.dtach/${1:-claude-$(date +%H%M%S)}" -e '^\' claude; }
|
||||
# alias cc='dtach_claude'
|
||||
#
|
||||
# # dashboard + reprise au login
|
||||
# [ -x "$HOME/.local/bin/dtach-router" ] && source "$HOME/.local/bin/dtach-router"
|
||||
#
|
||||
# # rappeler le menu depuis un shell :
|
||||
# alias d='source ~/.local/bin/dtach-router'
|
||||
#
|
||||
# chmod +x ~/.local/bin/dt ~/.local/bin/dtach-router
|
||||
#
|
||||
# USAGE :
|
||||
# cd ~/projets/seo && cc seo -> claude dans dtach, session "seo"
|
||||
# Ctrl-\ -> detache, retour au shell
|
||||
# (au prochain login) -> dashboard propose "seo", ENTER pour reprendre
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
|
||||
Loading…
Reference in New Issue
Block a user