部署收尾刷新 current 連結農場,讓升版後的技能路徑不再指著舊版 #61
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "jsc-cli",
|
"name": "jsc-cli",
|
||||||
"version": "0.3.2",
|
"version": "0.3.3",
|
||||||
"description": "CLI 偵測、模型能力標籤、子代理派工與技能庫批次部署",
|
"description": "CLI 偵測、模型能力標籤、子代理派工與技能庫批次部署",
|
||||||
"skills": "./skills",
|
"skills": "./skills",
|
||||||
"author": {
|
"author": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "jsc-cli",
|
"name": "jsc-cli",
|
||||||
"version": "0.3.2",
|
"version": "0.3.3",
|
||||||
"description": "CLI 偵測、模型能力標籤、子代理派工與技能庫批次部署",
|
"description": "CLI 偵測、模型能力標籤、子代理派工與技能庫批次部署",
|
||||||
"skills": "./skills",
|
"skills": "./skills",
|
||||||
"jsc": {
|
"jsc": {
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ Marketplace 統一為 `jsc`(https://gitea.jsc.idv.tw/plugins/meta.git),安
|
|||||||
| 工具 | 用途 |
|
| 工具 | 用途 |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `tools/detect-clis.sh` | 列出已安裝的 AI CLI 與執行檔路徑(TSV:name / path / version;antigravity 的執行檔為 `agy`、kiro 為 `kiro-cli`) |
|
| `tools/detect-clis.sh` | 列出已安裝的 AI CLI 與執行檔路徑(TSV:name / path / version;antigravity 的執行檔為 `agy`、kiro 為 `kiro-cli`) |
|
||||||
| `tools/deploy.sh` | 對單一 CLI 執行安裝、更新或解除安裝(`deploy.sh [-n] {mode} {cli} {domain}...`,mode 為 install / update / uninstall);印出每個指令與其結束碼,最後一行 `result` 標 ok 或 fail。`-n` 只印指令不執行。上表五個 CLI 的指令差異全部收在這支腳本裡。Codex 更新 `jsc-cli` 與 `jsc-hooks` 後會把舊版快取路徑補成指向新版的相容連結,避免正在跑的部署流程找不到 helper 腳本,也避免尚未重啟的工作階段在 Stop hook 階段找不到舊路徑。install 或 update 全數成功時,收尾轉呼叫 `jsc-hooks` 的 `restart-gate.sh require` 掛上重啟閘門,並印一行 `restart` 標出狀態檔位置;uninstall 不寫。尋找 `restart-gate.sh` 時優先用 `$JSC_HOME/current/jsc-hooks`、本地 clone 與 Kiro skills,最後才掃各 CLI 快取,避免部署收尾綁死單一 CLI 的版號路徑。狀態檔的路徑、格式與判讀全在 `restart-gate.sh`,這支腳本不自己拼——格式只留一個真實來源。站台取自 `GITEA_HOST`,本地 clone 目錄取自 `JSC_LOCAL_PLUGINS`,兩者的預設值見下表 |
|
| `tools/deploy.sh` | 對單一 CLI 執行安裝、更新或解除安裝(`deploy.sh [-n] {mode} {cli} {domain}...`,mode 為 install / update / uninstall);印出每個指令與其結束碼,最後一行 `result` 標 ok 或 fail。`-n` 只印指令不執行。上表五個 CLI 的指令差異全部收在這支腳本裡。Codex 更新 `jsc-cli` 與 `jsc-hooks` 後會把舊版快取路徑補成指向新版的相容連結,避免正在跑的部署流程找不到 helper 腳本,也避免尚未重啟的工作階段在 Stop hook 階段找不到舊路徑。收尾還會刷新 `$JSC_HOME/current/` 那一組不帶版本號的符號連結(技能文件的跨外掛路徑都以那一層當根):install 與 update 把每一條指到這次裝的版本目錄,uninstall 清掉指向已消失的那幾條,一條印一行 `link`,第五欄就是指向。一台機器只有一組農場,所以只有基準 CLI 那一輪會動它,基準取 claude、codex、copilot、kiro 之中第一支裝得到的;連結刷新失敗只記一行、不讓部署變成失敗。install 或 update 全數成功時,收尾轉呼叫 `jsc-hooks` 的 `restart-gate.sh require` 掛上重啟閘門,並印一行 `restart` 標出狀態檔位置;uninstall 不寫。尋找 `restart-gate.sh` 時優先用 `$JSC_HOME/current/jsc-hooks`、本地 clone 與 Kiro skills,最後才掃各 CLI 快取,避免部署收尾綁死單一 CLI 的版號路徑。狀態檔的路徑、格式與判讀全在 `restart-gate.sh`,這支腳本不自己拼——格式只留一個真實來源。站台取自 `GITEA_HOST`,本地 clone 目錄取自 `JSC_LOCAL_PLUGINS`,兩者的預設值見下表 |
|
||||||
| `tools/check-requires.sh` | `check-requires.sh {cli} {manifest}` 檢查 manifest 的 `jsc.requires` 最低版本。沒有宣告就通過;版本不符或缺相依 plugin 就回 `status=blocked` 與結束碼 1。`deploy.sh update` 在每個 domain 更新前呼叫它一次:結束碼 1 只印一行 `warn`,那個 domain 照樣更新——跳過會讓落後的 domain 永遠等不到相依版本,也就永遠更新不到,真正的阻擋由 `jsc-hooks` 的 `version-guard.sh` 在技能被叫用時執行;結束碼 2 以上是檢查腳本自己出錯,讀不到結論就不當成通過,印 `skip` 並跳過該 domain |
|
| `tools/check-requires.sh` | `check-requires.sh {cli} {manifest}` 檢查 manifest 的 `jsc.requires` 最低版本。沒有宣告就通過;版本不符或缺相依 plugin 就回 `status=blocked` 與結束碼 1。`deploy.sh update` 在每個 domain 更新前呼叫它一次:結束碼 1 只印一行 `warn`,那個 domain 照樣更新——跳過會讓落後的 domain 永遠等不到相依版本,也就永遠更新不到,真正的阻擋由 `jsc-hooks` 的 `version-guard.sh` 在技能被叫用時執行;結束碼 2 以上是檢查腳本自己出錯,讀不到結論就不當成通過,印 `skip` 並跳過該 domain |
|
||||||
| `tools/write-guides.sh` | 產生這台機器專屬的更新指引 `$JSC_HOME/update-guide.md` 與移除指引 `$JSC_HOME/remove-guide.md`(`write-guides.sh [-n] {install\|update} {domain}...`),一輪部署跑一次。CLI 清單取自 `detect-clis.sh`,每支 CLI 的指令字面直接取自 `deploy.sh -n` 的輸出,所以指引寫的就是實際會跑的指令;kiro 走不走本地複製退路也依實際偵測結果標注 |
|
| `tools/write-guides.sh` | 產生這台機器專屬的更新指引 `$JSC_HOME/update-guide.md` 與移除指引 `$JSC_HOME/remove-guide.md`(`write-guides.sh [-n] {install\|update} {domain}...`),一輪部署跑一次。CLI 清單取自 `detect-clis.sh`,每支 CLI 的指令字面直接取自 `deploy.sh -n` 的輸出,所以指引寫的就是實際會跑的指令;kiro 走不走本地複製退路也依實際偵測結果標注 |
|
||||||
| `tools/list-models.sh` | 讀各 CLI 設定檔列出模型(TSV:cli / model / in-use);設定檔缺失就不輸出該 CLI 的列,一律 exit 0。設定檔位置只寫在這支腳本裡 |
|
| `tools/list-models.sh` | 讀各 CLI 設定檔列出模型(TSV:cli / model / in-use);設定檔缺失就不輸出該 CLI 的列,一律 exit 0。設定檔位置只寫在這支腳本裡 |
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "jsc-cli",
|
"name": "jsc-cli",
|
||||||
"version": "0.3.2",
|
"version": "0.3.3",
|
||||||
"description": "CLI 偵測、模型能力標籤、子代理派工與技能庫批次部署",
|
"description": "CLI 偵測、模型能力標籤、子代理派工與技能庫批次部署",
|
||||||
"skills": "./skills/",
|
"skills": "./skills/",
|
||||||
"jsc": {
|
"jsc": {
|
||||||
|
|||||||
@@ -17,10 +17,10 @@
|
|||||||
| 項目 | 內容 |
|
| 項目 | 內容 |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| 觸發時機 | 整組 jsc 技能要在這台機器的每一支已安裝 CLI 上安裝、更新或解除安裝時用。只處理單一技能不用;只想知道版本落後與否,看 doctor 就夠 |
|
| 觸發時機 | 整組 jsc 技能要在這台機器的每一支已安裝 CLI 上安裝、更新或解除安裝時用。只處理單一技能不用;只想知道版本落後與否,看 doctor 就夠 |
|
||||||
| 關鍵步驟 | 先跑一次 `readlink -f "$JSC_HOME/current"` 解出 `current` 這個目錄的絕對路徑,解到那一層就停,不再往下解成帶版本號的快取路徑——那種路徑放不進允許清單,版本號寫成萬用字元也對不上;同一步再跑 `[ -d "{剛印出來的路徑}" ]` 確認目錄存在,`JSC_HOME` 沒設時它印的是 `/current`、結束碼 0,非空又是絕對路徑,只看那兩項擋不下來。整輪只解這一次,之後每一次跨外掛腳本呼叫都填成那個字面絕對路徑,不留 `$JSC_HOME` 也不留波浪號;技能自己那四支 `tools/*.sh` 不在 `current` 底下(那裡只有 `jsc-assist`、`jsc-gitea`、`jsc-hooks`),根目錄取自 CLI 載入這支技能時講明的外掛基底目錄,原樣當字面絕對路徑用,一個指令都不跑,四支一律寫成 `{外掛根目錄}/tools/{腳本}`;那個基底目錄帶版本號,四次呼叫都會跳權限詢問,這一支有人在現場(第三步要問模式)所以按得掉,無人值守的技能不得照抄,叫用文字沒講明基底目錄就回報外掛根目錄不明並停手,不猜前綴——權限層靜態比對路徑,帶未展開變數的呼叫一律要人核准,無人看管的輪次會停在第一支腳本,補權限規則也擋不住,因為規則字面同樣是靜態比對;解不出來就停手回報。接著同時取得三項事實(detect-clis.sh 的 CLI 清單、version-guard.sh 的 report 版本表與 recommend 結論、marketplace.json 的 domain 清單)、把版本表原樣秀出並定出建議、依 jsc-ask 決策樹問出模式(呼叫端已帶模式就沿用並標明來源)、每支 CLI 各開一個 sub agent 同時跑 tools/deploy.sh {mode} {cli} {domain}...、安裝或更新後把 CLI 清單交給 jsc-hooks:hooks-install、整台機器跑一次 tools/write-guides.sh、彙整每支 CLI 的結果並要求重新啟動工作階段,最後呼叫 jsc-hooks/tools/report-status.sh skill-end jsc-cli:deploy 寫下這一輪的結果。收尾那一筆接在彙整回報之後,不取代它;走每一條出口,連停在偵測不到 CLI 那一條也要寫。status 五選一,每支 CLI 都回 0、hooks-install 判定乾淨、兩份指引都寫成是 ok,偵測不到任何 CLI、整輪沒下過任何外掛命令是 blocked,marketplace 讀不到或每支 CLI 都失敗、冒煙結果出現 No such file 是 failed,部分 CLI 成功部分失敗、有 domain 被 skip、或 write-guides.sh 回 4 讓機器沒有最新指引是 degraded,使用者沒選模式或在第一支 CLI 開跑前停手是 aborted。detail 只放模式與各項筆數,cmd 與 exit 行留在回報裡 |
|
| 關鍵步驟 | 先跑一次 `readlink -f "$JSC_HOME/current"` 解出 `current` 這個目錄的絕對路徑,解到那一層就停,不再往下解成帶版本號的快取路徑——那種路徑放不進允許清單,版本號寫成萬用字元也對不上;同一步再跑 `[ -d "{剛印出來的路徑}" ]` 確認目錄存在,`JSC_HOME` 沒設時它印的是 `/current`、結束碼 0,非空又是絕對路徑,只看那兩項擋不下來。整輪只解這一次,之後每一次跨外掛腳本呼叫都填成那個字面絕對路徑,不留 `$JSC_HOME` 也不留波浪號;技能自己那四支 `tools/*.sh` 一律不走 `current`,即使那裡擺著 `jsc-cli` 那一條也一樣——第四步會為每個部署到的 domain 刷新連結,所以跑過一輪之後那一條通常在,但第一次建起它的正是這一輪,沒部署過的機器、或上一輪 link 回 fail 的機器,那一條不在或還停在舊版,四支腳本會解到不存在的路徑或自己的舊副本;根目錄取自 CLI 載入這支技能時講明的外掛基底目錄,原樣當字面絕對路徑用,一個指令都不跑,四支一律寫成 `{外掛根目錄}/tools/{腳本}`;那個基底目錄帶版本號,四次呼叫都會跳權限詢問,這一支有人在現場(第三步要問模式)所以按得掉,無人值守的技能不得照抄,叫用文字沒講明基底目錄就回報外掛根目錄不明並停手,不猜前綴——權限層靜態比對路徑,帶未展開變數的呼叫一律要人核准,無人看管的輪次會停在第一支腳本,補權限規則也擋不住,因為規則字面同樣是靜態比對;解不出來就停手回報。接著同時取得三項事實(detect-clis.sh 的 CLI 清單、version-guard.sh 的 report 版本表與 recommend 結論、marketplace.json 的 domain 清單)、把版本表原樣秀出並定出建議、依 jsc-ask 決策樹問出模式(呼叫端已帶模式就沿用並標明來源)、每支 CLI 各開一個 sub agent 同時跑 tools/deploy.sh {mode} {cli} {domain}...、讀 deploy.sh 收尾印出的 link 行確認 `current` 連結農場刷新到位(install 與 update 把每一條指到這次裝的版本目錄,uninstall 清掉指向已消失的那幾條;一台機器只有一組農場而五支 CLI 各有副本,所以只有基準 CLI 那一輪會動它,基準是 claude、codex、copilot、kiro 之中這台機器第一支裝得到的,其餘四支各印一行 link 標明基準是誰,平行覆寫會讓最後指到哪一份變成隨機;狀態 ok 要把指向抄進收尾報告,removed 不必處置,skip 帶 domain 是那個路徑上擺著非符號連結的東西、腳本刻意不覆寫而要人工處理,fail 是版本目錄取不到或連結建不起來、部署本身仍然成立但那條文件路徑可能還停在舊版,整輪判 degraded,連結失敗一律不記成部署失敗,否則會連重啟閘門與 result 行一起跳過,把一台裝好的機器講成失敗)、安裝或更新後把 CLI 清單交給 jsc-hooks:hooks-install、整台機器跑一次 tools/write-guides.sh、彙整每支 CLI 的結果並要求重新啟動工作階段,最後呼叫 jsc-hooks/tools/report-status.sh skill-end jsc-cli:deploy 寫下這一輪的結果。收尾那一筆接在彙整回報之後,不取代它;走每一條出口,連停在偵測不到 CLI 那一條也要寫。status 五選一,每支 CLI 都回 0、hooks-install 判定乾淨、兩份指引都寫成、基準 CLI 的 link 行全是 ok 或 removed 是 ok,偵測不到任何 CLI、整輪沒下過任何外掛命令是 blocked,marketplace 讀不到或每支 CLI 都失敗、冒煙結果出現 No such file 是 failed,部分 CLI 成功部分失敗、有 domain 被 skip、write-guides.sh 回 4 讓機器沒有最新指引、或有 link 行回 fail 與帶 domain 的 skip 是 degraded,使用者沒選模式或在第一支 CLI 開跑前停手是 aborted。detail 只放模式與各項筆數,cmd 與 exit 行留在回報裡 |
|
||||||
| 外部呼叫 | `readlink -f "$JSC_HOME/current"` 解出 `current` 這個目錄的絕對路徑,加上同一步的 `[ -d ]` 確認,是整輪唯一容許帶變數的兩個指令;跨外掛腳本一律用它組成的字面絕對路徑呼叫:{current 目錄}/jsc-hooks/hooks/version-guard.sh 的 report 與 recommend、{current 目錄}/jsc-gitea/tools/gitea.sh 讀 plugins/meta 的 marketplace.json、{current 目錄}/jsc-hooks/tools/report-status.sh skill-end。技能自己那四支腳本走外掛根目錄組成的字面絕對路徑:{外掛根目錄}/tools/detect-clis.sh、{外掛根目錄}/tools/deploy.sh、{外掛根目錄}/tools/write-guides.sh、{外掛根目錄}/tools/check-requires.sh(由 deploy.sh 在每個 domain 更新前轉呼叫)。第四步那段內文提到的 `jsc-hooks/hooks/version-guard.sh` 是在講擋人發生在哪一層,不是這支技能要下的呼叫,整輪只有第一步那一次真的跑它。另有 jsc-hooks/hooks/restart-gate.sh require(由 deploy.sh 收尾轉呼叫)、jsc-ask:ask、jsc-hooks:hooks-install |
|
| 外部呼叫 | `readlink -f "$JSC_HOME/current"` 解出 `current` 這個目錄的絕對路徑,加上同一步的 `[ -d ]` 確認,是整輪唯一容許帶變數的兩個指令;跨外掛腳本一律用它組成的字面絕對路徑呼叫:{current 目錄}/jsc-hooks/hooks/version-guard.sh 的 report 與 recommend、{current 目錄}/jsc-gitea/tools/gitea.sh 讀 plugins/meta 的 marketplace.json、{current 目錄}/jsc-hooks/tools/report-status.sh skill-end。技能自己那四支腳本走外掛根目錄組成的字面絕對路徑:{外掛根目錄}/tools/detect-clis.sh、{外掛根目錄}/tools/deploy.sh、{外掛根目錄}/tools/write-guides.sh、{外掛根目錄}/tools/check-requires.sh(由 deploy.sh 在每個 domain 更新前轉呼叫)。第四步那段內文提到的 `jsc-hooks/hooks/version-guard.sh` 是在講擋人發生在哪一層,不是這支技能要下的呼叫,整輪只有第一步那一次真的跑它。另有 jsc-hooks/hooks/restart-gate.sh require(由 deploy.sh 收尾轉呼叫)、jsc-ask:ask、jsc-hooks:hooks-install。`current` 連結農場的刷新不是另一支腳本,是 deploy.sh 自己收尾做的,技能只讀它印的 link 行,不自己下 ln 或 rm |
|
||||||
| 完成條件 | `current` 那個目錄在第一步就解出一條存在的絕對路徑(用 `[ -d ]` 查過,而且沒有再往下解成帶版本號的快取路徑),技能自己那四支腳本也有一條字面絕對的外掛根目錄可用,後續每一支腳本都用這兩條之一組成的字面絕對路徑呼叫;每一支偵測到的 CLI 都回報結束碼與 result 行,每個 skip、warn、compat 行都照實列出;安裝或更新還要拿到 hooks-install 對每支 CLI 的總結,兩份指引都印出 wrote,收尾印出重啟指示與兩份指引路徑。這一輪還要留下一筆 skill-end 事件,或是腳本不在而略過,兩者都算收好;略過不影響這支技能的結束碼 |
|
| 完成條件 | `current` 那個目錄在第一步就解出一條存在的絕對路徑(用 `[ -d ]` 查過,而且沒有再往下解成帶版本號的快取路徑),技能自己那四支腳本也有一條字面絕對的外掛根目錄可用,後續每一支腳本都用這兩條之一組成的字面絕對路徑呼叫;每一支偵測到的 CLI 都回報結束碼與 result 行,每個 skip、warn、compat、link 行都照實列出;基準 CLI 那一輪每個 domain 都有一條 link 行,狀態 ok 的把指向抄進收尾報告,狀態 fail 與帶 domain 的 skip 都點名外掛與原因並整輪判 degraded,非基準的那幾支各有一行標明基準是誰;安裝或更新還要拿到 hooks-install 對每支 CLI 的總結,兩份指引都印出 wrote,收尾印出重啟指示、兩份指引路徑,以及每個外掛的連結現在指到哪一個版本目錄。這一輪還要留下一筆 skill-end 事件,或是腳本不在而略過,兩者都算收好;略過不影響這支技能的結束碼 |
|
||||||
| 可驗證跡象 | 各 CLI 的外掛目錄多出或少掉 jsc-{domain}:claude 與 codex 在各自的 plugin 快取、copilot 在 installed-plugins、antigravity 與 kiro 走 $JSC_LOCAL_PLUGINS 的本地 clone 與 $JSC_KIRO_SKILLS 的複製。$JSC_HOME/restart-required.d/{cli} 出現這次的重啟狀態檔;$JSC_HOME/update-guide.md 與 $JSC_HOME/remove-guide.md 被重寫;各 CLI 的 hook 設定檔由 hooks-install 改寫;$JSC_HOME/usage/events.jsonl 會多一筆 {kind:skill,phase:end} 事件,name 是 jsc-cli:deploy,status 與 exit 就是這一輪的結果。回報與逐行紀錄裡出現的腳本路徑全是字面絕對路徑,找不到 `$JSC_HOME`、`$` 開頭或波浪號開頭的呼叫,唯一的例外是開頭那一次 `readlink -f "$JSC_HOME/current"` 與同一步的 `[ -d ]` 確認;跨外掛那幾支的路徑中段是 `current`,不是 `cache/jsc/{外掛}/{版本}`,技能自己那四支則一律是外掛根目錄接 `tools/`,沒有一支寫成裸的相對路徑 |
|
| 可驗證跡象 | 各 CLI 的外掛目錄多出或少掉 jsc-{domain}:claude 與 codex 在各自的 plugin 快取、copilot 在 installed-plugins、antigravity 與 kiro 走 $JSC_LOCAL_PLUGINS 的本地 clone 與 $JSC_KIRO_SKILLS 的複製。$JSC_HOME/current/ 底下每個外掛一條符號連結,安裝或更新之後拿 `readlink` 讀出來的指向,就是基準 CLI 這一輪裝到的那個帶版本號目錄,跟 link 行第五欄逐字相同,也跟 version-guard.sh report 那張表上該外掛的本機版本對得起來——這一項驗得到連結指向正確:連結上的版本號與剛裝上的版本號不一致,就是刷新沒做到;解除安裝之後,指向已消失的那幾條不再留在目錄裡,`readlink -e` 對每一條都解得出存在的目錄,沒有一條是斷的;那個目錄底下也不會出現實體目錄,非符號連結的項目腳本刻意不動並留下一行 skip。$JSC_HOME/restart-required.d/{cli} 出現這次的重啟狀態檔;$JSC_HOME/update-guide.md 與 $JSC_HOME/remove-guide.md 被重寫;各 CLI 的 hook 設定檔由 hooks-install 改寫;$JSC_HOME/usage/events.jsonl 會多一筆 {kind:skill,phase:end} 事件,name 是 jsc-cli:deploy,status 與 exit 就是這一輪的結果。回報與逐行紀錄裡出現的腳本路徑全是字面絕對路徑,找不到 `$JSC_HOME`、`$` 開頭或波浪號開頭的呼叫,唯一的例外是開頭那一次 `readlink -f "$JSC_HOME/current"` 與同一步的 `[ -d ]` 確認;跨外掛那幾支的路徑中段是 `current`,不是 `cache/jsc/{外掛}/{版本}`,技能自己那四支則一律是外掛根目錄接 `tools/`,沒有一支寫成裸的相對路徑 |
|
||||||
|
|
||||||
## doctor
|
## doctor
|
||||||
|
|
||||||
|
|||||||
+24
-7
@@ -29,7 +29,7 @@ Empty output, a non-zero exit, or a path that is not an existing directory → s
|
|||||||
|
|
||||||
### The second root — this skill's own `tools/`
|
### The second root — this skill's own `tools/`
|
||||||
|
|
||||||
`jsc-cli` is **not** one of the links under `{JSC_ROOT}`; that directory carries `jsc-assist`, `jsc-gitea` and `jsc-hooks` and nothing else. So `tools/detect-clis.sh`, `tools/deploy.sh`, `tools/check-requires.sh` and `tools/write-guides.sh` cannot be reached through `{JSC_ROOT}`, and none of them may be written as a bare relative path either — the rule above wants a literal absolute path at every call site, and a call site with no way to build one is where a prefix gets guessed.
|
**Do not reach this skill's own `tools/` through `{JSC_ROOT}`, even when a `jsc-cli` link is sitting there.** Step 4 refreshes the farm for every deployed domain, so after a round has run, `{JSC_ROOT}/jsc-cli` usually exists — but this skill cannot rely on it, because the round that first creates it is this very one. On a machine that has never deployed, and on any machine where the last round's link write came back `fail`, `{JSC_ROOT}/jsc-cli` is absent or stale, and `tools/detect-clis.sh`, `tools/deploy.sh`, `tools/check-requires.sh` and `tools/write-guides.sh` would resolve to nothing or to a superseded copy of themselves. None of the four may be written as a bare relative path either — the rule above wants a literal absolute path at every call site, and a call site with no way to build one is where a prefix gets guessed.
|
||||||
|
|
||||||
They sit at `{plugin root}/tools/`, and the plugin root is the base directory the CLI states when it loads this skill. Take that literal path verbatim, call it `{CLI_ROOT}`, and write all four calls as `{CLI_ROOT}/tools/{script}` — `/root/.claude/plugins/cache/jsc/jsc-cli/0.3.2/tools/deploy.sh`, for example. No command runs for this one, and it is taken once, like `{JSC_ROOT}`.
|
They sit at `{plugin root}/tools/`, and the plugin root is the base directory the CLI states when it loads this skill. Take that literal path verbatim, call it `{CLI_ROOT}`, and write all four calls as `{CLI_ROOT}/tools/{script}` — `/root/.claude/plugins/cache/jsc/jsc-cli/0.3.2/tools/deploy.sh`, for example. No command runs for this one, and it is taken once, like `{JSC_ROOT}`.
|
||||||
|
|
||||||
@@ -84,7 +84,7 @@ Nothing passed in → run every step as written below.
|
|||||||
|
|
||||||
4. Run `{CLI_ROOT}/tools/deploy.sh {mode} {cli} {domain}...` once per detected CLI, passing the whole domain list in one call so the marketplace command runs only once. This step **MUST run as a sub agent**, one sub agent per CLI, and **all of them start together** — the CLIs write to separate plugin directories, so serialising them only adds up their install times.
|
4. Run `{CLI_ROOT}/tools/deploy.sh {mode} {cli} {domain}...` once per detected CLI, passing the whole domain list in one call so the marketplace command runs only once. This step **MUST run as a sub agent**, one sub agent per CLI, and **all of them start together** — the CLIs write to separate plugin directories, so serialising them only adds up their install times.
|
||||||
|
|
||||||
The script prints `cmd` and `exit` lines for every command, one `requires` line before each domain update, optional `compat` lines for Codex cache links, and one `result` line at the end; `-n` prints the commands without running them.
|
The script prints `cmd` and `exit` lines for every command, one `requires` line before each domain update, optional `compat` lines for Codex cache links, `link` lines for the `current` link farm (see 4a), and one `result` line at the end; `-n` prints the commands without running them.
|
||||||
|
|
||||||
| Exit | Action |
|
| Exit | Action |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
@@ -95,7 +95,22 @@ Nothing passed in → run every step as written below.
|
|||||||
|
|
||||||
On update, `{CLI_ROOT}/tools/check-requires.sh {cli} {manifest}` checks each domain's `jsc.requires` before that domain is updated. Exit 0 updates the domain as usual. Exit 1 — a missing or too-old required jsc plugin — prints a `warn` line and the domain **is still updated**: skipping it would leave a behind domain permanently unable to reach the version its dependency needs. The block lives one layer up, at skill invocation time, where `jsc-hooks/hooks/version-guard.sh` stops that domain's skills. **That last mention is a description of where the block happens, not a call this skill makes** — nothing here runs `version-guard.sh` except step 1.2, which is written as `{JSC_ROOT}/jsc-hooks/hooks/version-guard.sh`, so do not read it as a call site that was left un-prefixed. Exit 4 — the manifest is unreadable, is not valid JSON, or python3 is missing — prints a `note` line and also still updates the domain: no verdict is not the same fact as behind, so it gets its own line rather than a `warn` that would send the operator hunting for a version problem that is not there. Exit 2 or any other code — a `check-requires.sh` usage error or a broken script — prints a `skip` line and leaves that domain untouched, because a checker that failed outright is not a pass. Codex update preserves old `jsc-cli` and `jsc-hooks` cache version paths as symlinks to the newest installed version, so a still-running Codex deploy can keep using its helper scripts and a still-running Codex session whose hook_run_id points at the old cache can finish without `No such file`. Antigravity cannot install from a Gitea URL, so the script clones each domain into the local plugin directory (`JSC_LOCAL_PLUGINS`, default `$JSC_HOME/plugins`) and installs from that path — keep that clone, because update pulls the same one. That default deliberately avoids a development checkout: when the directory holds uncommitted changes or unpushed commits, the script prints a `skip` line, leaves the tree untouched, and installs the on-disk content.
|
On update, `{CLI_ROOT}/tools/check-requires.sh {cli} {manifest}` checks each domain's `jsc.requires` before that domain is updated. Exit 0 updates the domain as usual. Exit 1 — a missing or too-old required jsc plugin — prints a `warn` line and the domain **is still updated**: skipping it would leave a behind domain permanently unable to reach the version its dependency needs. The block lives one layer up, at skill invocation time, where `jsc-hooks/hooks/version-guard.sh` stops that domain's skills. **That last mention is a description of where the block happens, not a call this skill makes** — nothing here runs `version-guard.sh` except step 1.2, which is written as `{JSC_ROOT}/jsc-hooks/hooks/version-guard.sh`, so do not read it as a call site that was left un-prefixed. Exit 4 — the manifest is unreadable, is not valid JSON, or python3 is missing — prints a `note` line and also still updates the domain: no verdict is not the same fact as behind, so it gets its own line rather than a `warn` that would send the operator hunting for a version problem that is not there. Exit 2 or any other code — a `check-requires.sh` usage error or a broken script — prints a `skip` line and leaves that domain untouched, because a checker that failed outright is not a pass. Codex update preserves old `jsc-cli` and `jsc-hooks` cache version paths as symlinks to the newest installed version, so a still-running Codex deploy can keep using its helper scripts and a still-running Codex session whose hook_run_id points at the old cache can finish without `No such file`. Antigravity cannot install from a Gitea URL, so the script clones each domain into the local plugin directory (`JSC_LOCAL_PLUGINS`, default `$JSC_HOME/plugins`) and installs from that path — keep that clone, because update pulls the same one. That default deliberately avoids a development checkout: when the directory holds uncommitted changes or unpushed commits, the script prints a `skip` line, leaves the tree untouched, and installs the on-disk content.
|
||||||
|
|
||||||
Done when every detected CLI has reported an exit code and a `result` line, every skipped domain has a checker-failure reason or a local-tree reason, and every `warn` domain is named with the version it still has to catch up to.
|
**The `link` lines say where `{JSC_ROOT}` now points, and they are the reason step 0's literal paths keep working.** `{JSC_ROOT}` is a farm of version-free symbolic links, one per plugin, each pointing at that plugin's versioned directory in a CLI's plugin cache. Every literal absolute path this skill builds rests on it, so a link left on an old version silently runs an old script — and a script that old version never shipped is simply absent, which stops a heartbeat without printing anything. `deploy.sh` refreshes the whole farm at the end of its run: install and update repoint every link at the version just installed, uninstall clears the ones whose target is gone.
|
||||||
|
|
||||||
|
Only one CLI's run touches the farm. The machine has one farm and five CLIs hold five copies, so the script picks the first of claude, codex, copilot, kiro that is installed and lets only that run write; every other CLI prints one `link<TAB>-<TAB>skip` line naming the base CLI, which is how a deliberate skip is told apart from a farm nobody refreshed. Read the base CLI's sub agent output for the real result. Each line is `link<TAB>{domain}<TAB>{status}<TAB>{link path}<TAB>{target or reason}`.
|
||||||
|
|
||||||
|
| `link` status | What it means and what to do |
|
||||||
|
| --- | --- |
|
||||||
|
| `ok` | The link now points at column 5, the version directory this round installed. Carry the target into step 7 for at least the plugins this skill itself reaches through `{JSC_ROOT}` |
|
||||||
|
| `removed` | Uninstall left the target gone, so the dangling link was cleared. Nothing to do |
|
||||||
|
| `skip` with a domain in column 2 | Something that is not a symbolic link already sits at that path, and the script deliberately refused to overwrite it. **Report it as an operator action** — until it is cleared by hand, that plugin's documented path keeps resolving to whatever is sitting there |
|
||||||
|
| `skip` with `-` in column 2 | This run is not the base CLI, or no base CLI is installed at all. The second case means no documented path got refreshed this round, so say so |
|
||||||
|
| `fail` | The version directory could not be found, or the link could not be written. The deploy still stands, but that plugin's documented path may still be on an old version. Name the plugin and the reason, and judge the round `degraded` |
|
||||||
|
| `dryrun` | `-n` only: column 5 is where the link would point. Nothing was written |
|
||||||
|
|
||||||
|
A failed link never fails the deploy. By the time the farm is refreshed the plugins are installed and working, and marking the round failed would skip the restart gate and the `result` line too, handing the operator a fully deployed machine described as a failure. The stale link is a real defect, but its remedy is a line the operator can see and act on.
|
||||||
|
|
||||||
|
Done when every detected CLI has reported an exit code and a `result` line, every skipped domain has a checker-failure reason or a local-tree reason, every `warn` domain is named with the version it still has to catch up to, and every `link` line has been read — with each `ok` target in hand for step 7 and each `fail` or domain-level `skip` named as an operator action.
|
||||||
|
|
||||||
5. After install or update, call `jsc-hooks:hooks-install` and **hand it the CLI list from step 1.1**, so it does not probe the same five executables a second time. `hooks-install` still detects for itself when it receives no list — that fallback is what keeps it usable on its own.
|
5. After install or update, call `jsc-hooks:hooks-install` and **hand it the CLI list from step 1.1**, so it does not probe the same five executables a second time. `hooks-install` still detects for itself when it receives no list — that fallback is what keeps it usable on its own.
|
||||||
|
|
||||||
@@ -114,11 +129,13 @@ Nothing passed in → run every step as written below.
|
|||||||
|
|
||||||
Done when both `wrote` lines are printed, or the failure is reported with the exit code and the paths involved.
|
Done when both `wrote` lines are printed, or the failure is reported with the exit code and the paths involved.
|
||||||
|
|
||||||
7. Report the run and close it, in one block. The result and any failure reason for every CLI × mode, plus every `skip` line, every `warn` line, every Codex `compat` line, and every CLI that could not be version-checked in step 2.
|
7. Report the run and close it, in one block. The result and any failure reason for every CLI × mode, plus every `skip` line, every `warn` line, every Codex `compat` line, every `link` line that is not a plain base-CLI skip, and every CLI that could not be version-checked in step 2.
|
||||||
|
|
||||||
|
State the farm explicitly: name `{JSC_ROOT}` and, per plugin, the version directory its link now points at, taken from the `ok` targets. That one list is what lets the next round's operator check by eye that a documented path leads to the version just deployed, instead of finding out through a heartbeat that quietly stopped.
|
||||||
|
|
||||||
For install or update, the same block ends with the restart instruction, in these words: 「請關閉目前的工作階段並重新啟動,新的技能內容才會載入」. `deploy.sh` recorded this round in `$JSC_HOME/restart-required.d/{cli}` — one file per CLI — and prints its path on a `restart` line; `jsc-hooks` reads only that CLI's own file and keeps reminding until that CLI restarts, with `JSC_RESTART_GATE=off` as the escape hatch. Restarting one CLI clears its own file and leaves the others' gates standing. Name the two guide paths from step 6 in that same closing block, so the operator knows where this machine's update and removal commands now live.
|
For install or update, the same block ends with the restart instruction, in these words: 「請關閉目前的工作階段並重新啟動,新的技能內容才會載入」. `deploy.sh` recorded this round in `$JSC_HOME/restart-required.d/{cli}` — one file per CLI — and prints its path on a `restart` line; `jsc-hooks` reads only that CLI's own file and keeps reminding until that CLI restarts, with `JSC_RESTART_GATE=off` as the escape hatch. Restarting one CLI clears its own file and leaves the others' gates standing. Name the two guide paths from step 6 in that same closing block, so the operator knows where this machine's update and removal commands now live.
|
||||||
|
|
||||||
Done when every detected CLI appears in the report with its `result` status, and — for install or update — the restart instruction is printed with both guide paths named, or step 6's failure is repeated in their place.
|
Done when every detected CLI appears in the report with its `result` status, every plugin's refreshed link target is named, and — for install or update — the restart instruction is printed with both guide paths named, or step 6's failure is repeated in their place.
|
||||||
|
|
||||||
8. **Record how the run ended.** This is the last thing this skill does, and it runs on every path out of the skill, the ones that stop at step 1 included. Call
|
8. **Record how the run ended.** This is the last thing this skill does, and it runs on every path out of the skill, the ones that stop at step 1 included. Call
|
||||||
|
|
||||||
@@ -130,10 +147,10 @@ Nothing passed in → run every step as written below.
|
|||||||
|
|
||||||
| status | When this skill uses it |
|
| status | When this skill uses it |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `ok` | Every detected CLI's `deploy.sh` exited 0, hooks-install returned a clean verdict for each of them with no `No such file` in any smoke result, and both guides printed their `wrote` line |
|
| `ok` | Every detected CLI's `deploy.sh` exited 0, hooks-install returned a clean verdict for each of them with no `No such file` in any smoke result, both guides printed their `wrote` line, and the base CLI's `link` lines are all `ok` or `removed` |
|
||||||
| `blocked` | Nothing was deployed because there was nothing to deploy to: `detect-clis.sh` exited 0 with no row, so none of claude, codex, copilot, antigravity, kiro is installed and the run stops before any plugin command |
|
| `blocked` | Nothing was deployed because there was nothing to deploy to: `detect-clis.sh` exited 0 with no row, so none of claude, codex, copilot, antigravity, kiro is installed and the run stops before any plugin command |
|
||||||
| `failed` | The run broke: the marketplace read in step 1.3 exited non-zero or returned no plugin entry, or every detected CLI's `deploy.sh` came back non-zero. Also used when a smoke result carries `No such file`, which this skill judges as a failed update even when hooks-install did not |
|
| `failed` | The run broke: the marketplace read in step 1.3 exited non-zero or returned no plugin entry, or every detected CLI's `deploy.sh` came back non-zero. Also used when a smoke result carries `No such file`, which this skill judges as a failed update even when hooks-install did not |
|
||||||
| `degraded` | The deploy landed on part of the machine only: some CLIs exited 0 while others failed, a domain was left untouched by a `skip` line from `check-requires.sh`, or `write-guides.sh` exited 4 so the plugins are installed but this machine has no up-to-date update and remove guide |
|
| `degraded` | The deploy landed on part of the machine only: some CLIs exited 0 while others failed, a domain was left untouched by a `skip` line from `check-requires.sh`, `write-guides.sh` exited 4 so the plugins are installed but this machine has no up-to-date update and remove guide, or a `link` line came back `fail` or skipped a plugin whose link path is not a symbolic link — the plugins are installed, but a documented path may still lead to the old version |
|
||||||
| `aborted` | The user chose none of `install`, `update` or `uninstall` at step 3, or stopped the run before step 4 launched the first CLI, so no plugin command ran |
|
| `aborted` | The user chose none of `install`, `update` or `uninstall` at step 3, or stopped the run before step 4 launched the first CLI, so no plugin command ran |
|
||||||
|
|
||||||
Done when exactly one `skill-end` line was recorded for this run, or the script was absent and the run finished without it.
|
Done when exactly one `skill-end` line was recorded for this run, or the script was absent and the run finished without it.
|
||||||
|
|||||||
+150
-1
@@ -12,10 +12,21 @@
|
|||||||
# warn<TAB>{domain}<TAB>{原因} 相依版本不符,仍照樣更新的提醒
|
# warn<TAB>{domain}<TAB>{原因} 相依版本不符,仍照樣更新的提醒
|
||||||
# compat<TAB>codex<TAB>{舊路徑}<TAB>{新路徑} codex 舊版快取路徑補成指向新版的相容連結
|
# compat<TAB>codex<TAB>{舊路徑}<TAB>{新路徑} codex 舊版快取路徑補成指向新版的相容連結
|
||||||
# note<TAB>{cli}<TAB>{原因} 非逐指令的說明(例:kiro 整批改走複製退路的理由)
|
# note<TAB>{cli}<TAB>{原因} 非逐指令的說明(例:kiro 整批改走複製退路的理由)
|
||||||
|
# link<TAB>{domain}<TAB>{狀態}<TAB>{連結路徑}<TAB>{指向或原因} current 連結農場這一條的刷新結果
|
||||||
# restart<TAB>{路徑} 這次寫下的重啟狀態檔
|
# restart<TAB>{路徑} 這次寫下的重啟狀態檔
|
||||||
# requires<TAB>{domain}<TAB>{檢查結果} update 前的 jsc.requires 檢查
|
# requires<TAB>{domain}<TAB>{檢查結果} update 前的 jsc.requires 檢查
|
||||||
# result<TAB>{cli}<TAB>{mode}<TAB>{domain 清單}<TAB>{ok|fail}
|
# result<TAB>{cli}<TAB>{mode}<TAB>{domain 清單}<TAB>{ok|fail}
|
||||||
# 結束碼:全部指令成功 0;任一指令失敗 1;參數錯誤 2。skip、warn、note 不算失敗,但呼叫端要據實回報。
|
# 結束碼:全部指令成功 0;任一指令失敗 1;參數錯誤 2。skip、warn、note、link 不算失敗,
|
||||||
|
# 但呼叫端要據實回報。
|
||||||
|
# link 那幾行講的是 $JSC_HOME/current/ 這一組不帶版本號的符號連結:一個 domain 一條,
|
||||||
|
# 指向該外掛在快取裡帶版本號的實體目錄。技能文件裡所有跨外掛的腳本呼叫都以那一層當根,
|
||||||
|
# 因為它不帶版本號、寫得進權限允許清單。install 與 update 收尾會把整組連結刷新一次,
|
||||||
|
# uninstall 收尾會清掉指向已消失的那幾條。狀態欄的五個值:
|
||||||
|
# ok 連結已經指到這次實際安裝的版本目錄
|
||||||
|
# removed 指向已消失,這一條清掉了
|
||||||
|
# skip 這一輪不動它,原因寫在最後一欄
|
||||||
|
# fail 該指的目標取不到或連結建不起來,這一條維持原樣,指向可能還是舊版本
|
||||||
|
# dryrun 乾跑,只算出會指到哪裡,沒有真的寫
|
||||||
# update 前每個 domain 都先跑一次同目錄的 check-requires.sh,它的四種結束碼分流如下:
|
# update 前每個 domain 都先跑一次同目錄的 check-requires.sh,它的四種結束碼分流如下:
|
||||||
# 0 相依符合,或沒有宣告相依 → 照常更新這個 domain
|
# 0 相依符合,或沒有宣告相依 → 照常更新這個 domain
|
||||||
# 1 相依版本不符或缺相依 plugin
|
# 1 相依版本不符或缺相依 plugin
|
||||||
@@ -57,6 +68,7 @@ MKT="$HOST/$OWNER/meta.git"
|
|||||||
REPO_BASE="$HOST/$OWNER"
|
REPO_BASE="$HOST/$OWNER"
|
||||||
LOCAL_DIR="${JSC_LOCAL_PLUGINS:-${JSC_HOME:-$HOME/.jsc}/plugins}"
|
LOCAL_DIR="${JSC_LOCAL_PLUGINS:-${JSC_HOME:-$HOME/.jsc}/plugins}"
|
||||||
KIRO_SKILLS="${JSC_KIRO_SKILLS:-$HOME/.kiro/skills}"
|
KIRO_SKILLS="${JSC_KIRO_SKILLS:-$HOME/.kiro/skills}"
|
||||||
|
CURRENT_DIR="${JSC_HOME:-$HOME/.jsc}/current"
|
||||||
DRYRUN="${JSC_DEPLOY_DRYRUN:-0}"
|
DRYRUN="${JSC_DEPLOY_DRYRUN:-0}"
|
||||||
FAILED=0
|
FAILED=0
|
||||||
|
|
||||||
@@ -113,6 +125,141 @@ codex_preserve_old_plugin_cache() { # $1=plugin name $2=required file under vers
|
|||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# 連結農場一台機器只有一組,五支 CLI 卻各裝各的副本,所以只能挑一支當基準。
|
||||||
|
# 挑法是固定的偏好順序,取這台機器上第一支裝得到的:
|
||||||
|
# claude 擺第一——現有那幾條連結本來就指向它的快取,換基準等於把每一條寫進技能文件的
|
||||||
|
# 路徑悄悄換到另一份副本上,而文件不會跟著改。
|
||||||
|
# codex 次之,同樣是 CLI 自己的外掛快取寫下來的帶版本目錄,版本號是它裝到什麼就是什麼。
|
||||||
|
# copilot 與 kiro 只當退路:它們的路徑不帶版本號,連結指得到,卻查不出指的是哪一版。
|
||||||
|
# antigravity 一律不當基準。它的來源是本地 clone,那份 clone 允許是維護者的開發樹,
|
||||||
|
# sync_local 碰到未提交的變更還會刻意不 pull;把全機器的路徑指到一份做到一半的樹,
|
||||||
|
# 正好就是這次要修掉的那種無聲跑錯腳本。
|
||||||
|
# 用 command -v 判斷裝沒裝,跟 detect-clis.sh 的認定一致,所以每一支平行跑的 deploy.sh
|
||||||
|
# 都會算出同一個基準,整輪只有一支真的去寫連結。五支都寫的話,平行行程會互相覆寫,
|
||||||
|
# 最後指到哪一份是隨機的,出了事也重現不出來。
|
||||||
|
link_base_cli() {
|
||||||
|
for _b in claude codex copilot kiro; do
|
||||||
|
if command -v "$(cli_bin "$_b")" >/dev/null 2>&1; then
|
||||||
|
printf '%s' "$_b"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# 某個外掛在快取根目錄底下的實體版本目錄。claude 與 codex 的快取一個版本一層,
|
||||||
|
# 取版本排序最大、而且真的有 plugin.json 的那一層:裝到一半的目錄沒有 plugin.json,
|
||||||
|
# 挑到它會讓連結指向一份不完整的外掛,而且照樣不會報錯。
|
||||||
|
# 本身就是符號連結的項目也要跳過——舊版相容連結就是那樣,指過去只是多繞一層,
|
||||||
|
# 原目標一被清掉還會跟著一起斷。
|
||||||
|
latest_version_dir() { # $1=某個外掛的快取根目錄
|
||||||
|
[ -d "$1" ] || return 1
|
||||||
|
_vfound=$(
|
||||||
|
for _vp in "$1"/*; do
|
||||||
|
[ -d "$_vp" ] || continue
|
||||||
|
[ -L "$_vp" ] && continue
|
||||||
|
[ -f "$_vp/plugin.json" ] || continue
|
||||||
|
printf '%s\n' "$_vp"
|
||||||
|
done | sort -V | tail -n1
|
||||||
|
)
|
||||||
|
[ -n "$_vfound" ] || return 1
|
||||||
|
printf '%s' "$_vfound"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 基準 CLI 底下這個 domain 的實體目錄。這裡只收得起當基準的那四支,
|
||||||
|
# antigravity 不列:它從來不會被選成基準,補一條沒人走的分支只會讓人以為那也是選項。
|
||||||
|
plugin_dir() { # $1=CLI 代號 $2=domain;印出實體目錄,取不到回 1
|
||||||
|
case "$1" in
|
||||||
|
claude) latest_version_dir "$HOME/.claude/plugins/cache/jsc/jsc-$2" ;;
|
||||||
|
codex) latest_version_dir "$(codex_plugin_cache_root "jsc-$2")" ;;
|
||||||
|
copilot)
|
||||||
|
_pd="${COPILOT_HOME:-$HOME/.copilot}/installed-plugins/jsc/jsc-$2"
|
||||||
|
[ -d "$_pd" ] || return 1
|
||||||
|
printf '%s' "$_pd"
|
||||||
|
;;
|
||||||
|
kiro)
|
||||||
|
_pd="$KIRO_SKILLS/jsc-$2"
|
||||||
|
[ -d "$_pd" ] || return 1
|
||||||
|
printf '%s' "$_pd"
|
||||||
|
;;
|
||||||
|
*) return 1 ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
link_line() { # $1=domain 或 - $2=狀態 $3=連結路徑 $4=指向或原因
|
||||||
|
printf 'link\t%s\t%s\t%s\t%s\n' "$1" "$2" "$3" "$4"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 刷新一條連結。
|
||||||
|
# 失敗只記一行、不中止整輪:這一段跑在外掛都裝好之後,部署本身已經成功了。把整輪記成
|
||||||
|
# 失敗會連帶跳過重啟閘門與 result 行,操作者拿到的是一台明明裝好、卻被說成失敗的機器,
|
||||||
|
# 反而更難處置。連結沒刷新確實是缺陷,但補救方式是一行看得見的紀錄加上人來判斷,
|
||||||
|
# 不是把一次成功的部署丟掉。
|
||||||
|
refresh_one() { # $1=基準 CLI $2=domain
|
||||||
|
_rl="$CURRENT_DIR/jsc-$2"
|
||||||
|
# 目標已經在、又不是符號連結時一律不覆寫。ln -sfn 對著一個實體目錄下手,會把連結建進
|
||||||
|
# 那個目錄裡面(變成 current/jsc-{domain}/jsc-{domain}),農場當場就壞了還不會報錯;
|
||||||
|
# 改成 rm -rf 清掉更糟,那是這支腳本沒建過、也可能沒有第二份的內容。
|
||||||
|
if [ -e "$_rl" ] && [ ! -L "$_rl" ]; then
|
||||||
|
link_line "$2" skip "$_rl" '目標已存在而且不是符號連結,這一條不覆寫,請人工確認'
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if ! _rt=$(plugin_dir "$1" "$2"); then
|
||||||
|
if [ "$DRYRUN" = 1 ]; then
|
||||||
|
link_line "$2" dryrun "$_rl" "乾跑沒有真的安裝,$1 底下還取不到 jsc-$2 的版本目錄"
|
||||||
|
else
|
||||||
|
link_line "$2" fail "$_rl" "$1 底下找不到 jsc-$2 的版本目錄,這一條維持原樣"
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if [ "$DRYRUN" = 1 ]; then
|
||||||
|
link_line "$2" dryrun "$_rl" "$_rt"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if mkdir -p "$CURRENT_DIR" 2>/dev/null && ln -sfn "$_rt" "$_rl" 2>/dev/null; then
|
||||||
|
link_line "$2" ok "$_rl" "$_rt"
|
||||||
|
else
|
||||||
|
link_line "$2" fail "$_rl" '連結建不起來,這一條維持原樣,指向可能還是舊版本'
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# 解除安裝之後清掉指向已經消失的那一條。判準是連結還在、指向卻沒了:
|
||||||
|
# 這一輪只解除安裝其中一支 CLI 時,連結可能還指著另一支 CLI 手上完好的副本,那一條要留著。
|
||||||
|
# 斷掉的連結不清,等於對外宣稱一支已經不在機器上的外掛還裝著,下一輪體檢會照著它報錯狀態。
|
||||||
|
prune_one() { # $1=domain
|
||||||
|
_ql="$CURRENT_DIR/jsc-$1"
|
||||||
|
[ -L "$_ql" ] || return 0
|
||||||
|
[ -e "$_ql" ] && return 0
|
||||||
|
if [ "$DRYRUN" = 1 ]; then
|
||||||
|
link_line "$1" dryrun "$_ql" '指向已消失,這一條會被清掉'
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if rm -f "$_ql" 2>/dev/null; then
|
||||||
|
link_line "$1" removed "$_ql" '-'
|
||||||
|
else
|
||||||
|
link_line "$1" fail "$_ql" '指向已消失卻清不掉,這一條還是斷的'
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# 整組連結的刷新收尾。跑在各 CLI 的部署動作全部結束之後,不夾在每個 domain 的安裝指令
|
||||||
|
# 中間:連結要指的是寫完的內容,中途去指有機會指到只寫了一半的目錄。
|
||||||
|
refresh_links() {
|
||||||
|
if ! _lbase=$(link_base_cli); then
|
||||||
|
link_line - skip "$CURRENT_DIR" '這台機器一支基準 CLI 都沒裝到,沒有版本目錄可指'
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if [ "$CLI" != "$_lbase" ]; then
|
||||||
|
link_line - skip "$CURRENT_DIR" "這一輪的基準 CLI 是 $_lbase,$CLI 這一支不動連結"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
for _ldom in $DOMAINS; do
|
||||||
|
case "$MODE" in
|
||||||
|
install|update) refresh_one "$_lbase" "$_ldom" ;;
|
||||||
|
uninstall) prune_one "$_ldom" ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
echo "用法:deploy.sh [-n] {install|update|uninstall} {claude|codex|copilot|antigravity|kiro} {domain} [domain...]" >&2
|
echo "用法:deploy.sh [-n] {install|update|uninstall} {claude|codex|copilot|antigravity|kiro} {domain} [domain...]" >&2
|
||||||
exit 2
|
exit 2
|
||||||
@@ -432,6 +579,8 @@ case "$CLI" in
|
|||||||
*) usage ;;
|
*) usage ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
|
refresh_links
|
||||||
|
|
||||||
if [ "$FAILED" -eq 0 ]; then
|
if [ "$FAILED" -eq 0 ]; then
|
||||||
mark_restart
|
mark_restart
|
||||||
printf 'result\t%s\t%s\t%s\tok\n' "$CLI" "$MODE" "$DOMAINS"
|
printf 'result\t%s\t%s\t%s\tok\n' "$CLI" "$MODE" "$DOMAINS"
|
||||||
|
|||||||
Reference in New Issue
Block a user