Files
ISEP/scripts/gitea-arm-request.sh
Leo c2638668e3 ISEP 0.1.0:環境設定收成一個 plugin,本機與雲端共用一份
leo 2026-08-20:「同一個 plugin 你用,薄殼也用,保證兩邊同步」
              「我要你幫雲端做薄殼,永遠都有問題,你要做的就是這組設定
                你自己可以 dogfooding」

搬進來:41 支 hook(51 條註冊)/7 支 command/2 支 skill/23 支腳本。
不搬 .env、wiki、docs——那些是知識不是環境。

51 條 hook 路徑全部從 $CLAUDE_PROJECT_DIR/.claude/hooks/ 改成 ${CLAUDE_PLUGIN_ROOT}/hooks/,
零漏網。那正是薄殼一直壞掉的根:雲端 cwd 不是真身,寫死路徑就斷。

尚未驗證:Claude Code 能不能從私有 Gitea repo 裝 marketplace(要憑證)。
下一步就是在本機實際裝一次,通了才動雲端 bootstrap.sh。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 11:41:46 +08:00

146 lines
6.7 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
# gitea-arm-request.sh — 開一個「等 leo 在 Gitea 上回覆」的請求
#
# 用法:scripts/gitea-arm-request.sh <票號> "任務描述" [有效分鐘,預設30,上限60]
#
# 這支**machine 自己就能跑**(不像 scripts/github-arm.sh 要求終端機互動)——
# 因為「請求」本身不是安全邊界,**leo 用 Leo 帳號回覆才是**。
# 這支只負責:生一組一次性代碼、貼上呼叫端指定的那張 Gitea 票、把代碼與人話訊息印出來
# (讓呼叫者拿去用 notify_leoTelegram 轉告 leo;本支不碰 Telegram)。
#
# 🪦 2026-08-16 起票號不再寫死(原本固定貼 inkstone/InkStoneCo#34「頻道票」)——
# leo:「我不要把所有的票都放在一個 issues,不然就難 track 歷史記錄」。
# **解哪張票的保險,就把請求貼在那張票上**:要出貨 X 就傳 X 那張票的號碼,
# 不要再統一貼 #34(#34 保留 open 當歷史,見 issues/34#issuecomment-2804)。
#
# 之後由 scripts/gitea-arm-check.sh 核對 leo 有沒有在同一張票回覆同一組代碼。
set -euo pipefail
SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
# shellcheck source=lib/gitea-arm-common.sh
. "$SCRIPT_DIR/lib/gitea-arm-common.sh"
ISSUE="${1:-}"
MISSION="${2:-}"
MINUTES="${3:-30}"
if [ -z "$ISSUE" ] || [ -z "$MISSION" ]; then
echo "用法:scripts/gitea-arm-request.sh <票號> \"任務描述\" [分鐘,預設30,上限60]" >&2
echo "例:scripts/gitea-arm-request.sh 41 \"出 prodxxx\"" >&2
exit 1
fi
# 票號可寫 `N`(預設 InkStoneCo)或 `repo#N`(同 org 的別的 repo,例:arcrun-rag#115
case "$ISSUE" in
*"#"*)
_R="${ISSUE%%#*}"; ISSUE="${ISSUE##*#}"
gitea_arm_set_repo "$_R" || exit 1;;
esac
if ! gitea_arm_valid_issue "$ISSUE"; then
echo "❌ 票號要是純數字,或 repo#N(收到:$ISSUE)——解哪張票的保險就填那張票" >&2
exit 1
fi
case "$MINUTES" in
''|*[!0-9]*)
echo "❌ 第三個參數要純數字分鐘(收到:$MINUTES" >&2
exit 1;;
esac
[ "$MINUTES" -le 60 ] || MINUTES=60
[ "$MINUTES" -ge 1 ] || MINUTES=1
TOKEN=$(gitea_arm_token) || { echo "❌ 讀不到 GITEA_TOKEN_CLAUDE_CODE(頂層 .env" >&2; exit 1; }
NONCE="ARM-$(python3 -c 'import secrets; print(secrets.token_hex(4))')"
NOW=$(date +%s)
EXPIRES=$((NOW + MINUTES * 60))
EXPIRES_HUMAN=$(date -r "$EXPIRES" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || date -d "@$EXPIRES" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || echo "$EXPIRES")
STATE_DIR=$(gitea_arm_state_dir)
PENDING_FILE="$STATE_DIR/pending/${NONCE}.json"
# ── 貼留言到呼叫端指定的那張票 ─────────────────────────────────────────
COMMENT_BODY=$(MISSION="$MISSION" NONCE="$NONCE" EXPIRES_HUMAN="$EXPIRES_HUMAN" MINUTES="$MINUTES" python3 -c '
import os
mission = os.environ["MISSION"]
nonce = os.environ["NONCE"]
expires_human = os.environ["EXPIRES_HUMAN"]
minutes = os.environ["MINUTES"]
print(f"""🔐 ARM 請求
任務:{mission}
有效時限:{minutes} 分鐘內({expires_human} 前)
📱 回這則、貼上下面這串就解鎖這一次(用完就失效,別的請求不能借用):
{nonce}
""")
')
REQUEST_JSON=$(jq -n --arg body "$COMMENT_BODY" '{body: $body}')
RESP=$(curl -s -w '\n%{http_code}' -X POST \
-H "Authorization: token $TOKEN" \
-H "Content-Type: application/json" \
-d "$REQUEST_JSON" \
"$GITEA_ARM_API/repos/$GITEA_ARM_OWNER/$GITEA_ARM_REPO/issues/$ISSUE/comments")
HTTP_CODE=$(printf '%s' "$RESP" | tail -1)
RESP_BODY=$(printf '%s' "$RESP" | sed '$d')
if [ "$HTTP_CODE" != "201" ]; then
echo "❌ 貼留言失敗(HTTP $HTTP_CODE),沒有建立請求——票號 #$ISSUE 存在嗎?" >&2
echo "$RESP_BODY" >&2
exit 1
fi
COMMENT_ID=$(printf '%s' "$RESP_BODY" | jq -r '.id // empty')
CREATED_AT=$(printf '%s' "$RESP_BODY" | jq -r '.created_at // empty')
if [ -z "$COMMENT_ID" ] || [ -z "$CREATED_AT" ]; then
echo "❌ Gitea 回應解不出留言 id/時間,視為失敗(fail-closed,沒寫本地狀態)" >&2
exit 1
fi
jq -n \
--arg nonce "$NONCE" \
--arg mission "$MISSION" \
--arg issue "$ISSUE" \
--arg repo "$GITEA_ARM_REPO" \
--argjson requested_at "$NOW" \
--argjson expires_at "$EXPIRES" \
--arg request_comment_id "$COMMENT_ID" \
--arg request_created_at "$CREATED_AT" \
'{nonce:$nonce, mission:$mission, issue:$issue, repo:$repo, requested_at:$requested_at, expires_at:$expires_at,
request_comment_id:$request_comment_id, request_created_at:$request_created_at}' \
> "$PENDING_FILE"
# ── 貼完請求就指派給 leo 掛 Humanleo 2026-08-16:「沒有指派,沒有加 Human」)──
#
# 為什麼要自動:總管同一天才立下「指派給 leo 的每一張都要真的在等他」,
# 貼完 ARM 就忘了做 ⇒ leo 的「指派給您的」清單漏掉那張真正在等他的票。
# ⇒ 這件事不該靠人記得:**貼出 ARM 請求 = 這張票此刻在等 leo**,兩者是同一件事。
_LBL=$(curl -s -H "Authorization: token $TOKEN" \
"$GITEA_ARM_API/repos/$GITEA_ARM_OWNER/$GITEA_ARM_REPO/labels?limit=60" \
| jq -r '.[] | select(.name=="Human") | .id' 2>/dev/null | head -1)
_CUR=$(curl -s -H "Authorization: token $TOKEN" \
"$GITEA_ARM_API/repos/$GITEA_ARM_OWNER/$GITEA_ARM_REPO/issues/$ISSUE" \
| jq -r '[.labels[].id] | @csv' 2>/dev/null | tr -d '"')
if [ -n "$_LBL" ]; then
_IDS=$(printf '%s,%s' "${_CUR:-}" "$_LBL" | tr ',' '\n' | grep -E '^[0-9]+$' | sort -u | paste -sd, -)
curl -s -o /dev/null -X PUT -H "Authorization: token $TOKEN" -H "Content-Type: application/json" \
-d "{\"labels\":[${_IDS}]}" \
"$GITEA_ARM_API/repos/$GITEA_ARM_OWNER/$GITEA_ARM_REPO/issues/$ISSUE/labels"
fi
curl -s -o /dev/null -X PATCH -H "Authorization: token $TOKEN" -H "Content-Type: application/json" \
-d "{\"assignees\":[\"$GITEA_ARM_APPROVER_LOGIN\"]}" \
"$GITEA_ARM_API/repos/$GITEA_ARM_OWNER/$GITEA_ARM_REPO/issues/$ISSUE"
echo " (已指派給 $GITEA_ARM_APPROVER_LOGIN 並掛上 Human——他的「指派給您的」看得到這張)"
echo "✅ 已在 Gitea 貼出請求:https://git.uncle6.me/$GITEA_ARM_OWNER/$GITEA_ARM_REPO/issues/$ISSUE"
echo ""
echo "── 轉告 leo(照這樣發,一句狀況+一個可回的詞)──────────────────"
echo "[總管] 需要你解一道鎖:$MISSION"
echo "回這則 Gitea 留言貼「$NONCE」就好($MINUTES 分鐘內有效):https://git.uncle6.me/$GITEA_ARM_OWNER/$GITEA_ARM_REPO/issues/$ISSUE"
echo "──────────────────────────────────────────────────────────────"
echo ""
echo "nonce=$NONCE"
echo "issue=$ISSUE"
echo "expires_at=$EXPIRES ($EXPIRES_HUMAN)"
echo "本地請求檔:$PENDING_FILE"