dsh-connect-agnes-token-plan 0.12.2 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +30 -0
- package/README.md +3 -1
- package/assets/agnescode-usage-capsule.png +0 -0
- package/client.js +69 -12
- package/docs/AGNESCODE-SCAN-LOGIN.md +251 -0
- package/docs/CONTRIBUTING.md +0 -88
- package/docs/DSH-PLUGIN.md +1 -1
- package/docs/PITFALLS.md +71 -4
- package/docs/README.md +2 -1
- package/docs/ROADMAP.md +509 -509
- package/docs/SETUP.md +1 -1
- package/docs/TESTING.md +8 -3
- package/lib/agnescode-stats-store.js +85 -70
- package/lib/index.js +455 -20
- package/lib/llm-models.js +20 -1
- package/locale/en.json +1 -1
- package/locale/zh.json +1 -1
- package/package.json +6 -3
- package/screenshots.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -23,6 +23,36 @@
|
|
|
23
23
|
|
|
24
24
|
## [Unreleased]
|
|
25
25
|
|
|
26
|
+
## [0.13.0] — 2026-10-21
|
|
27
|
+
|
|
28
|
+
本版新增 **AgnesCode 扫码登录**:桌面端没装、没登录也能用这条线。另外修了促销标丢失、并发记录丢失、令牌被悄悄改写三处问题,并扩大了凭据脱敏面。
|
|
29
|
+
|
|
30
|
+
### 新增:AgnesCode 扫码登录——不用先装桌面端
|
|
31
|
+
|
|
32
|
+
- **增(插件卡)**:AgnesCode 那一栏多一个「扫码登录」入口。点了会打开浏览器到 AgnesCode 登录页,你扫码(已登录就点继续)后,浏览器把一次性授权码交给插件,插件换回与桌面端同款的登录态。
|
|
33
|
+
- **增(登录流程)**:等待期间可以「取消等待」——取消只是不再等了,已经扫成功的那次登录照样会被保存下来。
|
|
34
|
+
- **修(登录流程)**:浏览器打不开时立刻回报「打不开浏览器」,不再干等到超时;换码失败时显示服务端给的可读原因(例如「授权码无效」),不再是一句没头绪的失败。
|
|
35
|
+
|
|
36
|
+
### 修复:桌面端的促销标不再整条丢掉
|
|
37
|
+
|
|
38
|
+
- **修(面板)**:桌面端模型列表里的促销标(如「限时七折」)此前在列表重建时整个丢失,这一列于是永远是空的。现在重建后仍带出促销标,没有促销文案时按「没有」处理。
|
|
39
|
+
|
|
40
|
+
### 修复:同时发生的记录不再丢一部分
|
|
41
|
+
|
|
42
|
+
- **修(面板)**:短时间内多条用量与限频记录一起写入时,只有部分能落盘(实测五次并发只留下一到三条),面板上的数字看着在涨、实际丢着。现在写入按顺序排队,记录条数与实际发生的一致。
|
|
43
|
+
|
|
44
|
+
### 修复:令牌不再被悄悄改写
|
|
45
|
+
|
|
46
|
+
- **修(面板)**:平台发来的登录令牌此前在读取时会被去掉首尾空白,令牌因此被改写,表现为一次查无出处的 401。现在按原样读取。
|
|
47
|
+
|
|
48
|
+
### 修复:凭据脱敏面扩大
|
|
49
|
+
|
|
50
|
+
- **修(面板与日志)**:脱敏原先只认几种键名和前缀。现在也按值的形状认:Basic 认证整段、Cookie、JSON 里没加引号的凭据字段、client_secret / code_verifier 这类键名。普通句子原样保留,不会把诊断信息一起吃掉。
|
|
51
|
+
|
|
52
|
+
### 修复:插件卡把密码说成了两处都在用
|
|
53
|
+
|
|
54
|
+
- **修(插件卡·中英文界面)**:介绍此前写「令牌到期自动重登(需 `AGNES_PASSWORD`)」,读起来像 AgnesCode 桌面端的令牌也要靠密码续期。实际是两件事:`AGNES_PASSWORD` 只在**重登 Agnes 控制台**时用到;桌面端的令牌到期是重新读取桌面应用自己的登录态,不需要密码。现在两种语言都把这两句分开说清。
|
|
55
|
+
|
|
26
56
|
## [0.12.2] — 2026-10-19
|
|
27
57
|
|
|
28
58
|
本版三件事:**插件卡顶栏收成一行**,卡片底部不再多留一块空白;**中文介绍换写成「装上能得到什么」**,与英文卡片说同一件事;以及**装上后 README 里指向 `docs/` 的链接不再全部断掉**。
|
package/README.md
CHANGED
|
@@ -68,6 +68,8 @@
|
|
|
68
68
|
- 两条上游**互不借用**:`agnescode` 的会话只读它自己的路由、绝不画控制台窗口,反之亦然;切到**别的 provider**(其它插件)的模型时胶囊直接消失,也不再发任何请求;
|
|
69
69
|
- 读不到时**保留上一次数字并标「上次数据」**,不会清零或伪装成 0%;余额里的 `0` 是「烧完了」的读数(红色告警),`—` 才是「这次没读到」——这个区别是刻意的。
|
|
70
70
|
|
|
71
|
+

