Files
ISEP/scripts/isep-doctor
T
Leo 8eb4c58d2d isep-doctor:講出這台的 ISEP plugin 有沒有真的載入(inkstone/ISEP#150)
根因不是「Desktop 不跑 hook」:Desktop 1.52386.0/CC 2.1.266 內實測專案
PreToolUse hook 照擋。真正斷的是 ~/.claude/settings.json
extraKnownMarketplaces.inkstone.source 多了 path,CC 把它當 git 抓取欄位,
marketplace 被忽略 ⇒ isep@inkstone「Marketplace inkstone not found」,
59 支閘在 CLI 與 Desktop 一起失效,而且沒有任何閘講得出來。

本支從 plugin 外面查(claude plugin list --json + settings 對 known_marketplaces),
點名多出的欄位並印出一行修法。迴歸測試 scripts/test-isep-doctor.sh 5/5。

假設:修 ~/.claude/settings.json 是 leo 的個人設定,由 leo 親手執行,本 commit 不動它。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 14:55:26 +08:00

103 lines
4.3 KiB
Python
Executable File
Raw 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.
#!/usr/bin/env python3
"""isep-doctor — 這台機器的 Claude Code 到底有沒有把 ISEP 載進來(inkstone/ISEP#150
為什麼要有這支:
plugin 載入失敗時,ISEP 的 59 支閘**一支都不會跑**,而且**沒有任何閘能講出來**——
講話的 isep-presence-beacon 本身就在那個沒載入的 plugin 裡。
2026-09-05 起 `~/.claude/settings.json` 的 `extraKnownMarketplaces.inkstone.source`
多了一個 `path`(本機 checkout 路徑),Claude Code 把它當成 git 來源的抓取欄位
debug log:「its network source differs from the one declared for it in settings
(… path …)」),整個 marketplace 被忽略 ⇒ `isep@inkstone` 報 "Marketplace inkstone not found"。
CLI 與 Desktop 同病,當時被誤判成「Desktop 不跑 hook」。
判準(狀態,不是措辭):
① `claude plugin list --json` 裡 isep@inkstone 帶 errors ⇒ ❌
② settings 對 inkstone 的宣告,比 known_marketplaces.json 記的多了抓取欄位 ⇒ 指出是哪一個、給修法
都沒有 ⇒ ✅,離開碼 0
離開碼:0 通|1 沒載入(附原因與一行修法)|2 查不了(找不到 claude 可執行檔等)
測試用環境變數:ISEP_DOCTOR_PLUGIN_LIST_JSON(直接給 plugin list 的輸出檔)、
ISEP_DOCTOR_CLAUDE_HOME(代替 ~/.claude)。迴歸測試:scripts/test-isep-doctor.sh
"""
import glob
import json
import os
import shutil
import subprocess
import sys
PLUGIN_ID = "isep@inkstone"
MARKET = "inkstone"
FETCH_FIELDS = ("path", "ref", "headers", "sparsePaths")
home = os.environ.get("ISEP_DOCTOR_CLAUDE_HOME") or os.path.expanduser("~/.claude")
def find_claude():
c = shutil.which("claude")
if c:
return c
cands = sorted(glob.glob(os.path.expanduser(
"~/Library/Application Support/Claude/claude-code/*/claude.app/Contents/MacOS/claude")))
return cands[-1] if cands else None
def plugin_list():
f = os.environ.get("ISEP_DOCTOR_PLUGIN_LIST_JSON")
if f:
return json.load(open(f))
exe = find_claude()
if not exe:
return None
out = subprocess.run([exe, "plugin", "list", "--json"], capture_output=True, text=True, timeout=60)
return json.loads(out.stdout)
def load(p):
try:
return json.load(open(p))
except Exception:
return {}
try:
plugins = plugin_list()
except Exception as e: # noqa: BLE001
print(f"◐ 查不了:claude plugin list 失敗({e}")
sys.exit(2)
if plugins is None:
print("◐ 查不了:找不到 claude 可執行檔")
sys.exit(2)
entry = next((p for p in plugins if p.get("id") == PLUGIN_ID), None)
declared = (load(os.path.join(home, "settings.json")).get("extraKnownMarketplaces") or {}).get(MARKET, {}).get("source")
known = (load(os.path.join(home, "plugins", "known_marketplaces.json")).get(MARKET) or {}).get("source")
extra = []
if isinstance(declared, dict):
extra = [k for k in FETCH_FIELDS if k in declared and (not isinstance(known, dict) or declared.get(k) != known.get(k))]
if entry is None:
print(f"❌ {PLUGIN_ID} 沒有安裝(這台的閘全部不存在)")
print(" 修法:claude plugin marketplace add https://git.uncle6.me/inkstone/ISEP.git && claude plugin install isep@inkstone")
sys.exit(1)
errors = entry.get("errors") or []
if not errors and not extra:
print(f"✅ {PLUGIN_ID} {entry.get('version')} 已載入(enabled={entry.get('enabled')}")
sys.exit(0)
print(f"❌ {PLUGIN_ID} {entry.get('version')} 沒有載入——這台 Claude CodeCLI 與 Desktop 都一樣)一支 ISEP 閘都不會跑")
for e in errors:
print(f" Claude Code 回報:{e}")
if extra:
print(f" 原因:~/.claude/settings.json 的 extraKnownMarketplaces.{MARKET}.source 多了 {', '.join(extra)}"
"Claude Code 把它當 git 抓取欄位,跟已安裝的 marketplace 對不上,整個 marketplace 被忽略")
fields = ", ".join(repr(k) for k in extra)
print(" 修法(一行,改完重開 Claude DesktopCLI session):")
print(f" python3 -c \"import json,os;p=os.path.expanduser('~/.claude/settings.json');d=json.load(open(p));"
f"s=d['extraKnownMarketplaces']['{MARKET}']['source'];[s.pop(k,None) for k in ({fields},)];"
"json.dump(d,open(p,'w'),ensure_ascii=False,indent=2)\"")
sys.exit(1)