dsh-subscription-login

identitytested: passnpmMIT

One settings page that lists every subscription sign-in the harness can offer and walks you through it — ChatGPT (Codex), Claude, GitHub Copilot, OpenRouter, Kimi and xAI — reading the list from the authorization seam instead of hardcoding providers, and showing credential records no registered flow

下载量 / Downloads
1,426
依赖体积 / node_modules
0.1 MB
测试时间 / Tested
2026-11-06
DSH 版本 / Version
0.2.2-rc.1

Install

dsh plugin --profile web add dsh-subscription-login

Sandbox test results

StageResult
1 install✓
2 list✓
3 mount✓
4 boot—

Four stages run in an isolated sandbox: install (network on) → list → mount → boot (last three with no network).

README

dsh-subscription-login

把非 DeepSeek 的 AI 订阅账号(ChatGPT Codex、Claude、GitHub Copilot、OpenRouter、Kimi Code、xAI SuperGrok/X Premium)接进 DeepSeek Harness —— 一个页面管全部。

装在 设置 → 插件 → 订阅登录 里。它列出你这个部署实际注册的每一个登录流程,走完它,看到结果。

它和别的订阅插件不一样的地方

生态里已经有五六个同类插件,每个都硬编码自己的 provider。这个不硬编码任何东西:

  • 列表来自 ctx.authorization,由 dsh-llm-pi-ai 按"每个带登录的 pi-ai provider 注册一个 flow"填充。上游 pi-ai 加一家 provider,这里自动出现,本插件一行不用改。
  • 它把缺的那道缝补上了——只在需要时。 旧版官方 web profile 没有组合 @deepseek-ai/dsh-authorization,而 dsh-llm-pi-ai 是在 ctx.inject(['authorization'], …) 里注册登录流程的。没有这个服务,适配器永远等不到它,任何 provider 的登录流程都不会被注册——这正是那些能用的订阅插件全都自己重写一遍 OAuth 的原因。@deepseek-ai/dsh-base 0.1.7-rc.2 起自己挂载了这道 seam;本插件的补丁只在没有别的启用行挂载该包时插入一行,因此旧 profile 上补位,新 profile 上自动退让。
  • 无主凭据会被列出来。 llm-pi-ai 作用域里、当前没有任何流程认领的记录(provider 不再提供登录、或提供者插件被卸载)会单独成节,可一键删除。官方文档明说"识别这种孤儿记录由调用方负责",此前没人做。
  • 诚实。 登出只删本机记录,界面里就这么写,不假装吊销;未定价的东西不显示;宿主没有流程时显示"没有可登录的账号"而不是一个点不动的按钮。

安全

  • 每个请求先过 ctx.connection.requestRejection():Host/Origin 栅栏(挡 DNS rebinding 与跨站调用)+ 浏览器登录态。裸请求(无会话 cookie)得到 401,不是 200。
  • 密钥永不经过这个插件的任何一行。 它只调用 describeRecord(),那个返回值里没有能装密钥的字段。登录流程本身跑在 dsh-llm-pi-ai 里,那是唯一的写入方,记录直接落进 DSH 凭据存储。
  • 通知字段白名单投影。 notice 只保留 message/url/code,其它一律丢弃(有测试盯着)。
  • 删除范围刻意窄。 只有 llm-pi-ai/<provider> 这一族记录可列、可删。别的插件写的记录既不显示也不能在这里删。
  • 请求体上限 64 KiB,非 JSON 体得到 400 而不是崩溃。

安装

dsh plugin --profile web add dsh-subscription-login

然后重启 dsh web。

⚠️ 重启是必须的。bundle 补丁会插入本插件的行,并在旧 profile 上补上授权 seam 行;而 profile 补丁的热重载在实测中没有对进程外的文件写入生效(见下面"验证到哪一步")。改 profile 的 dsh.profile.bundles 之后,冷启动是唯一可靠的路径。

装完打开 设置 → 插件 → 订阅登录。

