Cursor 突然找不到 Claude 模型?一招搞定!

最近几天,很多 Cursor 用户(尤其是 Team 账号)发现模型列表里突然少了一大半,Opus 5.5、Sonnet 5.5 等 Claude 模型全都不见了。

原因其实很简单:Cursor 桌面版请求时,默认把客户端身份标记成了 ide,导致官方后台直接隐藏了 Claude 系列。只要把身份“伪装”成 desktop 就能解决!

原作者写了一个 Python 脚本,只需三步就能一键修复。

🚀 极简解决步骤:

第一步:彻底关闭 Cursor
记得检查一下电脑右下角/右上角的系统托盘,确保完全退出程序。

第二步:运行修复脚本
下载作者的 apply.py 脚本,在终端(命令行)里运行:

点击查看 apply.py 脚本
#!/usr/bin/env python3
"""把 Cursor IDE 的模型目录身份从 glass/ide 改成 desktop。

desktop 和 cli 拿到的模型目录相同,包含 Opus / Sonnet。
glass 和 ide 的目录里没有 Claude。

只改两类位置:
1. header.set("x-cursor-client-type", <变量>??"ide")
   改成 header.set("x-cursor-client-type","desktop")
2. clientIdentity:{clientType:"ide"}
   改成 clientIdentity:{clientType:"desktop"}

不改 main.js 里登录用的 "x-cursor-client-type":"ide"。

用法:
  先完全退出 Cursor,再执行。

  python3 apply.py              # 自动找安装目录,先预览
  python3 apply.py --apply      # 真正写入
  python3 apply.py --apply /path/to/resources/app
  sudo python3 apply.py --apply # Linux 系统目录需要 root

  python3 apply.py --revert --apply
"""

from __future__ import annotations

import argparse
import re
import sys
from pathlib import Path

HEADER_RE = re.compile(
    r'header\.set\("x-cursor-client-type",(?P<expr>[A-Za-z_$][\w$]*\?\?"ide"|\"desktop\")\)'
)
HEADER_NEW = 'header.set("x-cursor-client-type","desktop")'
IDENTITY_OLD = 'clientIdentity:{clientType:"ide"}'
IDENTITY_NEW = 'clientIdentity:{clientType:"desktop"}'

RELATIVE_FILES = [
    "out/vs/workbench/workbench.glass.main.js",
    "out/vs/workbench/workbench.desktop.main.js",
    "out/vs/workbench/api/node/extensionHostProcess.js",
    "out/vs/workbench/api/worker/extensionHostWorkerMain.js",
    "extensions/cursor-agent-host/dist/main.js",
]


def candidate_apps() -> list[Path]:
    home = Path.home()
    found: list[Path] = []
    roots = [
        Path("/usr/share/cursor/resources/app"),
        Path("/usr/lib/cursor/resources/app"),
        Path("/opt/Cursor/resources/app"),
        Path("/opt/cursor/resources/app"),
        home / ".local/share/cursor/resources/app",
        Path("/Applications/Cursor.app/Contents/Resources/app"),
        home / "Applications/Cursor.app/Contents/Resources/app",
        Path.home() / "AppData/Local/Programs/cursor/resources/app",
        Path.home() / "AppData/Local/cursor/resources/app",
    ]
    local = Path.home() / "AppData/Local"
    if local.is_dir():
        roots.append(local / "Programs/cursor/resources/app")
    for root in roots:
        if (root / "out/vs/workbench").is_dir():
            found.append(root)
    return found


def cache_dirs() -> list[Path]:
    home = Path.home()
    dirs = [
        home / ".config/Cursor/CachedData",
        home / "Library/Application Support/Cursor/CachedData",
        home / "AppData/Roaming/Cursor/CachedData",
    ]
    return [d for d in dirs if d.is_dir()]


