claude360 0.1.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 +355 -0
- package/bin/claude360.js +10 -0
- package/install/install.ps1 +57 -0
- package/install/install.sh +57 -0
- package/install/verification-matrix.md +18 -0
- package/package.json +24 -0
- package/src/api-client.js +80 -0
- package/src/auth.js +82 -0
- package/src/config-store.js +69 -0
- package/src/diagnostics.js +206 -0
- package/src/group-manager.js +36 -0
- package/src/index.js +160 -0
- package/src/menu.js +72 -0
- package/src/platform.js +9 -0
- package/src/token-manager.js +99 -0
- package/src/tool-installer.js +71 -0
- package/src/tool-launcher.js +244 -0
- package/src/topup.js +109 -0
package/README.md
ADDED
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
# Claude360 CLI
|
|
2
|
+
|
|
3
|
+
面向 [Claude360](https://claude360.xyz) 已注册用户的跨平台命令行工具,让不熟悉终端配置的用户也能一行命令完成 Claude Code / Codex 的安装、授权、Key 选择、余额查询、微信扫码充值与日常启动。
|
|
4
|
+
|
|
5
|
+
- 不要求用户在终端输入站点账号密码,授权全程在浏览器完成。
|
|
6
|
+
- 不修改 shell profile / PowerShell profile / 全局环境变量。
|
|
7
|
+
- 分组倍率、充值金额、API 凭证生命周期均以 NewAPI 后端为唯一来源,CLI 不内置任何业务口径。
|
|
8
|
+
|
|
9
|
+
## 目录
|
|
10
|
+
|
|
11
|
+
- [功能特性](#功能特性)
|
|
12
|
+
- [系统要求](#系统要求)
|
|
13
|
+
- [快速开始](#快速开始)
|
|
14
|
+
- [首次配置流程](#首次配置流程)
|
|
15
|
+
- [日常使用](#日常使用)
|
|
16
|
+
- [本地配置](#本地配置)
|
|
17
|
+
- [后端接口](#后端接口)
|
|
18
|
+
- [Claude Code 与 Codex 集成](#claude-code-与-codex-集成)
|
|
19
|
+
- [诊断与故障排查](#诊断与故障排查)
|
|
20
|
+
- [开发与测试](#开发与测试)
|
|
21
|
+
- [项目结构](#项目结构)
|
|
22
|
+
- [安全模型](#安全模型)
|
|
23
|
+
- [已知风险与路线图](#已知风险与路线图)
|
|
24
|
+
- [许可证](#许可证)
|
|
25
|
+
|
|
26
|
+
## 功能特性
|
|
27
|
+
|
|
28
|
+
| 功能 | 说明 |
|
|
29
|
+
| --- | --- |
|
|
30
|
+
| 一行命令安装 | Windows PowerShell / macOS / Linux bash 一行命令拉起 bootstrap,自动检测 Node/npm 与全局 npm 权限 |
|
|
31
|
+
| 浏览器设备授权 | 通过 `/api/cli/auth/*` 设备码流程换取受限 CLI 凭证,不接触账号密码 |
|
|
32
|
+
| API Key 选择/创建 | 已有 Key 列表展示并选择;无 Key 时按后端分组倍率向导创建 |
|
|
33
|
+
| 余额与分组展示 | 日常主菜单展示账户余额、当前 Key、当前分组与倍率(来自后端) |
|
|
34
|
+
| 微信 Native 扫码充值 | 终端内渲染二维码;金额选项与最低充值额由后端返回,CLI 不内置 |
|
|
35
|
+
| 启动 Claude Code | 自动注入 `ANTHROPIC_BASE_URL` 和 `ANTHROPIC_AUTH_TOKEN`,以子进程启动 |
|
|
36
|
+
| 启动 Codex | 写入隔离的 `[model_providers.claude360]` / `[profiles.claude360]` 配置,并以 `codex --profile claude360` 启动 |
|
|
37
|
+
| 工具更新 | 菜单内更新 `claude360` 自身、Claude Code、Codex,全部需要二次确认 |
|
|
38
|
+
| 诊断报告 | OS/Node/npm/全局 npm 权限/工具版本/连通性/授权状态/余额/Key/支付端点一站式检查 |
|
|
39
|
+
|
|
40
|
+
## 系统要求
|
|
41
|
+
|
|
42
|
+
- Node.js **18+** 与 npm(bootstrap 脚本仅检测,不自动安装系统运行时)。
|
|
43
|
+
- 网络可达 `https://claude360.xyz`。
|
|
44
|
+
- macOS / Linux:当前用户对其 `~/.claude360/` 拥有写权限。
|
|
45
|
+
- Windows:当前用户对 `%USERPROFILE%\.claude360\` 拥有写权限。
|
|
46
|
+
- 任何包管理或全局 npm 操作执行前都需要在 CLI 内显式确认。
|
|
47
|
+
|
|
48
|
+
## 快速开始
|
|
49
|
+
|
|
50
|
+
### macOS / Linux
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
curl -fsSL https://claude360.xyz/install.sh | bash
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Windows PowerShell
|
|
57
|
+
|
|
58
|
+
```powershell
|
|
59
|
+
irm https://claude360.xyz/install.ps1 | iex
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Bootstrap 脚本职责:
|
|
63
|
+
|
|
64
|
+
1. 识别 OS / 架构。
|
|
65
|
+
2. 检查 Node.js 与 npm(缺失或 < 18 直接退出并给出修复建议)。
|
|
66
|
+
3. 展示影响范围并要求确认。
|
|
67
|
+
4. 执行 `npm install -g claude360@latest`。
|
|
68
|
+
5. 执行 `claude360 setup` 进入首次配置向导。
|
|
69
|
+
|
|
70
|
+
### 手动安装
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
npm install -g claude360@latest
|
|
74
|
+
claude360
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## 首次配置流程
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
┌───────────────────┐
|
|
81
|
+
│ 启动 claude360 │
|
|
82
|
+
└─────────┬─────────┘
|
|
83
|
+
↓
|
|
84
|
+
读取 ~/.claude360/config.json
|
|
85
|
+
↓
|
|
86
|
+
┌─ 无 cliToken ─┐ ┌─ 有 cliToken ─┐
|
|
87
|
+
│ 设备码授权 │ │ 跳过授权步骤 │
|
|
88
|
+
│ → 浏览器 │ └──────┬───────┘
|
|
89
|
+
│ → 轮询批准 │ │
|
|
90
|
+
└──────┬───────┘ │
|
|
91
|
+
↓ ↓
|
|
92
|
+
读取或创建 API Key(按分组倍率向导)
|
|
93
|
+
↓
|
|
94
|
+
写入本地配置(mode 0600)
|
|
95
|
+
↓
|
|
96
|
+
进入主菜单
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
关键约束:
|
|
100
|
+
|
|
101
|
+
- 授权码短期有效;CLI 仅在浏览器无法自动打开时把 `verification_url` 与 `user_code` 输出到终端。
|
|
102
|
+
- 多个 Key 时展示:名称 / 脱敏 Key / 分组 / 状态 / 剩余额度(或无限额度)/ 过期时间。
|
|
103
|
+
- 无 Key 时,CLI 通过 `/api/cli/groups` 拉取分组并按 `display_name(倍率 1.0x)` 风格展示,由用户选择后调用 `POST /api/cli/tokens` 创建。
|
|
104
|
+
|
|
105
|
+
## 日常使用
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
Claude360
|
|
109
|
+
1. 查看余额
|
|
110
|
+
2. 微信扫码充值
|
|
111
|
+
3. 启动 Claude Code
|
|
112
|
+
4. 启动 Codex
|
|
113
|
+
5. 安装或更新工具
|
|
114
|
+
6. 切换 API Key / 分组
|
|
115
|
+
7. 诊断
|
|
116
|
+
8. 退出
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
- **查看余额**:调用 `GET /api/cli/me`,展示账号、余额、已用、当前 Key 名称与分组。
|
|
120
|
+
- **微信扫码充值**:先取 `/api/cli/topup/options`,按后端校验通过的金额或最低额提交,再用 `qrcode-terminal` 在终端渲染 `code_url`,渲染失败时降级为纯文本 URL。轮询 `/api/cli/topup/:order_id`,支付完成自动刷新余额。
|
|
121
|
+
- **启动 Claude Code**:调用 `claude` 子进程,并通过 `ANTHROPIC_BASE_URL` + `ANTHROPIC_AUTH_TOKEN` 注入。
|
|
122
|
+
- **启动 Codex**:写入 `~/.codex/config.toml` 中 `[model_providers.claude360]` 与 `[profiles.claude360]`,发现既有冲突字段会先要求用户确认再覆盖,然后 `codex --profile claude360`,并通过 `CLAUDE360_API_KEY` 注入。
|
|
123
|
+
- **安装或更新工具**:三选一菜单(仅 Claude Code / 仅 Codex / 两者),每步都需要确认。
|
|
124
|
+
- **切换 API Key / 分组**:重新进入 Token 向导。
|
|
125
|
+
- **诊断**:见下方“诊断与故障排查”。
|
|
126
|
+
|
|
127
|
+
## 本地配置
|
|
128
|
+
|
|
129
|
+
| 平台 | 路径 |
|
|
130
|
+
| --- | --- |
|
|
131
|
+
| macOS / Linux | `~/.claude360/config.json` (权限 `0600`) |
|
|
132
|
+
| Windows | `%USERPROFILE%\.claude360\config.json` |
|
|
133
|
+
|
|
134
|
+
示例:
|
|
135
|
+
|
|
136
|
+
```json
|
|
137
|
+
{
|
|
138
|
+
"baseUrl": "https://claude360.xyz",
|
|
139
|
+
"cliToken": "cli_xxx",
|
|
140
|
+
"apiKey": "sk-xxx",
|
|
141
|
+
"selectedTokenId": 456,
|
|
142
|
+
"tokenName": "daily-key",
|
|
143
|
+
"group": "default"
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
注意事项:
|
|
148
|
+
|
|
149
|
+
- 第一版接受**本地明文**保存 `cliToken` 与 `apiKey`,CLI 仅收紧文件权限,不接入系统密钥链。
|
|
150
|
+
- CLI 日志与诊断输出会脱敏 API Key。
|
|
151
|
+
- 若需要重置,删除 `~/.claude360/config.json` 即可触发完整重新授权。
|
|
152
|
+
|
|
153
|
+
## 后端接口
|
|
154
|
+
|
|
155
|
+
CLI 仅依赖一组隔离的 `/api/cli` 接口,由 `newapi/controller/cli.go` 提供:
|
|
156
|
+
|
|
157
|
+
| 方法 | 路径 | 说明 |
|
|
158
|
+
| --- | --- | --- |
|
|
159
|
+
| POST | `/api/cli/auth/start` | 创建设备码授权会话 |
|
|
160
|
+
| POST | `/api/cli/auth/confirm` | 浏览器端登录用户确认授权(站点 Web Session) |
|
|
161
|
+
| POST | `/api/cli/auth/poll` | CLI 轮询授权结果,状态:`pending` / `approved` / `denied` / `expired` / `consumed` |
|
|
162
|
+
| GET | `/api/cli/me` | 返回当前用户、分组、余额、已用额度、展示单位 |
|
|
163
|
+
| GET | `/api/cli/tokens` | 当前用户 Key 列表(脱敏) |
|
|
164
|
+
| POST | `/api/cli/tokens/:id/reveal` | 校验归属并返回明文 Key,被限频且禁用缓存 |
|
|
165
|
+
| POST | `/api/cli/tokens` | 按分组创建新 Key(受 `operation_setting.GetMaxUserTokens()` 约束) |
|
|
166
|
+
| GET | `/api/cli/groups` | 用户可用分组(含 `display_name`、`ratio`、`description`) |
|
|
167
|
+
| GET | `/api/cli/topup/options` | 后端配置的充值金额选项与最低额,包含 WeChat 启用状态 |
|
|
168
|
+
| POST | `/api/cli/topup/wechat` | 创建微信 Native 订单,返回 `code_url`、`order_id`、`pay_money` |
|
|
169
|
+
| GET | `/api/cli/topup/:order_id` | 查询订单状态(仅订单所属用户) |
|
|
170
|
+
|
|
171
|
+
实现要点:
|
|
172
|
+
|
|
173
|
+
- 设备码、user_code、CLI 凭证均有过期机制;CLI 凭证可后台吊销。
|
|
174
|
+
- 已批准会话被消费后保持 `consumed` 状态,CLI 凭证有独立有效期,设备码窗口过期不影响已发出的凭证。
|
|
175
|
+
- Key reveal 强制鉴权 + 限频;后端日志不记录明文 Key。
|
|
176
|
+
- 充值订单仅订单所属用户可查询。
|
|
177
|
+
- 详细字段格式见 `prd/claude360-cli-prd.md` 第 7 章。
|
|
178
|
+
|
|
179
|
+
## Claude Code 与 Codex 集成
|
|
180
|
+
|
|
181
|
+
### Claude Code
|
|
182
|
+
|
|
183
|
+
启动时注入:
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
ANTHROPIC_BASE_URL=https://claude360.xyz
|
|
187
|
+
ANTHROPIC_AUTH_TOKEN=<selected_api_key>
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
依据 [Claude Code LLM gateway 官方文档](https://docs.anthropic.com/en/docs/claude-code/llm-gateway),`ANTHROPIC_BASE_URL` 指向网关时,`ANTHROPIC_AUTH_TOKEN` 会作为 bearer token 发送到 `/v1/messages` 与 `/v1/messages/count_tokens`。
|
|
191
|
+
|
|
192
|
+
### Codex
|
|
193
|
+
|
|
194
|
+
写入 `~/.codex/config.toml`(仅修改 `model_providers.claude360` 与 `profiles.claude360` 两个表,不动其他 provider):
|
|
195
|
+
|
|
196
|
+
```toml
|
|
197
|
+
[model_providers.claude360]
|
|
198
|
+
name = "Claude360"
|
|
199
|
+
base_url = "https://claude360.xyz/v1"
|
|
200
|
+
env_key = "CLAUDE360_API_KEY"
|
|
201
|
+
wire_api = "responses"
|
|
202
|
+
|
|
203
|
+
[profiles.claude360]
|
|
204
|
+
model_provider = "claude360"
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
启动命令:
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
codex --profile claude360
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
并通过子进程环境变量 `CLAUDE360_API_KEY=<selected_api_key>` 注入凭证。
|
|
214
|
+
|
|
215
|
+
> **风险说明**:当前 Codex 自定义 provider 使用 Responses 协议(`wire_api = "responses"`)。Claude360 / NewAPI 必须支持 `/v1/responses` 或在网关层把 Responses 请求适配到现有模型能力,否则 Codex 调用会失败。详见 `prd/claude360-cli-verification.md` 中“Release Risks”。
|
|
216
|
+
|
|
217
|
+
## 诊断与故障排查
|
|
218
|
+
|
|
219
|
+
执行主菜单中“诊断”项,CLI 会汇报以下检查项:
|
|
220
|
+
|
|
221
|
+
- 操作系统与架构。
|
|
222
|
+
- 终端二维码渲染能力。
|
|
223
|
+
- Node.js / npm 版本。
|
|
224
|
+
- 全局 npm 安装权限。
|
|
225
|
+
- Claude Code / Codex 是否安装及版本。
|
|
226
|
+
- `https://claude360.xyz` API 连通性。
|
|
227
|
+
- CLI 授权状态、账户余额、Key 列表拉取状态。
|
|
228
|
+
- 微信支付端点可用性。
|
|
229
|
+
|
|
230
|
+
常见异常及处理建议:
|
|
231
|
+
|
|
232
|
+
| 现象 | 处理 |
|
|
233
|
+
| --- | --- |
|
|
234
|
+
| 浏览器未自动打开 | 复制终端打印的 `verification_url` 与 `user_code` 到任意浏览器 |
|
|
235
|
+
| 授权超时 / 拒绝 / 过期 | 重新执行 `claude360`,会自动发起新的授权会话 |
|
|
236
|
+
| 无可用 Key | 进入创建 Key 流程,按分组倍率提示完成 |
|
|
237
|
+
| 二维码无法渲染 | CLI 自动降级为打印 `code_url` 文本 |
|
|
238
|
+
| 启动 Claude Code 失败 | 检查 `claude` 是否在 PATH 中(`npm install -g @anthropic-ai/claude-code`) |
|
|
239
|
+
| 启动 Codex 失败 | 检查 `codex` 是否在 PATH 中(`npm i -g @openai/codex`),并复核 `~/.codex/config.toml` |
|
|
240
|
+
| Node 版本过低 | 升级到 Node.js 18 LTS 后重新运行 bootstrap |
|
|
241
|
+
| 全局 npm 权限不足 | 修复 npm 全局目录权限,或使用 `nvm` 等用户级 Node 管理器 |
|
|
242
|
+
|
|
243
|
+
## 开发与测试
|
|
244
|
+
|
|
245
|
+
项目使用 Node 内置 test runner,避免引入额外测试框架。
|
|
246
|
+
|
|
247
|
+
```bash
|
|
248
|
+
# 进入 CLI 包
|
|
249
|
+
cd claude360-cli
|
|
250
|
+
|
|
251
|
+
# 安装依赖(仅 qrcode-terminal)
|
|
252
|
+
npm install
|
|
253
|
+
|
|
254
|
+
# 跑全部测试
|
|
255
|
+
npm test
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
期望结果:全部测试通过。
|
|
259
|
+
|
|
260
|
+
后端 Go 测试(在仓库根目录执行):
|
|
261
|
+
|
|
262
|
+
```bash
|
|
263
|
+
go test ./model ./middleware ./controller -run 'TestCli|TestGetUserTopUpByTradeNo' -count=1
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### 调试入口
|
|
267
|
+
|
|
268
|
+
```bash
|
|
269
|
+
# 直接以源码模式运行
|
|
270
|
+
node ./bin/claude360.js
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
修改源码后无需重启全局安装,可通过 `npm link` 把当前 CLI 包链到全局:
|
|
274
|
+
|
|
275
|
+
```bash
|
|
276
|
+
cd claude360-cli && npm link
|
|
277
|
+
claude360
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
调试时可注入自定义提示器、API client、浏览器打开函数等:`src/index.js` 中 `runCli({...})` 全部依赖均可注入,便于自动化测试和插桩。
|
|
281
|
+
|
|
282
|
+
## 项目结构
|
|
283
|
+
|
|
284
|
+
```
|
|
285
|
+
claude360-cli/
|
|
286
|
+
├── bin/
|
|
287
|
+
│ └── claude360.js # CLI 入口
|
|
288
|
+
├── install/
|
|
289
|
+
│ ├── install.sh # macOS / Linux bootstrap
|
|
290
|
+
│ ├── install.ps1 # Windows PowerShell bootstrap
|
|
291
|
+
│ └── verification-matrix.md
|
|
292
|
+
├── scripts/ # 测试与本地脚本
|
|
293
|
+
├── src/
|
|
294
|
+
│ ├── api-client.js # HTTP + NewAPI 信封解析
|
|
295
|
+
│ ├── auth.js # 设备码授权流程
|
|
296
|
+
│ ├── config-store.js # 本地配置读写(0600)
|
|
297
|
+
│ ├── diagnostics.js # 诊断报告
|
|
298
|
+
│ ├── group-manager.js # 分组与倍率展示(无本地映射)
|
|
299
|
+
│ ├── index.js # 顶层编排:授权 → Key → 主菜单
|
|
300
|
+
│ ├── menu.js # 主菜单
|
|
301
|
+
│ ├── platform.js # 平台路径与能力探测
|
|
302
|
+
│ ├── token-manager.js # Key 选择 / 创建 / reveal
|
|
303
|
+
│ ├── tool-installer.js # 全局 npm 安装 / 更新(需确认)
|
|
304
|
+
│ ├── tool-launcher.js # Claude Code / Codex 子进程启动与配置写入
|
|
305
|
+
│ └── topup.js # 微信 Native 充值流程
|
|
306
|
+
├── test/ # 11 个测试文件
|
|
307
|
+
├── package.json
|
|
308
|
+
└── README.md
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
后端配套文件(位于仓库根目录):
|
|
312
|
+
|
|
313
|
+
```
|
|
314
|
+
controller/cli.go # /api/cli/* handler
|
|
315
|
+
controller/cli_test.go
|
|
316
|
+
middleware/cli_auth.go # CLI bearer 中间件
|
|
317
|
+
middleware/cli_auth_test.go
|
|
318
|
+
model/cli_auth.go # CliAuthSession 模型
|
|
319
|
+
model/cli_auth_test.go
|
|
320
|
+
model/topup.go # 新增 GetUserTopUpByTradeNo
|
|
321
|
+
model/main.go # 增加 CliAuthSession AutoMigrate
|
|
322
|
+
router/api-router.go # 注册 /api/cli/* 路由
|
|
323
|
+
web/src/pages/CliAuthorize/ # 浏览器授权页
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
## 安全模型
|
|
327
|
+
|
|
328
|
+
- CLI 不接收、不保存站点账号密码。
|
|
329
|
+
- 设备码、user_code、CLI 凭证均短期有效;CLI 凭证可后台吊销。
|
|
330
|
+
- 授权轮询限频(`middleware.CriticalRateLimit()`)。
|
|
331
|
+
- `POST /api/cli/tokens/:id/reveal` 强制鉴权 + 限频 + 禁用缓存。
|
|
332
|
+
- 后端日志不记录明文 Key;CLI 日志不打印完整 Key。
|
|
333
|
+
- 充值订单只能被订单所属用户查询。
|
|
334
|
+
- 本地配置文件 macOS/Linux 写入 mode `0600`;Windows 写入当前用户目录。
|
|
335
|
+
- 任何全局 npm、系统运行时安装都需用户在终端显式确认。
|
|
336
|
+
- CLI 不修改 shell profile / PowerShell profile / 全局环境变量。
|
|
337
|
+
|
|
338
|
+
## 已知风险与路线图
|
|
339
|
+
|
|
340
|
+
| 项 | 状态 |
|
|
341
|
+
| --- | --- |
|
|
342
|
+
| 自动化测试(Backend Go + CLI Node) | ✅ 全部通过(CLI Node + Go 后端 PASS) |
|
|
343
|
+
| 真实站点手动端到端验证 | ⚠️ 待执行(见 `prd/claude360-cli-verification.md` Task 13 Step 3) |
|
|
344
|
+
| Codex `wire_api = "responses"` 端到端兼容 | ⚠️ 需 NewAPI/Claude360 网关确认 `/v1/responses` 兼容性 |
|
|
345
|
+
| 微信 Native 真实商户回调 | ⚠️ 需对接生产商户配置后验证 |
|
|
346
|
+
| 系统密钥链保存 | ❌ 第一版不做 |
|
|
347
|
+
| 新用户注册闭环 | ❌ 第一版不做 |
|
|
348
|
+
| 多支付方式聚合 | ❌ 第一版不做 |
|
|
349
|
+
| 多域名 / 私有部署切换 | ❌ 第一版不做 |
|
|
350
|
+
|
|
351
|
+
完整需求边界见 `prd/claude360-cli-prd.md`。
|
|
352
|
+
|
|
353
|
+
## 许可证
|
|
354
|
+
|
|
355
|
+
随仓库根目录 `LICENSE` 发布,未单独声明。
|
package/bin/claude360.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Website one-line command:
|
|
2
|
+
# irm https://claude360.xyz/install.ps1 | iex
|
|
3
|
+
|
|
4
|
+
$ErrorActionPreference = "Stop"
|
|
5
|
+
|
|
6
|
+
function Confirm-Action {
|
|
7
|
+
param([string]$Message)
|
|
8
|
+
Write-Host $Message
|
|
9
|
+
$answer = Read-Host "输入 yes 确认继续"
|
|
10
|
+
return @("yes", "y", "Y", "YES", "是", "确认", "继续") -contains $answer
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function Invoke-LoggedCommand {
|
|
14
|
+
param(
|
|
15
|
+
[string]$Command,
|
|
16
|
+
[string[]]$Arguments
|
|
17
|
+
)
|
|
18
|
+
Write-Host ("+ " + $Command + " " + ($Arguments -join " "))
|
|
19
|
+
& $Command @Arguments
|
|
20
|
+
if ($LASTEXITCODE -ne 0) {
|
|
21
|
+
throw "$Command exited with code $LASTEXITCODE"
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
$os = [System.Runtime.InteropServices.RuntimeInformation]::OSDescription
|
|
26
|
+
$arch = [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture
|
|
27
|
+
Write-Host "Claude360 bootstrap"
|
|
28
|
+
Write-Host "OS: $os"
|
|
29
|
+
Write-Host "Arch: $arch"
|
|
30
|
+
|
|
31
|
+
$node = Get-Command "node" -ErrorAction SilentlyContinue
|
|
32
|
+
$npm = Get-Command "npm" -ErrorAction SilentlyContinue
|
|
33
|
+
if (-not $node -or -not $npm) {
|
|
34
|
+
Write-Host "未检测到 Node.js/npm。Claude360 CLI 需要 Node.js 18+ 和 npm。"
|
|
35
|
+
Write-Host "请先通过 winget、Node.js 官网安装 Node.js LTS 后重新运行本脚本。"
|
|
36
|
+
if (-not (Confirm-Action "本脚本不会自动安装系统运行时。确认已了解 Node.js/npm 前置要求?")) {
|
|
37
|
+
exit 1
|
|
38
|
+
}
|
|
39
|
+
exit 1
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
& "node" @("--version")
|
|
43
|
+
& "npm" @("--version")
|
|
44
|
+
$nodeVersion = (& "node" @("--version")).TrimStart("v")
|
|
45
|
+
$nodeMajor = [int]($nodeVersion.Split(".")[0])
|
|
46
|
+
if ($nodeMajor -lt 18) {
|
|
47
|
+
Write-Host "当前 Node.js 版本低于 18,请升级到 Node.js LTS 后重新运行本脚本。"
|
|
48
|
+
exit 1
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (-not (Confirm-Action "将执行全局 npm 操作:npm install -g claude360@latest`n影响范围:安装或更新 Claude360 CLI 到当前用户 npm 全局目录,并随后启动 claude360 setup 首次配置向导;配置向导可能打开浏览器授权并写入用户目录配置。是否继续?")) {
|
|
52
|
+
Write-Host "已取消安装。"
|
|
53
|
+
exit 0
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
Invoke-LoggedCommand "npm" @("install", "-g", "claude360@latest")
|
|
57
|
+
Invoke-LoggedCommand "claude360" @("setup")
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# Website one-line command:
|
|
5
|
+
# curl -fsSL https://claude360.xyz/install.sh | bash
|
|
6
|
+
|
|
7
|
+
confirm() {
|
|
8
|
+
local message="$1"
|
|
9
|
+
printf "%s\n" "$message"
|
|
10
|
+
printf "输入 yes 确认继续: "
|
|
11
|
+
if [ -r /dev/tty ]; then
|
|
12
|
+
read -r answer </dev/tty
|
|
13
|
+
else
|
|
14
|
+
read -r answer
|
|
15
|
+
fi
|
|
16
|
+
case "${answer}" in
|
|
17
|
+
yes|y|Y|YES|是|确认|继续) return 0 ;;
|
|
18
|
+
*) return 1 ;;
|
|
19
|
+
esac
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
run_cmd() {
|
|
23
|
+
printf "+ %s\n" "$*"
|
|
24
|
+
"$@"
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
OS_NAME="$(uname -s)"
|
|
28
|
+
ARCH_NAME="$(uname -m)"
|
|
29
|
+
printf "Claude360 bootstrap\nOS: %s\nArch: %s\n" "$OS_NAME" "$ARCH_NAME"
|
|
30
|
+
|
|
31
|
+
if ! command -v node >/dev/null 2>&1 || ! command -v npm >/dev/null 2>&1; then
|
|
32
|
+
cat <<'EOF'
|
|
33
|
+
未检测到 Node.js/npm。Claude360 CLI 需要 Node.js 18+ 和 npm。
|
|
34
|
+
请先通过系统包管理器或 Node.js 官网安装 Node.js LTS 后重新运行本脚本。
|
|
35
|
+
EOF
|
|
36
|
+
if ! confirm "本脚本不会自动安装系统运行时。确认已了解 Node.js/npm 前置要求?"; then
|
|
37
|
+
exit 1
|
|
38
|
+
fi
|
|
39
|
+
exit 1
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
node --version
|
|
43
|
+
npm --version
|
|
44
|
+
NODE_MAJOR="$(node --version | sed 's/^v//' | cut -d. -f1)"
|
|
45
|
+
if [ "${NODE_MAJOR}" -lt 18 ]; then
|
|
46
|
+
printf "当前 Node.js 版本低于 18,请升级到 Node.js LTS 后重新运行本脚本。\n"
|
|
47
|
+
exit 1
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
if ! confirm "将执行全局 npm 操作:npm install -g claude360@latest
|
|
51
|
+
影响范围:安装或更新 Claude360 CLI 到当前用户 npm 全局目录,并随后启动 claude360 setup 首次配置向导;配置向导可能打开浏览器授权并写入用户目录配置。是否继续?"; then
|
|
52
|
+
printf "已取消安装。\n"
|
|
53
|
+
exit 0
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
run_cmd npm install -g claude360@latest
|
|
57
|
+
run_cmd claude360 setup
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Claude360 Bootstrap Manual Verification Matrix
|
|
2
|
+
|
|
3
|
+
## One-Line Commands
|
|
4
|
+
|
|
5
|
+
- Windows PowerShell: `irm https://claude360.xyz/install.ps1 | iex`
|
|
6
|
+
- macOS/Linux bash: `curl -fsSL https://claude360.xyz/install.sh | bash`
|
|
7
|
+
|
|
8
|
+
## Required Checks
|
|
9
|
+
|
|
10
|
+
| Scenario | Expected result |
|
|
11
|
+
| --- | --- |
|
|
12
|
+
| Windows PowerShell | Detects OS/architecture, checks `"node"` and `"npm"`, asks confirmation before `npm install -g claude360@latest`, and confirmation text discloses that `claude360 setup` may open browser auth and write user config. |
|
|
13
|
+
| macOS Terminal | Detects `uname -s`/`uname -m`, checks Node/npm, asks confirmation before global npm install, and confirmation text discloses that `claude360 setup` may open browser auth and write user config. |
|
|
14
|
+
| Ubuntu bash | Same as macOS Terminal; failure messages should mention Node.js/npm prerequisite when missing. |
|
|
15
|
+
| Node missing | Script explains Node.js 18+/npm requirement and exits without running npm install. |
|
|
16
|
+
| Node present | Script prints Node/npm versions before asking for global npm install confirmation. |
|
|
17
|
+
| Node present but version < 18 | Script exits before npm install and tells the user to upgrade to Node.js LTS. |
|
|
18
|
+
| npm global permission missing | `npm install -g claude360@latest` failure is visible to the user; user should rerun after fixing npm global directory permissions. |
|
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "claude360",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Interactive Claude360 CLI for browser auth, API key setup, balance checks, top-up, Claude Code and Codex launch.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"claude360": "bin/claude360.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin/",
|
|
11
|
+
"src/",
|
|
12
|
+
"install/",
|
|
13
|
+
"README.md"
|
|
14
|
+
],
|
|
15
|
+
"engines": {
|
|
16
|
+
"node": ">=18"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"qrcode-terminal": "^0.12.0"
|
|
20
|
+
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"test": "node ./scripts/test.js"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
export class ApiError extends Error {
|
|
2
|
+
constructor(message, { status, response } = {}) {
|
|
3
|
+
super(message);
|
|
4
|
+
this.name = "ApiError";
|
|
5
|
+
this.status = status;
|
|
6
|
+
this.response = response;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export class ApiClient {
|
|
11
|
+
constructor({
|
|
12
|
+
baseUrl = "https://claude360.xyz",
|
|
13
|
+
cliToken = "",
|
|
14
|
+
fetch: fetchImpl = globalThis.fetch,
|
|
15
|
+
} = {}) {
|
|
16
|
+
if (typeof fetchImpl !== "function") {
|
|
17
|
+
throw new ApiError("当前 Node 环境不支持 fetch");
|
|
18
|
+
}
|
|
19
|
+
this.baseUrl = baseUrl.replace(/\/+$/, "");
|
|
20
|
+
this.cliToken = cliToken;
|
|
21
|
+
this.fetch = fetchImpl;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async get(path) {
|
|
25
|
+
return this.request("GET", path);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async post(path, body) {
|
|
29
|
+
return this.request("POST", path, body);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async request(method, path, body) {
|
|
33
|
+
const requestUrl = this.resolveUrl(path);
|
|
34
|
+
const headers = {};
|
|
35
|
+
const init = { method, headers };
|
|
36
|
+
if (this.cliToken && this.isSameOrigin(requestUrl)) {
|
|
37
|
+
headers.Authorization = `Bearer ${this.cliToken}`;
|
|
38
|
+
}
|
|
39
|
+
if (body !== undefined) {
|
|
40
|
+
headers["Content-Type"] = "application/json";
|
|
41
|
+
init.body = JSON.stringify(body);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const response = await this.fetch(requestUrl, init);
|
|
45
|
+
const text = typeof response.text === "function" ? await response.text() : "";
|
|
46
|
+
let envelope;
|
|
47
|
+
try {
|
|
48
|
+
envelope = text ? JSON.parse(text) : {};
|
|
49
|
+
} catch {
|
|
50
|
+
throw new ApiError(`响应不是有效 JSON${text ? `: ${text}` : ""}`, {
|
|
51
|
+
status: response.status,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (!response.ok) {
|
|
56
|
+
throw new ApiError(`HTTP ${response.status}: ${envelope?.message || "请求失败"}`, {
|
|
57
|
+
status: response.status,
|
|
58
|
+
response: envelope,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
if (!envelope?.success) {
|
|
62
|
+
throw new ApiError(envelope?.message || "请求失败", {
|
|
63
|
+
status: response.status,
|
|
64
|
+
response: envelope,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
return envelope.data ?? null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
resolveUrl(path) {
|
|
71
|
+
if (/^https?:\/\//i.test(path)) {
|
|
72
|
+
return path;
|
|
73
|
+
}
|
|
74
|
+
return `${this.baseUrl}${path.startsWith("/") ? "" : "/"}${path}`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
isSameOrigin(requestUrl) {
|
|
78
|
+
return new URL(requestUrl).origin === new URL(this.baseUrl).origin;
|
|
79
|
+
}
|
|
80
|
+
}
|
package/src/auth.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
const defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
2
|
+
|
|
3
|
+
export async function startAuth({
|
|
4
|
+
api,
|
|
5
|
+
openBrowser,
|
|
6
|
+
writeLine = console.log,
|
|
7
|
+
sleep = defaultSleep,
|
|
8
|
+
now = Date.now,
|
|
9
|
+
} = {}) {
|
|
10
|
+
if (!api || typeof api.post !== "function") {
|
|
11
|
+
throw new Error("缺少 API client");
|
|
12
|
+
}
|
|
13
|
+
if (typeof openBrowser !== "function") {
|
|
14
|
+
throw new Error("缺少浏览器打开函数");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const start = await api.post("/api/cli/auth/start");
|
|
18
|
+
if (!start?.device_code || !start?.verification_url || !start?.user_code) {
|
|
19
|
+
throw new Error("授权服务返回数据不完整");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const verificationUrl = withAuthCodes(start.verification_url, {
|
|
23
|
+
userCode: start.user_code,
|
|
24
|
+
deviceCode: start.device_code,
|
|
25
|
+
});
|
|
26
|
+
writeLine(`授权地址:${verificationUrl}`);
|
|
27
|
+
writeLine(`用户码:${start.user_code}`);
|
|
28
|
+
await openBrowser(verificationUrl);
|
|
29
|
+
|
|
30
|
+
const intervalMs = secondsToMs(start.interval, 5);
|
|
31
|
+
const expiresInMs = secondsToMs(start.expires_in, 600);
|
|
32
|
+
const deadline = now() + expiresInMs;
|
|
33
|
+
|
|
34
|
+
while (true) {
|
|
35
|
+
const beforePollAt = now();
|
|
36
|
+
if (beforePollAt >= deadline) {
|
|
37
|
+
throw new Error("授权超时");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const poll = await api.post("/api/cli/auth/poll", {
|
|
41
|
+
device_code: start.device_code,
|
|
42
|
+
});
|
|
43
|
+
const afterPollAt = now();
|
|
44
|
+
if (afterPollAt >= deadline) {
|
|
45
|
+
throw new Error("授权超时");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
switch (poll?.status) {
|
|
49
|
+
case "approved":
|
|
50
|
+
if (!poll.cli_token) {
|
|
51
|
+
throw new Error("授权成功但未返回 CLI token");
|
|
52
|
+
}
|
|
53
|
+
return poll.cli_token;
|
|
54
|
+
case "pending":
|
|
55
|
+
await sleep(Math.min(intervalMs, Math.max(deadline - afterPollAt, 0)));
|
|
56
|
+
break;
|
|
57
|
+
case "denied":
|
|
58
|
+
throw new Error("用户拒绝授权");
|
|
59
|
+
case "expired":
|
|
60
|
+
throw new Error("授权已过期");
|
|
61
|
+
default:
|
|
62
|
+
throw new Error(`未知授权状态: ${poll?.status || "empty"}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export const authenticateWithBrowser = startAuth;
|
|
68
|
+
|
|
69
|
+
function secondsToMs(value, fallback) {
|
|
70
|
+
const seconds = Number(value);
|
|
71
|
+
if (!Number.isFinite(seconds) || seconds <= 0) {
|
|
72
|
+
return fallback * 1000;
|
|
73
|
+
}
|
|
74
|
+
return seconds * 1000;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function withAuthCodes(url, { userCode, deviceCode }) {
|
|
78
|
+
const parsed = new URL(url);
|
|
79
|
+
parsed.searchParams.set("user_code", userCode);
|
|
80
|
+
parsed.searchParams.set("device_code", deviceCode);
|
|
81
|
+
return parsed.toString();
|
|
82
|
+
}
|