Windows: поддержка сборки MSIX (Microsoft Store и установщик с claude.com)

Скрипт не находил приложение, если оно стоит как пакет MSIX в
C:\Program Files\WindowsApps\Claude_<версия>_x64__pzs8sxrjxfjjc.

- Поиск через Get-AppxPackage с запасным путём по папке Packages.
- Запуск пакетного приложения по shell:AppsFolder\<PackageFamilyName>!Claude,
  запасные варианты: explorer.exe, alias в WindowsApps, протокол claude://.
- Сторонний режим у обеих сборок в %LOCALAPPDATA%\Claude-3p (по коду
  приложения 1.44121.2, в манифесте MSIX папка исключена из виртуализации).
- Флаг разработчика кладётся во все родные папки: %APPDATA%\Claude,
  LocalCache\Roaming\Claude у MSIX, %LOCALAPPDATA%\Claude.
- Честная проверка перезапуска и в setup.sh для Windows.
This commit is contained in:
2026-09-08 08:15:53 +03:00
parent fcf78a774f
commit ee06a11172
2 changed files with 216 additions and 30 deletions
+103 -16
View File
@@ -26,13 +26,70 @@ case "$(uname -s)" in
*) echo "Эта система не поддерживается (нужен Мак или Windows с Git Bash)."; exit 1 ;;
esac
# ---------- Обнаружение приложения (Windows) ----------
# Сборок две, и ведут они себя по-разному.
# · Классическая (Squirrel): %LOCALAPPDATA%\AnthropicClaude\app-<версия>\claude.exe,
# родные данные в %APPDATA%\Claude.
# · MSIX (Microsoft Store и установщик с claude.com): C:\Program Files\WindowsApps\
# Claude_<версия>_x64__pzs8sxrjxfjjc\app\Claude.exe. Папка закрыта системой, exe
# оттуда напрямую не запускается, приложение поднимают по идентификатору пакета,
# а родные данные Windows подменяет на
# %LOCALAPPDATA%\Packages\Claude_pzs8sxrjxfjjc\LocalCache\Roaming\Claude.
# Сторонний режим у ОБЕИХ сборок живёт в %LOCALAPPDATA%\Claude-3p: так прописано
# в коде приложения, а в манифесте MSIX эта папка исключена из подмены.
WIN_KIND=""; WIN_AUMID=""; WIN_PFN=""; WIN_EXE=""; WIN_VER=""
WIN_PROBE='
$ErrorActionPreference = "SilentlyContinue"
$exe = @("$env:LOCALAPPDATA\AnthropicClaude", "$env:LOCALAPPDATA\Programs") | Where-Object { Test-Path $_ } |
ForEach-Object { Get-ChildItem $_ -Recurse -Filter "claude.exe" -ErrorAction SilentlyContinue } | Select-Object -First 1
$pkg = $null
try {
if (-not (Get-Command Get-AppxPackage -ErrorAction SilentlyContinue) -and $PSVersionTable.PSVersion.Major -ge 7) { Import-Module Appx -UseWindowsPowerShell -ErrorAction SilentlyContinue }
$all = @(Get-AppxPackage -Name "*Claude*" | Where-Object { $_.Name -eq "Claude" -or $_.Name -like "*ClaudeDesktop*" -or $_.Publisher -like "*Anthropic*" })
$pkg = $all | Where-Object { $_.Name -eq "Claude" } | Select-Object -First 1
if (-not $pkg) { $pkg = $all | Select-Object -First 1 }
} catch { }
$pfn = ""; $aumid = ""; $ver = ""
if ($pkg) {
$pfn = "$($pkg.PackageFamilyName)"; $ver = "$($pkg.Version)"; $id = "Claude"
try { $ids = @((Get-AppxPackageManifest $pkg).Package.Applications.Application.Id); if ($ids.Count -gt 0 -and $ids[0]) { $id = "$($ids[0])" } } catch { }
$aumid = "$pfn!$id"
} else {
$d = Get-ChildItem (Join-Path $env:LOCALAPPDATA "Packages") -Directory -Filter "Claude_*" | Select-Object -First 1
if ($d) { $pfn = $d.Name; $aumid = "$pfn!Claude" }
}
$kind = ""
if ($pfn) { $launched = Test-Path (Join-Path $env:LOCALAPPDATA "Packages\$pfn\LocalCache\Roaming\Claude"); if ($launched -or -not $exe) { $kind = "msix" } }
if (-not $kind -and $exe) { $kind = "classic" }
$exePath = ""; if ($exe) { $exePath = $exe.FullName }
Write-Output "$kind|$aumid|$pfn|$exePath|$ver"
'
win_probe() {
local out
out="$(powershell.exe -NoProfile -ExecutionPolicy Bypass -Command "$WIN_PROBE" 2>/dev/null | tr -d '\r' | tail -n 1)" || out=""
IFS='|' read -r WIN_KIND WIN_AUMID WIN_PFN WIN_EXE WIN_VER <<EOF
$out
EOF
}
# ---------- Пути ----------
# P1_DIRS: родные папки приложения, туда идёт только флаг разработчика и чистка
# кэшей. Их несколько, потому что настоящая зависит от сборки, а лишний флаг
# ничему не вредит. C3P: папка стороннего режима, туда идут конфиги.
if [ "$OS" = "mac" ]; then
C1P="$HOME/Library/Application Support/Claude"
C3P="$HOME/Library/Application Support/Claude-3p"
P1_DIRS=("$HOME/Library/Application Support/Claude")
else
C1P="$LOCALAPPDATA/Claude"
win_probe
WLA="$(cygpath -u "$LOCALAPPDATA")"
WAP="$(cygpath -u "$APPDATA")"
C3P="$WLA/Claude-3p"
P1_DIRS=()
if [ "$WIN_KIND" = "msix" ]; then P1_DIRS+=("$WLA/Packages/$WIN_PFN/LocalCache/Roaming/Claude"); fi
for d in "$WAP/Claude" "$WLA/Claude" "$WLA/Claude-Data"; do
if [ -d "$d" ] || [ "${#P1_DIRS[@]}" -eq 0 ]; then P1_DIRS+=("$d"); fi
done
fi
# Папка стороннего режима это та же папка с суффиксом -3p.
C3P="${C1P}-3p"
LIB="$C3P/configLibrary"
ID_OLLAMA="a11a0000-0000-4000-8000-0000000000aa"
@@ -73,11 +130,11 @@ have_ps() { command -v powershell.exe >/dev/null 2>&1; }
# Developer не будет. Поэтому пишем в ОБЕ папки.
write_dev_flag() {
local d
for d in "$C1P" "$C3P"; do
for d in "${P1_DIRS[@]}" "$C3P"; do
mkdir -p "$d"
printf '{"allowDevTools": true}\n' > "$d/developer_settings.json"
done
ok "Режим разработчика включён в обеих папках приложения."
ok "Режим разработчика включён во всех папках приложения."
}
# ---------- deploymentMode ----------
@@ -107,13 +164,14 @@ PY
# Windows (он по умолчанию сохраняет UTF-8 именно так) и из старых скриптов.
repair_json_bom() {
[ -n "$PY" ] || return 0
"$PY" - "$LIB" "$C1P" "$C3P" <<'PY'
"$PY" - "$LIB" "${P1_DIRS[@]}" "$C3P" <<'PY'
import os, sys
lib, c1p, c3p = sys.argv[1:4]
lib = sys.argv[1]
dirs = sys.argv[2:]
targets = []
if os.path.isdir(lib):
targets += [os.path.join(lib, f) for f in os.listdir(lib) if f.endswith(".json")]
for d in (c1p, c3p):
for d in dirs:
for n in ("developer_settings.json", "claude_desktop_config.json"):
p = os.path.join(d, n)
if os.path.isfile(p): targets.append(p)
@@ -225,7 +283,7 @@ PY
# ВАЖНО: не путать с соседним пунктом Reset App Data, тот сносит папку целиком.
clear_caches() {
local d
for d in "$C1P" "$C3P"; do
for d in "${P1_DIRS[@]}" "$C3P"; do
[ -d "$d" ] || continue
rm -rf "$d/Cache" "$d/Code Cache" "$d/GPUCache" \
"$d/DawnGraphiteCache" "$d/DawnWebGPUCache" "$d/Service Worker" 2>/dev/null || true
@@ -283,10 +341,33 @@ restart_claude() {
Get-Process -Name "Claude*" -ErrorAction SilentlyContinue | Stop-Process -Force
Start-Sleep 3' 2>/dev/null || true
clear_caches
powershell.exe -NoProfile -Command '
$exe=@("$env:LOCALAPPDATA\AnthropicClaude","$env:LOCALAPPDATA\Programs") | ForEach-Object { Get-ChildItem $_ -Recurse -Filter "claude.exe" -ErrorAction SilentlyContinue } | Select-Object -First 1
if($exe){ Start-Process $exe.FullName } else { exit 1 }
' || { warn "Не смог перезапустить Claude, закрой и открой его вручную."; return 0; }
if [ "$WIN_KIND" = "msix" ]; then
# Пакетное приложение запускают по идентификатору, а не по пути к exe.
AK_AUMID="$WIN_AUMID" powershell.exe -NoProfile -Command '
$t = "shell:AppsFolder\" + $env:AK_AUMID
try { Start-Process $t -ErrorAction Stop; exit 0 } catch { }
try { Start-Process explorer.exe $t -ErrorAction Stop; exit 0 } catch { }
$a = Join-Path $env:LOCALAPPDATA "Microsoft\WindowsApps\Claude.exe"
if (Test-Path $a) { try { Start-Process $a -ErrorAction Stop; exit 0 } catch { } }
try { Start-Process "claude://" -ErrorAction Stop; exit 0 } catch { }
exit 1
' || { warn "Не смог запустить Claude, открой его вручную из меню Пуск."; return 0; }
elif [ -n "$WIN_EXE" ]; then
AK_EXE="$WIN_EXE" powershell.exe -NoProfile -Command 'Start-Process $env:AK_EXE' \
|| { warn "Не смог перезапустить Claude, закрой и открой его вручную."; return 0; }
else
warn "Не нашёл приложение Claude, открой его вручную."; return 0
fi
# Проверяем, что процесс реально поднялся: рапортовать успех вслепую нельзя.
j=0
while [ "$j" -lt 12 ]; do
if powershell.exe -NoProfile -Command 'if (Get-Process -Name "Claude*" -ErrorAction SilentlyContinue) { exit 0 } else { exit 1 }' 2>/dev/null; then break; fi
sleep 1; j=$((j+1))
done
if [ "$j" -ge 12 ]; then
warn "Claude не поднялся автоматически. Конфиги на месте, открой приложение вручную."
return 0
fi
fi
ok "Claude перезапущен."
}
@@ -339,11 +420,17 @@ fi
say "[1/4] Ищу приложение Claude..."
CLAUDE_OK=false
if [ "$OS" = "mac" ] && [ -d "/Applications/Claude.app" ]; then CLAUDE_OK=true; fi
if [ "$OS" = "win" ] && find "$LOCALAPPDATA/AnthropicClaude" "$LOCALAPPDATA/Programs" -maxdepth 3 -iname "claude.exe" 2>/dev/null | grep -q .; then CLAUDE_OK=true; fi
if [ "$OS" = "win" ] && [ -n "$WIN_KIND" ]; then CLAUDE_OK=true; fi
if [ "$CLAUDE_OK" = "true" ]; then
ok "Claude на месте."
case "$WIN_KIND" in
msix) ok "Claude на месте: сборка MSIX (Microsoft Store или установщик с claude.com)${WIN_VER:+, версия $WIN_VER}." ;;
classic) ok "Claude на месте: классическая сборка." ;;
*) ok "Claude на месте." ;;
esac
ok "Конфиги стороннего режима: $C3P"
else
warn "Приложение Claude не найдено, а без него настраивать нечего."
[ "$OS" = "win" ] && echo " Смотрел классическую сборку в AppData\\Local и пакеты Microsoft Store."
echo
echo " Поставь его руками, это одна минута:"
echo " 1. Открой https://claude.com/download и скачай версию для своей системы."