Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,17 @@ irm https://raw.githubusercontent.com/TokenNotIncluded/lmm-scripts/main/menu.ps1
| 文件名(`.sh` / `.ps1`) | 安装方式 | 启动 |
|---|---|---|
| `pi` | 官方安装器安装 Pi,再通过 npm 的 LMM 安装入口清理重复来源 | `pi`,然后 `/login` |
| `dsh` | npm + pnpm,从 npm 安装 LMM 插件;默认 web profile | `dsh web` |
| `dsh` | npm + pnpm,安装当前 GitHub Release 的 LMM 插件;默认 web profile | `dsh web` |
| `codex` | 官方原生安装器,参数直接传给官方 | `codex` |
| `claude-code` | 官方原生安装器,参数直接传给官方 | `claude` |
| `cc-switch` | Linux 发行包/AUR,macOS Homebrew,Windows 官方 MSI | 桌面应用 |
| `clash-verge-rev` | Linux deb/rpm/AUR,macOS Homebrew,Windows WinGet | 桌面应用 |
| `lmm` | 0.1.0 预览版发行包;另支持 `--from-source` / `-FromSource` | 安装结束打印的完整路径 |
| `codewhale` | 官方 npm 安装 + 固定版本 LMM OAuth 适配器 | `bash codewhale.sh run` / `.\codewhale.ps1 run` |

Pi 的版本、安装位置、权限和 Windows Git Bash 交给官方安装器。官方正常安装后,脚本用 npm 运行 LMM 插件的来源切换入口,并把官方安装器选出的 Pi 路径传给它。该入口先安装 npm 版本,成功后只移除同一插件在当前项目和用户配置中的 Git、本地等其他来源;失败时保留旧来源。LMM alpha 支持 Pi 0.86.1 至 0.87.x,其他版本明确跳过插件。安装 LMM 插件另需 Node.js 22.19+ 和 npm。DSH 的宿主继续固定已适配版本,LMM 插件改从 npm 安装;其当前官方 README 使用 npx,没有现行的独立安装脚本,不能拿归档记录中的旧脚本替代。
Pi 的版本、安装位置、权限和 Windows Git Bash 交给官方安装器。官方正常安装后,脚本用 npm 运行 LMM 插件的来源切换入口,并把官方安装器选出的 Pi 路径传给它。该入口先安装 npm 版本,成功后只移除同一插件在当前项目和用户配置中的 Git、本地等其他来源;失败时保留旧来源。LMM alpha 支持 Pi 0.86.1 至 0.87.x,其他版本明确跳过插件。安装 LMM 插件另需 Node.js 22.19+ 和 npm。DSH 安装官方 npm `latest` 宿主和 pnpm,LMM 插件安装当前 GitHub Release 的已构建包;其当前官方 README 使用 npx,没有现行的独立安装脚本,不能拿归档记录中的旧脚本替代。

`dsh.sh` / `dsh.ps1` 只配置 CLI 的 `web` profile,固定 DSH `0.1.5-rc.2` 和对应插件 `0.1.0-alpha.3`。官方 DSH Desktop `0.1.7-alpha.2` 使用独立的 `desktop` profile,应在桌面端“插件”页安装 `@tokennotincluded/dsh-lmm-provider@0.1.0-alpha.4`;CLI 安装不会进入桌面端。
`dsh.sh` / `dsh.ps1` 默认配置 CLI 的 `web` profile,可传入其他 profile。宿主使用官方 `latest`,插件通过 GitHub 当前 Release 解析版本化下载链接,避免安装过时 npm 预览包或缺少构建文件的源码。最新插件已验证官方 DSH `0.2.0-rc.2`,此版本是验证基线,没有人为上限。官方 DSH Desktop `0.1.7-alpha.2` 使用独立的 `desktop` profile,应在桌面端“插件”页安装 `@tokennotincluded/dsh-lmm-provider@0.1.0-alpha.4`;CLI 安装不会进入桌面端。

原有安装器不写供应商配置、不登录账号、不启用系统代理或 TUN。Codewhale 默认 `setup` 会引导用户在浏览器确认 OAuth;`install` 只安装、不登录。两者都不覆盖原配置,见 [Codewhale 安装与配置](CODEWHALE.md)。

