docs(cloud): 雲端 session 用 claude plugin marketplace 裝真身 ISEP,不再複製薄殼
inkstone/ISEP#5 —— 用 code-on-web 的 Setup script(session 啟動前跑)+ claude plugin marketplace add/install 把 ISEP 裝成 user-scope plugin, 內容 100% 來自 ISEP repo HEAD,滿足 docs/governance §11.3 三條硬規則。 本機隔離 $HOME 實測兩輪(token-in-URL、git url.insteadOf 重寫兩種認證寫法皆可行), skills/commands/hooks 逐條數字對上 ISEP 當時的 main HEAD(含途中 main 前進, 下次 marketplace update 立刻拿到新內容,證明非快照複製)。 明確未驗:真實 code-on-web session、hook runtime 攔截、7 天 environment 快照 新鮮度窗口——皆記在 docs/cloud-session-bootstrap.md「沒驗到的」段。 不動 youlinhsieh/inkstoneco(新機制不需要它)、不 push GitHub、不 push main。
This commit is contained in:
@@ -0,0 +1,171 @@
|
||||
# 雲端 session 怎麼載到 ISEP —— inkstone/ISEP#5
|
||||
|
||||
對照 `docs/governance/sdd-gitea-governance.md` §11.3「載入契約」三條硬規則
|
||||
(L11.3.1/L11.3.2/L11.3.3)與其驗收標準(L11.3.4:能貼出雲端實際載到的清單,
|
||||
逐條對上 ISEP 的註冊條數)。本檔記錄機制、實測結果、還缺什麼。
|
||||
|
||||
## 舊模型死在哪(不要重蹈)
|
||||
|
||||
`InkStoneCo/.claude/cloud-shell/`(`generate-shell-payload.py` + 薄殼)的模型是:
|
||||
真身 hook/command/skill → 產生器跑出「薄殼該長的樣子」→ 人推一份**複製本**進
|
||||
GitHub 私 repo `youlinhsieh/inkstoneco`。這條鏈上兩個環節都「要有人記得」:
|
||||
|
||||
- 忘了重跑產生器 → 產物落後真身
|
||||
- 產物沒推 → 薄殼落後產物
|
||||
|
||||
`inkstone/InkStoneCo#57` 的實測:薄殼比真身少 7 支閘,其中兩支才立一天。
|
||||
`#14` 更早:雲端 33 支閘一支都沒生效。**兩次同一個病**:任何「複製一份」的設計,
|
||||
新鮮度只能靠人記得,而人會忘。
|
||||
|
||||
## 新機制:讓 Claude Code 自己的 plugin marketplace 去裝真身
|
||||
|
||||
不做複製,改用 Claude Code 原生支援、且經官方文件證實可行的路徑:
|
||||
|
||||
1. **ISEP 本身已經是一個合法 plugin**(`.claude-plugin/plugin.json` +
|
||||
`.claude-plugin/marketplace.json`,另一張票的產物),hook 一律用
|
||||
`${CLAUDE_PLUGIN_ROOT}`,不寫死路徑。
|
||||
2. Cloud environment 的 **Setup script**(code-on-web 原生功能,
|
||||
在 Claude Code 啟動**之前**跑,跑在同一台會被拍成快照的 VM 上)裡跑:
|
||||
```
|
||||
claude plugin marketplace add https://git.uncle6.me/inkstone/ISEP.git --scope user
|
||||
claude plugin install isep@inkstone --scope user
|
||||
```
|
||||
這兩行**不是複製**——跟本機 `claude plugin install` 是同一條路徑,裝的內容
|
||||
100% 來自 ISEP 這個 repo 的 HEAD,沒有第二份、沒有產生器、沒有「子集」。
|
||||
3. Setup script 跑完,Anthropic 把整個檔案系統(含 `~/.claude/plugins/`)拍成快照,
|
||||
之後每個新 session 直接沿用快照,**在 Claude Code 啟動當下**(不是「clone 完才補」)
|
||||
plugin 就已經在磁碟上——`command`/`skill` 啟動時的目錄掃描掃得到,不再是
|
||||
`#14` 那個「hook 可以晚到、skill/command 不行」的破口(見 L11.3.2)。
|
||||
|
||||
私有 repo 的認證:不把 token 寫進任何檔案,改用官方文件建議的 CI/CD 寫法——
|
||||
用 `GITEA_TOKEN_CLAUDE_CODE`(既有機器帳號 token,`InkStoneCo#14` 已建立的同一把,
|
||||
沒有新造)在 Setup script 裡做一次 git URL 重寫:
|
||||
|
||||
```sh
|
||||
git config --global url."https://x-access-token:${GITEA_TOKEN_CLAUDE_CODE}@git.uncle6.me/".insteadOf \
|
||||
"https://git.uncle6.me/"
|
||||
```
|
||||
|
||||
`marketplace add` 用乾淨網址(不帶 token),認證完全交給上面那條重寫,
|
||||
所以 `known_marketplaces.json` 裡存的來源網址也不帶 token
|
||||
(本機實測驗過,見下面「已驗」第 2 輪)。
|
||||
|
||||
完整腳本:`docs/cloud-setup-script.sh`(貼進 code-on-web 的 Setup script 欄位用)。
|
||||
|
||||
## 已驗(本機,隔離環境,不影響本機正在跑的任何 session)
|
||||
|
||||
🔴 **怎麼保證沒有干擾**:全程把 `$HOME` 指到 scratchpad 底下的隔離目錄
|
||||
(`isep-test-home`/`isep-test-home2`),從未寫到真正的 `~/.claude/`,
|
||||
也沒有動到 `InkStoneCo/.claude/` 那份舊設定。兩者互不相干,
|
||||
本機目前跑著的其他 session/agent 全程沒受影響。
|
||||
|
||||
**第 1 輪**(token 直接嵌在 marketplace URL 裡,沿用 ISEP 這個 git checkout
|
||||
本來就有的、已解析好的 origin 憑證——不是我另外造的憑證,是既有機制解析出來的那份):
|
||||
|
||||
```
|
||||
$ claude plugin marketplace add "https://claude-code:<token>@git.uncle6.me/inkstone/ISEP.git" --scope user
|
||||
Adding marketplace…Refreshing marketplace cache (timeout: 120s)…
|
||||
Cloning repository (timeout: 120s): https://***:***@git.uncle6.me/inkstone/ISEP.git
|
||||
Clone complete, validating marketplace…
|
||||
✔ Successfully added marketplace: inkstone (declared in user settings)
|
||||
|
||||
$ claude plugin install isep@inkstone --scope user
|
||||
Installing plugin "isep@inkstone"...✔ Successfully installed plugin: isep@inkstone (scope: user)
|
||||
|
||||
$ claude plugin list
|
||||
Installed plugins:
|
||||
❯ isep@inkstone
|
||||
Version: 0.0.0
|
||||
Scope: user
|
||||
Status: ✔ enabled
|
||||
```
|
||||
|
||||
**第 2 輪**(重跑一次,改用實際要交付的「乾淨 URL + git config url.insteadOf 重寫」
|
||||
寫法,驗證 §建議腳本 那段真的可行,而不是理論上可行):
|
||||
|
||||
```
|
||||
$ git config --global url."https://x-access-token:<token>@git.uncle6.me/".insteadOf "https://git.uncle6.me/"
|
||||
$ claude plugin marketplace add https://git.uncle6.me/inkstone/ISEP.git --scope user
|
||||
✔ Successfully added marketplace: inkstone (declared in user settings)
|
||||
$ claude plugin install isep@inkstone --scope user
|
||||
✔ Successfully installed plugin: isep@inkstone (scope: user)
|
||||
|
||||
$ cat ~/.claude/plugins/known_marketplaces.json
|
||||
{
|
||||
"inkstone": {
|
||||
"source": { "source": "git", "url": "https://git.uncle6.me/inkstone/ISEP.git" },
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
⇒ 存在磁碟上的 marketplace 來源紀錄**不帶 token**——符合 L11.3.3「憑證只准取名字」。
|
||||
|
||||
**逐條對照**(`claude plugin details isep@inkstone` 的輸出 + 直接數快取目錄裡的檔案,
|
||||
對照 ISEP 這次測試當下的 Gitea `main` HEAD):
|
||||
|
||||
| | 裝到本機隔離環境的 | ISEP `main` 當下的來源 | 對上了嗎 |
|
||||
|---|---|---|---|
|
||||
| Skills | 9(`cp-write`/`deep-recall`/`issue-handle`/`sdd-check`/`ship-check`/`wiki-capture`/`wiki-init`/`wiki-recall`/`wiki-update`) | `commands/` 7 支 + `skills/` 2 支 = 9 | ✅ 逐支比對名稱一致 |
|
||||
| Commands 目錄 | 7 個 `.md` | 7 個 `.md` | ✅ `diff` 兩邊檔名清單完全一致 |
|
||||
| Skills 目錄 | `deep-recall`/`ship-check` 2 個 | 同 | ✅ |
|
||||
| Hook 腳本(`hooks/*.sh` 實體檔) | 42 支 | 42 支 | ✅ `ls` 兩邊都是 42 |
|
||||
| `hooks.json` 裡註冊的 hook 腳本路徑(去重) | 39 支唯一路徑 | 39 支 | ✅ `diff` 兩邊 grep 結果完全一致 |
|
||||
|
||||
(42 支實體檔 vs 39 支被 `hooks.json` 引用:差的 3 支是 `hooks.json` 自己
|
||||
+ `pre-write-guard.sh`/`pre-write-guard.template.sh` 這類非直接掛註冊的輔助檔,
|
||||
兩邊都一樣,不是漏裝。)
|
||||
|
||||
**意外的額外證據**:測試途中 ISEP 的 `main` 因為別的票(`#6`/`#9` 等)合併而往前推進
|
||||
(多出 `hooks/release-tag-guard.sh`、`docs/governance/`…),**下一次 `claude plugin
|
||||
marketplace update` / 重裝立刻拿到新內容**——證明這條路徑讀的是 Gitea 當下的 HEAD,
|
||||
不是任何時間點的快照複製本。
|
||||
|
||||
## 沒驗到的(明講,不含糊)
|
||||
|
||||
- ❌ **沒有在真正的 code-on-web 雲端 session 裡跑過。** 本機能做到的最接近測試是
|
||||
「隔離 `$HOME` + 真的私有 repo + 真的 `claude plugin` CLI」,但 Cloud environment
|
||||
的 Setup script 欄位、Environment variables 欄位是 claude.ai 帳號層級的設定,
|
||||
我沒有去改——那是 leo 的 dashboard,不是這台機器上的檔案,我也判斷這件事
|
||||
不屬於「可以自己裁」的範圍(不是 GitHub push,但同樣是帳號層級設定,
|
||||
比照 D20 的精神交給 leo 動手)。
|
||||
- ❌ **沒有驗到「hook 真的攔下第一個工具呼叫」這一步的完整 runtime 行為**——
|
||||
只驗到「plugin 在磁碟上正確就位、`claude plugin list` 回報 enabled」。
|
||||
完整跑一個已登入的 `claude -p` session需要這台機器的 Claude Code 登入憑證
|
||||
(存在 macOS Keychain,不是可複製的檔案),我判斷把它匯出到隔離測試環境
|
||||
超出這張票該做的事,沒有做。
|
||||
Q5(plugin 是否在 SessionStart 前同步就位、保證第一個工具呼叫就有效)
|
||||
這格的證據來自官方文件(`plugin-marketplaces.md` §Pre-populate plugins for
|
||||
containers:「At startup, Claude Code registers marketplaces found in the
|
||||
seed's `known_marketplaces.json`... This works in both interactive mode and
|
||||
non-interactive mode with the `-p` flag.」),**不是我自己重現的 runtime 實測**。
|
||||
- ❌ **Environment caching 的 ~7 天新鮮度窗口沒有解**——setup script 只在
|
||||
「這個 environment 第一次開 session」跑一次,之後沿用快照,直到快照過期
|
||||
(約 7 天)或 leo 改了 setup script/allowed network hosts 才重跑。
|
||||
這代表 ISEP 若在窗口期內更新,雲端會暫時停在舊版本,直到快照重建。
|
||||
這不是本票要解的「載不載得到」問題,而是另一種新鮮度問題,**留給 leo 決定
|
||||
要不要另開票**(例如:leo 定期手動點一下「rebuild environment」,或接受
|
||||
7 天週期)。
|
||||
|
||||
## 需要 leo 做的(帳號層級設定,非 GitHub push,但同樣是我不該自己動的地方)
|
||||
|
||||
去 code-on-web 的 **Cloud environments** 設定(`claude.ai` 帳號設定,不是
|
||||
GitHub、不是 Gitea):
|
||||
|
||||
1. 選 InkStoneCo 這條線在用的 environment(或建一個新的),
|
||||
**Environment variables** 欄位加一行:`GITEA_TOKEN_CLAUDE_CODE=<既有那把值>`
|
||||
(名字沿用 `InkStoneCo#14` 已建立的那把,不要新造;值只有 leo 知道要填什麼,
|
||||
我這邊沒有也不該有)。
|
||||
2. **Setup script** 欄位貼 `docs/cloud-setup-script.sh` 的內容。
|
||||
3. 開一個新 session(或用 dashboard 的「rebuild environment」逼快照重建),
|
||||
驗 `claude plugin list` 顯示 `isep@inkstone enabled`,且照 §逐條對照 那張表
|
||||
再核一次數字。
|
||||
|
||||
## 這張票沒動、也不會動的東西
|
||||
|
||||
- 沒有動 `youlinhsieh/inkstoneco`(GitHub 薄殼 repo)——這個新機制**完全不需要
|
||||
改它**:安裝目標是 `--scope user`(VM 家目錄),跟 session 從哪個 cwd 啟動無關。
|
||||
舊模型需要在薄殼裡放 `.claude/settings.json` 指標,新模型不需要。
|
||||
- 沒有 push 到 GitHub、沒有 push 到本 repo的 `main`。全部改動只在
|
||||
`leaf/5-cloud` 這條分支。
|
||||
- 沒有把任何 token 值寫進這個 repo 的任何檔案(`docs/cloud-setup-script.sh`
|
||||
只引用環境變數名字 `GITEA_TOKEN_CLAUDE_CODE`)。
|
||||
@@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env bash
|
||||
# 貼進 code-on-web「Cloud environments → 你的環境 → Setup script」欄位的內容。
|
||||
# 不是 ISEP 的一部分(不會被 Claude Code 當 hook/command/skill 掃描),
|
||||
# 純粹是給 leo 複製貼上的參考檔,見 docs/cloud-session-bootstrap.md。
|
||||
#
|
||||
# 前提(要先在同一個 Cloud environment 的 Environment variables 欄位加好):
|
||||
# GITEA_TOKEN_CLAUDE_CODE ← 既有機器帳號 token,名字沿用 InkStoneCo#14 已建立的那把,
|
||||
# 不要新造一把。值本身不寫在這支腳本或任何檔案裡。
|
||||
#
|
||||
# 這支腳本做兩件事:
|
||||
# 1. 設定 git URL 重寫,讓任何對 git.uncle6.me 的 clone 都能用 GITEA_TOKEN_CLAUDE_CODE 認證
|
||||
# (官方文件對「CI/CD 裝私有 marketplace」建議的寫法,見 references 段)。
|
||||
# 2. 直接把 ISEP 裝成 user-scope plugin ——不是「複製一份」,是跟本機一樣走
|
||||
# `claude plugin marketplace add` + `claude plugin install`,裝的東西
|
||||
# 100% 來自 inkstone/ISEP 這個 repo 本身,沒有第二份內容。
|
||||
#
|
||||
# 何時跑:只在「這個 Cloud environment 第一次開 session」時跑一次,
|
||||
# 跑完 Anthropic 會把整個檔案系統拍成快照,之後的 session 直接沿用快照
|
||||
# (不重跑,除非改了這支腳本本身、改了 allowed network hosts、或快照滿 7 天過期)。
|
||||
# ⇒ 這是唯一會讓「ISEP 改了但雲端還是舊的」重新出現的地方,
|
||||
# 緩解法見 docs/cloud-session-bootstrap.md「已知限制」段。
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
if [ -z "${GITEA_TOKEN_CLAUDE_CODE:-}" ]; then
|
||||
echo "❌ 找不到 GITEA_TOKEN_CLAUDE_CODE —— 去 Cloud environment 的 Environment variables 加這個名字" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 官方文件建議的私有 marketplace 認證寫法:只重寫這個 host 的 URL,不動其他 git 操作。
|
||||
git config --global url."https://x-access-token:${GITEA_TOKEN_CLAUDE_CODE}@git.uncle6.me/".insteadOf \
|
||||
"https://git.uncle6.me/"
|
||||
|
||||
# 用乾淨網址(不帶 token)加 marketplace,實際認證交給上面那條 URL 重寫。
|
||||
claude plugin marketplace add https://git.uncle6.me/inkstone/ISEP.git --scope user
|
||||
claude plugin install isep@inkstone --scope user
|
||||
|
||||
echo "✅ ISEP 已裝成 user-scope plugin,之後每個 session 啟動時直接生效。"
|
||||
Reference in New Issue
Block a user