|
|
72
|
+
|
|
71
73
|
## 边界
|
|
72
74
|
|
|
73
75
|
- 面板**不代你操作账务**——不改套餐、不代扣额度、不碰 Key 明文;数据来自 Agnes 控制台自己的 API,与网页控制台口径一致。
|
|
@@ -114,7 +116,7 @@ npm run doctor:json # 机器读:JSON(可进你的巡检 / 工单脚本
|
|
|
114
116
|
- [docs/API.md](docs/API.md) — 路由与控制台端点
|
|
115
117
|
- [docs/AGNES-API.md](docs/AGNES-API.md) — Agnes 接口全集(控制台额度侧 + 推理侧)
|
|
116
118
|
- [docs/TESTING.md](docs/TESTING.md) — 测试体系
|
|
117
|
-
- [docs/PITFALLS.md](docs/PITFALLS.md) — 真实踩坑(
|
|
119
|
+
- [docs/PITFALLS.md](docs/PITFALLS.md) — 真实踩坑(61 条)
|
|
118
120
|
- [CHANGELOG.md](CHANGELOG.md) — 版本变化
|
|
119
121
|
|
|
120
122
|
AI 协作会话请先读 [AGENTS.md](AGENTS.md)。人类贡献者先读 [SETUP.md](docs/SETUP.md) 与 [ARCHITECTURE.md](docs/ARCHITECTURE.md) §2 理解结构与改动代价,改代码前再读 [AGENTS.md](AGENTS.md) 的红线与 [TESTING.md](docs/TESTING.md) 的 `npm test` 门禁。
|
|
Binary file
|
package/client.js
CHANGED
|
@@ -240,6 +240,12 @@ var dsh_connect_agnes_token_plan_client = (function() {
|
|
|
240
240
|
"agnescode.bffBase": "接口地址 {base}",
|
|
241
241
|
"agnescode.notLogged": "未关联——请先在 AgnesCode 桌面端登录(微信扫码),再点「检测本机登录态」。",
|
|
242
242
|
"agnescode.harvestWindowsOnly": "本机登录态采集目前仅支持 Windows(macOS / Linux 未实现)。",
|
|
243
|
+
"agnescode.login": "扫码登录",
|
|
244
|
+
"agnescode.cancelLogin": "取消等待",
|
|
245
|
+
"agnescode.loginCancelled": "已取消等待。若已在 App 中完成扫码,登录仍会在后台完成并生效。",
|
|
246
|
+
"agnescode.loginOk": "扫码登录成功。",
|
|
247
|
+
"agnescode.loginFail": "扫码登录失败:{error}",
|
|
248
|
+
"agnescode.loginHint": "无需桌面 App——浏览器打开登录页,用 AgnesCode App 扫码即可(macOS / Linux 可用)。",
|
|
243
249
|
"agnescode.downloadCta": "下载 AgnesCode 桌面客户端,领取限时积分 →",
|
|
244
250
|
"agnescode.balanceLabel": "积分余额",
|
|
245
251
|
"agnescode.balanceTimeLabel": "时效",
|
|
@@ -494,6 +500,12 @@ var dsh_connect_agnes_token_plan_client = (function() {
|
|
|
494
500
|
"agnescode.bffBase": "API base {base}",
|
|
495
501
|
"agnescode.notLogged": "Not linked — sign in inside the AgnesCode desktop App (WeChat scan) first, then run the detection.",
|
|
496
502
|
"agnescode.harvestWindowsOnly": "Local login-state detection currently supports Windows only (macOS / Linux not implemented).",
|
|
503
|
+
"agnescode.login": "Scan-to-sign-in",
|
|
504
|
+
"agnescode.cancelLogin": "Cancel waiting",
|
|
505
|
+
"agnescode.loginCancelled": "Waiting cancelled. If the scan was completed, the login still finishes in the background.",
|
|
506
|
+
"agnescode.loginOk": "Scan-to-sign-in succeeded.",
|
|
507
|
+
"agnescode.loginFail": "Scan-to-sign-in failed: {error}",
|
|
508
|
+
"agnescode.loginHint": "No desktop App needed — the browser opens the login page for the AgnesCode App to scan (works on macOS / Linux).",
|
|
497
509
|
"agnescode.downloadCta": "Download the AgnesCode desktop client and claim your limited-time credits →",
|
|
498
510
|
"agnescode.balanceLabel": "Credit balance",
|
|
499
511
|
"agnescode.balanceTimeLabel": "Time-limited",
|
|
@@ -641,7 +653,7 @@ var dsh_connect_agnes_token_plan_client = (function() {
|
|
|
641
653
|
//#endregion
|
|
642
654
|
//#region src/client/http.ts
|
|
643
655
|
/** One POST with the panel's fixed request shape; the parsed body or null. */
|
|
644
|
-
async function postRaw(path, payload) {
|
|
656
|
+
async function postRaw(path, payload, signal) {
|
|
645
657
|
const response = await fetch(path, {
|
|
646
658
|
method: "POST",
|
|
647
659
|
headers: {
|
|
@@ -649,7 +661,8 @@ var dsh_connect_agnes_token_plan_client = (function() {
|
|
|
649
661
|
accept: "application/json"
|
|
650
662
|
},
|
|
651
663
|
cache: "no-store",
|
|
652
|
-
body: JSON.stringify(payload)
|
|
664
|
+
body: JSON.stringify(payload),
|
|
665
|
+
...signal !== void 0 ? { signal } : {}
|
|
653
666
|
});
|
|
654
667
|
const body = await response.json().catch(() => null);
|
|
655
668
|
return {
|
|
@@ -658,12 +671,12 @@ var dsh_connect_agnes_token_plan_client = (function() {
|
|
|
658
671
|
};
|
|
659
672
|
}
|
|
660
673
|
/** POST and return the parsed body, or null when the response is not JSON. */
|
|
661
|
-
function postJson(path, payload) {
|
|
662
|
-
return postRaw(path, payload).then(({ body }) => body);
|
|
674
|
+
function postJson(path, payload, signal) {
|
|
675
|
+
return postRaw(path, payload, signal).then(({ body }) => body);
|
|
663
676
|
}
|
|
664
677
|
/** POST and demand `ok:true`; refuse by throwing the Host's own wording. */
|
|
665
|
-
async function postJsonOrThrow(path, payload) {
|
|
666
|
-
const { status, body } = await postRaw(path, payload);
|
|
678
|
+
async function postJsonOrThrow(path, payload, signal) {
|
|
679
|
+
const { status, body } = await postRaw(path, payload, signal);
|
|
667
680
|
if (body === null || body.ok !== true) throw new Error(typeof body?.error === "string" ? body.error : `HTTP ${status}`);
|
|
668
681
|
return body;
|
|
669
682
|
}
|
|
@@ -3624,10 +3637,12 @@ var dsh_connect_agnes_token_plan_client = (function() {
|
|
|
3624
3637
|
const [error, setError] = useState(null);
|
|
3625
3638
|
const [updatedAt, setUpdatedAt] = useState(0);
|
|
3626
3639
|
const [harvestBusy, setHarvestBusy] = useState(false);
|
|
3640
|
+
const [loginBusy, setLoginBusy] = useState(false);
|
|
3627
3641
|
const [note, setNote] = useState(null);
|
|
3628
3642
|
const alive = useRef(true);
|
|
3629
3643
|
const generation = useRef(0);
|
|
3630
3644
|
const inFlight = useRef(null);
|
|
3645
|
+
const loginAbort = useRef(null);
|
|
3631
3646
|
const load = useCallback(async () => {
|
|
3632
3647
|
generation.current += 1;
|
|
3633
3648
|
const mine = generation.current;
|
|
@@ -3750,6 +3765,34 @@ var dsh_connect_agnes_token_plan_client = (function() {
|
|
|
3750
3765
|
setState(body);
|
|
3751
3766
|
}
|
|
3752
3767
|
}, []);
|
|
3768
|
+
const login = useCallback(async () => {
|
|
3769
|
+
const controller = new AbortController();
|
|
3770
|
+
loginAbort.current = controller;
|
|
3771
|
+
setLoginBusy(true);
|
|
3772
|
+
setNote(null);
|
|
3773
|
+
try {
|
|
3774
|
+
const body = await postJson(AGNESCODE_PATH, { action: "login" }, controller.signal);
|
|
3775
|
+
if (alive.current) {
|
|
3776
|
+
retireInFlight();
|
|
3777
|
+
if (body !== null) setState(body);
|
|
3778
|
+
if (body?.ok === true) setNote(tt("agnescode.loginOk"));
|
|
3779
|
+
else setNote(format(tt("agnescode.loginFail"), { error: typeof body?.error === "string" && body.error !== "" ? body.error : tt("agnescode.loadErrorDetail") }));
|
|
3780
|
+
}
|
|
3781
|
+
} catch (why) {
|
|
3782
|
+
if (alive.current) {
|
|
3783
|
+
if (loginAbort.current === controller && controller.signal.aborted) setNote(tt("agnescode.loginCancelled"));
|
|
3784
|
+
else setNote(format(tt("agnescode.error"), { error: why instanceof Error ? why.message : String(why) }));
|
|
3785
|
+
}
|
|
3786
|
+
} finally {
|
|
3787
|
+
if (loginAbort.current === controller) loginAbort.current = null;
|
|
3788
|
+
if (alive.current) setLoginBusy(false);
|
|
3789
|
+
}
|
|
3790
|
+
}, [tt]);
|
|
3791
|
+
const cancelLogin = useCallback(() => {
|
|
3792
|
+
loginAbort.current?.abort();
|
|
3793
|
+
if (alive.current) setLoginBusy(false);
|
|
3794
|
+
}, []);
|
|
3795
|
+
const loginDisabled = harvestBusy || loginBusy;
|
|
3753
3796
|
const enabled = state?.enabled === true;
|
|
3754
3797
|
const view = agnescodeView(state, error);
|
|
3755
3798
|
const loggedIn = view.linked;
|
|
@@ -3779,8 +3822,13 @@ var dsh_connect_agnes_token_plan_client = (function() {
|
|
|
3779
3822
|
type: "button",
|
|
3780
3823
|
style: S.button,
|
|
3781
3824
|
onClick: () => void harvest(),
|
|
3782
|
-
disabled:
|
|
3783
|
-
}, harvestBusy ? tt("agnescode.harvesting") : tt("agnescode.harvest"))
|
|
3825
|
+
disabled: loginDisabled
|
|
3826
|
+
}, harvestBusy ? tt("agnescode.harvesting") : tt("agnescode.harvest")), h("button", {
|
|
3827
|
+
type: "button",
|
|
3828
|
+
style: S.button,
|
|
3829
|
+
onClick: () => void (loginBusy ? cancelLogin() : login()),
|
|
3830
|
+
disabled: !loginBusy && loginDisabled
|
|
3831
|
+
}, loginBusy ? tt("agnescode.cancelLogin") : tt("agnescode.login"))), state?.bffBase !== void 0 && state?.bffBase !== "" || typeof state?.expiresAtMs === "number" && state.expiresAtMs > 0 ? h("div", null, h("div", { style: {
|
|
3784
3832
|
...S.quotaFoot,
|
|
3785
3833
|
marginTop: 8,
|
|
3786
3834
|
color: "var(--dsw-alias-label-secondary)"
|
|
@@ -3808,14 +3856,23 @@ var dsh_connect_agnes_token_plan_client = (function() {
|
|
|
3808
3856
|
type: "button",
|
|
3809
3857
|
style: S.button,
|
|
3810
3858
|
onClick: () => void harvest(),
|
|
3811
|
-
disabled:
|
|
3812
|
-
}, harvestBusy ? tt("agnescode.harvesting") : tt("agnescode.harvest"))
|
|
3859
|
+
disabled: loginDisabled
|
|
3860
|
+
}, harvestBusy ? tt("agnescode.harvesting") : tt("agnescode.harvest")), h("button", {
|
|
3861
|
+
type: "button",
|
|
3862
|
+
style: S.button,
|
|
3863
|
+
onClick: () => void (loginBusy ? cancelLogin() : login()),
|
|
3864
|
+
disabled: !loginBusy && loginDisabled
|
|
3865
|
+
}, loginBusy ? tt("agnescode.cancelLogin") : tt("agnescode.login"))), h("div", { style: {
|
|
3813
3866
|
...S.muted,
|
|
3814
3867
|
fontSize: 12,
|
|
3815
3868
|
marginTop: 6
|
|
3816
|
-
} }, tt("agnescode.harvestWindowsOnly"))
|
|
3869
|
+
} }, tt("agnescode.harvestWindowsOnly")), h("div", { style: {
|
|
3870
|
+
...S.muted,
|
|
3871
|
+
fontSize: 12,
|
|
3872
|
+
marginTop: 2
|
|
3873
|
+
} }, tt("agnescode.loginHint"))), h(Switch, {
|
|
3817
3874
|
checked: enabled,
|
|
3818
|
-
disabled:
|
|
3875
|
+
disabled: loginDisabled,
|
|
3819
3876
|
onChange: () => void toggle(!enabled),
|
|
3820
3877
|
label: tt("agnescode.switch"),
|
|
3821
3878
|
title: tt("agnescode.switchTip"),
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
# AgnesCode 扫码登录(设备流)可行性
|
|
2
|
+
|
|
3
|
+
本文件回答一件事:**能否不依赖桌面 App,让 `agnescode` 提供方直接拿到同一份 JWT**。
|
|
4
|
+
答案是**能**,且**完整闭环已实测打通**(2026-10-20,真实扫码换出 access_token,见 §6)。
|
|
5
|
+
当前 `agnescode` 线只读桌面 App 的 os_crypt 会话文件
|
|
6
|
+
(Chromium os_crypt + DPAPI + AES-256-GCM,Windows-only);扫码登录是获取**同一份
|
|
7
|
+
access_token** 的另一条路径,覆盖 macOS / Linux,且不需桌面 App。
|
|
8
|
+
|
|
9
|
+
- **状态**:可行性 + 闭环已实测确认(2026-10-20,无凭据探针,真实换出 JWT)。**已接入插件**
|
|
10
|
+
(2026-10-20:`src/host/agnescode-login.ts` + 路由 `login` 分支 + 面板「扫码登录」按钮,见 §8);
|
|
11
|
+
真实凭据仍未入库、不进日志(红线①)。
|
|
12
|
+
- **探针**:`upstream/probe-login-flow.mjs`(`node probe-login-flow.mjs` 干跑;`--live` 真扫码)。
|
|
13
|
+
- **为什么值得**:见下文「跨平台收益」。**为什么慎做**:见「风险」。
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## 1. 换 token 端点(关键事实)
|
|
18
|
+
|
|
19
|
+
**这是本项目此前一直找不到、导致整条链路断掉的那一格。**
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
POST {BFF}/api/v1/code/auth/exchange-code
|
|
23
|
+
Content-Type: application/json
|
|
24
|
+
origin: https://app.agnes-ai.cn # cn 域;intl 用 app.agnes-ai.com
|
|
25
|
+
x-platform: 1
|
|
26
|
+
|
|
27
|
+
{
|
|
28
|
+
"code": "<回调里拿到的授权码>",
|
|
29
|
+
"redirect_uri": "<发起时的 redirect_uri>",
|
|
30
|
+
"state": "<发起时的 state>",
|
|
31
|
+
"client_id": "agnes-code"
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
| 域 | BFF 前缀 | 登录页 |
|
|
36
|
+
|---|---|---|
|
|
37
|
+
| cn | `https://api-agnes-code.agnes-ai.cn` | `https://app.agnes-ai.cn/` |
|
|
38
|
+
| intl | `https://api-agnes-code.agnes-ai.com` | `https://app.agnes-ai.com/` |
|
|
39
|
+
|
|
40
|
+
**实测判据(为什么敢说它是真端点)**:用假 code 打过去,两端点都返回
|
|
41
|
+
`{"code":"010006","message":"Authorization code is invalid, expired, or already used"}`。
|
|
42
|
+
这个 `010006` 是**业务层**的「code 不对」,不是 `000501 Login expired`(那是 BFF 全局鉴权闸)。
|
|
43
|
+
请求穿过了闸、到达了处理逻辑——这就是换 token 端点。
|
|
44
|
+
|
|
45
|
+
成功响应形状(逆向件记录,见 §4;`code === 0` 或 `"000000"`):
|
|
46
|
+
|
|
47
|
+
```json
|
|
48
|
+
{ "code": 0, "data": { "access_token": "<jwt>", "user_info": { "id": "…", "name": "…", "email": "…" } } }
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### 为什么之前找不到
|
|
52
|
+
|
|
53
|
+
早期盲猜的是**标准 OAuth 路径**:`/v1/oauth/token`、`/v1/auth/token`、`/oauth/token`、
|
|
54
|
+
`/token`、`/login/token` 等。结果分两种,都判不了活:
|
|
55
|
+
|
|
56
|
+
- `/v1/...`(含 `grant_type=authorization_code` 表单 POST)→ **401 `000501 Login expired`**,
|
|
57
|
+
是被 BFF 中间件挡,不是「端点不存在」也不是「code 不对」。
|
|
58
|
+
- 非 `/v1` 路径 → **404 `000201 Resource not found`**。
|
|
59
|
+
|
|
60
|
+
结论:换 token 端点在 `/v1` 的 allow-list 之外,路径是**业务自定义**的
|
|
61
|
+
`/api/v1/code/auth/exchange-code`。**教训:猜标准 OAuth 路径在这家 BFF 上必然撞墙。**
|
|
62
|
+
|
|
63
|
+
### 深链模式的实际签发入口(实测确认为关键路径)
|
|
64
|
+
|
|
65
|
+
`agnes_sdk.py` 里的**反向**端点是「已有 JWT → 再签发一个一次性授权码」:
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
POST {BFF}/api/v1/user/issue-authorization-code
|
|
69
|
+
Authorization: Bearer <已有 JWT>
|
|
70
|
+
body: { "redirect_uri": "agnes://auth/callback", "state": "…", "client_id": "agnes-code", "ttl_seconds": 60 }
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
**实测更正**:这不是「不需要」的备胎——它**正是深链模式的签发入口**。反查登录页 chunk
|
|
74
|
+
(`/_next/static/chunks/app/(auth)/login/page-*.js`)的 `S()` 函数:
|
|
75
|
+
|
|
76
|
+
```js
|
|
77
|
+
let i = await (0,N.a4)({ redirect_uri: t, state: e, client_id: s, ttl_seconds: 60 });
|
|
78
|
+
if ("000000" !== i.code) return { success: false, message: i.message || "Authorization failed" };
|
|
79
|
+
let a = i.data.code; // 一次性授权码(60 秒 TTL)
|
|
80
|
+
if (n) { let s = await P(t, a, e); return { success: s, message: s ? undefined : "cliConnectFailed" } }
|
|
81
|
+
window.location.href = "".concat(t, "?code=").concat(encodeURIComponent(a), "&state=").concat(encodeURIComponent(e));
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
`N.a4` 就是这个端点。**流程是:用户在登录页完成登录 → 登录页拿已有 JWT 调
|
|
85
|
+
`issue-authorization-code` 签发一次性 code → 把 code 回传本地**。
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## 2. 登录页(已实测)
|
|
90
|
+
|
|
91
|
+
- `GET https://app.agnes-ai.cn/login?client=agnes-code&redirect_uri=<uri>&state=<uuid>` → **HTTP 200**。
|
|
92
|
+
- 微信登录走**官方 SDK**:登录页动态加载
|
|
93
|
+
`https://res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js`(微信开放平台「网站应用微信登录」)。
|
|
94
|
+
**QR 由 AgnesCode 自己的 appid 生成**——第三方不需要注册微信开放平台 appid。
|
|
95
|
+
- `redirect_uri` 白名单(反查桌面包 main.js):
|
|
96
|
+
- client 必须是 `agnes-code` 或 `agnes-cli*`(AgnesCode 本身有 CLI 登录变体)。
|
|
97
|
+
- `redirect_uri` 必须是 `agnes://auth/callback`,或**满足 localhost 回调**:
|
|
98
|
+
`http:` 协议 + 主机 `127.0.0.1` 或 `[::1]` + **有端口** + 路径 `/auth/callback` +
|
|
99
|
+
无 username / password / query / hash。
|
|
100
|
+
|
|
101
|
+
最后那条 localhost 白名单是整件事能落地的支点:**插件可以起本地临时 HTTP 服务器做回调,
|
|
102
|
+
不必注册 `agnes://` 协议、不必用 webview、不必拦截深链**。
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## 3. 完整设备流(设计 + 实测修正)
|
|
107
|
+
|
|
108
|
+
**关键实测更正**:localhost 回调走的是 **POST JSON** 而非 GET query 跳转。登录页 chunk 里:
|
|
109
|
+
|
|
110
|
+
```js
|
|
111
|
+
async function P(e, t, s) {
|
|
112
|
+
try {
|
|
113
|
+
let n = await fetch(e, { method: "POST", headers: { "Content-Type": "application/json" },
|
|
114
|
+
body: JSON.stringify({ code: t, state: s }) });
|
|
115
|
+
return !!(await n.json()).ok;
|
|
116
|
+
} catch { return false; }
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
localhost 模式(`isLocalhostCallback(redirect_uri) === true`)下,登录页**不发 `window.location.href` 跳转**,
|
|
121
|
+
而是把 `{ code, state }` **POST** 回 `redirect_uri` 本身,并要求响应 JSON `{ ok: true }` 才算「已连接 CLI 终端」;
|
|
122
|
+
返回失败则显示 `cliConnectFailed`("无法连接到 CLI 终端")。非 localhost 的 `redirect_uri`
|
|
123
|
+
(如 `agnes://auth/callback`)才走 GET 跳转。
|
|
124
|
+
|
|
125
|
+
```
|
|
126
|
+
插件 浏览器 AgnesCode 后端
|
|
127
|
+
│ 起 http server 绑 127.0.0.1:随机端口 │ │
|
|
128
|
+
│ state = randomUUID() │ │
|
|
129
|
+
│ ── 拼登录 URL ────────────────────────▶│ │
|
|
130
|
+
│ │ 用户扫微信 QR │
|
|
131
|
+
│ │ (wxLogin.js 官方 SDK) │
|
|
132
|
+
│ │◀── 登录成功 ── app.agnes-ai.cn
|
|
133
|
+
│ │ 登录页调 issue-authorization-code 签发一次性 code
|
|
134
|
+
│ │──────────────────────▶│
|
|
135
|
+
│ │◀── { code } ──────────│
|
|
136
|
+
│ │ P() 发 OPTIONS 预检 │
|
|
137
|
+
│◀── OPTIONS /auth/callback ──────────── │ │
|
|
138
|
+
│ 回 204 + Access-Control-Allow-Origin │ │
|
|
139
|
+
│ (没有 CORS 头,POST 会被浏览器拦截) │ │
|
|
140
|
+
│ │ P() 发 POST JSON │
|
|
141
|
+
│◀── POST /auth/callback { code, state } │ │
|
|
142
|
+
│ 校验 state === uuid(CSRF) │ │
|
|
143
|
+
│ 回 200 { ok:true } │ │
|
|
144
|
+
│ 关闭 server │ │
|
|
145
|
+
│ ── POST {BFF}/api/v1/code/auth/exchange-code ─────────────────▶│
|
|
146
|
+
│◀── { code:"000000", data:{ access_token } } ───────────────────│
|
|
147
|
+
│ token → DSH 凭据服务(kind:"grant")│ │
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
本地回调端点的校验点:
|
|
151
|
+
|
|
152
|
+
- 只认路径 `/auth/callback`;
|
|
153
|
+
- `state` 必须等于插件生成的 `randomUUID()`,否则 400(CSRF 防护);
|
|
154
|
+
- **必须响应 CORS 头**(`Access-Control-Allow-Origin` 等),否则跨域 POST 被浏览器拦;
|
|
155
|
+
- 拿到 `code` 后立即关服务器(一次性);
|
|
156
|
+
- 整个扫码阶段加 5 分钟超时。
|
|
157
|
+
|
|
158
|
+
`redirect_uri` 在**发起与换 token 两步必须传同一个值**(都是 `http://127.0.0.1:<port>/auth/callback`),
|
|
159
|
+
因为后端校验的是这个白名单。
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## 4. 事实来源与版本漂移
|
|
164
|
+
|
|
165
|
+
- 换 token 端点路径、请求体形状、成功响应形状来自 `upstream/AgnesCodeReverseEngineering/`:
|
|
166
|
+
`acp_client.py:273-307`、`agnes_sdk.py:195-210`、`auth_flow_test.py:75-110`、`exchange_token.py`。
|
|
167
|
+
**该逆向件基于 1.0.17**。
|
|
168
|
+
- 本机桌面包是 **1.0.68**(`upstream/AgnesCode-desktop-1.0.68/`)。在它的 `main.js` 里
|
|
169
|
+
grep 不到 `code/auth/exchange-code` —— 说明**换 token 在渲染层远程加载的 web bundle**,
|
|
170
|
+
桌面包里没有。所以 `main.js` 只能证明「登录是开窗加载网页 + OAuth code/state 回调」,
|
|
171
|
+
不能证明端点路径。
|
|
172
|
+
- **判据**:端点以本段 `§1` 的**实测**为准(`010006` = 真端点),不是以逆向件的路径字符串为准。
|
|
173
|
+
逆向件路径只提供了「往哪个方向找」的线索,实测确认了它还对。
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## 5. 跨平台收益(为什么值得)
|
|
178
|
+
|
|
179
|
+
当前 `agnescode` 线 Windows-only,因为 os_crypt + DPAPI 是 Windows 专属,
|
|
180
|
+
且 macOS `state.vscdb` 采集路径未被探测(`docs/ROADMAP.md` §6.3 记录:
|
|
181
|
+
「macOS IDE variant reads state.vscdb (sqlite) and neither non-Windows crypto path
|
|
182
|
+
has been probed」)。扫码登录绕开本地加密存储:
|
|
183
|
+
|
|
184
|
+
- **macOS / Linux 都能用**,只需浏览器扫一次码;
|
|
185
|
+
- **不需装桌面 App**;
|
|
186
|
+
- 拿到的还是那份 `access_token`,与桌面线**同源**,BFF 调用契约不变。
|
|
187
|
+
|
|
188
|
+
这是把 `agnescode` 从「Windows-only 本机登录态采集」升级成「跨平台登录」的正路。
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## 6. 实测验证(2026-10-20,闭环成功)
|
|
193
|
+
|
|
194
|
+
无凭据探针 `upstream/probe-login-flow.mjs --live` 完整跑通设备流:
|
|
195
|
+
|
|
196
|
+
1. 探针起 `http://127.0.0.1:<port>/auth/callback`,拼登录 URL,用浏览器打开;
|
|
197
|
+
2. 用户在登录页完成登录(账号已登录态,点「继续」即可);
|
|
198
|
+
3. 登录页调 `issue-authorization-code` 签发一次性 code,POST 到本地;
|
|
199
|
+
4. 探针回 `{ ok: true }`,关服务器;
|
|
200
|
+
5. 探针 POST `{ code, redirect_uri, state, client_id }` 到 `exchange-code` →
|
|
201
|
+
**HTTP 200 `{"code":"000000","message":"success"}`**;
|
|
202
|
+
6. 拿到 `access_token`(JWT),payload 字段:`sub`(用户 UUID)、`iat`、`nbf`、`exp`。
|
|
203
|
+
**有效期约 28 天**(`exp - iat ≈ 2.43e6 秒`),与桌面线一致,无 refresh。
|
|
204
|
+
|
|
205
|
+
**实测踩到的两个坑**(均已写进探针,接入时必知):
|
|
206
|
+
|
|
207
|
+
- **localhost 回调是 POST JSON 不是 GET query**。最初按 GET 写,登录页 POST 回来时探针
|
|
208
|
+
返回 400 纯文本(非 `{ok:true}`),登录页判 `cliConnectFailed`。
|
|
209
|
+
- **必须回 CORS 头**。登录页(`https://app.agnes-ai.cn`)跨域 POST 到本地 `http`,
|
|
210
|
+
浏览器先发 OPTIONS 预检;探针不回 `Access-Control-Allow-Origin`,POST 被浏览器直接拦,
|
|
211
|
+
同样报 `cliConnectFailed`。
|
|
212
|
+
|
|
213
|
+
**CN 域状态**:`https://app.agnes-ai.cn/` 根路径实测返回 **HTTP 403 维护页**
|
|
214
|
+
(title「网站维护中」),但 `/login` 子路由正常 200,登录与换 token 全链路不受影响。
|
|
215
|
+
intl 域(`.com`)只有 Google / Apple 登录,无微信扫码,本次验证走 CN 域。
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## 7. 风险(照实说)
|
|
220
|
+
|
|
221
|
+
1. **令牌仍约 28 天、无 refresh**。扫码不解决续期问题——用户每周期要再扫一次。
|
|
222
|
+
(与桌面线一样,这是 AgnesCode 侧的设计,不是本插件能改的。)
|
|
223
|
+
2. **逆向商用产品实时登录流**。上游改 `redirect_uri` 白名单、换端点路径、或给 `agnes-cli*`
|
|
224
|
+
之外的前缀收紧 client 校验,设备流即破。这是**持续维护负担**,不是一次写死。
|
|
225
|
+
3. **插件生命周期内要短暂开 localhost socket**(仅回调期间,收完即关)。
|
|
226
|
+
4. **token 落点**:必须进 DSH 凭据服务,`kind: "grant"`;token 永不落盘、不进日志。
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## 8. 接入落地(2026-10-20,已接入插件)
|
|
231
|
+
|
|
232
|
+
闭环已实测通过(§6),工程化工作已落地:
|
|
233
|
+
|
|
234
|
+
- **host 登录模块**:`src/host/agnescode-login.ts` 实现设备流——`listenOnRandomPort` +
|
|
235
|
+
`waitForCallback` + `openExternal` + `exchangeCodeForToken`。localhost 回调是 POST JSON
|
|
236
|
+
`{ code, state }`(非 GET query),且必须回 CORS 头——实现与 `upstream/probe-login-flow.mjs`
|
|
237
|
+
一致,并保留 GET 回退。
|
|
238
|
+
- **路由**:`POST /api/<panel>/agnescode { action: "login" }`(`src/host/routes/agnescode.ts`
|
|
239
|
+
的 login 分支):domain 白名单(cn / intl)+ 单飞(`agnescodeLoginInFlight`)+ 失败文本过
|
|
240
|
+
`redactSecrets`(红线①)。
|
|
241
|
+
- **面板**:「AgnesCode」tab 的「扫码登录」按钮(跨平台,无需桌面 App;macOS / Linux 可用),
|
|
242
|
+
与「检测本机登录态」(Windows-only)并列;登录期间按钮转「等待扫码」态,两按钮互相禁用。
|
|
243
|
+
|
|
244
|
+
- token 存凭据服务 `kind:"grant"`,复用现有 publisher / store / 凭据隔离。
|
|
245
|
+
- 登录 trace 必须过 `onTrace`(红线⑤)。
|
|
246
|
+
- 节流进 `throttle-store.ts` 迁移缝。
|
|
247
|
+
- 采集侧写权限仍为零——扫码只是换一条「拿 token」的入口,不改变「拿 token 之后」的一切边界。
|
|
248
|
+
- `openExternal` 在 Windows 上用 `rundll32 url.dll,FileProtocolHandler` 直接 CreateProcess 传 argv,
|
|
249
|
+
不经 shell(URL 含 `&` 不会被拆成命令分隔符)。缺少浏览器二进制时,spawn 的**异步** `error`
|
|
250
|
+
事件(ENOENT)经 Promise race fail-fast(`OPEN_EXTERNAL_GRACE_MS` 兜底),登录不会干等回调超时。
|
|
251
|
+
- 已知限制照旧:令牌仍约 28 天、无 refresh(见 §7)——扫码不解决续期,用户每周期需再扫一次。
|
package/docs/CONTRIBUTING.md
CHANGED
|
@@ -82,91 +82,3 @@ git commit -m "chore: stop tracking DSH internal _asar_extract dump"
|
|
|
82
82
|
凭据服务里的 key、memory 层的 key 与 env 里的 key 都能被读到。仍不对称的部分:API key 无写路径(不通过本插件修改),
|
|
83
83
|
所以不给 `token-store.ts` 加 `modifyRecord`;`fetchModelCatalog` 只接字符串参数,不关心供方是谁。
|
|
84
84
|
读 key 必须每次轮询时调用(凭据服务可能晚于插件挂载注册),不能在 `apply` 开头缓存。
|
|
85
|
-
|
|
86
|
-
---
|
|
87
|
-
|
|
88
|
-
## 8. 投稿 awesome-dsh-plugin 列表(插件市场收录)
|
|
89
|
-
|
|
90
|
-
投稿指南(唯一权威,改规则以它为准):<https://github.com/awesome-dsh-plugin/awesome-dsh-plugin/blob/main/contributing.md>
|
|
91
|
-
|
|
92
|
-
**不要看错仓库**:投稿是向 `awesome-dsh-plugin/awesome-dsh-plugin` 提 PR,加且仅加一个文件
|
|
93
|
-
`data/plugins/eghrhegpe__dsh-connect-agnes-token-plan.yml`;不要手工编辑对方仓库生成出来的
|
|
94
|
-
README。一个 PR 最多 3 条。
|
|
95
|
-
|
|
96
|
-
本仓库只放**拟稿镜像**:[`awesome-dsh-plugin-submission/eghrhegpe__dsh-connect-agnes-token-plan.yml`](../awesome-dsh-plugin-submission/eghrhegpe__dsh-connect-agnes-token-plan.yml)。
|
|
97
|
-
提 PR 时把这份文件原样复制到对方仓库的 `data/plugins/` 下即可——不要在这里再维护第二份 yaml(两处一定会漂移)。
|
|
98
|
-
描述只能陈述功能、不带营销词,每句都会被维护者对照代码核对;逐条溯源写在拟稿的注释里。
|
|
99
|
-
|
|
100
|
-
含 `: ` 的描述必须加引号,否则 YAML 解析失败;`en` 必填且以句号结尾,`zh` 可选。
|
|
101
|
-
|
|
102
|
-
**截图(`screenshots.json`)必须与当前 UI 一致**——它是市场页的第一屏,比描述更先被看到,却也是最容易在 UI 迁移后烂掉的东西:
|
|
103
|
-
|
|
104
|
-
- **当前 3 张已重截**(2026-10-01,0.4.3 面板归位之后):`assets/panel-credit.png`(「积分额度」tab:池卡 + 每模型消耗)、`assets/panel-API-provider.png`(「接入 API」tab:语言模型开关 + 花名册勾选)、`assets/panel-AgnesCode.png`(「AgnesCode」tab:登录态检测 / 余额 / 花名册)。旧两张 `panel-credit-pools.png` / `panel-provider-setup.png` 拍于归位之前(标题还是「积分面板」、右上角挂着已摘掉的「返回会话」按钮、无 tab bar、模型行带已删的「纯文本」徽章),已删除。
|
|
105
|
-
- 重截条目与 tab 覆盖同理,**每张必须对应一个真实 tab**;三张已与三个 tab 一一对应(积分额度 / 接入 API / AgnesCode),见 `screenshots.json`。
|
|
106
|
-
- **清单与资产必须同一次提交**(2026-10-01 实测事故):重截换名那次 `assets/` 与 git 都已换成新名,唯独 `screenshots.json` 还指着两个已不存在的文件——工作树干净、构建通过、其余十条检查全绿,**没有任何东西在报错**,而市场按这份清单取图,推上去就是图裂。现由 `docs.test.mjs` 检查 `SCREENSHOTS` 兜住:每条路径必须真实存在、是图片扩展名、1–8 张、且为仓库根相对路径。
|
|
107
|
-
- **不要凭想象补图**——画一个不存在的界面比没有图更坏,本插件的市场描述是「每句都会被对照代码核对」。
|
|
108
|
-
|
|
109
|
-
投稿前的硬门槛(CI 自动检查 + 维护者人工读码):
|
|
110
|
-
|
|
111
|
-
- `package.json` 已声明 `dsh.bundle` 且根目录有 `cordis.patch.yml`——已满足(只声明 `dsh.client` 会被拒);
|
|
112
|
-
- 官方 `@deepseek-ai/*` 包走 `peerDependencies`——已满足;
|
|
113
|
-
- GitHub 仓库需打 `dsh-plugin` topic——已满足(2026-09-28 补上);
|
|
114
|
-
- 仓库创建满 24 小时(CI 按 GitHub `created_at` 自动卡);本仓 2026-09-28T05:03:11Z 建仓;
|
|
115
|
-
- 真实可用代码、非占位——已满足;仓库需公开且处于活跃维护。
|
|
116
|
-
|
|
117
|
-
已发布 npm 包(registry 上最新为 **0.12.2**,2026-10-19 发布;0.2.0 于 2026-09-28 首发、
|
|
118
|
-
0.4.3 于 2026-10-01 发布,二者都发在旧包名 `dsh-connect-sensenova-token-plan` 下;本包名
|
|
119
|
-
`dsh-connect-agnes-token-plan` 自 **0.5.0** 起,其后 0.5.0 / 0.6.0(2026-10-01)、
|
|
120
|
-
0.7.0 / 0.8.0 / 0.8.1(2026-10-02)、0.9.0(2026-10-03)、0.10.0(2026-10-04)、
|
|
121
|
-
0.10.1(2026-10-06)、0.10.2(2026-10-10)、0.11.0 / 0.11.1(2026-10-16)、0.11.2(2026-10-17)、0.12.0(2026-10-18)、0.12.1 / 0.12.2(2026-10-19)均已发布;
|
|
122
|
-
0.4.1 与 0.4.2 打了 git tag 但**未发布到 npm**,其内容随 **0.4.3** 一并发布。`repository` 指回本仓,列表会
|
|
123
|
-
自动按下载量关联,yml 里无需任何 npm 字段)。注意本机默认 registry 是 npmmirror 镜像,登录与发布都
|
|
124
|
-
必须显式带 `--registry=https://registry.npmjs.org`;发新版前先在 package.json 升版本号(已发布版本
|
|
125
|
-
不可覆盖)。**完整发布清单见根目录 [RELEASING.md](../RELEASING.md)**——尤其第 6 步 GitHub Release
|
|
126
|
-
没有任何自动化,漏掉时不会有任何东西报错。
|
|
127
|
-
|
|
128
|
-
> **发布状态怎么自己核实**(别靠本文档记着的版本号——它会陈旧,且**曾经真的陈旧过**):
|
|
129
|
-
> ```bash
|
|
130
|
-
> npm view dsh-connect-agnes-token-plan version --registry=https://registry.npmjs.org
|
|
131
|
-
> npm view dsh-connect-agnes-token-plan time --registry=https://registry.npmjs.org
|
|
132
|
-
> ```
|
|
133
|
-
> 上一版这里写着「registry 上最新为 0.4.3」,而 registry 早已是 0.8.1——漂了四个版本。
|
|
134
|
-
> 教训不在于「该勤改」,而在于**这一格不该承载事实**:本文档记的是一个每次发版都会变的量,
|
|
135
|
-
> 而 `docs.test.mjs` 的版本快照只钉 `docs/DSH-PLUGIN.md`,`COUNT_GUARD` 只管数字漂移不管版本号,
|
|
136
|
-
> 于是这行谁也不审。**registry 是唯一权威**,本行只在与它不一致时提醒你去查。
|
|
137
|
-
|
|
138
|
-
> **已发布 ⇒ tag 不可移**(2026-10-01 实测,别踩):`v0.4.3` 已同时存在于 npm 与 GitHub Release,
|
|
139
|
-
> 因此**不能**按 RELEASING §4 的告警去「删除并强制移动 tag」来补齐后来的提交——那会让 npm 上的
|
|
140
|
-
> 0.4.3 与 tag 内容不符。同理适用于其后每一个已发布的版本(0.5.0 起至 0.10.0)。
|
|
141
|
-
> `v0.4.3` 之后落地的改动(e2e 进程树修复、`.gitignore`、以及自述面文档追平)
|
|
142
|
-
> **已随 0.5.0 到达用户**。
|
|
143
|
-
>
|
|
144
|
-
> 实测证据(2026-10-01,当时 registry 上是 0.4.3):
|
|
145
|
-
> `npm view dsh-connect-agnes-token-plan readme --registry=https://registry.npmjs.org`
|
|
146
|
-
> 抓下来的 README 仍含 3 处「侧边栏」、0 处「小浣熊」——**修好的 README 在发新版前对用户不存在**。
|
|
147
|
-
>
|
|
148
|
-
> **该结论已随 0.5.0 起的后续版本作废**(2026-10-03 实测 registry 上的 0.8.1):
|
|
149
|
-
> README 现在写的是 Plugins 页(「面板是页内的内联卡片,**不在侧边栏**」),「积分额度」与 AgnesCode
|
|
150
|
-
> 都在。教训不变,只是**这条事实也会过期**——上面那段留作判例,现状请按上面那段 `npm view` 自己查。
|
|
151
|
-
|
|
152
|
-
**市场收录状态(2026-10-01 实测)**:投稿 PR 已提且**仍处于 open、未合并**——
|
|
153
|
-
[awesome-dsh-plugin#6139](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin/pull/6139)
|
|
154
|
-
(2026-09-29 提交,`merged_at: null`,0 评论);`data/plugins/` 目录下尚无
|
|
155
|
-
`eghrhegpe__dsh-connect-agnes-token-plan.yml`,与实际一致。**该 PR 的正文与条目文件已于 2026-10-01 更新**(原先两处都停在旧形态):原文写的是 "for the Harness Web sidebar",原文引用 yml 里的 "sidebar panel" 与「侧边栏面板」;两处现都改为 Plugins 页的配置卡,并补上三个默认关闭的可选开关(provider 注册 / 出图工具 / 第二上游)。**原文其实提过 provider 注册**——但只提了它一个,且未提第二上游(此前本节误写为「未提 provider 注册」,2026-10-01 读到 PR 原文后更正)。**yml 才是市场条目的实际描述**:只改正文不改 yml,是另一种「分头走路」。收录后市场按 `screenshots.json` 取图,动图只需更新清单与资产(**改名就必须同步清单**,见检查 `SCREENSHOTS` 与 PITFALLS §26)。
|
|
156
|
-
|
|
157
|
-
**更新一个已投稿的条目**(2026-10-01 实操,两处都要改):条目文件在 **PR 的 head fork** 上(`eghrhegpe/awesome-dsh-plugin`,分支 `add-sensenova-token-plan`),既不在本仓、也不在 base 仓。
|
|
158
|
-
|
|
159
|
-
```bash
|
|
160
|
-
# 1) 条目文件本身——市场条目的实际描述由它决定
|
|
161
|
-
SHA=$(gh api "repos/eghrhegpe/awesome-dsh-plugin/contents/data/plugins/eghrhegpe__dsh-connect-agnes-token-plan.yml?ref=add-sensenova-token-plan" --jq .sha)
|
|
162
|
-
# 请求体 {message, content: <新内容的 base64>, sha: $SHA, branch: "add-sensenova-token-plan"}
|
|
163
|
-
gh api repos/eghrhegpe/awesome-dsh-plugin/contents/data/plugins/eghrhegpe__dsh-connect-agnes-token-plan.yml \
|
|
164
|
-
--method PUT --input <body.json>
|
|
165
|
-
# 2) PR 正文——人读的说明,用 --body-file 避免 shell 转义踩坑
|
|
166
|
-
gh pr edit 6139 -R awesome-dsh-plugin/awesome-dsh-plugin --body-file <body.md>
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
两个坑:① 访问该 PR **必须带 `-R awesome-dsh-plugin/awesome-dsh-plugin`**,否则 `gh pr view 6139` 会按本仓的 PR 号去解析,直接报 `Could not resolve to a PullRequest`;② `gh pr edit` 会改 PR 的更新时间(在按更新时间排序的队列里位置会变),但不会引入 review,属可接受代价。
|
|
170
|
-
|
|
171
|
-
其余可选增强:根目录放 `screenshots.json` 声明 1–8 张截图;或在 GitHub Release 挂版本无关文件名的
|
|
172
|
-
`.tgz`(yml 的 `tarball:` 字段)。本仓可从源码安装,tarball 不需要。
|
package/docs/DSH-PLUGIN.md
CHANGED
|
@@ -25,7 +25,7 @@ DSH 插件是一段在 **Host**(桌面版或 `dsh web`)进程内运行的代
|
|
|
25
25
|
{
|
|
26
26
|
"name": "dsh-connect-agnes-token-plan",
|
|
27
27
|
"icon": "./icon.svg", // Plugins 页插件卡图标:Host 读此字段按 SVG/PNG/JPEG/WebP 渲染(相对路径、落在 files 内、≤256 KiB,任一不满足会让整条元数据报错)
|
|
28
|
-
"version": "0.
|
|
28
|
+
"version": "0.13.0",
|
|
29
29
|
"main": "./lib/index.js", // Host 半边入口:src/host/*.ts 经 tsdown 打成的单条 bundle
|
|
30
30
|
"exports": {
|
|
31
31
|
".": "./lib/index.js", // 源码在 src/host/,lib/ 为纯构建产物(随库提交,git/市场直装零构建)
|