diff --git a/claude_relogin.sh b/claude_relogin.sh new file mode 100755 index 0000000..fca3341 --- /dev/null +++ b/claude_relogin.sh @@ -0,0 +1,105 @@ +#!/usr/bin/env bash +# +# claude_relogin.sh — 이 폴더의 Claude 계정을 OAuth로 재로그인한다. +# +# 사용법: +# ./claude_relogin.sh +# +# 동작: +# 1) 이 스크립트가 있는 폴더의 .env 에서 CLAUDE_CREDENTIALS_PATH 를 읽어온다. +# 2) 인증(authorize) URL 을 출력한다 → 브라우저로 열어 로그인한다. +# 3) 로그인 완료 후 화면에 나오는 코드를 붙여넣는다. +# 4) 코드를 토큰으로 교환해 credentials 파일에 저장한다. +# 5) 해당 systemd(--user) 서비스를 재시작한다. +# +set -euo pipefail + +# --- 이 스크립트가 있는 폴더로 이동 --------------------------------------- +DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$DIR" + +# --- bun 실행 파일 찾기 ---------------------------------------------------- +BUN="$(command -v bun 2>/dev/null || true)" +[ -z "$BUN" ] && [ -x "$HOME/.bun/bin/bun" ] && BUN="$HOME/.bun/bin/bun" +if [ -z "$BUN" ]; then + echo "❌ bun 을 찾을 수 없습니다. PATH 를 확인하세요." >&2 + exit 1 +fi + +# --- .env 에서 CLAUDE_CREDENTIALS_PATH 읽기 -------------------------------- +if [ ! -f .env ]; then + echo "❌ $DIR/.env 가 없습니다." >&2 + exit 1 +fi +CRED_PATH="$(grep -E '^CLAUDE_CREDENTIALS_PATH=' .env | tail -n1 | cut -d= -f2- | tr -d '"'"'"'' )" +if [ -z "${CRED_PATH:-}" ]; then + echo "❌ .env 에 CLAUDE_CREDENTIALS_PATH 가 설정되어 있지 않습니다." >&2 + exit 1 +fi +export CLAUDE_CREDENTIALS_PATH="$CRED_PATH" + +# --- 서비스명 자동 감지 (폴더명 소문자 + .service) ------------------------- +SERVICE="$(basename "$DIR" | tr '[:upper:]' '[:lower:]').service" + +echo "======================================================================" +echo " Claude 재로그인" +echo " 폴더 : $DIR" +echo " credentials : $CLAUDE_CREDENTIALS_PATH" +echo " 서비스 : $SERVICE" +echo "======================================================================" +echo + +# --- 1단계: authorize URL 발급 -------------------------------------------- +echo "▶ 1단계: 로그인 URL 생성 중..." +PHASE1_OUT="$("$BUN" setup/index.ts --step login 2>/dev/null)" +URL="$(printf '%s\n' "$PHASE1_OUT" | grep -E '^AUTHORIZE_URL=' | head -n1 | sed 's/^AUTHORIZE_URL=//')" +if [ -z "$URL" ]; then + echo "❌ 인증 URL 생성 실패. 출력:" >&2 + printf '%s\n' "$PHASE1_OUT" >&2 + exit 1 +fi + +echo +echo "▼ 아래 URL 을 브라우저에서 열어 로그인하세요:" +echo "----------------------------------------------------------------------" +echo "$URL" +echo "----------------------------------------------------------------------" +echo +echo "로그인 완료 후 화면에 표시되는 코드를 복사하세요." +echo "(코드는 'xxxx' 또는 'xxxx#yyyy' 형태 모두 가능합니다.)" +echo + +# --- 코드 입력 받기 -------------------------------------------------------- +printf "붙여넣을 코드: " +read -r CODE +CODE="$(printf '%s' "$CODE" | tr -d '[:space:]')" +if [ -z "$CODE" ]; then + echo "❌ 코드가 비어 있습니다. 중단합니다." >&2 + exit 1 +fi + +# --- 2단계: 코드 → 토큰 교환 ---------------------------------------------- +echo +echo "▶ 2단계: 코드 교환 및 credentials 저장 중..." +if "$BUN" setup/index.ts --step login --code "$CODE"; then + echo "✅ 로그인 성공 — credentials 저장 완료: $CLAUDE_CREDENTIALS_PATH" +else + echo "❌ 코드 교환 실패. 코드가 만료되었거나 잘못되었을 수 있습니다." >&2 + echo " 스크립트를 다시 실행해 새 URL 로 시도하세요." >&2 + exit 1 +fi + +# --- 3단계: 서비스 재시작 -------------------------------------------------- +echo +echo "▶ 3단계: $SERVICE 재시작..." +if systemctl --user restart "$SERVICE" 2>/dev/null; then + sleep 1 + STATE="$(systemctl --user is-active "$SERVICE" 2>/dev/null || true)" + echo "✅ $SERVICE 상태: $STATE" +else + echo "⚠ $SERVICE 재시작을 건너뜀(서비스가 없거나 권한 없음). 수동으로 재시작하세요:" >&2 + echo " systemctl --user restart $SERVICE" >&2 +fi + +echo +echo "완료. 🎉" diff --git a/codex_relogin.sh b/codex_relogin.sh new file mode 100755 index 0000000..e08ed12 --- /dev/null +++ b/codex_relogin.sh @@ -0,0 +1,95 @@ +#!/usr/bin/env bash +# +# codex_relogin.sh — Codex(ChatGPT) 계정을 device-code OAuth 로 재로그인한다. +# +# 사용법: +# ./codex_relogin.sh # 기본 슬롯(0) 재로그인 +# ./codex_relogin.sh 1 # ~/.codex-accounts/1 슬롯 재로그인 +# +# 참고: +# * Codex 로그인은 이 호스트에서 두 봇(EJClaw / KAClaw)이 "공유"한다. +# 계정 저장 위치가 폴더별이 아니라 ~/.codex-accounts/{n} 이기 때문. +# → 한 번만 재로그인하면 두 봇 모두에 반영된다. +# * codex CLI 가 URL + 일회용 코드를 출력한다(15분 유효). 브라우저에서 +# 로그인/코드입력을 마치면 codex 가 auth.json 을 자동 저장한다. +# +set -euo pipefail + +DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$DIR" + +# --- codex 실행 파일 찾기 -------------------------------------------------- +CODEX="$(command -v codex 2>/dev/null || true)" +if [ -z "$CODEX" ]; then + # fnm 로 설치된 node 전역 bin 에서 탐색 + for c in "$HOME"/.local/share/fnm/node-versions/*/installation/bin/codex; do + [ -x "$c" ] && CODEX="$c" && break + done +fi +if [ -z "$CODEX" ]; then + echo "❌ codex CLI 를 찾을 수 없습니다. 'command -v codex' 로 확인하세요." >&2 + exit 1 +fi + +# --- 대상 슬롯 결정 -------------------------------------------------------- +# 앱은 ~/.codex-accounts/{n}/auth.json 를 읽는다. 없으면 ~/.codex 로 폴백. +SLOT="${1:-0}" +ACCOUNTS_DIR="$HOME/.codex-accounts" +if [ -d "$ACCOUNTS_DIR/$SLOT" ]; then + CODEX_TARGET="$ACCOUNTS_DIR/$SLOT" +elif [ -d "$ACCOUNTS_DIR" ] && ls -d "$ACCOUNTS_DIR"/[0-9]* >/dev/null 2>&1; then + echo "❌ 슬롯 '$SLOT' 이(가) 없습니다. 사용 가능한 슬롯:" >&2 + ls -d "$ACCOUNTS_DIR"/[0-9]* 2>/dev/null | sed 's#.*/# - #' >&2 + exit 1 +else + # accounts 디렉터리가 비어있으면 기본 codex home 사용 + CODEX_TARGET="$HOME/.codex" +fi +export CODEX_HOME="$CODEX_TARGET" + +# --- 서비스명 자동 감지 (폴더명 소문자 + .service) ------------------------- +SERVICE="$(basename "$DIR" | tr '[:upper:]' '[:lower:]').service" + +echo "======================================================================" +echo " Codex 재로그인 (device-code OAuth)" +echo " codex : $CODEX" +echo " CODEX_HOME : $CODEX_HOME (auth.json 저장 위치)" +echo " 서비스 : $SERVICE" +echo " ※ Codex 로그인은 EJClaw/KAClaw 두 봇이 공유합니다." +echo "======================================================================" +echo +echo "▶ 아래 안내대로 브라우저에서 URL 을 열고 표시되는 코드를 입력하세요." +echo " (로그인이 끝나면 자동으로 저장되고 이 창이 넘어갑니다.)" +echo "----------------------------------------------------------------------" + +# --- 로그인 실행 (codex 가 URL/코드 출력 + 폴링까지 담당) ------------------ +if "$CODEX" login --device-auth; then + echo "----------------------------------------------------------------------" + echo "✅ Codex 로그인 성공 — auth.json 저장: $CODEX_HOME/auth.json" +else + echo "----------------------------------------------------------------------" + echo "❌ Codex 로그인 실패 또는 취소됨. 다시 실행해 시도하세요." >&2 + exit 1 +fi + +# --- 상태 확인 ------------------------------------------------------------- +echo +echo "▶ 로그인 상태 확인:" +"$CODEX" login status 2>&1 | sed 's/^/ /' || true + +# --- 서비스 재시작 --------------------------------------------------------- +echo +echo "▶ $SERVICE 재시작..." +if systemctl --user restart "$SERVICE" 2>/dev/null; then + sleep 1 + STATE="$(systemctl --user is-active "$SERVICE" 2>/dev/null || true)" + echo "✅ $SERVICE 상태: $STATE" + echo " (공유 계정이므로 다른 봇도 새 로그인을 사용합니다. 필요하면" + echo " 상대 봇도 재시작하세요: systemctl --user restart <다른서비스>)" +else + echo "⚠ $SERVICE 재시작 건너뜀(서비스 없음/권한 없음). 수동 재시작:" >&2 + echo " systemctl --user restart $SERVICE" >&2 +fi + +echo +echo "완료. 🎉" diff --git a/src/claude-credentials-path.ts b/src/claude-credentials-path.ts new file mode 100644 index 0000000..fa21e91 --- /dev/null +++ b/src/claude-credentials-path.ts @@ -0,0 +1,40 @@ +import os from 'os'; +import path from 'path'; + +function readPathEnv(key: string): string | null { + const value = process.env[key]?.trim(); + return value ? path.resolve(value) : null; +} + +export function getClaudeCredentialsPath( + accountIndex: number, + options: { allowHomeFallback?: boolean } = {}, +): string | null { + const explicitPath = readPathEnv('CLAUDE_CREDENTIALS_PATH'); + if (accountIndex === 0 && explicitPath) return explicitPath; + + const explicitAccountsDir = readPathEnv('CLAUDE_ACCOUNTS_DIR'); + if (accountIndex > 0 && explicitAccountsDir) { + return path.join( + explicitAccountsDir, + String(accountIndex), + '.credentials.json', + ); + } + + if (options.allowHomeFallback === false) return null; + + if (accountIndex === 0) { + return path.join(os.homedir(), '.claude', '.credentials.json'); + } + return path.join( + os.homedir(), + '.claude-accounts', + String(accountIndex), + '.credentials.json', + ); +} + +export function hasExplicitClaudeCredentialsPath(): boolean { + return Boolean(readPathEnv('CLAUDE_CREDENTIALS_PATH')); +}