兼容性

  • DSH 0.1.7-rc.2 及以后:@deepseek-ai/dsh-base 自带 authorization 行。本补丁用独立的 loader id(subscription-login-authorization),并在任何其它启用行已经挂载 @deepseek-ai/dsh-authorization 时把自己禁用,因此不会和基础行重复。
  • 更旧的 profile 没有这道 seam 时,仍由本插件补上。
  • 宿主与客户端契约已对照 DSH 0.2.1-rc.2 检查(authorization、credentials、webServer、connection、settings.plugins.tab、locale),58 个离线测试通过。

发布说明

0.1.2 —— 修复 DSH 0.1.7-rc.2 及以后版本上的冷启动失败:dsh-base 已经挂载授权 seam,而补丁又用同一个 loader id(authorization)插入自己的行,loader 拒绝重复 id。现在该行改用 subscription-login-authorization,并在任何其它启用行挂载该包时自动退让。文档同步更新;运行时代码无改动。58 个测试通过。

用法

每一行是一个可登录的账号:

  • 登录 —— 点一下开始。流程会告诉你该打开哪个页面、该填什么码;需要你回答的问题(粘贴授权码、选择账号)会就地出现。随时可以取消登录。
  • 登出 —— 两步确认。只删本机记录。
  • 无主的登录凭据 —— 没有流程认领的旧记录,可删。

登录成功后,去官方的 设置 → 模型 页面就能看到对应 provider 的行和登录状态——本插件复用官方那条路,没有另起一套模型目录。

它不做什么

  • 不自己实现 OAuth。 协议在 pi-ai 里,刷新、跨进程锁都在那儿。本插件是界面和编排。
  • 不做服务端吊销。 授权 seam 没有吊销通道,登出就是删本地记录。
  • 不做登录中断恢复。 一次尝试只活在发起它的进程里,刷新页面要重来——这是 seam 的既有约束。
  • 不替你做决定。 不推荐用哪个 provider,不算额度,不做健康检查。

开发

npm test      # 58 个离线测试
npm run check # 发布守卫,npm publish 会先跑它

没有构建步骤。 lib/client.js 是手写的客户端模块格式(window.__ModuleLoader__.load + 工厂函数),仓库里那个文件就是发布出去、也是实际运行的那个文件。require("react") 走 shell 的静态模块表。

测试分四层,都在 Node 里跑,不需要浏览器、不需要装 DSH:

文件 · 覆盖

test/registry.test.mjs · 登录生命周期:通知、提问、回答、竞态撤回、取消、超时、失败、登出、卸载。用桩实现两个 seam 的文档契约,不是本插件的期望

test/router.test.mjs · HTTP 形状:状态码、错误码、游标与超时钳制、路由匹配

test/host.test.mjs · apply() 在桩 Cordis 上接线;信任闸门先于一切(被拒的请求不得到达 seam);请求体解析与上限;卸载时取消在跑的登录

test/client.test.mjs · 真实的 lib/client.js 配桩 window.__ModuleLoader__ 与桩 React:注册、字典一致性、渲染、点击登录走完一次尝试并渲染出提问

验证到哪一步(诚实交代)

真机端到端验证已完成,方式是不打扰你:用一个隔离的 DSH_HOME 起第二个实例(端口 3099、独立会话存储),在那里验证完就关掉删除。你运行中的服务器全程没动过。

实机结果:

探针 · 结果

裸 GET /plugins/dsh-subscription-login/flows · 401 ✅ 路由已注册,信任闸门先于一切生效

带会话 cookie 请求同一路径 · 200,返回真实载荷 ✅

裸 POST .../attempts · 401 ✅ 写路径同样受闸门保护

未知流程 · 404 NO_FLOW ✅

未知登录方式 · 400 UNKNOWN_METHOD,并列出真实可用的 oauth, api-key ✅

作用域外的登出 · 404 NO_RECORD(不泄露该记录是否存在)✅

缺 key · 400 BAD_REQUEST ✅

