wkgeo-cli 0.2.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/README.md +681 -0
- package/SKILL.md +598 -0
- package/lib/api.mjs +178 -0
- package/lib/args.mjs +196 -0
- package/lib/cli.mjs +134 -0
- package/lib/commands/apps.mjs +116 -0
- package/lib/commands/auth.mjs +974 -0
- package/lib/commands/billing.mjs +421 -0
- package/lib/commands/index.mjs +160 -0
- package/lib/commands/orders.mjs +444 -0
- package/lib/commands/sites.mjs +140 -0
- package/lib/commands/skill.mjs +119 -0
- package/lib/config.mjs +260 -0
- package/lib/credentials.mjs +160 -0
- package/lib/device-store.mjs +132 -0
- package/lib/device.mjs +427 -0
- package/lib/errors.mjs +268 -0
- package/lib/format.mjs +204 -0
- package/lib/http.mjs +71 -0
- package/lib/index.mjs +39 -0
- package/lib/login.mjs +1332 -0
- package/lib/page.mjs +396 -0
- package/lib/pkce.mjs +48 -0
- package/lib/proxy.mjs +454 -0
- package/lib/qr.mjs +31 -0
- package/lib/runtime.mjs +287 -0
- package/package.json +51 -0
- package/wkgeo.mjs +17 -0
package/README.md
ADDED
|
@@ -0,0 +1,681 @@
|
|
|
1
|
+
# 云站官方 CLI(`wkgeo-cli`)
|
|
2
|
+
|
|
3
|
+
> 命令行/桌面端/**给 AI 端**用的官方客户端。
|
|
4
|
+
> **授权登录一次**(浏览器点一次「同意」)→ 令牌存本地 30 天 → 之后**建站 / 下单 / 钱包余额支付 / 查余额 / 查订单**全程不用再登录。
|
|
5
|
+
|
|
6
|
+
* 技术形态:Node.js,**零第三方依赖**(只用 Node 内置模块),Node ≥ 20
|
|
7
|
+
* 授权方式:OAuth 授权码 + **PKCE**(公共客户端,**代码里没有任何 client_secret**)
|
|
8
|
+
* 另一种授权:**设备码**(`login --device`,不需要回调地址,**对代理/防火墙免疫**;见 §3.2)
|
|
9
|
+
* ⭐ **开着代理也能成功**:**只要检测到系统代理就直接改用设备码**(不看代理绕过列表),用户什么都不用做(见 §3.4)
|
|
10
|
+
* 输出:人类可读 + **稳定 JSON**(默认非终端环境自动 JSON),失败也是结构化 JSON
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## 0. 给 AI 端的最短路径(TL;DR)
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npx -y wkgeo-cli@latest login # 1) 打印/打开授权网址,等用户点「同意」
|
|
18
|
+
npx -y wkgeo-cli@latest whoami # 2) 确认是谁(只显示账号标识,不打印令牌)
|
|
19
|
+
npx -y wkgeo-cli@latest wallet # 3) 查余额
|
|
20
|
+
npx -y wkgeo-cli@latest sites create --name 我的站
|
|
21
|
+
npx -y wkgeo-cli@latest products list
|
|
22
|
+
npx -y wkgeo-cli@latest orders create --product storage_gb --units 1 # 余额够就当场付清
|
|
23
|
+
npx -y wkgeo-cli@latest orders list
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
* **所有命令都在非终端(管道/AI 子进程)下自动输出 JSON**;人看的时候自动是人类可读表格。
|
|
27
|
+
* 退出码:**0** 成功 | **1** 失败 | **2** 用法错误 | **3** 需要(重新)授权 | **4** 被限流。
|
|
28
|
+
* 失败 JSON:`{"ok":false,"command":"...","error":{"kind","code","message","hint","retry_after"},"meta":{...}}`
|
|
29
|
+
—— `hint` 是"下一步该做什么",AI 可以直接照做。
|
|
30
|
+
* 完整的给 AI 的说明书见 [`SKILL.md`](./SKILL.md);**也可以一条命令直接取出来**:
|
|
31
|
+
`npx -y wkgeo-cli@latest skill`(默认打**纯文本原文**;`--json` 时在 `data.markdown` 里)。
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## 1. 安装与分发(三条路径,主线是 npm)
|
|
36
|
+
|
|
37
|
+
| 形态 | 适合谁 | 怎么装 | 体积 | 备注 |
|
|
38
|
+
| --- | --- | --- | --- | --- |
|
|
39
|
+
| **① npm 包(主线)** | 开发者、我们自己的 **AI 端**、有 Node 的任何环境 | `npx -y wkgeo-cli@latest <命令>` 或 `npm i -g wkgeo-cli` | ~51 KB | **一种形态覆盖全平台**;升级只需改 `@latest` |
|
|
40
|
+
| ② zip 单文件(备用) | 有 Node、但不想用 npm 的普通用户 | 解压 → 双击 `wkgeo.cmd` / `./wkgeo.sh` | ~64 KB | 无需 `npm install`;仍需机器有 Node 20+ |
|
|
41
|
+
| ③ exe 独立可执行(备用) | **完全没有 Node**、也不想装 Node 的人 | 下载对应平台的二进制直接跑 | **60~100 MB / 平台** | 每个平台要分别打包,发版成本最高 |
|
|
42
|
+
|
|
43
|
+
### ① npm(推荐)
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
# 不安装,直接跑(AI 端首选:-y 免掉安装确认,避免自动化卡住)
|
|
47
|
+
npx -y wkgeo-cli@latest --help
|
|
48
|
+
|
|
49
|
+
# 或全局安装后用短命令
|
|
50
|
+
npm i -g wkgeo-cli
|
|
51
|
+
wkgeo --help
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
包内容用 `package.json` 的 **`files` 白名单**收口(`wkgeo.mjs` + `lib/` + `README.md` + `SKILL.md`),
|
|
55
|
+
**不含**测试、脚本、`dist/`、任何凭据。核对方式:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
cd cli && npm pack --dry-run # 输出即为"将要发布的确切文件清单"
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
> ⚠️ **取舍:发 npm = 源码公开**。npm 上的包任何人都能下载查看(我们的包是纯客户端、零密钥,
|
|
62
|
+
> 公开不影响安全;但如果你不想让别人看到实现细节,就只能走 zip/exe 私有分发)。
|
|
63
|
+
> 另一个取舍:一旦某个版本被别人依赖,**不能随意删除**(见「发布流程」)。
|
|
64
|
+
|
|
65
|
+
### ② zip(备用,给有 Node 的普通用户)
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
cd cli && npm run build:zip
|
|
69
|
+
# → cli/dist/wkgeo-cli-0.1.0.zip(含入口 + lib + README + 快速开始.txt + wkgeo.cmd/.sh)
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
用户侧:解压 → 双击 `wkgeo.cmd`(Windows)或 `./wkgeo.sh`(macOS/Linux)→ 按提示 `login`。
|
|
73
|
+
⚠️ **仍然需要机器上有 Node 20+**(zip 里去掉了 `node_modules`,因为本来就没有依赖)。
|
|
74
|
+
|
|
75
|
+
### ③ exe(备用,给完全没 Node 的非技术客户)
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
cd cli && npm run build:exe # 打印三种方案的完整命令与取舍(零副作用)
|
|
79
|
+
cd cli && node scripts/build-exe.mjs --sea # 跑 Node SEA 的离线两步(生成 blob + 复制 node 壳)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
推荐 **Node SEA**(官方)或 `bun build --compile`(最省事),**不推荐 `pkg`**(已停止维护)。
|
|
83
|
+
⚠️ **每个平台(Windows/Linux/macOS)必须分别打包,二进制互不通用**,且 macOS 还要 x64/arm64 分开;
|
|
84
|
+
产物 60~100 MB。脚本不会替你下载任何大依赖(postject/bun 需要时自己装)。
|
|
85
|
+
|
|
86
|
+
### 版本与升级策略
|
|
87
|
+
|
|
88
|
+
* 当前版本 **`0.1.0`**(早期版;稳定后升 `1.0.0`)。
|
|
89
|
+
* **同一个版本号不能覆盖**:改了代码必须 `npm version patch|minor|major` 再发布。
|
|
90
|
+
* AI 端统一用 **`npx -y wkgeo-cli@latest`** → 每次自动拿最新版;
|
|
91
|
+
**全局安装的用户不会自动升级**(要手动 `npm i -g wkgeo-cli@latest`)。
|
|
92
|
+
* 发布后 **几秒~几分钟** 生效(npm CDN 缓存);⚠️ **72 小时后基本无法删除**,只能 `deprecate` + 发修正版。
|
|
93
|
+
* 详细步骤(含 2FA、包名检查、发布前安全检查清单)见 **[`发布流程.md`](./发布流程.md)**。
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## 2. 不发布也能测(三种本地方式)
|
|
98
|
+
|
|
99
|
+
| 方式 | 命令 | 像不像真安装 | 适合什么时候 |
|
|
100
|
+
| --- | --- | --- | --- |
|
|
101
|
+
| ① **直接跑** | `node cli/wkgeo.mjs login` | 不像 | 改完立刻验证,最快 |
|
|
102
|
+
| ② **全局软链** | `cd cli && npm link` → 之后直接敲 `wkgeo login` | **最像**(真实 shim、真实 PATH) | 想验证"装完之后是什么体验";改代码即时生效 |
|
|
103
|
+
| ③ **打包彩排 ⭐** | `cd cli && npm pack` → `npm i -g ./wkgeo-cli-0.1.0.tgz` | **就是真安装** | **发布前必做**:同时验证"包里文件齐不齐、有没有混进不该有的东西" |
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
# ① 直接跑
|
|
107
|
+
node cli/wkgeo.mjs --help
|
|
108
|
+
|
|
109
|
+
# ② npm link(卸载:npm unlink -g wkgeo-cli)
|
|
110
|
+
cd cli && npm link && wkgeo --version
|
|
111
|
+
|
|
112
|
+
# ③ 打包彩排(发布前必做)
|
|
113
|
+
cd cli
|
|
114
|
+
npm test # 离线单测(273 个用例)
|
|
115
|
+
npm pack --dry-run # 看清单:只会包含 wkgeo.mjs / lib / README.md / SKILL.md
|
|
116
|
+
npm pack # 产出 wkgeo-cli-0.1.0.tgz
|
|
117
|
+
npm i -g ./wkgeo-cli-0.1.0.tgz
|
|
118
|
+
wkgeo login && wkgeo wallet && wkgeo logout # 走一遍真链路
|
|
119
|
+
npm uninstall -g wkgeo-cli
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
> 第 ③ 步是**唯一**能证明"发出去的包能跑"的方式(①②都绕过 `files` 白名单)。
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## 3. 登录与授权
|
|
127
|
+
|
|
128
|
+
### 3.1 本地回调模式(默认,推荐)
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
wkgeo login
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
会发生什么(**用户实际会看到的东西**):
|
|
135
|
+
|
|
136
|
+
1. 终端打印一条授权网址(同时**尽力自动打开浏览器**);
|
|
137
|
+
2. 浏览器里是**我们自己的登录页**:支持「账密登录 / 手机号登录 / 邮箱登录」三个页签
|
|
138
|
+
(手机号/邮箱是**验证码登录**,验证码由平台下发到已绑定的手机/邮箱);
|
|
139
|
+
3. 登录后进入**同意页**,显示应用名「官方 CLI」和将获得的权限,点「同意」;
|
|
140
|
+
4. 浏览器跳回 `http://127.0.0.1:53682/callback` —— CLI 的本地小服务会显示
|
|
141
|
+
**「✅ 授权成功 / 请回到终端(客户端)继续操作,本页面可以关闭了」** 的中文页面;
|
|
142
|
+
5. 终端显示「✅ 授权成功,已登录:<账号>」,令牌写入 `~/.wkongai/credentials.json`(0600)。
|
|
143
|
+
|
|
144
|
+
于是"点同意"这件事**只需要 30 天做一次**(之后令牌过期或被吊销才需要重来)。
|
|
145
|
+
|
|
146
|
+
选项:
|
|
147
|
+
|
|
148
|
+
| 选项 | 说明 |
|
|
149
|
+
| --- | --- |
|
|
150
|
+
| `--no-browser` | **完全不尝试**自动打开浏览器(只打印网址,适合远程/无 GUI、自动化;★ 这是**唯一**的"不打开"开关) |
|
|
151
|
+
| `--open-browser` | 强制尝试打开系统浏览器(⚠️ 现在与**默认行为等价** —— 默认就会尝试打开;保留只为显式语义与兼容,见下) |
|
|
152
|
+
| `--scope "site:read site:write billing:self"` | 申请的能力范围(默认就是这三个,够建站+下单+支付) |
|
|
153
|
+
| `--client-id <id>` | 换一个 OAuth 客户端(默认 `wkgeo-cli`) |
|
|
154
|
+
| `--callback-port <n>` | 本地回调端口(默认 53682;被占用会自动退到随机端口) |
|
|
155
|
+
| `--timeout <秒>` | 等待授权的超时。**默认单位是秒**:`--timeout 600` = 600 秒;也支持显式后缀 `600s` / `500ms`;默认 300 秒(5 分钟)。非法值直接报用法错误(退出码 2),不会静默兜底 |
|
|
156
|
+
|
|
157
|
+
> ⚠️ **单位踩过的坑**:`--timeout` 早期按**毫秒**解释,于是 `--timeout 600` 变成 600 毫秒 ——
|
|
158
|
+
> 0.8 秒就超时失败,而帮助里连单位都没写。现在**默认按秒**,并把单位写进了 `--help`。
|
|
159
|
+
|
|
160
|
+
> ⚠️ **自动打开浏览器**:**多策略依次尝试**(Windows:`rundll32 url.dll,FileProtocolHandler <url>` → `cmd /c start "" "<url>"` → `powershell -NoProfile -Command Start-Process`;
|
|
161
|
+
> macOS:`open` → `open -a Safari`;Linux:`xdg-open` → `sensible-browser` → `x-www-browser`),每条都看**退出码/错误/超时**,失败**静默**试下一条。
|
|
162
|
+
> ⚠️ Windows 那条 `cmd` 的**引号必须在字符串里**:`&` 在 cmd 里是命令分隔符 ——
|
|
163
|
+
> 授权 URL 的 6 个参数会被当成"另一条命令"吃掉,浏览器打开的是光秃秃的
|
|
164
|
+
> `/admin/oauth/authorize`(页面显示「授权请求不完整」)。
|
|
165
|
+
> 全部失败才提示,且**只 warn**(`⚠️ 浏览器没能自动打开…` 后面紧跟**完整 URL** 供复制),**绝不中断**授权流程。
|
|
166
|
+
>
|
|
167
|
+
> ★ **默认就是"总是尝试打开"**(2026-10 老板最终拍板走**传统路线**):
|
|
168
|
+
> 「还是返回以前那种**打开跳到一个浏览器**,然后打开授权那个吧,**大家也都是那个的**……
|
|
169
|
+
> 我们在右侧这个……能力还不会太完善……**还是走传统的路吧**。」
|
|
170
|
+
> * **任何场景**(**人在终端**、**AI 客户端 / 管道 / 脚本(非 TTY 或 `--json`)**)→ **都会尝试**自动打开系统浏览器;
|
|
171
|
+
> * 打开成功 → `✅ 已为你打开浏览器,请在那里完成授权。`;打开失败 → ⚠️ 一段**含完整 URL** 的醒目提示
|
|
172
|
+
> (AI 场景再多一句"客户端支持内置浏览器也可以在那里打开"的**可选**提示,不是主路径);
|
|
173
|
+
> * **唯一**的"不打开"开关是 `--no-browser`(**优先级最高**,压过 `--open-browser`);
|
|
174
|
+
> * `--open-browser` **保留**,但现在与默认行为**等价**(只为显式语义与兼容,别删);
|
|
175
|
+
> * `--json` 的 `open_browser.skipped_reason` 现在只有 `"no_browser"` 或 `null` 两种
|
|
176
|
+
> (2026-10-01 那版的 `"non_tty_agent_mode"` **已弃用**);
|
|
177
|
+
> * 为什么走传统路线:**稳定**(各平台都有成熟的系统启动器)+ **`window.close()` 在独立浏览器里生效**
|
|
178
|
+
> (授权页/收银台完成后的那一屏能自己关掉);
|
|
179
|
+
> * ⚠️ **不弹浏览器 ≠ 失败**、**打开失败也 ≠ 失败**:地址照旧完整打印、流程照旧等回调/照旧返回 `pay_url`,
|
|
180
|
+
> 命令**绝不会**因此失败。
|
|
181
|
+
|
|
182
|
+
> ⚠️ 回调**只监听 `127.0.0.1`**(绝不 `0.0.0.0`);`state` 每次随机并在本地回调时**强校验**
|
|
183
|
+
> (服务端只负责原样回传,防 CSRF 是客户端自己的责任)。
|
|
184
|
+
> 端口被占用会自动换随机端口 —— 服务端按 RFC 8252 对回环地址**忽略端口**比对。
|
|
185
|
+
|
|
186
|
+
### 3.2 设备码模式(`--device`)—— **代理/防火墙环境的推荐路径**
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
wkgeo login --device # 申请短码 → 打印网址/短码 → 轮询到出结果(人盯着终端时用这条)
|
|
190
|
+
wkgeo login --device --no-wait # ★ 只申请短码就退出(AI 端友好,见下)
|
|
191
|
+
wkgeo login --device-continue # 用上次保存的设备码换票(配合 --no-wait)
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
一条**不需要任何回调地址/监听端口**的授权路径:CLI 申请一个短码(如 `ABCD-1234`),
|
|
195
|
+
打印一段**可以直接复制**的话:
|
|
196
|
+
|
|
197
|
+
```
|
|
198
|
+
请在浏览器中完成授权(设备码模式,不需要任何回调地址):
|
|
199
|
+
|
|
200
|
+
1) 打开:https://oauth.wkgeo.com/oauth/device/?user_code=ABCD-1234
|
|
201
|
+
2) 如果页面没有自动填好短码,请输入:ABCD-1234
|
|
202
|
+
|
|
203
|
+
📋 可以直接复制这段发给 AI 端或转给用户:请打开 https://oauth.wkgeo.com/oauth/device/?user_code=ABCD-1234 并完成「同意授权」(短码:ABCD-1234)。
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
> ✅ 线上**已支持**这条路径(2026-10-02 实测:`metadata` 声明了 `device_authorization_endpoint`,
|
|
207
|
+
> `grant_types_supported` 含 device_code,确认页 `/admin/oauth/device/` 也在)。
|
|
208
|
+
> 端到端真跑记录见云函数测试目录的 `cloudfunctions/_tests/oauth-device-live-e2e.js`(默认只读,`OAUTH_DEVICE_E2E_LIVE=1` 才实跑)。
|
|
209
|
+
|
|
210
|
+
#### `--no-wait` + `--device-continue`:给 **AI 端(Agent)** 用的"先给码、后领票"
|
|
211
|
+
|
|
212
|
+
AI 端是**一轮一轮**工作的:一次工具调用里挂着等 10 分钟会把用户晾住,也容易撞工具超时。
|
|
213
|
+
所以拆成两条命令,**中间不需要任何长驻进程**:
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
# ① Agent 调用(立刻返回,进程随即退出)
|
|
217
|
+
wkgeo login --device --no-wait --json
|
|
218
|
+
# → {"ok":true,"command":"login","stage":"awaiting_user","user_code":"ABCD-1234",
|
|
219
|
+
# "verification_uri":"https://oauth.wkgeo.com/oauth/device/",
|
|
220
|
+
# "verification_uri_complete":"…/?user_code=ABCD-1234","paste_text":"…","expires_in":600,
|
|
221
|
+
# "interval":5,"resume_command":"wkgeo login --device-continue","pending_file":"…","next_step":"…",
|
|
222
|
+
# "user_action_required":"请让用户访问 … 并输入短码 ABCD-1234","no_wait":true}
|
|
223
|
+
|
|
224
|
+
# ② Agent 把 paste_text(或 user_action_required)原样念给用户,让他在**自己的浏览器**里点「同意」
|
|
225
|
+
|
|
226
|
+
# ③ 用户说"点好了"之后,Agent 领票
|
|
227
|
+
wkgeo login --device-continue --json
|
|
228
|
+
# → {"ok":true,"command":"login","stage":"done","data":{…凭据已写入…}}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
* 状态文件写在凭据文件旁边(`<credentials.json>.device-pending.json`,**0600**),
|
|
232
|
+
所以第二步与第三步之间**隔多久都行**(只要没超过 `expires_in`,默认 10 分钟);
|
|
233
|
+
* 交互式终端里**不必**拆开用:直接 `login --device` 一口气跑完更省事;
|
|
234
|
+
* ⚠️ `--no-wait` **隐含**设备码模式(回调模式没有"先返回、稍后再领"这回事)。
|
|
235
|
+
|
|
236
|
+
> 🔴 **安全边界(AI 端必须知道)**:`device_confirm`(用户在页面上点「同意」这一步)
|
|
237
|
+
> **需要用户自己的平台登录会话**。AI 端**没有、也不该有**用户的浏览器会话,
|
|
238
|
+
> 所以"用户把短码发到 AI 对话框"之后,AI 端能做的是**把网址 + 短码清楚地转达给他、
|
|
239
|
+
> 引导他自己去点确认**;**不能**替用户点同意。CLI 只负责"申请码 + 等结果"。
|
|
240
|
+
|
|
241
|
+
#### 设备码模式与回调模式的差别(一眼看懂)
|
|
242
|
+
|
|
243
|
+
| | 本地回调(默认) | 设备码(`--device`) |
|
|
244
|
+
| --- | --- | --- |
|
|
245
|
+
| 需要本地监听端口 | 需要(127.0.0.1:53682) | **不需要** |
|
|
246
|
+
| 需要浏览器能访问 127.0.0.1 | 需要(⚠️ 代理/防火墙可能挡住) | **不需要** |
|
|
247
|
+
| 用户操作 | 点一下「同意」 | 打开网址(短码已预填)+ 点一下「同意」 |
|
|
248
|
+
| 适合 | 本机有浏览器、网络干净 | 远程/容器/CI、**开着代理**、防火墙受限 |
|
|
249
|
+
|
|
250
|
+
### 3.3 登录状态的输出(JSON 事件流)
|
|
251
|
+
|
|
252
|
+
`login` 是唯一会**长时间阻塞**的命令,所以 JSON 模式下它输出 **JSONL 事件流**(每行一个 JSON):
|
|
253
|
+
|
|
254
|
+
```jsonc
|
|
255
|
+
// 本地回调模式:第一行就给出网址,AI 可以立刻转述给用户
|
|
256
|
+
// (注意 `authorize/` 的**尾斜杠**:托管侧对不带尾斜杠的目录路径会 302 且丢掉 query)
|
|
257
|
+
{"ok":true,"command":"login","mode":"callback","stage":"awaiting_callback",
|
|
258
|
+
"authorization_url":"https://oauth.wkgeo.com/oauth/authorize/?client_id=…",
|
|
259
|
+
"redirect_uri":"http://127.0.0.1:53682/callback",
|
|
260
|
+
"user_action_required":"请让用户在浏览器中打开上面的 authorization_url,登录并点「同意」"}
|
|
261
|
+
// 结束(成功)
|
|
262
|
+
{"ok":true,"command":"login","stage":"done","data":{"account":{…},"expires_at":"2026-10-30T…","credentials_file":"…"}}
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
设备码模式第一行是 `stage:"awaiting_user"`,带 `user_code` / `verification_uri` /
|
|
266
|
+
`verification_uri_complete` / `open_url` / `expires_in` / `interval` / `paste_text`。
|
|
267
|
+
失败时输出 `{"ok":false,"command":"login","error":{…}}` 并返回非 0 退出码。
|
|
268
|
+
|
|
269
|
+
### 3.4 ⭐ 代理 / VPN 环境下怎么办(`127.0.0.1` 被代理接管)
|
|
270
|
+
|
|
271
|
+
**现象**:用户在同意页点了「同意」,浏览器却报"无法访问此网站"或干脆没反应,
|
|
272
|
+
**终端一直卡在"等待你在浏览器里完成授权…",最后超时失败**。
|
|
273
|
+
|
|
274
|
+
**机理**:本地回调模式要求**浏览器**访问 `http://127.0.0.1:<端口>/callback?code=…`。
|
|
275
|
+
多数代理默认绕过回环地址,但**全局/系统代理 + 绕过列表为空**时,浏览器会把这条回环请求
|
|
276
|
+
也交给代理 → CLI 的本地服务永远收不到 `code`。**问题在浏览器那一侧,不在 CLI 出网**。
|
|
277
|
+
|
|
278
|
+
**CLI 的处理(2026-10-02 第二版加固)**:
|
|
279
|
+
|
|
280
|
+
1. **检测**(只读,绝不改你的代理设置):Windows 读注册表 `…\Internet Settings` 的
|
|
281
|
+
`ProxyEnable`/`ProxyServer`/`ProxyOverride`/`AutoConfigURL`;macOS 读 `scutil --proxy`;
|
|
282
|
+
三平台都看 `HTTP_PROXY`/`HTTPS_PROXY`/`ALL_PROXY`/`NO_PROXY`(大小写都认)。
|
|
283
|
+
2. ★ **只要检测到"生效中的代理"就直接改设备码** —— **不再看绕过列表**。
|
|
284
|
+
真机教训(2026-10-02):老板的 `ProxyOverride` 里**写着** `localhost;127.*`,
|
|
285
|
+
旧版据此判"安全"、走了回调,结果他点了【同意授权】之后 CLI **一直拿不到令牌**;
|
|
286
|
+
独立探测 `curl -x http://127.0.0.1:7890 http://127.0.0.1:1/x` **返回 502**,
|
|
287
|
+
说明那台机器上的代理**会尝试接管回环请求**(绕过列表并没有真正生效)。
|
|
288
|
+
⇒ 结论:**"绕过列表里有 127.*"不等于绕过一定生效**(不同代理软件/浏览器解释不一致)。
|
|
289
|
+
宁可让你多按一次短码,也绝不让你卡在"点了同意却没反应"。
|
|
290
|
+
`risk` / `loopback_bypassed` 仍在 `--json` 里**只作诊断上报**,不再作为放行回调的依据。
|
|
291
|
+
3. ⭐ **自动改用设备码模式**(**默认开**,用户什么都不用做):
|
|
292
|
+
```
|
|
293
|
+
⚠️ 检测到系统代理(127.0.0.1:7890)。为避免“点了同意却一直收不到结果”,已自动改用【设备码模式】。
|
|
294
|
+
想强制用浏览器回调模式:加 --no-auto-device,并把 127.0.0.1,localhost 加入代理绕过列表。
|
|
295
|
+
(检测详情:源=windows-registry 代理=127.0.0.1:7890 绕过列表=localhost,127.*,<local>;
|
|
296
|
+
⚠️ 绕过列表里就算写了 127.*/localhost 也不再作为放行回调的依据 —— 真机上出现过"写了却仍被代理接管"。)
|
|
297
|
+
```
|
|
298
|
+
4. ⭐ **回调模式"等太久"能自愈(第二条保险)**:★ **只在检测到系统代理时才启用** ——
|
|
299
|
+
有代理时首轮等待默认 **180 秒**(`--callback-first-wait 90` 或
|
|
300
|
+
`WKONGAI_CALLBACK_FIRST_WAIT_MS=90000` 可显式指定,`0`/`off` 关闭):
|
|
301
|
+
这段时间没等到回调就**自动改用设备码继续**(不退出),不用再干等整段 `--timeout`
|
|
302
|
+
(默认 300 秒,可到 900 秒)。
|
|
303
|
+
⚠️ **2026-10-03 修了两处**(老板真机反馈):
|
|
304
|
+
* **没有代理时不再自愈**。以前它**无条件**挂在 60 秒上,而"用户把授权页读完再点同意"
|
|
305
|
+
经常超过 60 秒 ⇒ 明明没开任何代理也被切成设备码(原话:「现在变成设备码了,
|
|
306
|
+
我们因为没有开任何VPN,所以这个是不对的。」)。现在没代理就老老实实等满 `--timeout`,
|
|
307
|
+
只有你自己写了 `--callback-first-wait` 才按你写的来(**显式永远优先**)。
|
|
308
|
+
* **切换设备码时不再关掉本地回调监听**:两条路**并存**、**哪条先成功就用哪条**。
|
|
309
|
+
旧版在切换那一刻就把监听关了,于是你**之后**才点【同意授权】时浏览器只会显示
|
|
310
|
+
**ERR_CONNECTION_REFUSED**("无法访问此网站")——你会以为授权失败,其实马上就成功了。
|
|
311
|
+
现在那次点击照样能完成登录;若设备码先成功,之后到达的回调会看到一张
|
|
312
|
+
「本次登录已经完成」的页面。
|
|
313
|
+
本地回环端口**起不来**(防火墙/安全软件)时同样自动降级;
|
|
314
|
+
**换票失败、用户点了拒绝这类"已经走到下一步"的错误不会降级**(避免让你白做一遍)。
|
|
315
|
+
5. **不想被自动改道**:加 `--no-auto-device`,或设 `WKONGAI_AUTO_DEVICE=0`。
|
|
316
|
+
⚠️ 此时 CLI 会**明确警告**"若代理会接管 127.0.0.1,点了【同意授权】也收不到结果",
|
|
317
|
+
并且**关掉**第 4 条的自愈(你要求只走回调,就不会背着你偷偷改道)。
|
|
318
|
+
6. **排障入口**:`wkgeo config` 会直接打印本机代理情况与结论(`--json` 里是
|
|
319
|
+
`data.proxy` / `meta.proxy_warning`),支持同学让用户跑这一条就能判断。
|
|
320
|
+
|
|
321
|
+
`--json` 里的结构化字段(AI 端据此判断"是不是代理的锅、该让用户做什么"):
|
|
322
|
+
|
|
323
|
+
```jsonc
|
|
324
|
+
"proxy_warning": {
|
|
325
|
+
// ★ callback_may_fail 现在**等价于 detected**(与绕过列表无关)
|
|
326
|
+
"detected": true, "callback_may_fail": true, "loopback_bypass_trusted": false,
|
|
327
|
+
// 下面两个只作诊断(如实反映"绕过列表写了什么",但不影响是否降级)
|
|
328
|
+
"risk": false, "loopback_bypassed": true,
|
|
329
|
+
"platform": "win32", "proxy": "127.0.0.1:7890", "source": "windows-registry",
|
|
330
|
+
"bypass": ["localhost", "127.*", "<local>"],
|
|
331
|
+
// 诊断用:哪些来源"看起来没有绕过回环"(旧口径的判定依据,现在只作参考)
|
|
332
|
+
"risky_sources": [],
|
|
333
|
+
"actions": [
|
|
334
|
+
{ "type": "use_device_code", "label": "改用设备码模式(不需要本地回调,推荐)", "command": "wkgeo login --device" },
|
|
335
|
+
{ "type": "bypass_loopback", "label": "把 127.0.0.1,localhost 加进代理绕过列表(想继续用浏览器回调模式时;注意它不保证生效)" },
|
|
336
|
+
{ "type": "disable_proxy", "label": "临时关闭代理后重试" }
|
|
337
|
+
]
|
|
338
|
+
}
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
失败时的 `error.hint` 里也会带上同样的三选一(例如回调超时的 JSON 错误信封里,
|
|
342
|
+
`meta.proxy_warning` 依然在)。
|
|
343
|
+
|
|
344
|
+
> ⚠️ 两个容易误读的点:
|
|
345
|
+
> 1. **CLI 自己的出网不受系统代理影响**:Node 的 fetch(undici)默认既不读系统代理、
|
|
346
|
+
> 也不读 `HTTP_PROXY`(要 Node ≥ 24 的 `--use-env-proxy` / `NODE_USE_ENV_PROXY=1`)。
|
|
347
|
+
> 我们检测代理是**为了浏览器那一侧的回调**,不是去改 CLI 的网络行为。
|
|
348
|
+
> 2. **PAC 只能保守判定**:绕过规则写在自动配置脚本里,零依赖无法求值,所以命中 PAC 时
|
|
349
|
+
> 一律按"有风险"处理(代价只是一次设备码授权,仍然顺畅)。
|
|
350
|
+
|
|
351
|
+
---
|
|
352
|
+
|
|
353
|
+
## 4. 命令表
|
|
354
|
+
|
|
355
|
+
> 记法:`<必填>` / `[可选]`。所有命令都支持 `--json`(默认非终端下开启)、`--text`(强制人类可读)、
|
|
356
|
+
> `--base <url>`、`--client-id <id>`、`--credentials <path>`、`--verbose`。
|
|
357
|
+
> **任何命令都不会打印令牌**。
|
|
358
|
+
> 带单位的通用参数:`--timeout <秒>`(默认秒,支持 `600s` / `500ms`)、`--http-timeout <毫秒>`。
|
|
359
|
+
|
|
360
|
+
| 命令 | 作用 | 关键参数 |
|
|
361
|
+
| --- | --- | --- |
|
|
362
|
+
| `login` | 授权一次,令牌存本地 | `--device`、`--no-wait`、`--device-continue`、`--auto-device`/`--no-auto-device`、`--callback-first-wait <秒>`、`--no-browser`、`--open-browser`、`--scope`、`--callback-port`、`--timeout <秒>` |
|
|
363
|
+
| `whoami` | 当前登录的是谁(账号标识) | `--verbose`(额外显示令牌**掩码**与文件权限) |
|
|
364
|
+
| `logout` | 删本地凭据(默认还调服务端吊销) | `--local-only` |
|
|
365
|
+
| `config` | 显示生效配置与登录状态(无令牌) | — |
|
|
366
|
+
| `sites list` | 我的站点 | `--limit`、`--offset` |
|
|
367
|
+
| `sites create` | 建站(**建在令牌所属用户名下**) | `--name`(必填)、`--customer`、`--template`、`--domain` |
|
|
368
|
+
| `sites delete` | 删站(服务端是**软删除**,进回收站) | `<站点ID>` |
|
|
369
|
+
| `wallet` | 余额 + 最近流水 | `--limit`、`--entitlements` |
|
|
370
|
+
| `wallet txns` | 钱包流水(普通用户自己的那份) | `--limit` |
|
|
371
|
+
| `wallet recharge` | **充值下单**:返 `code_url` + **`pay_url`(自家收银台页面,带订单支付令牌 `token`;★ 默认会自动尝试用系统浏览器打开,`--no-browser` 可关;**支付成功后页面会自己关掉**)**(**不会自动扣款**) | `--amount-cents`(必填,单位分)、`--no-browser`、`--open-browser`、`--dry-run` |
|
|
372
|
+
| `products list` | 能买什么(找 `--product` 该填什么) | — |
|
|
373
|
+
| `orders create` | 下单(**余额够就当场用钱包付清**) | `--product <代码>` + `--units <份数>`,或 `--quota <键>` + `--amount <数量>`;`--allow-wechat` |
|
|
374
|
+
| `orders check` | 查支付结果(微信侧已付则就地履约)+ 最新余额 | `<订单ID>` |
|
|
375
|
+
| `orders pay` | 同上(**`orders check` 的别名**,任务表里叫 pay) | `<订单ID>` |
|
|
376
|
+
| `orders get` | 订单详情(翻自己的订单列表,**不触发微信查询**) | `<订单ID>`、`--max-scan` |
|
|
377
|
+
| `orders list` | 我的订单 | `--limit`、`--offset`、`--status` |
|
|
378
|
+
| `apps list` | 我授权过的应用(过期时间/最后使用/令牌数) | — |
|
|
379
|
+
| `apps revoke` | 一键吊销某应用的授权(**令牌立即失效**) | `<client_id>` |
|
|
380
|
+
| `skill` | **打印给 AI 端的操作手册**(`SKILL.md` 原文,随包分发) | `--json`(默认**纯文本原文**,不受"非终端自动 JSON"影响) |
|
|
381
|
+
|
|
382
|
+
### 「钱包余额支付」到底发生在哪一步(**别被命令名骗了**)
|
|
383
|
+
|
|
384
|
+
服务端 `placeOrder()` 的行为是:**下单时就先用余额抵扣**,余额够就立刻 `moveWallet(-金额)` +
|
|
385
|
+
履约,直接返回 `paid:true`。也就是说:
|
|
386
|
+
|
|
387
|
+
* **`orders create` 就已经完成了"用钱包支付"**(返回值里的 `paid` 说明结果);
|
|
388
|
+
* `orders pay` / `orders check` 只是对**未支付**的订单做"查单/收敛状态"(如微信侧已付而本地还是 pending → 就地履约);
|
|
389
|
+
* CLI **永远不会**自己扫微信码、也不会发起任何真实扣款;余额不足时**默认直接拒绝下单**,
|
|
390
|
+
只有显式加 `--allow-wechat` 才会创建"待扫码"订单(并只打印链接)。
|
|
391
|
+
* ⚠️ 服务端**没有**"把余额补到一个已存在的 pending 订单上"的 action,所以 CLI 也没有这个命令
|
|
392
|
+
(要做需要服务端新增接口,见 §9)。
|
|
393
|
+
|
|
394
|
+
### JSON 出入参示例
|
|
395
|
+
|
|
396
|
+
成功(`wkgeo wallet`):
|
|
397
|
+
|
|
398
|
+
```json
|
|
399
|
+
{
|
|
400
|
+
"ok": true,
|
|
401
|
+
"command": "wallet",
|
|
402
|
+
"data": {
|
|
403
|
+
"balance_cents": 10000,
|
|
404
|
+
"balance_text": "¥100.00",
|
|
405
|
+
"rows": [
|
|
406
|
+
{ "id": 12, "kind": "pay", "kind_label": "支付", "amount_cents": -100,
|
|
407
|
+
"amount_text": "-¥1.00", "balance_after_cents": 9900, "balance_after_text": "¥99.00",
|
|
408
|
+
"order_id": "95fe2dfc-…", "remark": "加购 空间容量 1GB(余额抵扣)",
|
|
409
|
+
"created_at": "2026-09-30T07:29:07.1+00:00" }
|
|
410
|
+
]
|
|
411
|
+
},
|
|
412
|
+
"meta": { "base": "https://…tcloudbase.com", "client_id": "wkgeo-cli",
|
|
413
|
+
"credentials_file": "C:\\Users\\…\\.wkongai\\credentials.json", "duration_ms": 412 }
|
|
414
|
+
}
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
失败(令牌过期):
|
|
418
|
+
|
|
419
|
+
```json
|
|
420
|
+
{
|
|
421
|
+
"ok": false,
|
|
422
|
+
"command": "wallet",
|
|
423
|
+
"error": {
|
|
424
|
+
"kind": "expired",
|
|
425
|
+
"code": 40101,
|
|
426
|
+
"message": "访问令牌已过期,请重新授权",
|
|
427
|
+
"hint": "运行 `wkgeo login` 重新授权(浏览器里通常还有登录态,点一下同意即可)",
|
|
428
|
+
"retry_after": null
|
|
429
|
+
},
|
|
430
|
+
"meta": { "base": "https://…", "client_id": "wkgeo-cli", "credentials_file": "…", "duration_ms": 88 }
|
|
431
|
+
}
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
---
|
|
435
|
+
|
|
436
|
+
## 5. 错误处理速查(**照着 `hint` 做就行**)
|
|
437
|
+
|
|
438
|
+
| 服务端返回 | `kind` | 退出码 | 应该怎么做 |
|
|
439
|
+
| --- | --- | --- | --- |
|
|
440
|
+
| `code=40101` 令牌已过期 | `expired` | 3 | **重新 `login`**(不要重新输密码登录) |
|
|
441
|
+
| `code=40102` 令牌已被吊销 | `revoked` | 3 | **重新 `login`**;若你没主动吊销过,提醒用户去「我已授权的应用」页看看 |
|
|
442
|
+
| `code=401` + 文案"登录已过期"(旧版) | `unauthenticated` | 3 | 旧版分不清过期/吊销/未登录 → 一律 **重新 `login`**(CLI 会在 hint 里说明这一点) |
|
|
443
|
+
| HTTP 401 `invalid_token`(userinfo 等标准端点) | `revoked` / `expired` / `unauthenticated` | 3 | 同上;CLI 会**按服务端文案**细化(文案说"已被吊销"就归类 revoked) |
|
|
444
|
+
| `code=429` 调用过于频繁 | `rate_limited` | 4 | **等 `retry_after` 秒后重试**。⚠️ **绝对不要**引导用户重新授权(重授权解决不了限流) |
|
|
445
|
+
| `code=403` 权限/scope 不足 | `forbidden` | 1 | 需要该能力就 `login --scope "…"` 重新授权;若是"动钱/改权限"类动作,**只能去网页后台做** |
|
|
446
|
+
| `code=428` 需要二次验证 | `step_up` | 1 | 该动作要求 5 分钟内重新输密码,**OAuth 令牌永远过不了** → 网页后台操作 |
|
|
447
|
+
| `code=400/404` | `bad_request` / `not_found` | 1 | 参数/资源问题,`message` 是服务端原文 |
|
|
448
|
+
| `code≥500` | `server` | 1 | 服务端错误,保留原文;可稍后重试 |
|
|
449
|
+
| 连不上/超时 | `network` | 1 | 检查网络;原始 message 会原样保留(如 `getaddrinfo ENOTFOUND`) |
|
|
450
|
+
| 动作未登记(新功能没发版) | `not_deployed` | 1 | **服务端缺口**,不是命令用错(`apps list/revoke` 已实测可用) |
|
|
451
|
+
|
|
452
|
+
**所有这些错误都不会打印令牌**:错误里只带 `code` / `message` / `hint`。
|
|
453
|
+
|
|
454
|
+
---
|
|
455
|
+
|
|
456
|
+
## 6. 凭据文件与安全
|
|
457
|
+
|
|
458
|
+
| 项 | 事实 |
|
|
459
|
+
| --- | --- |
|
|
460
|
+
| 位置 | `~/.wkongai/credentials.json`(Windows:`%USERPROFILE%\.wkongai\credentials.json`);可用 `WKONGAI_CREDENTIALS` 或 `--credentials` 改 |
|
|
461
|
+
| 权限 | 目录 `0700`、文件 **`0600`**。⚠️ 这在 POSIX 上有效;**Windows 上 NTFS 的 `chmod` 基本是空操作**,实际靠用户目录 ACL 保护 —— 这点如实写明,不假装在 Windows 上也成立(`whoami --verbose` 会显示"无法判断(Windows)") |
|
|
462
|
+
| 内容 | `access_token` / `refresh_token` / `scope` / `expires_at` / 账号标识;写入用**原子写**(先写临时文件再 rename,避免半截 JSON) |
|
|
463
|
+
| 令牌 | 30 天有效(`expires_in=2592000`)。CLI **不会**自动刷新(老板口径:过期就重新授权,30 天已经够长) |
|
|
464
|
+
| 绝不出现明文令牌的地方 | 终端输出(含 `--verbose`,只给**掩码** `AbCdEf…(43 位)`)、错误信息、JSON 输出、日志 |
|
|
465
|
+
| 公共客户端 | 代码里**没有 client_secret**,只用 PKCE(`code_verifier` 只在内存里,换票后就丢) |
|
|
466
|
+
| 回调 | 只监听 `127.0.0.1`;`state` 每次随机并强校验 |
|
|
467
|
+
| 吊销 | `logout` 会调 `POST /api/oauth/revoke`;网页端「我已授权的应用」也能一键吊销(实测可用) |
|
|
468
|
+
|
|
469
|
+
> 安全提醒:凭据文件等同密码 —— **不要**把它复制到别的机器、**不要**提交进仓库、**不要**发给任何第三方。
|
|
470
|
+
|
|
471
|
+
---
|
|
472
|
+
|
|
473
|
+
## 7. 端到端自测(老板要亲自验的路径)
|
|
474
|
+
|
|
475
|
+
### 路径 ① 授权登录(手机验证码那条路)
|
|
476
|
+
|
|
477
|
+
| 步骤 | 在 CLI/终端做什么 | 在浏览器看到什么 | 期望结果 |
|
|
478
|
+
| --- | --- | --- | --- |
|
|
479
|
+
| 1 | `node cli/wkgeo.mjs login`(或 `npx -y wkgeo-cli@latest login`) | — | 终端打印授权网址,并尝试自动打开浏览器 |
|
|
480
|
+
| 2 | — | 若未登录 → 平台的登录页(三个页签:**账密 / 手机号登录 / 邮箱登录**)。选「手机号登录」→ 填手机号 → 点「获取验证码」→ 填收到的验证码 → 登录 | 登录成功,跳到同意页 |
|
|
481
|
+
| 3 | — | 同意页显示应用名「官方 CLI(命令行工具)」和将授予的能力(读取站点/管理站点/账户充值与支付) | 点「同意」 |
|
|
482
|
+
| 4 | — | 跳回 `http://127.0.0.1:53682/callback`,显示 **「✅ 授权成功 / 请回到终端(客户端)继续操作,本页面可以关闭了」** | **不能**是"无法访问此网站"(若有,说明 CLI 已经退出/超时 —— 请保持 `login` 在前台运行) |
|
|
483
|
+
| 5 | 终端显示「✅ 授权成功,已登录:<账号>」 | 关闭页面即可 | 凭据写入 `~/.wkongai/credentials.json` |
|
|
484
|
+
|
|
485
|
+
> ⚠️ 若第 4 步看到浏览器错误页:原因是**那一刻本机没有 CLI 在监听 53682**
|
|
486
|
+
> (比如 `login` 被 Ctrl+C、或超过 5 分钟超时)。重新 `login` 并在点到「同意」前保持它在前台即可。
|
|
487
|
+
|
|
488
|
+
### 路径 ② 充值(生成订单 → **打开自家收银台页面** → 支付 → 余额增加)
|
|
489
|
+
|
|
490
|
+
> ⚠️ **真钱步骤,只能人工做**:CLI 只负责"创建待支付订单 + 打开我们网站的收银台页面",
|
|
491
|
+
> **绝不代替你付款**,自动化测试里也**绝不**跑这条路径。
|
|
492
|
+
|
|
493
|
+
| 步骤 | 命令 / 操作 | 期望结果 |
|
|
494
|
+
| --- | --- | --- |
|
|
495
|
+
| 1 | `wkgeo wallet`(记下当前余额,例如 ¥100.00) | 打印余额与流水 |
|
|
496
|
+
| 2 | `wkgeo wallet recharge --amount-cents 1000 --dry-run` | 只打印"将创建 ¥10.00 订单",**不产生任何订单** |
|
|
497
|
+
| 3 | `wkgeo wallet recharge --amount-cents 1000` | 返回 `order_id` / `code_url` / **`pay_url`**,并**自动在浏览器打开 `pay_url`** —— 也就是我们自己的收银台 `https://pay.wkgeo.com/pay/?order_id=…&token=…` |
|
|
498
|
+
| 4 | 在**收银台页面**用手机微信扫码支付 ¥10.00(人工) | 页面自动检测到支付成功;**支付成功后页面会自动尝试关闭**(关不掉就手动关掉即可);`pay_url` 里的订单号在任何设备/浏览器打开都能看到同一笔单 |
|
|
499
|
+
| 5 | `wkgeo orders check <order_id>` | `paid:true` + **最新余额 = 原余额 + 1000 分** |
|
|
500
|
+
| 6 | `wkgeo wallet txns` | 出现一条 `kind=recharge`、金额 `+¥10.00` 的流水 |
|
|
501
|
+
| 7 | 网页端登录同一账号 → 钱包页 | 余额与第 5 步一致 |
|
|
502
|
+
|
|
503
|
+
> **为什么是"打开我们网站的收银台"而不是"在终端画二维码"**:
|
|
504
|
+
> 老板明确否掉了"本地生成一个二维码页面让别人扫"的做法 —— 充值必须走**我们网上的充值页面**。
|
|
505
|
+
> 终端二维码只在"浏览器确实打不开"时作为**兜底**出现(文案会写明这一点)。
|
|
506
|
+
> `--no-browser` 用于无桌面环境:**完全不尝试打开**,`pay_url` 照常输出。
|
|
507
|
+
> ★ **默认会尝试弹系统浏览器**(2026-10 老板最终拍板走传统路线,人在终端与 AI 场景都一样):
|
|
508
|
+
> 打开成功 → `✅ 已为你打开浏览器,请在那里完成支付(微信扫码)。`;
|
|
509
|
+
> 打开失败 → ⚠️ 一段**含完整收银台地址**的醒目提示(走 stderr,stdout 仍然只有机器可读 JSON);
|
|
510
|
+
> `--json` 里 `open_browser.skipped_reason` 只有 `"no_browser"` 或 `null`(`"non_tty_agent_mode"` 已弃用);
|
|
511
|
+
> `--open-browser` 保留但已与默认行为**等价**(只为显式语义与兼容)。
|
|
512
|
+
>
|
|
513
|
+
> ★ **`pay_url` 上的 `token=`(2026-10-02 新增)**:服务端在**下单那一刻**把"这一单是谁的"
|
|
514
|
+
> 签成一把**只对这一单有效**的令牌(`createRechargeOrder` 返回的 `pay_token`),
|
|
515
|
+
> 收银台凭它就能在**没有网页登录态**的环境里读这一单的支付参数 ——
|
|
516
|
+
> 这正是老板要的"**以那个 token 为基础来进行支付,而不是还要他用用户名那一套**"
|
|
517
|
+
> (典型场景:新开的/无痕的浏览器窗口,那里通常没有登录态)。
|
|
518
|
+
> ⚠️ 它**只**授权"读这一单的支付参数":读不到钱包、读不到订单列表、不能下单、不能取消,
|
|
519
|
+
> 30 分钟(= 订单有效期)后自动失效(失效时收银台会明确说"支付链接已过期")。
|
|
520
|
+
> ⚠️ 老服务端没有 `pay_token` 时 `pay_url` 只有 `order_id`,那条路**仍然要求网页登录**(行为不变)。
|
|
521
|
+
>
|
|
522
|
+
> ★ **支付成功后页面会自己关掉**(老板 2026-10-02):
|
|
523
|
+
> 「我支付也成功了,但**支付成功之后,他也应该关闭**……**支付成功直接关闭就可以了**。那个跳也直接关闭可以了。」
|
|
524
|
+
> 收银台不再跳"我的订单",而是显示一屏完成态并尝试 `window.close()`;
|
|
525
|
+
> ⚠️ 浏览器只允许脚本关闭**由脚本打开的**窗口,所以**关不掉是常态** —— 页面上有「关闭本页」按钮兜底,
|
|
526
|
+
> CLI 的提示语里也会带上"没关就手动关掉即可"。
|
|
527
|
+
|
|
528
|
+
### 路径 ③ 网页端能看到账户信息
|
|
529
|
+
|
|
530
|
+
浏览器打开 `https://user.wkgeo.com/` → 登录同一账号 → 依次核对:
|
|
531
|
+
|
|
532
|
+
- **个人中心/账号信息**:邮箱或手机号、昵称与 CLI 里 `whoami` 显示的一致;
|
|
533
|
+
- **钱包/账单**:余额 = `wkgeo wallet` 的余额;订单列表里能看到 CLI 下的单(同一个 `订单号`);
|
|
534
|
+
- **站点**:`wkgeo sites create` 建的站在"我的站点"里可见,并且**归属是自己**;
|
|
535
|
+
- **我已授权的应用**:能看到「官方 CLI(命令行工具)」,含过期时间/最后使用时间,可一键吊销(实测可用)。
|
|
536
|
+
|
|
537
|
+
### 路径 ④ CLI 端能查到余额 / 流水 / 订单
|
|
538
|
+
|
|
539
|
+
```bash
|
|
540
|
+
wkgeo whoami # 账号标识(不显示令牌)
|
|
541
|
+
wkgeo wallet # 余额 + 最近流水
|
|
542
|
+
wkgeo wallet txns --limit 20
|
|
543
|
+
wkgeo orders list --limit 10
|
|
544
|
+
wkgeo orders get <订单ID>
|
|
545
|
+
wkgeo orders check <订单ID> # 顺带显示最新余额
|
|
546
|
+
wkgeo apps list # 我授权了哪些应用
|
|
547
|
+
wkgeo logout # 退出(删本地凭据 + 服务端吊销)
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
---
|
|
551
|
+
|
|
552
|
+
## 8. 开发者
|
|
553
|
+
|
|
554
|
+
```
|
|
555
|
+
cli/
|
|
556
|
+
├── wkgeo.mjs 可执行入口(bin;薄到只剩一行调用)
|
|
557
|
+
├── package.json name/bin/files/engines/scripts(无 dependencies)
|
|
558
|
+
├── lib/
|
|
559
|
+
│ ├── cli.mjs 入口逻辑(帮助/版本/错误收口/退出码)
|
|
560
|
+
│ ├── runtime.mjs 运行期上下文 + 输出契约(人类/JSON 两套)
|
|
561
|
+
│ ├── args.mjs 手写参数解析 + 命令 spec 校验
|
|
562
|
+
│ ├── config.mjs 默认值/环境变量/命令行覆盖
|
|
563
|
+
│ ├── errors.mjs ★ 错误码 → 提示 的映射(本项目最容易出事的地方)
|
|
564
|
+
│ ├── credentials.mjs 凭据文件(0600/原子写)
|
|
565
|
+
│ ├── api.mjs /api/data + /api/oauth/* 封装
|
|
566
|
+
│ ├── http.mjs 超时/JSON/错误不吞
|
|
567
|
+
│ ├── login.mjs 本地回调授权(起服务、校验 state、换票、成功页)
|
|
568
|
+
│ ├── proxy.mjs ★ 系统代理检测(只读;判定"回调会不会被代理接管")
|
|
569
|
+
│ ├── device.mjs 设备码授权(RFC 8628:申请 / 交给用户 / 轮询三步可拆开)
|
|
570
|
+
│ ├── device-store.mjs ★ `--no-wait` 的待完成状态(0600,支撑 --device-continue)
|
|
571
|
+
│ ├── pkce.mjs verifier/S256/state
|
|
572
|
+
│ ├── qr.mjs 可选终端二维码(有则用、无则略)
|
|
573
|
+
│ ├── format.mjs 金额/掩码/脱敏/表格
|
|
574
|
+
│ └── commands/ 各命令实现(auth/sites/billing/orders/apps)
|
|
575
|
+
├── test/*.test.mjs 离线单测(node:test,零依赖)
|
|
576
|
+
├── scripts/
|
|
577
|
+
│ ├── live-verify.mjs 真环境**可逆**端到端验证(--write 才做写操作)
|
|
578
|
+
│ ├── live-sql.mjs 只读 SQL 排查工具(写语句直接拒绝)
|
|
579
|
+
│ ├── build-zip.mjs 打 zip(自实现 ZIP 写入,零依赖)
|
|
580
|
+
│ └── build-exe.mjs 独立可执行方案(默认只打印,`--sea` 跑离线两步)
|
|
581
|
+
└── docs/设备码授权-服务端最小方案.md
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
```bash
|
|
585
|
+
cd cli
|
|
586
|
+
npm test # 离线单测(273 个用例,约 9 秒)
|
|
587
|
+
node scripts/build-zip.mjs # 打 zip(带安全闸门扫描)
|
|
588
|
+
node scripts/live-verify.mjs # 真环境只读验证(需 TEST_ACCOUNT/TEST_PASSWORD)
|
|
589
|
+
node scripts/live-verify.mjs --write # 追加可逆写操作(建站→下单→支付→退款→回收,零残留)
|
|
590
|
+
node scripts/live-sql.mjs --sql "select …" # 只读查库(只允许 SELECT)
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
设备码这条路的**真环境**端到端验证在云函数测试目录(默认只读,实跑需显式开关):
|
|
594
|
+
|
|
595
|
+
```bash
|
|
596
|
+
node cloudfunctions/_tests/oauth-device-live-e2e.js # 只读预检(零副作用)
|
|
597
|
+
$env:OAUTH_DEVICE_E2E_LIVE='1' # 实跑:造临时用户 + 真跑 CLI
|
|
598
|
+
$env:OAUTH_DEVICE_E2E_CONFIRM='yes-i-accept-creating-a-temp-user'
|
|
599
|
+
node cloudfunctions/_tests/oauth-device-live-e2e.js # 结束按 sub 全量清理并回读复核
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
环境变量(都有默认值,通常不用设):
|
|
603
|
+
|
|
604
|
+
| 变量 | 默认 | 说明 |
|
|
605
|
+
| --- | --- | --- |
|
|
606
|
+
| `WKONGAI_BASE` | 线上网关 | API 基址 |
|
|
607
|
+
| `WKONGAI_OAUTH_WEB_BASE` | `https://oauth.wkgeo.com` | **授权站**基址(同意页 / 设备码页兜底地址) |
|
|
608
|
+
| `WKONGAI_PAY_WEB_BASE` | `https://pay.wkgeo.com` | **收银台**基址(`pay_url` 的域名) |
|
|
609
|
+
| `WKONGAI_USER_WEB_BASE` | `https://user.wkgeo.com` | **会员站**基址(登录页 / 我的订单等) |
|
|
610
|
+
| `WKONGAI_WEB_BASE` | 未设置 | ⚠️ **总覆盖**:设了它上面三个基址**全部**用它(联调 / 回滚用)。它**不再**指向老站 |
|
|
611
|
+
| `WKONGAI_CLIENT_ID` | `wkgeo-cli` | OAuth 客户端 id |
|
|
612
|
+
| `WKONGAI_SCOPE` | `site:read site:write billing:self` | 申请的能力 |
|
|
613
|
+
| `WKONGAI_CREDENTIALS` | `~/.wkongai/credentials.json` | 凭据文件路径 |
|
|
614
|
+
| `WKONGAI_CALLBACK_PORT` | `53682` | 本地回调端口 |
|
|
615
|
+
| `WKONGAI_LOGIN_TIMEOUT_MS` | `300000` | 等待授权超时(⚠️ 变量名里带 `MS`,所以这里填**毫秒**;命令行 `--timeout` 填的是**秒**) |
|
|
616
|
+
| `WKONGAI_CALLBACK_FIRST_WAIT_MS` | 未设置 | ★ 回调模式的**首轮等待**(毫秒):显式设了它(或命令行 `--callback-first-wait`)这段时间没等到回调就自动改用设备码继续(**本地回调监听会同时保留**);`0` 关闭(等价于 `--callback-first-wait 0`)。⚠️ **未设置时看代理**:检测到系统代理才用默认值 **180000**(180 秒),**没有代理则不启用**(这是 2026-10-03 修掉的"没开 VPN 也被切成设备码") |
|
|
617
|
+
| `WKONGAI_AUTO_DEVICE` | `1`(开) | 检测到系统代理时是否自动改用设备码;设 `0`/`false`/`no`/`off` 关闭(等价于 `--no-auto-device`,会打印"代理可能吃掉回调"的警告) |
|
|
618
|
+
| `NO_COLOR` | — | 设了就不上色 |
|
|
619
|
+
|
|
620
|
+
---
|
|
621
|
+
|
|
622
|
+
## 9. 已知缺口 / 未验证项(**不假装验证过**)
|
|
623
|
+
|
|
624
|
+
| # | 项 | 现状 | 谁来处理 |
|
|
625
|
+
| --- | --- | --- | --- |
|
|
626
|
+
| 1 | **设备码端点** | ✅ **已上线并端到端真跑通过**(2026-10-02:metadata 声明 + 申请/确认/换票/重放全绿,真 CLI 子进程 `stage:"done"`)。协议细节见 `docs/设备码授权-服务端最小方案.md`(该文档已更新为"已实现") | 已完成;回归用 `_tests/oauth-device-live-e2e.js` |
|
|
627
|
+
| 1b | **代理自动降级 / 首轮等待自愈** | ✅ 已实现(**检测到代理即改设备码**,不再相信绕过列表 + 整段超时/端口失败自动改道);2026-10-02 **真机翻车已复现并加固**(老板机器:绕过列表写了 `127.*` 却仍被代理接管,证据 `curl -x …7890 http://127.0.0.1:1/x` → 502)。★ 2026-10-03 **真机反馈已修**:① 首轮等待自愈**只在检测到代理时启用**(无代理不再误切设备码)、默认 180 秒、显式 `--callback-first-wait` 永远优先;② 切设备码时**保留本地回调监听**,两条路并存先到先得(旧版关掉监听 ⇒ 用户之后点【同意授权】得到 ERR_CONNECTION_REFUSED) | 已完成(单测 ①~⑤ 含鉴别力自证);建议老板在**没开代理**的机器上跑一次 `wkgeo login` 验收"不再自动变成设备码" |
|
|
628
|
+
| 1c | **设备码页"没有完成态/不自动关"** | ⚠️ 老板 2026-10-02 反馈:点完设备码页的【同意授权】后页面**不给明确的完成态、也不自动关**,用户不知道要不要继续等。**并行改动**已在 `admin-web/src/pages/device/index.tsx` 补上终态卡片(`✅ 授权成功` + "本页面可以关闭了" + 返回首页)。本轮 CLI 侧**未改前端** | 见下面 §9.1 的补充建议(可选) |
|
|
629
|
+
| 2 | **错误码 40101/40102** | `/api/data` **已返回 40102**(实测"访问令牌已被吊销,请重新授权");但 `userinfo` 仍是标准 `401 invalid_token`(靠文案区分)。CLI 两条路都兼容 | 服务端(可选,统一到 40101/40102) |
|
|
630
|
+
| 3 | **`listMyOAuthAuthorizations.token_count` 虚高** | 该 SQL 用 `count(*)` 统计 `left join lateral unnest(t.scopes)` 后的行数 → **数的是 scope 个数,不是令牌数**(实测:1 把令牌、3 个 scope → 显示"3 把")。正确写法 `count(distinct t.access_token_hash)` | **主线程/服务端** |
|
|
631
|
+
| 4 | **`apps revoke` 会连带清掉同意记忆** | 服务端设计如此(避免"取消授权"变成假动作)。副作用:下次 `login` 会重新弹同意页 | 已知,接受 |
|
|
632
|
+
| 5 | **没有"用余额补付一个 pending 订单"的接口** | `placeOrder` 的余额抵扣只在建单那一刻发生;CLI 因此没有对应命令 | 需服务端新增 action(可选) |
|
|
633
|
+
| 6 | **普通用户没有独立的"我的流水分页"接口** | 只有 `getMyWallet` 自带 `limit`(无 offset/时间筛选);`adminListWalletTxns` 是管理员接口(`billing:manage`) | 服务端(可选) |
|
|
634
|
+
| 7 | **`owner/admin` 账号建站必须传 `customer_id`** | 既有实现:`data_scope != 'self'` 且无 `customer_id` → 400「无法确定所属客户」。CLI 会把这条错误翻译成"加 `--customer`" | 产品口径待定 |
|
|
635
|
+
| 8 | **Windows 上 0600 权限无法真正生效** | NTFS 的 chmod 是空操作,靠用户目录 ACL;`whoami --verbose` 会如实说"无法判断(Windows)" | 已知 |
|
|
636
|
+
| 9 | **真实微信支付全流程** | 按红线**没有**跑真钱:`wallet recharge` 只用离线桩验证了请求体,真钱路径由老板手动测(§7 路径②) | 人工 |
|
|
637
|
+
| 10 | **exe 产物** | 只验证了 `--sea` 能生成 blob + 复制 node 壳(93MB),**没有**真装 postject 注入、也没在无 Node 机器上跑 | 需要时再做 |
|
|
638
|
+
| 11 | **`login` 的浏览器交互本身** | 真环境验证脚本扮演了"浏览器/同意页"的角色(调 authorize/consent),**HTML 页面本身的点击**没有被自动化覆盖(同意页由前端/另一个改动负责) | 人工(§7 路径①) |
|
|
639
|
+
|
|
640
|
+
### 9.1 ⚠️ 设备码页"没有完成态 / 不自动关"(**建议改法,本轮 CLI 侧未改动**)
|
|
641
|
+
|
|
642
|
+
**现象**(老板 2026-10-02 反馈,原话:"他没有自动关…也没有说授权成功"):
|
|
643
|
+
用户在**设备码页**点了【同意授权】之后,页面**既不自动关闭、也不给明确的"授权成功"终态**,
|
|
644
|
+
于是用户不知道自己到底成了没有 —— 而 CLI 那边其实已经可以拿令牌了(可能已经在轮询中成功)。
|
|
645
|
+
|
|
646
|
+
**为什么 CLI 侧不能替它解决**:设备码流程里 CLI 完全不掌控那个页面(它由 `admin-web` 渲染),
|
|
647
|
+
CLI 只能在终端/事件流里说"已在等待你确认"。**页面本身**必须给出终态。
|
|
648
|
+
|
|
649
|
+
**现状与补充建议(本轮 CLI 侧只写建议,**没有**动前端;并行改动已加终态卡片)**:
|
|
650
|
+
|
|
651
|
+
1. ✅(并行改动已做)**确认成功后给明确终态**:「✅ 授权成功 / 请回到客户端继续操作,本页面可以关闭了」——
|
|
652
|
+
与回调模式的 `page.mjs` 成功页保持同一口径(**先确认成功再宣告**,失败要显示失败)。
|
|
653
|
+
2. (建议,可选)**给一个显式的"关闭本页"按钮**:`window.close()` 只对脚本打开的窗口有效,
|
|
654
|
+
所以按钮是兜底,自动关只作增强(`setTimeout` + `try/catch`,失败时按钮仍可用)。
|
|
655
|
+
3. (建议,可选)**别把"轮询中"当成终态**:设备码页面只能知道"用户确认了",
|
|
656
|
+
最好补一句「如果客户端还在等待,请等它显示『授权成功』」,避免用户提前关掉终端。
|
|
657
|
+
4. 🔴 **不要在这一页回显任何令牌 / device_code**(沿用 `lib/page.mjs` 的安全底线)。
|
|
658
|
+
|
|
659
|
+
**验证建议**:真机跑一次 `wkgeo login --device`(或 `--no-wait` + `--device-continue`),
|
|
660
|
+
确认点完【同意授权】后页面**明确说成功**,并且用户可以一键关闭。
|
|
661
|
+
|
|
662
|
+
---
|
|
663
|
+
|
|
664
|
+
## 10. 常见问题
|
|
665
|
+
|
|
666
|
+
**Q:`npx` 每次都要下载吗?**
|
|
667
|
+
A:`npx -y wkgeo-cli@latest` 会检查并复用 npm 缓存;要长期使用建议 `npm i -g wkgeo-cli`。
|
|
668
|
+
|
|
669
|
+
**Q:令牌提示过期/被吊销怎么办?**
|
|
670
|
+
A:`wkgeo login` 再来一次即可 —— 浏览器里通常还有登录态,点一下「同意」就完事。**不需要**重新输密码注册。
|
|
671
|
+
|
|
672
|
+
**Q:提示"调用过于频繁"要不要重新登录?**
|
|
673
|
+
A:**不要**。那是限流(每令牌 60/分、写操作 20/分),按 `retry_after` 等几秒就好。
|
|
674
|
+
|
|
675
|
+
**Q:为什么下单就直接扣钱了?**
|
|
676
|
+
A:服务端设计如此:下单时余额够就直接扣款并履约(返回 `paid:true`)。这正是"钱包余额支付"最顺的形态;
|
|
677
|
+
余额不够时 CLI 会**默认拒绝下单**,避免产生一张要扫码的单。
|
|
678
|
+
|
|
679
|
+
**Q:`sites create` 报「无法确定所属客户」?**
|
|
680
|
+
A:这是账号属性问题(`owner/admin` 这类 `data_scope != 'self'` 的账号必须显式指定客户):
|
|
681
|
+
加 `--customer <客户ID>` 重试(客户 ID 在网页后台的客户列表里)。
|