release: v0.1.9 develop 到 master #29
Executable
+264
@@ -0,0 +1,264 @@
|
||||
#!/usr/bin/env sh
|
||||
# write-guides.sh — 產生這台機器專屬的更新指引與移除指引。
|
||||
# 用法:
|
||||
# write-guides.sh [-n] {install|update} {domain} [domain...]
|
||||
# -n 或 --dry-run:只印會寫到哪兩個檔案,不寫入。
|
||||
# {domain} 裸名或帶 jsc- 前綴皆可,交給 deploy.sh 正規化。
|
||||
# 產出(兩個檔案,整份覆寫):
|
||||
# $JSC_HOME/update-guide.md 下次更新照著做的指引
|
||||
# $JSC_HOME/remove-guide.md 要整組移除時照著做的指引
|
||||
# 輸出(TSV,一行一筆):
|
||||
# cli<TAB>{cli}<TAB>{path}<TAB>{version} 這台機器上偵測到的 CLI
|
||||
# plan<TAB>{path} dry-run 時會寫入的檔案
|
||||
# wrote<TAB>{path} 實際寫入的檔案
|
||||
# note<TAB>{原因} 非致命的說明(例:一個 CLI 都沒偵測到)
|
||||
# 結束碼:兩份都寫成 0;參數錯誤 2;目錄或檔案寫不進去 4。
|
||||
#
|
||||
# 指引內容一律依實際偵測結果生成,不寫死:
|
||||
# CLI 清單來自 detect-clis.sh;每支 CLI 的實際指令來自 deploy.sh 的 dry-run(-n)輸出。
|
||||
# 指令字面因此與 deploy.sh 真正會跑的完全一致——各 CLI 的差異只有 deploy.sh 一個真實來源,
|
||||
# 這裡再抄一份就會有兩套指令,改了一邊忘了另一邊,指引就開始騙人。
|
||||
# kiro 走不走本地複製退路,也是讀 deploy.sh 的 note 行判斷,不自己再探測一次。
|
||||
# 環境變數:
|
||||
# JSC_HOME 指引寫入的目錄(預設 ~/.jsc)
|
||||
# GITEA_HOST Gitea 站台,可省略 scheme(預設 https://gitea.jsc.idv.tw)
|
||||
# JSC_GITEA_OWNER 存取庫的 owner(預設 plugins)
|
||||
# JSC_LOCAL_PLUGINS antigravity/kiro 用的本地 clone 目錄(預設 $JSC_HOME/plugins)
|
||||
# JSC_KIRO_SKILLS kiro 退路用的 skills 目錄(預設 ~/.kiro/skills)
|
||||
set -u
|
||||
|
||||
HERE=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||
DETECT="$HERE/detect-clis.sh"
|
||||
DEPLOY="$HERE/deploy.sh"
|
||||
JSC_HOME="${JSC_HOME:-$HOME/.jsc}"
|
||||
HOST="${GITEA_HOST:-https://gitea.jsc.idv.tw}"
|
||||
case "$HOST" in http://*|https://*) ;; *) HOST="https://$HOST" ;; esac
|
||||
HOST="${HOST%/}"
|
||||
OWNER="${JSC_GITEA_OWNER:-plugins}"
|
||||
MKT="$HOST/$OWNER/meta.git"
|
||||
LOCAL_DIR="${JSC_LOCAL_PLUGINS:-$JSC_HOME/plugins}"
|
||||
KIRO_SKILLS="${JSC_KIRO_SKILLS:-$HOME/.kiro/skills}"
|
||||
UPDATE_GUIDE="$JSC_HOME/update-guide.md"
|
||||
REMOVE_GUIDE="$JSC_HOME/remove-guide.md"
|
||||
DRYRUN=0
|
||||
TAB=$(printf '\t')
|
||||
|
||||
usage() {
|
||||
echo "用法:write-guides.sh [-n] {install|update} {domain} [domain...]" >&2
|
||||
exit 2
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
-n|--dry-run) DRYRUN=1; shift ;;
|
||||
esac
|
||||
|
||||
[ $# -ge 2 ] || usage
|
||||
MODE=$1
|
||||
shift
|
||||
case "$MODE" in install|update) ;; *) usage ;; esac
|
||||
[ -x "$DETECT" ] || [ -f "$DETECT" ] || { echo "找不到 detect-clis.sh:$DETECT" >&2; exit 4; }
|
||||
[ -f "$DEPLOY" ] || { echo "找不到 deploy.sh:$DEPLOY" >&2; exit 4; }
|
||||
|
||||
DOMAINS=""
|
||||
for _d in "$@"; do
|
||||
case "$_d" in jsc-*) _d="${_d#jsc-}" ;; esac
|
||||
DOMAINS="$DOMAINS $_d"
|
||||
done
|
||||
DOMAINS="${DOMAINS# }"
|
||||
|
||||
if [ "$DRYRUN" = 1 ]; then
|
||||
printf 'plan\t%s\n' "$UPDATE_GUIDE"
|
||||
printf 'plan\t%s\n' "$REMOVE_GUIDE"
|
||||
fi
|
||||
|
||||
# 偵測到的 CLI,一行一筆 name<TAB>path<TAB>version。
|
||||
CLIS=$(mktemp) || { echo "無法建立暫存檔" >&2; exit 4; }
|
||||
NOTES=$(mktemp) || { rm -f "$CLIS"; echo "無法建立暫存檔" >&2; exit 4; }
|
||||
CMDS=$(mktemp) || { rm -f "$CLIS" "$NOTES"; echo "無法建立暫存檔" >&2; exit 4; }
|
||||
cleanup() { rm -f "$CLIS" "$NOTES" "$CMDS"; }
|
||||
|
||||
sh "$DETECT" > "$CLIS" 2>/dev/null || true
|
||||
while IFS="$TAB" read -r name path ver; do
|
||||
[ -n "${name:-}" ] || continue
|
||||
printf 'cli\t%s\t%s\t%s\n' "$name" "$path" "$ver"
|
||||
done < "$CLIS"
|
||||
[ -s "$CLIS" ] || printf 'note\t%s\n' "一個 CLI 都沒偵測到,指引只會寫下 marketplace 與 domain 清單"
|
||||
|
||||
# 跑一次 deploy.sh 的 dry-run,把 cmd 行與 note 行分開收好。
|
||||
# $1=mode $2=cli;cmd 行寫進 $CMDS,note 行寫進 $NOTES,兩個檔案每次都重寫。
|
||||
harvest() {
|
||||
: > "$CMDS"
|
||||
: > "$NOTES"
|
||||
JSC_DEPLOY_DRYRUN=1 sh "$DEPLOY" -n "$1" "$2" $DOMAINS 2>/dev/null \
|
||||
| while IFS="$TAB" read -r kind a b; do
|
||||
case "$kind" in
|
||||
cmd) printf '%s\n' "$a" >> "$CMDS" ;;
|
||||
note) printf '%s\n' "$b" >> "$NOTES" ;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
# 這支 CLI 的 plugin 安裝方式,一句話。kiro 讀 $NOTES 判斷走不走複製退路。
|
||||
# codex 分模式講:它的 update 有陷阱(沒有 plugin update),移除段落講這件事只會讓人分心。
|
||||
cli_method() { # $1=cli $2=mode
|
||||
case "$1" in
|
||||
claude|copilot)
|
||||
printf '原生 plugin 指令,來源是統一 marketplace `jsc`' ;;
|
||||
codex)
|
||||
if [ "$2" = uninstall ]; then
|
||||
printf '原生 plugin 指令;移除用 `plugin remove`,不是 `plugin uninstall`'
|
||||
else
|
||||
printf '原生 plugin 指令;沒有 `plugin update` 子指令,更新一律重跑 `plugin add` 就地升級'
|
||||
fi ;;
|
||||
antigravity)
|
||||
printf '不接受 Gitea URL,先把存取庫 clone 到 `%s`,再從本地路徑安裝' "$LOCAL_DIR" ;;
|
||||
kiro)
|
||||
if [ -s "$NOTES" ]; then
|
||||
printf '這個版本沒有 `plugin` 子指令,整批走本地複製退路(複製到 `%s`)' "$KIRO_SKILLS"
|
||||
else
|
||||
printf '原生 plugin 指令;單一 domain 失敗才退回本地複製(複製到 `%s`)' "$KIRO_SKILLS"
|
||||
fi ;;
|
||||
*)
|
||||
printf '原生 plugin 指令' ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# 指令區塊:一支 CLI 一個 sh 圍欄,內容就是 deploy.sh 會跑的每一行。
|
||||
emit_cmds() { # $1=mode $2=cli
|
||||
harvest "$1" "$2"
|
||||
printf '### %s\n\n' "$2"
|
||||
printf '%s\n\n' "$(cli_method "$2" "$1")"
|
||||
if [ -s "$NOTES" ]; then
|
||||
while IFS= read -r n; do
|
||||
[ -n "$n" ] || continue
|
||||
printf '> %s\n\n' "$n"
|
||||
done < "$NOTES"
|
||||
fi
|
||||
printf '```sh\n'
|
||||
if [ -s "$CMDS" ]; then
|
||||
cat "$CMDS"
|
||||
else
|
||||
printf '# deploy.sh 這一輪沒有要對 %s 執行的指令\n' "$2"
|
||||
fi
|
||||
printf '```\n\n'
|
||||
}
|
||||
|
||||
domain_list() {
|
||||
_out=""
|
||||
for d in $DOMAINS; do
|
||||
[ -z "$_out" ] && _out="\`jsc-$d\`" || _out="$_out、\`jsc-$d\`"
|
||||
done
|
||||
printf '%s' "$_out"
|
||||
}
|
||||
|
||||
domain_count() {
|
||||
set -- $DOMAINS
|
||||
printf '%s' "$#"
|
||||
}
|
||||
|
||||
STAMP=$(date '+%Y-%m-%d %H:%M:%S %z' 2>/dev/null || date)
|
||||
# 主機與帳號分兩欄寫。併成一欄要用斜線隔開,那是 STE100 的並列斜線違規。
|
||||
HOSTNAME_NOW=$(hostname 2>/dev/null || echo 未知主機)
|
||||
USER_NOW=$(id -un 2>/dev/null || echo 未知帳號)
|
||||
|
||||
# 兩份指引共用的抬頭:說清楚這份檔案是誰產生的、什麼時候產生的、依據是什麼。
|
||||
header() { # $1=標題 $2=一句用途
|
||||
printf '# %s\n\n' "$1"
|
||||
printf '%s\n\n' "$2"
|
||||
printf '本檔由 `jsc-cli/tools/write-guides.sh` 產生,內容依產生當下這台機器的偵測結果生成。重跑 `/jsc-cli:deploy` 會整份覆寫。\n\n'
|
||||
printf '| 項目 | 內容 |\n| --- | --- |\n'
|
||||
printf '| 產生時間 | %s |\n' "$STAMP"
|
||||
printf '| 產生時機 | `/jsc-cli:deploy` 的 %s 收尾 |\n' "$MODE"
|
||||
printf '| 主機 | %s |\n' "$HOSTNAME_NOW"
|
||||
printf '| 登入帳號 | %s |\n' "$USER_NOW"
|
||||
printf '| Marketplace | `jsc`(`%s`) |\n' "$MKT"
|
||||
printf '| 安裝 token | `jsc-{domain}@jsc` |\n'
|
||||
printf '| Domain 數量 | %s |\n' "$(domain_count)"
|
||||
printf '| Domain 清單 | %s |\n' "$(domain_list)"
|
||||
printf '| 資料目錄 | `%s` |\n\n' "$JSC_HOME"
|
||||
}
|
||||
|
||||
cli_table() {
|
||||
printf '## 這台機器偵測到的 CLI\n\n'
|
||||
if [ -s "$CLIS" ]; then
|
||||
printf '| CLI | 執行檔 | 版本 | plugin 安裝方式 |\n| --- | --- | --- | --- |\n'
|
||||
while IFS="$TAB" read -r name path ver; do
|
||||
[ -n "${name:-}" ] || continue
|
||||
harvest "$MODE" "$name"
|
||||
printf '| %s | `%s` | %s | %s |\n' "$name" "$path" "${ver:-未知}" "$(cli_method "$name" "$MODE")"
|
||||
done < "$CLIS"
|
||||
printf '\n'
|
||||
else
|
||||
printf '一個 CLI 都沒偵測到。裝好任一支 CLI 之後重跑 `/jsc-cli:deploy`,這份指引才會有指令可循。\n\n'
|
||||
fi
|
||||
}
|
||||
|
||||
# 各 CLI 的指令段落。$1=mode
|
||||
cli_sections() {
|
||||
[ -s "$CLIS" ] || return 0
|
||||
while IFS="$TAB" read -r name path ver; do
|
||||
[ -n "${name:-}" ] || continue
|
||||
emit_cmds "$1" "$name"
|
||||
done < "$CLIS"
|
||||
}
|
||||
|
||||
write_update_guide() {
|
||||
header 'jsc 技能組更新指引' '整組 jsc plugins 要更新時,照這份指引走。首選一律是 `/jsc-cli:deploy` 選 `update`;下面的指令是同一件事的手動版本,CLI 壞掉或不想開工作階段時用。'
|
||||
cli_table
|
||||
printf '## 更新指令\n\n'
|
||||
printf '每支 CLI 各自一組,指令與 `jsc-cli/tools/deploy.sh update {cli} {domain}...` 實際會跑的完全相同。\n\n'
|
||||
cli_sections update
|
||||
printf '## 更新完要做的事\n\n'
|
||||
printf '1. 重新接線 hooks:`/jsc-hooks:hooks-install`。plugin 換版後接線檔會過期。\n'
|
||||
printf '2. 關閉目前的工作階段並重新啟動。新的技能內容要重開工作階段才載入得到。\n'
|
||||
printf '3. 體檢一次:`/jsc-cli:doctor`。確認版本、接線與設定都對得上。\n\n'
|
||||
printf '`%s` 這個檔案存在,就代表有一輪部署還沒重啟。重啟提示由 `jsc-hooks` 判讀,逃生門是 `JSC_RESTART_GATE=off`。\n\n' "$JSC_HOME/restart-required"
|
||||
printf '## 移除\n\n'
|
||||
printf '要整組移除看 `%s`。\n' "$REMOVE_GUIDE"
|
||||
}
|
||||
|
||||
write_remove_guide() {
|
||||
header 'jsc 技能組移除指引' '整組 jsc plugins 要移除時,照這份指引走。首選一律是 `/jsc-cli:deploy` 選 `uninstall`;下面的指令是同一件事的手動版本。'
|
||||
cli_table
|
||||
printf '## 移除指令\n\n'
|
||||
printf '每支 CLI 各自一組,指令與 `jsc-cli/tools/deploy.sh uninstall {cli} {domain}...` 實際會跑的完全相同。marketplace 指令一輪只跑一次,排在各 domain 之後。\n\n'
|
||||
cli_sections uninstall
|
||||
printf '## 移除後的殘留物\n\n'
|
||||
printf 'plugin 指令只管 plugin 自己。下面這些是 jsc 另外寫在機器上的東西,要不要清掉自己決定:\n\n'
|
||||
printf '| 路徑 | 內容 | 清掉的影響 |\n| --- | --- | --- |\n'
|
||||
printf '| `%s` | hook 資料目錄:工作階段計時、用量統計、版本快取、模型標籤表、備份 |' "$JSC_HOME"
|
||||
printf ' 用量統計與設定備份一起消失,救不回來 |\n'
|
||||
printf '| `%s` | antigravity 與 kiro 用的本地 clone | 下次安裝要重新 clone |\n' "$LOCAL_DIR"
|
||||
printf '| `%s` | kiro 複製退路的技能目錄 | kiro 的 jsc 技能完全消失 |\n' "$KIRO_SKILLS"
|
||||
printf '| shell rc 檔的 `# jsc-config` 段落 | `/jsc-cli:setup` 寫入的環境變數 | jsc 相關環境變數失效,段落外的內容不受影響 |\n\n'
|
||||
printf 'rc 檔那一段用 `jsc-cli/tools/apply-config.sh unset {KEY}` 逐項移除,或直接手動刪掉 `# jsc-config` 與 `# /jsc-config` 之間的內容。動手前先備份。\n\n'
|
||||
printf '## 移除完要做的事\n\n'
|
||||
printf '1. 關閉目前的工作階段並重新啟動。已載入的技能要重開工作階段才會消失。\n'
|
||||
printf '2. 確認殘留:`/jsc-cli:doctor` 若還列得出 jsc 版本,代表某支 CLI 還留著 plugin。\n'
|
||||
}
|
||||
|
||||
[ "$DRYRUN" = 1 ] && { cleanup; exit 0; }
|
||||
|
||||
mkdir -p "$JSC_HOME" 2>/dev/null || { cleanup; echo "無法建立目錄:$JSC_HOME" >&2; exit 4; }
|
||||
|
||||
rc=0
|
||||
tmp="$JSC_HOME/.update-guide.md.tmp"
|
||||
if write_update_guide > "$tmp" 2>/dev/null && mv "$tmp" "$UPDATE_GUIDE" 2>/dev/null; then
|
||||
printf 'wrote\t%s\n' "$UPDATE_GUIDE"
|
||||
else
|
||||
rm -f "$tmp"
|
||||
echo "無法寫入 $UPDATE_GUIDE" >&2
|
||||
rc=4
|
||||
fi
|
||||
|
||||
tmp="$JSC_HOME/.remove-guide.md.tmp"
|
||||
if write_remove_guide > "$tmp" 2>/dev/null && mv "$tmp" "$REMOVE_GUIDE" 2>/dev/null; then
|
||||
printf 'wrote\t%s\n' "$REMOVE_GUIDE"
|
||||
else
|
||||
rm -f "$tmp"
|
||||
echo "無法寫入 $REMOVE_GUIDE" >&2
|
||||
rc=4
|
||||
fi
|
||||
|
||||
cleanup
|
||||
exit "$rc"
|
||||
Reference in New Issue
Block a user