前缀下的未知路径(带 cookie) · 404 NOT_FOUND ✅

最有价值的一条:flows 返回了 40 个 provider 的登录流程——这直接证明了我的诊断。没有补丁里那一行,这 40 个流程一个都不会存在。其中 6 家带 OAuth:

anthropic(Claude Pro/Max)、openai-codex(ChatGPT Plus/Pro)、github-copilot、openrouter、kimi-coding(Sign in with Kimi Code)、xai(SuperGrok / X Premium)。

后两家是我原来不知道的。那些硬编码 provider 的插件全都漏了它们——这正是"列表从 seam 读、不硬编码"的回报。

真机验证还抓到一个真 bug。 dsh-host-webserver 匹配前缀路由用的是 pathname === prefix || pathname.startsWith(prefix + '/')。我最初把前缀注册成带尾斜杠的 /plugins/dsh-subscription-login/,于是匹配器去找 ...//flows,永远匹配不上——只有裸路径命中,所有真实端点都掉进 SPA 回退变成 404。第一轮探针就是这个现象(/plugins/dsh-subscription-login/ → 401,但 /flows → 404),修掉之后全绿。现在这条约束被 test/host.test.mjs 钉住了。

真实 provider 的完整 OAuth(GitHub Copilot,2026-09-18)

用免费 GitHub 账号在用户自己的 DSH 实例上跑完了一次真实 device flow,全程经本插件的 HTTP 接口驱动:

环节 · 实测结果

流程列表 · 39 个流程,其中 6 家带 OAuth:Anthropic / GitHub Copilot / Kimi For Coding / OpenAI Codex / OpenRouter / xAI

发起登录 · POST attempts → attemptId=a1 ✅

流程提问 · GitHub Enterprise URL/domain (blank for github.com) ✅

回答 · 空值 → {"ok":true},流程继续 ✅

流程通知 · https://github.com/login/device + 一次性码 03B3-2CA7 ✅

人在 GitHub 授权 · 成功(GitHub 确认已登录 woodfood111)✅

换 Copilot token · 403,结构化原因:no_copilot_access、can_signup_for_limited: true ✅ 被如实上报

失败后一致性 · 无半截凭据记录、无孤儿、key 已释放可立刻重试 ✅

也就是说:人在环回路的每个环节都在真实 provider 流程上走通了,唯一失败的是订阅权益本身——这正是预期的结果(免费账号)。而那条 403 带着明确的 notification_id 和注册链接,说明失败是可诊断的,不是静默卡死,这是本插件相对"自己重写 OAuth"的插件的核心差异。

顺带一提:can_signup_for_limited: true 说明该账号可以注册免费的 Copilot Free,注册后再跑一次同一条流程就会成功并写入凭据。

这次实跑还改进了失败信息的呈现:provider 的拒绝原样打印在一个独立的诊断块里(等宽、换行、不截断),而不是当成一行灰色小字——不裁剪、不转述,因为改写 provider 的话就是在编答案。

重跑这条流程时:失败后别立刻重试

GitHub 对未认证的 device flow 端点有频率限制,而这条路上很容易踩到。实测(同一天、同一台机器):

次序 · 结果

第 1 次 · 完整跑通:设备码 → 人工授权 → GitHub 返回结构化 403

第 2 次 · 设备码发出后约 30 秒 fetch failed

第 3 次 · 约 11 秒 fetch failed,连设备码都没发出

同一时刻整机外网是好的:市场 1.5 秒拉完 3.5 MB,GitHub REST 也正常解析了 github: 依赖的 HEAD。所以那不是断网,是被限流。

插件这一侧不会因此卡住——inFlight 归零、key 立即释放、可以马上重发——但重试再快也绕不过 GitHub 那侧的限制。失败后等一段时间,再做一次干净尝试。

###

…(truncated)

Rendered from the README published on npm. Formatted only, not edited.

Versions

Published on npm::3,最后更新 2026-10-18

Links

GitHub · Homepage · npm

Same category