diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 16ac502..7963e05 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "jsc-cli", - "version": "0.2.3", + "version": "0.2.4", "description": "CLI 偵測、模型能力標籤、子代理派工與技能庫批次部署", "skills": "./skills", "author": { diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index a615bfd..84ac778 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "jsc-cli", - "version": "0.2.3", + "version": "0.2.4", "description": "CLI 偵測、模型能力標籤、子代理派工與技能庫批次部署", "skills": "./skills", "jsc": { diff --git a/README.md b/README.md index d14dc05..7040c9a 100644 --- a/README.md +++ b/README.md @@ -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/deploy.sh` | 對單一 CLI 執行安裝、更新或解除安裝(`deploy.sh [-n] {mode} {cli} {domain}...`,mode 為 install / update / uninstall);印出每個指令與其結束碼,最後一行 `result` 標 ok 或 fail。`-n` 只印指令不執行。上表五個 CLI 的指令差異全部收在這支腳本裡。Codex 更新 `jsc-hooks` 後會把舊版快取路徑補成指向新版的相容連結,避免尚未重啟的工作階段在 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 階段找不到舊路徑。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`。`deploy.sh update` 在每個 domain 更新前呼叫它,不符就跳過該 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/list-models.sh` | 讀各 CLI 設定檔列出模型(TSV:cli / model / in-use);設定檔缺失就不輸出該 CLI 的列,一律 exit 0。設定檔位置只寫在這支腳本裡 | @@ -49,7 +49,7 @@ Marketplace 統一為 `jsc`(https://gitea.jsc.idv.tw/plugins/meta.git),安 ### `deploy` -技能庫批次安裝、更新、解除安裝:偵測 CLI → **先比對各 plugin 的本機與已發佈版本並列表,只要有任一個落後就把「更新」設為推薦選項** → 決策樹選模式 → 每個 CLI 一個 sub agent 呼叫 `tools/deploy.sh` 執行原生 plugin 指令(統一 marketplace `jsc`,token `jsc-{domain}@jsc`)→ update 前逐一檢查 `jsc.requires`,版本不符就跳過該 domain 並回報缺哪一版 → Codex 更新 `jsc-hooks` 時保留舊快取相容連結 → install、update 後呼叫 `jsc-hooks:hooks-install`,並把每支 CLI 的 smoke 結果納入部署成敗。domain 名單動態取自 `plugins/meta` 的 marketplace.json,不硬編碼。 +技能庫批次安裝、更新、解除安裝:偵測 CLI → **先比對各 plugin 的本機與已發佈版本並列表,只要有任一個落後就把「更新」設為推薦選項** → 決策樹選模式 → 每個 CLI 一個 sub agent 呼叫 `tools/deploy.sh` 執行原生 plugin 指令(統一 marketplace `jsc`,token `jsc-{domain}@jsc`)→ update 前逐一檢查 `jsc.requires`,版本不符就跳過該 domain 並回報缺哪一版 → Codex 更新 `jsc-cli` 與 `jsc-hooks` 時保留舊快取相容連結 → install、update 後呼叫 `jsc-hooks:hooks-install`,並把每支 CLI 的 smoke 結果納入部署成敗。domain 名單動態取自 `plugins/meta` 的 marketplace.json,不硬編碼。 ### `doctor` diff --git a/plugin.json b/plugin.json index 57d3a71..0305931 100644 --- a/plugin.json +++ b/plugin.json @@ -1,6 +1,6 @@ { "name": "jsc-cli", - "version": "0.2.3", + "version": "0.2.4", "description": "CLI 偵測、模型能力標籤、子代理派工與技能庫批次部署", "skills": "./skills/", "jsc": { diff --git a/skills/deploy/SKILL.md b/skills/deploy/SKILL.md index c0e9dad..50dc125 100644 --- a/skills/deploy/SKILL.md +++ b/skills/deploy/SKILL.md @@ -21,7 +21,7 @@ description: Batch install, update, or uninstall the whole jsc skill set on ever 4. Get the domain list (**never hardcode it**; this skill follows automatically when domains are added or removed): read `plugins[].name` from the unified marketplace via `jsc-gitea/tools/gitea.sh api GET /repos/plugins/meta/raw/.claude-plugin/marketplace.json`. The marketplace is unified as `jsc`; the install token is `jsc-{domain}@jsc`. Each `plugins[].name` already carries the `jsc-` prefix (e.g. `jsc-ask`) — pass it to `tools/deploy.sh` as-is, prefixed or not; the script normalizes it. Done when the domain list comes from that response and holds at least one name. -5. Run `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). 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. On update, `tools/check-requires.sh {cli} {manifest}` checks each domain's `jsc.requires` before that domain is updated. A missing or too-old required jsc plugin prints a `skip` line and leaves that domain untouched. Codex update preserves old `jsc-hooks` cache version paths as symlinks to the newest installed version, so 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 status for every command it ran, and every skipped domain has a dependency reason or local-tree reason. +5. Run `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). 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. On update, `tools/check-requires.sh {cli} {manifest}` checks each domain's `jsc.requires` before that domain is updated. A missing or too-old required jsc plugin prints a `skip` line and leaves that domain untouched. 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 status for every command it ran, and every skipped domain has a dependency reason or local-tree reason. 6. After install or update, call `jsc-hooks:hooks-install` to rewire the hooks. The hook installer must refresh `$JSC_HOME/current/jsc-hooks` and must run `tools/wire-cli.sh smoke {cli}` for every detected CLI. Treat any `No such file` in those smoke results as a failed update and report it; do not let the deploy finish as successful when a rewritten hook path cannot execute. Done when hooks-install reports purge, wiring, smoke and scan results for each detected CLI, and every smoke result is either `status=ok` or explicitly reported as the update failure. 7. After install or update, run `tools/write-guides.sh {mode} {domain}...` **once for the whole machine**, after every CLI in step 5 has finished. It rewrites `$JSC_HOME/update-guide.md` and `$JSC_HOME/remove-guide.md` from the live detection result, so the later update and removal runs have the real commands for this machine. Skip it for `uninstall`: the guides describe an installed skill set. Done when the script printed a `wrote` line for both files. 8. Report the result and any failure reason for every CLI × mode, plus every `skip` line, every Codex `compat` line, and every CLI that could not be version-checked in step 2. Done when every detected CLI appears in the report with its `result` status. diff --git a/tools/deploy.sh b/tools/deploy.sh index 510b9c4..f155550 100755 --- a/tools/deploy.sh +++ b/tools/deploy.sh @@ -72,22 +72,26 @@ codex_plugin_versions() { # $1=plugin name find "$_dir" -mindepth 1 -maxdepth 1 \( -type d -o -type l \) -exec basename {} \; 2>/dev/null | sort -V } -codex_latest_hooks_dir() { - _dir=$(codex_plugin_cache_root jsc-hooks) +codex_latest_plugin_dir() { # $1=plugin name $2=required file under version dir + _plugin="$1" + _required="$2" + _dir=$(codex_plugin_cache_root "$_plugin") [ -d "$_dir" ] || return 1 for _p in "$_dir"/*; do [ -d "$_p" ] || continue [ ! -L "$_p" ] || continue - [ -f "$_p/hooks/session-timer.sh" ] || continue + [ -f "$_p/$_required" ] || continue printf '%s\n' "$_p" done | sort -V | tail -n1 } -codex_preserve_old_hooks_cache() { # stdin=更新前版本清單 +codex_preserve_old_plugin_cache() { # $1=plugin name $2=required file under version dir;stdin=更新前版本清單 [ "$DRYRUN" = 1 ] && return 0 - _new=$(codex_latest_hooks_dir || true) + _plugin="$1" + _required="$2" + _new=$(codex_latest_plugin_dir "$_plugin" "$_required" || true) [ -n "$_new" ] || return 0 - _root=$(codex_plugin_cache_root jsc-hooks) + _root=$(codex_plugin_cache_root "$_plugin") while IFS= read -r _version; do [ -n "$_version" ] || continue _old="$_root/$_version" @@ -258,10 +262,17 @@ deploy_codex() { for d in $DOMAINS; do run "$bin" plugin add "jsc-$d@jsc"; done ;; update) + _old_cli_versions=$(codex_plugin_versions jsc-cli) _old_hooks_versions=$(codex_plugin_versions jsc-hooks) run "$bin" plugin marketplace upgrade jsc - for d in $DOMAINS; do check_requires "$d" && run "$bin" plugin add "jsc-$d@jsc"; done - printf '%s\n' "$_old_hooks_versions" | codex_preserve_old_hooks_cache + for d in $DOMAINS; do + check_requires "$d" && run "$bin" plugin add "jsc-$d@jsc" + if [ "$d" = cli ]; then + printf '%s\n' "$_old_cli_versions" | codex_preserve_old_plugin_cache jsc-cli tools/check-requires.sh + elif [ "$d" = hooks ]; then + printf '%s\n' "$_old_hooks_versions" | codex_preserve_old_plugin_cache jsc-hooks hooks/session-timer.sh + fi + done ;; uninstall) for d in $DOMAINS; do run "$bin" plugin remove "jsc-$d@jsc"; done