def transform(text: str, revert: bool) -> tuple[str, list[str]]:
    notes: list[str] = []

    def header_sub(match: re.Match[str]) -> str:
        expr = match.group("expr")
        if revert:
            if expr == '"desktop"':
                notes.append("header desktop -> <var>??\"ide\" 无法自动还原变量名,跳过;请用 .bak 文件")
                return match.group(0)
            notes.append("header 已是原样")
            return match.group(0)
        if expr == '"desktop"':
            notes.append("header 已经是 desktop")
            return match.group(0)
        notes.append(f"header {expr} -> \"desktop\"")
        return HEADER_NEW

    new_text, header_count = HEADER_RE.subn(header_sub, text)
    if header_count == 0 and '??"ide"' in text:
        notes.append('文件里还有 ??"ide",但没匹配到 header.set,需要手工看')

    if revert:
        count = new_text.count(IDENTITY_NEW)
        if count:
            new_text = new_text.replace(IDENTITY_NEW, IDENTITY_OLD)
            notes.append(f"clientIdentity desktop -> ide x{count}")
        elif IDENTITY_OLD in new_text:
            notes.append("clientIdentity 已是 ide")
    else:
        count = new_text.count(IDENTITY_OLD)
        if count:
            new_text = new_text.replace(IDENTITY_OLD, IDENTITY_NEW)
            notes.append(f"clientIdentity ide -> desktop x{count}")
        elif IDENTITY_NEW in new_text:
            notes.append("clientIdentity 已经是 desktop")
    return new_text, notes


def backup(path: Path) -> Path:
    bak = path.with_name(path.name + ".bak-before-desktop-identity")
    if not bak.exists():
        bak.write_bytes(path.read_bytes())
    return bak


def clear_cache() -> None:
    for cache in cache_dirs():
        for child in cache.iterdir():
            if child.is_dir():
                for sub in child.rglob("*"):
                    if sub.is_file():
                        sub.unlink()
                for sub in sorted(child.rglob("*"), reverse=True):
                    if sub.is_dir():
                        sub.rmdir()
                child.rmdir()
                print(f"cleared {child}")


def main() -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument("app", nargs="?", help="Cursor 的 resources/app 目录")
    parser.add_argument("--apply", action="store_true", help="写入文件。不加这个参数只预览")
    parser.add_argument("--revert", action="store_true", help="用备份还原;没有备份时只能还原 clientIdentity")
    parser.add_argument("--clear-cache", action="store_true", help="删掉 CachedData,避免沿用旧字节码")
    args = parser.parse_args()

    if args.app:
        apps = [Path(args.app)]
    else:
        apps = candidate_apps()
    if not apps:
        print("没找到 Cursor 安装目录。请传入 resources/app 的路径。", file=sys.stderr)
        return 1

    for app in apps:
        print(f"app {app}")
        for rel in RELATIVE_FILES:
            path = app / rel
            if not path.is_file():
                print(f"  skip 缺少 {rel}")
                continue
            original = path.read_text(encoding="utf-8", errors="replace")
            if args.revert:
                bak = path.with_name(path.name + ".bak-before-desktop-identity")
                if bak.is_file():
                    print(f"  revert {rel} <- {bak.name}")
                    if args.apply:
                        path.write_bytes(bak.read_bytes())
                    continue
            updated, notes = transform(original, revert=args.revert)
            print(f"  {rel}")
            for note in notes:
                print(f"    {note}")
            if updated == original:
                continue
            if not args.apply:
                print("    预览,未写入")
                continue
            bak = backup(path)
            path.write_text(updated, encoding="utf-8")
            print(f"    已写入,备份 {bak.name}")

    if args.apply and args.clear_cache:
        clear_cache()
    elif args.apply:
        print("下次启动前可以再跑一次并加上 --clear-cache,或者手动删掉 Cursor 的 CachedData。")
    else:
        print("这是预览。确认后加上 --apply。Linux 系统目录要加 sudo。")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())

python3 apply.py --apply

(注:如果是 Linux 系统目录,前面加 sudo;Windows 如果没自动识别路径,可在命令后加上你的 resources/app 路径)

第三步:重新打开 Cursor
重启后,去模型列表看看,Opus 5.5 是不是已经回来了?

⚠️ 温馨提示:

  • 会失效:每次 Cursor 官方更新后,这个修改会被覆盖。如果 Claude 又没了,重新跑一遍上面的脚本就行。
  • 想还原:运行 python3 apply.py --revert --apply 即可恢复原版。

如果你也遇到了同样的问题,赶紧去试试吧!

https://linux.do/t/topic/3040582

posted @ 2026-10-28 16:49  流年灬似氺  阅读(67)  评论(0)    收藏  举报