Expand Down
10 changes: 8 additions & 2 deletions dsh.ps1
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
param([ValidatePattern('^[a-zA-Z0-9][a-zA-Z0-9_-]*$')][string]$Profile = 'web')
$ErrorActionPreference = 'Stop'
if ($args.Count) { throw 'Usage: .\dsh.ps1 [-Profile name]' }
npm.cmd install -g @deepseek-ai/dsh@0.1.5-rc.2 pnpm@11.7.0
npm.cmd install -g @deepseek-ai/dsh@latest pnpm@latest
if ($LASTEXITCODE) { exit $LASTEXITCODE }
dsh.cmd plugin --profile $Profile add @tokennotincluded/dsh-lmm-provider@0.1.0-alpha.3 --ignore-scripts
$response = Invoke-WebRequest -UseBasicParsing -Method Head -Uri 'https://github.com/TokenNotIncluded/dsh-lmm-provider/releases/latest'
$finalUri = $response.BaseResponse.ResponseUri
if (-not $finalUri) { $finalUri = $response.BaseResponse.RequestMessage.RequestUri }
$url = [Uri]$finalUri
if (-not $url -or $url.Scheme -ne 'https' -or $url.Host -ne 'github.com' -or -not $url.IsDefaultPort -or $url.UserInfo -or $url.Query -or $url.Fragment -or $url.AbsolutePath -notmatch '^/TokenNotIncluded/dsh-lmm-provider/releases/tag/([a-zA-Z0-9][a-zA-Z0-9._-]*)$') { throw 'Unexpected plugin release URL' }
$pluginUrl = 'https://github.com/TokenNotIncluded/dsh-lmm-provider/releases/download/' + $Matches[1] + '/dsh-lmm-provider.tgz'
dsh.cmd plugin --profile $Profile add $pluginUrl --ignore-scripts
exit $LASTEXITCODE
13 changes: 11 additions & 2 deletions dsh.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
#!/usr/bin/env bash
set -euo pipefail
[[ $# -le 1 && ${1:-web} =~ ^[a-zA-Z0-9][a-zA-Z0-9_-]*$ ]] || { echo 'Usage: bash dsh.sh [profile]' >&2; exit 2; }
npm install -g @deepseek-ai/dsh@0.1.5-rc.2 pnpm@11.7.0
dsh plugin --profile "${1:-web}" add @tokennotincluded/dsh-lmm-provider@0.1.0-alpha.3 --ignore-scripts
npm install -g @deepseek-ai/dsh@latest pnpm@latest
# Resolve the current built release rather than installing an unbuilt Git checkout
# or a cached npm preview package. A versioned asset URL also refreshes pnpm's source.
release_url=$(curl -fsSLI -o /dev/null -w '%{url_effective}' https://github.com/TokenNotIncluded/dsh-lmm-provider/releases/latest)
plugin_url=$(node --input-type=module -e '
const url=new URL(process.argv[1]);
const prefix="/TokenNotIncluded/dsh-lmm-provider/releases/tag/", tag=url.pathname.slice(prefix.length);
if(url.origin!=="https://github.com" || url.username || url.password || url.search || url.hash || !url.pathname.startsWith(prefix) || !/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(tag)) throw Error("Unexpected plugin release URL");
console.log("https://github.com/TokenNotIncluded/dsh-lmm-provider/releases/download/"+tag+"/dsh-lmm-provider.tgz");
' "$release_url")
dsh plugin --profile "${1:-web}" add "$plugin_url" --ignore-scripts
5 changes: 4 additions & 1 deletion test.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,11 @@ Check 'dsh.ps1' $npmFailure 9 'npm failed'
$npmSuccess = @'
function npm.cmd { Write-Output ($args -join '|'); $global:LASTEXITCODE=0 }
function dsh.cmd { Write-Output ($args -join '|'); $global:LASTEXITCODE=0 }
function Invoke-WebRequest { param($Uri,$Method,[switch]$UseBasicParsing) if ($Uri -ne 'https://github.com/TokenNotIncluded/dsh-lmm-provider/releases/latest' -or $Method -ne 'Head') { throw 'wrong release URL' }; return [pscustomobject]@{BaseResponse=[pscustomobject]@{ResponseUri=[Uri]'https://github.com/TokenNotIncluded/dsh-lmm-provider/releases/tag/v0.1.0-alpha.5'}} }
'@
Check 'dsh.ps1' $npmSuccess 0 'plugin\|--profile\|headless' '-Profile headless'
Check 'dsh.ps1' $npmSuccess 0 'plugin\|--profile\|headless.*releases/download/v0.1.0-alpha.5/dsh-lmm-provider.tgz' '-Profile headless'
Check 'dsh.ps1' ($npmSuccess+"`nfunction Invoke-WebRequest { throw 'missing-release' }") 1 'missing-release'
Check 'dsh.ps1' ($npmSuccess+"`nfunction Invoke-WebRequest { [pscustomobject]@{BaseResponse=[pscustomobject]@{ResponseUri=[Uri]'https://evil.invalid/plugin.tgz'}} }") 1 'Unexpected plugin release URL'
$upstream = @'
function Invoke-RestMethod { return 'param([string]$Version) Write-Output "upstream:$Version"' }
'@
Expand Down
18 changes: 15 additions & 3 deletions test.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@
print('printf "%s\\n" "$@" > "$HOME/received-args"')
elif url.endswith('/desktop.sh'):
print(Path(os.environ['PROJECT'], 'desktop.sh').read_text())
elif url == 'https://github.com/TokenNotIncluded/dsh-lmm-provider/releases/latest':
if os.environ.get('NO_ASSET'): sys.exit(22)
print(os.environ.get('DSH_ASSET_URL','https://github.com/TokenNotIncluded/dsh-lmm-provider/releases/tag/v0.1.0-alpha.5'))
elif '/releases/latest' in url:
print(json.dumps({'assets': [] if os.environ.get('NO_ASSET') else [
{'name': 'Tool-amd64.deb', 'browser_download_url': 'https://github.com/official/tool.deb'},
Expand Down Expand Up @@ -79,7 +82,7 @@ def setUp(self):
self.log = self.home/'commands.jsonl'
self.log.touch()
# Keep host package managers out of simulated platform tests.
for name in ('bash','sh','dirname','mktemp','rm','mkdir','install'):
for name in ('bash','sh','dirname','mktemp','rm','mkdir','install','node'):
os.symlink(shutil.which(name), self.bin/name)
for name in ('curl','npm','pi','dsh','uname','jq','tar','proot-distro','brew','cargo','sudo','pkg'):
self.stub(name)
Expand Down Expand Up @@ -148,7 +151,16 @@ def test_pi_termux_dependency_failure_stops_before_official_bootstrap(self):

def test_dsh_keeps_documented_commands_and_lmm_plugin(self):
self.assertEqual(self.run_script('dsh.sh','headless').returncode,0)
self.assertEqual(self.calls()[-1][1][:3],['plugin','--profile','headless'])
self.assertEqual(self.calls()[0], ['npm',['install','-g','@deepseek-ai/dsh@latest','pnpm@latest']])
self.assertEqual(self.calls()[-1], ['dsh',['plugin','--profile','headless','add','https://github.com/TokenNotIncluded/dsh-lmm-provider/releases/download/v0.1.0-alpha.5/dsh-lmm-provider.tgz','--ignore-scripts']])

def test_dsh_missing_release_does_not_install_plugin(self):
self.assertNotEqual(self.run_script('dsh.sh',NO_ASSET='1').returncode,0)
self.assertTrue(all(name!='dsh' for name,_ in self.calls()))

def test_dsh_rejects_foreign_release_urls(self):
self.assertNotEqual(self.run_script('dsh.sh',DSH_ASSET_URL='https://evil.invalid/plugin.tgz').returncode,0)
self.assertTrue(all(name!='dsh' for name,_ in self.calls()))

def test_npm_failure_never_installs_plugin(self):
self.assertEqual(self.run_script('dsh.sh',NPM_EXIT='9').returncode,9)
Expand Down Expand Up @@ -249,7 +261,7 @@ def test_flat_layout_and_size_budget(self):
self.assertNotIn('@MENU_REV@',text)
self.assertNotIn('@DESKTOP_REV@',text)
for name in ('codex','claude-code','pi','dsh'):
self.assertLessEqual(len((ROOT/f'{name}.sh').read_text().splitlines()),13 if name=='pi' else 10)
self.assertLessEqual(len((ROOT/f'{name}.sh').read_text().splitlines()),20 if name=='dsh' else 13 if name=='pi' else 10)
for ext in ('sh','ps1'):
text=(ROOT/f'pi.{ext}').read_text()
self.assertIn(f'https://pi.dev/install.{ext}',text)
Expand Down
Loading