claude360 0.1.0 → 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 +7 -4
- package/bin/claude360.js +5 -2
- package/install/install.ps1 +61 -15
- package/install/install.sh +75 -13
- package/install/verification-matrix.md +2 -2
- package/package.json +3 -2
- package/src/account-status.js +97 -0
- package/src/api-client.js +6 -3
- package/src/auth.js +5 -3
- package/src/banner.js +42 -0
- package/src/cc-switch.js +206 -0
- package/src/diagnostics.js +115 -38
- package/src/group-manager.js +6 -3
- package/src/index.js +824 -59
- package/src/mcp-skill.js +319 -0
- package/src/menu.js +108 -61
- package/src/onboarding.js +83 -0
- package/src/sanitize.js +70 -0
- package/src/token-manager.js +46 -22
- package/src/tool-installer.js +34 -5
- package/src/tool-launcher.js +82 -21
- package/src/topup.js +8 -1
package/README.md
CHANGED
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
|
|
40
40
|
## 系统要求
|
|
41
41
|
|
|
42
|
-
- Node.js **18+** 与 npm
|
|
42
|
+
- Node.js **18+** 与 npm(没有时 bootstrap 脚本会经用户确认后通过系统包管理器 winget / brew / apt / dnf 安装 Node.js LTS;不修改 shell profile,不持久化环境变量)。
|
|
43
43
|
- 网络可达 `https://claude360.xyz`。
|
|
44
44
|
- macOS / Linux:当前用户对其 `~/.claude360/` 拥有写权限。
|
|
45
45
|
- Windows:当前用户对 `%USERPROFILE%\.claude360\` 拥有写权限。
|
|
@@ -98,6 +98,9 @@ claude360
|
|
|
98
98
|
|
|
99
99
|
关键约束:
|
|
100
100
|
|
|
101
|
+
- 首次运行(无 `cliToken`)会先做环境检查(Node≥18 / npm / 全局 npm 权限),缺失或版本过低则给出修复指引并中止;CLI 自身不安装系统运行时(连 Node/npm 都没有的用户请使用上文 bootstrap 一键脚本)。
|
|
102
|
+
- 环境检查通过后进入工具安装引导(Claude Code / Codex / 两者 / 跳过),已安装的工具会标注,安装失败不阻断后续授权。
|
|
103
|
+
- 授权服务不可用(接口返回 404 或网络不可达)时给出友好提示并退出,已安装工具不受影响,可稍后运行 `claude360 setup` 重新完成授权与 Key 配置。
|
|
101
104
|
- 授权码短期有效;CLI 仅在浏览器无法自动打开时把 `verification_url` 与 `user_code` 输出到终端。
|
|
102
105
|
- 多个 Key 时展示:名称 / 脱敏 Key / 分组 / 状态 / 剩余额度(或无限额度)/ 过期时间。
|
|
103
106
|
- 无 Key 时,CLI 通过 `/api/cli/groups` 拉取分组并按 `display_name(倍率 1.0x)` 风格展示,由用户选择后调用 `POST /api/cli/tokens` 创建。
|
|
@@ -117,7 +120,7 @@ Claude360
|
|
|
117
120
|
```
|
|
118
121
|
|
|
119
122
|
- **查看余额**:调用 `GET /api/cli/me`,展示账号、余额、已用、当前 Key 名称与分组。
|
|
120
|
-
- **微信扫码充值**:先取 `/api/cli/topup/options`,按后端校验通过的金额或最低额提交,再用 `qrcode-terminal` 在终端渲染 `code_url`,渲染失败时降级为纯文本 URL。轮询 `/api/cli/topup
|
|
123
|
+
- **微信扫码充值**:先取 `/api/cli/topup/options`,按后端校验通过的金额或最低额提交,再用 `qrcode-terminal` 在终端渲染 `code_url`,渲染失败时降级为纯文本 URL。轮询 `/api/cli/topup/order?order_id=`,支付完成自动刷新余额。
|
|
121
124
|
- **启动 Claude Code**:调用 `claude` 子进程,并通过 `ANTHROPIC_BASE_URL` + `ANTHROPIC_AUTH_TOKEN` 注入。
|
|
122
125
|
- **启动 Codex**:写入 `~/.codex/config.toml` 中 `[model_providers.claude360]` 与 `[profiles.claude360]`,发现既有冲突字段会先要求用户确认再覆盖,然后 `codex --profile claude360`,并通过 `CLAUDE360_API_KEY` 注入。
|
|
123
126
|
- **安装或更新工具**:三选一菜单(仅 Claude Code / 仅 Codex / 两者),每步都需要确认。
|
|
@@ -248,7 +251,7 @@ codex --profile claude360
|
|
|
248
251
|
# 进入 CLI 包
|
|
249
252
|
cd claude360-cli
|
|
250
253
|
|
|
251
|
-
#
|
|
254
|
+
# 安装依赖(qrcode-terminal、smol-toml)
|
|
252
255
|
npm install
|
|
253
256
|
|
|
254
257
|
# 跑全部测试
|
|
@@ -296,7 +299,7 @@ claude360-cli/
|
|
|
296
299
|
│ ├── config-store.js # 本地配置读写(0600)
|
|
297
300
|
│ ├── diagnostics.js # 诊断报告
|
|
298
301
|
│ ├── group-manager.js # 分组与倍率展示(无本地映射)
|
|
299
|
-
│ ├── index.js #
|
|
302
|
+
│ ├── index.js # 顶层编排:环境检查 → 工具引导 → 授权 → Key → 主菜单
|
|
300
303
|
│ ├── menu.js # 主菜单
|
|
301
304
|
│ ├── platform.js # 平台路径与能力探测
|
|
302
305
|
│ ├── token-manager.js # Key 选择 / 创建 / reveal
|
package/bin/claude360.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { runCli } from "../src/index.js";
|
|
4
|
+
import { safeErrorMessage } from "../src/sanitize.js";
|
|
5
|
+
|
|
6
|
+
const command = process.argv[2] || "";
|
|
4
7
|
|
|
5
8
|
try {
|
|
6
|
-
await runCli();
|
|
9
|
+
await runCli({ command, forceSetup: command === "setup" });
|
|
7
10
|
} catch (error) {
|
|
8
|
-
console.error(
|
|
11
|
+
console.error(safeErrorMessage(error));
|
|
9
12
|
process.exitCode = 1;
|
|
10
13
|
}
|
package/install/install.ps1
CHANGED
|
@@ -1,7 +1,20 @@
|
|
|
1
|
+
# Claude360 CLI 一键安装引导脚本(Windows PowerShell)
|
|
2
|
+
#
|
|
1
3
|
# Website one-line command:
|
|
2
4
|
# irm https://claude360.xyz/install.ps1 | iex
|
|
5
|
+
#
|
|
6
|
+
# 行为:
|
|
7
|
+
# 1. 检测 Node.js >= 18 与 npm;缺失或版本过低时,经用户确认后
|
|
8
|
+
# 使用 winget 安装 Node.js LTS(OpenJS.NodeJS.LTS)
|
|
9
|
+
# 2. Node 就绪后,经用户确认执行 npm install -g claude360@latest 并启动 claude360 setup
|
|
10
|
+
# 3. 全程不修改 PowerShell profile、不持久化任何环境变量
|
|
11
|
+
#
|
|
12
|
+
# 注意:本文件与 newapi/controller/install_scripts/install.ps1 保持同步,
|
|
13
|
+
# 后端以 embed 版本为准(由 GET /install.ps1 下发)。
|
|
3
14
|
|
|
4
15
|
$ErrorActionPreference = "Stop"
|
|
16
|
+
$NodeMinMajor = 18
|
|
17
|
+
$NodeJsUrl = "https://nodejs.org"
|
|
5
18
|
|
|
6
19
|
function Confirm-Action {
|
|
7
20
|
param([string]$Message)
|
|
@@ -22,35 +35,68 @@ function Invoke-LoggedCommand {
|
|
|
22
35
|
}
|
|
23
36
|
}
|
|
24
37
|
|
|
38
|
+
function Test-NodeOk {
|
|
39
|
+
$node = Get-Command "node" -ErrorAction SilentlyContinue
|
|
40
|
+
$npm = Get-Command "npm" -ErrorAction SilentlyContinue
|
|
41
|
+
if (-not $node -or -not $npm) { return $false }
|
|
42
|
+
try {
|
|
43
|
+
$version = (& "node" @("--version")).TrimStart("v")
|
|
44
|
+
$major = [int]($version.Split(".")[0])
|
|
45
|
+
return $major -ge $NodeMinMajor
|
|
46
|
+
} catch {
|
|
47
|
+
return $false
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function Show-ManualHint {
|
|
52
|
+
Write-Host "请手动安装 Node.js $NodeMinMajor+(LTS 版本)后重新运行本脚本:"
|
|
53
|
+
Write-Host " $NodeJsUrl"
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function Update-SessionPath {
|
|
57
|
+
# 仅刷新当前会话的 PATH(读取系统注册值,不写入、不持久化任何环境变量)
|
|
58
|
+
$machinePath = [System.Environment]::GetEnvironmentVariable("Path", "Machine")
|
|
59
|
+
$userPath = [System.Environment]::GetEnvironmentVariable("Path", "User")
|
|
60
|
+
$env:Path = "$machinePath;$userPath"
|
|
61
|
+
}
|
|
62
|
+
|
|
25
63
|
$os = [System.Runtime.InteropServices.RuntimeInformation]::OSDescription
|
|
26
64
|
$arch = [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture
|
|
27
65
|
Write-Host "Claude360 bootstrap"
|
|
28
66
|
Write-Host "OS: $os"
|
|
29
67
|
Write-Host "Arch: $arch"
|
|
30
68
|
|
|
31
|
-
|
|
32
|
-
$
|
|
33
|
-
if (-not $
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
69
|
+
if (-not (Test-NodeOk)) {
|
|
70
|
+
$winget = Get-Command "winget" -ErrorAction SilentlyContinue
|
|
71
|
+
if (-not $winget) {
|
|
72
|
+
Write-Host "未检测到 Node.js $NodeMinMajor+,且当前系统没有 winget。"
|
|
73
|
+
Show-ManualHint
|
|
74
|
+
return
|
|
75
|
+
}
|
|
76
|
+
if (-not (Confirm-Action "未检测到 Node.js $NodeMinMajor+。将使用 winget 安装 Node.js LTS(OpenJS.NodeJS.LTS)。是否继续?")) {
|
|
77
|
+
Show-ManualHint
|
|
78
|
+
return
|
|
79
|
+
}
|
|
80
|
+
& winget install --id OpenJS.NodeJS.LTS --source winget --accept-package-agreements --accept-source-agreements
|
|
81
|
+
if ($LASTEXITCODE -ne 0) {
|
|
82
|
+
Write-Host "winget 安装失败(退出码 $LASTEXITCODE)。"
|
|
83
|
+
Show-ManualHint
|
|
84
|
+
return
|
|
85
|
+
}
|
|
86
|
+
Update-SessionPath
|
|
87
|
+
if (-not (Test-NodeOk)) {
|
|
88
|
+
Write-Host "Node.js 已安装,但当前终端尚未识别到新的 PATH。"
|
|
89
|
+
Write-Host "请关闭并重新打开终端,然后重新运行本脚本。"
|
|
90
|
+
return
|
|
38
91
|
}
|
|
39
|
-
exit 1
|
|
40
92
|
}
|
|
41
93
|
|
|
42
94
|
& "node" @("--version")
|
|
43
95
|
& "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
96
|
|
|
51
97
|
if (-not (Confirm-Action "将执行全局 npm 操作:npm install -g claude360@latest`n影响范围:安装或更新 Claude360 CLI 到当前用户 npm 全局目录,并随后启动 claude360 setup 首次配置向导;配置向导可能打开浏览器授权并写入用户目录配置。是否继续?")) {
|
|
52
98
|
Write-Host "已取消安装。"
|
|
53
|
-
|
|
99
|
+
return
|
|
54
100
|
}
|
|
55
101
|
|
|
56
102
|
Invoke-LoggedCommand "npm" @("install", "-g", "claude360@latest")
|
package/install/install.sh
CHANGED
|
@@ -1,13 +1,28 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
set -euo pipefail
|
|
3
3
|
|
|
4
|
+
# Claude360 CLI 一键安装引导脚本(macOS / Linux)
|
|
5
|
+
#
|
|
4
6
|
# Website one-line command:
|
|
5
7
|
# curl -fsSL https://claude360.xyz/install.sh | bash
|
|
8
|
+
#
|
|
9
|
+
# 行为:
|
|
10
|
+
# 1. 检测 Node.js >= 18 与 npm;缺失或版本过低时,经用户确认后
|
|
11
|
+
# 使用系统包管理器(brew / apt / dnf)安装 Node.js LTS
|
|
12
|
+
# 2. Node 就绪后,经用户确认执行 npm install -g claude360@latest 并启动 claude360 setup
|
|
13
|
+
# 3. 全程不修改任何 shell 配置文件、不持久化任何环境变量
|
|
14
|
+
#
|
|
15
|
+
# 注意:本文件与 newapi/controller/install_scripts/install.sh 保持同步,
|
|
16
|
+
# 后端以 embed 版本为准(由 GET /install.sh 下发)。
|
|
17
|
+
|
|
18
|
+
NODE_MIN_MAJOR=18
|
|
19
|
+
NODEJS_URL="https://nodejs.org"
|
|
6
20
|
|
|
7
21
|
confirm() {
|
|
8
22
|
local message="$1"
|
|
9
23
|
printf "%s\n" "$message"
|
|
10
24
|
printf "输入 yes 确认继续: "
|
|
25
|
+
# curl | bash 时 stdin 被管道占用,必须从 /dev/tty 读取确认
|
|
11
26
|
if [ -r /dev/tty ]; then
|
|
12
27
|
read -r answer </dev/tty
|
|
13
28
|
else
|
|
@@ -24,28 +39,75 @@ run_cmd() {
|
|
|
24
39
|
"$@"
|
|
25
40
|
}
|
|
26
41
|
|
|
42
|
+
manual_hint() {
|
|
43
|
+
printf "请手动安装 Node.js %s+(LTS 版本)后重新运行本脚本:\n %s\n" "$NODE_MIN_MAJOR" "$NODEJS_URL"
|
|
44
|
+
exit 1
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
node_ok() {
|
|
48
|
+
command -v node >/dev/null 2>&1 || return 1
|
|
49
|
+
command -v npm >/dev/null 2>&1 || return 1
|
|
50
|
+
local major
|
|
51
|
+
major="$(node --version 2>/dev/null | sed 's/^v//' | cut -d. -f1)"
|
|
52
|
+
[ -n "$major" ] && [ "$major" -ge "$NODE_MIN_MAJOR" ] 2>/dev/null
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
maybe_sudo() {
|
|
56
|
+
if [ "$(id -u)" -eq 0 ]; then
|
|
57
|
+
"$@"
|
|
58
|
+
elif command -v sudo >/dev/null 2>&1; then
|
|
59
|
+
sudo "$@"
|
|
60
|
+
else
|
|
61
|
+
printf "需要 root 权限安装,且未找到 sudo。\n"
|
|
62
|
+
manual_hint
|
|
63
|
+
fi
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
install_node() {
|
|
67
|
+
local os
|
|
68
|
+
os="$(uname -s)"
|
|
69
|
+
if [ "$os" = "Darwin" ]; then
|
|
70
|
+
if command -v brew >/dev/null 2>&1; then
|
|
71
|
+
if ! confirm "未检测到 Node.js ${NODE_MIN_MAJOR}+。将使用 Homebrew 安装 Node.js LTS(brew install node)。是否继续?"; then
|
|
72
|
+
manual_hint
|
|
73
|
+
fi
|
|
74
|
+
run_cmd brew install node || manual_hint
|
|
75
|
+
else
|
|
76
|
+
printf "未检测到 Homebrew。\n"
|
|
77
|
+
manual_hint
|
|
78
|
+
fi
|
|
79
|
+
elif command -v apt-get >/dev/null 2>&1; then
|
|
80
|
+
if ! confirm "未检测到 Node.js ${NODE_MIN_MAJOR}+。将使用 apt 安装 nodejs 与 npm(需要 sudo)。是否继续?"; then
|
|
81
|
+
manual_hint
|
|
82
|
+
fi
|
|
83
|
+
maybe_sudo apt-get update || manual_hint
|
|
84
|
+
maybe_sudo apt-get install -y nodejs npm || manual_hint
|
|
85
|
+
elif command -v dnf >/dev/null 2>&1; then
|
|
86
|
+
if ! confirm "未检测到 Node.js ${NODE_MIN_MAJOR}+。将使用 dnf 安装 nodejs 与 npm(需要 sudo)。是否继续?"; then
|
|
87
|
+
manual_hint
|
|
88
|
+
fi
|
|
89
|
+
maybe_sudo dnf install -y nodejs npm || manual_hint
|
|
90
|
+
else
|
|
91
|
+
printf "未识别到受支持的包管理器(brew / apt / dnf)。\n"
|
|
92
|
+
manual_hint
|
|
93
|
+
fi
|
|
94
|
+
}
|
|
95
|
+
|
|
27
96
|
OS_NAME="$(uname -s)"
|
|
28
97
|
ARCH_NAME="$(uname -m)"
|
|
29
98
|
printf "Claude360 bootstrap\nOS: %s\nArch: %s\n" "$OS_NAME" "$ARCH_NAME"
|
|
30
99
|
|
|
31
|
-
if !
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
exit 1
|
|
100
|
+
if ! node_ok; then
|
|
101
|
+
install_node
|
|
102
|
+
# 系统源安装的 Node 可能仍低于 18(如旧版发行版),装完后必须复查
|
|
103
|
+
if ! node_ok; then
|
|
104
|
+
printf "已安装的 Node.js 版本低于 %s,不满足要求。\n" "$NODE_MIN_MAJOR"
|
|
105
|
+
manual_hint
|
|
38
106
|
fi
|
|
39
|
-
exit 1
|
|
40
107
|
fi
|
|
41
108
|
|
|
42
109
|
node --version
|
|
43
110
|
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
111
|
|
|
50
112
|
if ! confirm "将执行全局 npm 操作:npm install -g claude360@latest
|
|
51
113
|
影响范围:安装或更新 Claude360 CLI 到当前用户 npm 全局目录,并随后启动 claude360 setup 首次配置向导;配置向导可能打开浏览器授权并写入用户目录配置。是否继续?"; then
|
|
@@ -12,7 +12,7 @@
|
|
|
12
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
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
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. |
|
|
15
|
+
| Node missing | Script explains Node.js 18+/npm requirement, asks confirmation, then installs Node.js LTS via the system package manager (winget / brew / apt / dnf) and re-checks the version; without a supported package manager or interactive TTY it prints the nodejs.org manual hint and exits without running npm install. |
|
|
16
16
|
| Node present | Script prints Node/npm versions before asking for global npm install confirmation. |
|
|
17
|
-
| Node present but version < 18 |
|
|
17
|
+
| Node present but version < 18 | Treated the same as Node missing: asks confirmation to install Node.js LTS via the system package manager, re-checks the version afterwards, and exits with the nodejs.org manual hint if it is still below 18. |
|
|
18
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
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude360",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Interactive Claude360 CLI for browser auth, API key setup, balance checks, top-up, Claude Code and Codex launch.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
"node": ">=18"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"qrcode-terminal": "^0.12.0"
|
|
19
|
+
"qrcode-terminal": "^0.12.0",
|
|
20
|
+
"smol-toml": "^1.6.1"
|
|
20
21
|
},
|
|
21
22
|
"scripts": {
|
|
22
23
|
"test": "node ./scripts/test.js"
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// 账户状态区:日常菜单顶部显著展示账号 / 余额 / 今日用量 / 当前 Key /
|
|
2
|
+
// Claude Code / Codex 配置状态。业务口径(余额、低余额阈值、今日用量格式)
|
|
3
|
+
// 全部以后端 /api/cli/me 返回为准,CLI 仅做兜底格式化。
|
|
4
|
+
|
|
5
|
+
import { sanitizeError } from "./sanitize.js";
|
|
6
|
+
|
|
7
|
+
export async function loadAccountStatus({ api, config = {} } = {}) {
|
|
8
|
+
let me = null;
|
|
9
|
+
let error = null;
|
|
10
|
+
if (api && config.cliToken) {
|
|
11
|
+
try {
|
|
12
|
+
me = await api.get("/api/cli/me");
|
|
13
|
+
} catch (err) {
|
|
14
|
+
error = err;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return { me, error, config };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// 兜底格式化:今日 token 用量,million 口径,如 128.6k/m、1.25m/m
|
|
21
|
+
export function formatTokensPerMillion(tokens) {
|
|
22
|
+
const value = Number(tokens);
|
|
23
|
+
if (!Number.isFinite(value) || value < 0) {
|
|
24
|
+
return "-";
|
|
25
|
+
}
|
|
26
|
+
if (value >= 1_000_000) {
|
|
27
|
+
return `${(value / 1_000_000).toFixed(2)}m/m`;
|
|
28
|
+
}
|
|
29
|
+
if (value >= 1_000) {
|
|
30
|
+
return `${(value / 1_000).toFixed(1)}k/m`;
|
|
31
|
+
}
|
|
32
|
+
return `${Math.round(value)}/m`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// 账号脱敏:je***@example.com / wa***en
|
|
36
|
+
export function maskAccount(account) {
|
|
37
|
+
if (!account) {
|
|
38
|
+
return "-";
|
|
39
|
+
}
|
|
40
|
+
const text = String(account);
|
|
41
|
+
const atIndex = text.indexOf("@");
|
|
42
|
+
if (atIndex > 0) {
|
|
43
|
+
const name = text.slice(0, atIndex);
|
|
44
|
+
const domain = text.slice(atIndex);
|
|
45
|
+
return `${name.slice(0, 2)}***${domain}`;
|
|
46
|
+
}
|
|
47
|
+
if (text.length <= 4) {
|
|
48
|
+
return `${text.slice(0, 1)}***`;
|
|
49
|
+
}
|
|
50
|
+
return `${text.slice(0, 2)}***${text.slice(-2)}`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function formatAccountStatus({ me, error, config = {} } = {}) {
|
|
54
|
+
const lines = ["Claude360 账户状态", ""];
|
|
55
|
+
const configured = config.configuredTools || {};
|
|
56
|
+
const claudeCodeState = configured.claudeCode ? "已配置" : "未配置";
|
|
57
|
+
const codexState = configured.codex ? "已配置" : "未配置";
|
|
58
|
+
|
|
59
|
+
if (!config.cliToken) {
|
|
60
|
+
lines.push(
|
|
61
|
+
"账号:未登录",
|
|
62
|
+
"余额:-",
|
|
63
|
+
"今日用量:-",
|
|
64
|
+
"当前 Key:-",
|
|
65
|
+
`Claude Code:${claudeCodeState}`,
|
|
66
|
+
`Codex:${codexState}`,
|
|
67
|
+
);
|
|
68
|
+
return lines.join("\n");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (!me) {
|
|
72
|
+
lines.push(
|
|
73
|
+
`账号:${maskAccount(config.account)}(状态获取失败${sanitizeError(error) ? `:${sanitizeError(error)}` : ""})`,
|
|
74
|
+
"余额:-",
|
|
75
|
+
"今日用量:-",
|
|
76
|
+
`当前 Key:${config.tokenName || "-"}`,
|
|
77
|
+
`Claude Code:${claudeCodeState}`,
|
|
78
|
+
`Codex:${codexState}`,
|
|
79
|
+
);
|
|
80
|
+
return lines.join("\n");
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const account = me.email || me.display_name || me.username;
|
|
84
|
+
const balance = me.balance_display ?? String(me.quota ?? "-");
|
|
85
|
+
const lowBalanceTag = me.low_balance ? " ! 余额较低" : "";
|
|
86
|
+
const usage = me.today_usage_display || formatTokensPerMillion(me.today_tokens);
|
|
87
|
+
|
|
88
|
+
lines.push(
|
|
89
|
+
`账号:${maskAccount(account)}`,
|
|
90
|
+
`余额:${balance}${lowBalanceTag}`,
|
|
91
|
+
`今日用量:${usage}`,
|
|
92
|
+
`当前 Key:${config.tokenName || "-"}`,
|
|
93
|
+
`Claude Code:${claudeCodeState}`,
|
|
94
|
+
`Codex:${codexState}`,
|
|
95
|
+
);
|
|
96
|
+
return lines.join("\n");
|
|
97
|
+
}
|
package/src/api-client.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { sanitizeText } from "./sanitize.js";
|
|
2
|
+
|
|
1
3
|
export class ApiError extends Error {
|
|
2
4
|
constructor(message, { status, response } = {}) {
|
|
3
5
|
super(message);
|
|
@@ -47,19 +49,20 @@ export class ApiClient {
|
|
|
47
49
|
try {
|
|
48
50
|
envelope = text ? JSON.parse(text) : {};
|
|
49
51
|
} catch {
|
|
50
|
-
|
|
52
|
+
// 非 JSON 正文可能回显请求里的凭证(如网关错误页),必须脱敏后再进入错误信息
|
|
53
|
+
throw new ApiError(`响应不是有效 JSON${text ? `: ${sanitizeText(text)}` : ""}`, {
|
|
51
54
|
status: response.status,
|
|
52
55
|
});
|
|
53
56
|
}
|
|
54
57
|
|
|
55
58
|
if (!response.ok) {
|
|
56
|
-
throw new ApiError(`HTTP ${response.status}: ${envelope?.message || "请求失败"}`, {
|
|
59
|
+
throw new ApiError(`HTTP ${response.status}: ${sanitizeText(envelope?.message) || "请求失败"}`, {
|
|
57
60
|
status: response.status,
|
|
58
61
|
response: envelope,
|
|
59
62
|
});
|
|
60
63
|
}
|
|
61
64
|
if (!envelope?.success) {
|
|
62
|
-
throw new ApiError(envelope?.message || "请求失败", {
|
|
65
|
+
throw new ApiError(sanitizeText(envelope?.message) || "请求失败", {
|
|
63
66
|
status: response.status,
|
|
64
67
|
response: envelope,
|
|
65
68
|
});
|
package/src/auth.js
CHANGED
|
@@ -21,7 +21,6 @@ export async function startAuth({
|
|
|
21
21
|
|
|
22
22
|
const verificationUrl = withAuthCodes(start.verification_url, {
|
|
23
23
|
userCode: start.user_code,
|
|
24
|
-
deviceCode: start.device_code,
|
|
25
24
|
});
|
|
26
25
|
writeLine(`授权地址:${verificationUrl}`);
|
|
27
26
|
writeLine(`用户码:${start.user_code}`);
|
|
@@ -58,6 +57,8 @@ export async function startAuth({
|
|
|
58
57
|
throw new Error("用户拒绝授权");
|
|
59
58
|
case "expired":
|
|
60
59
|
throw new Error("授权已过期");
|
|
60
|
+
case "consumed":
|
|
61
|
+
throw new Error("该授权请求已被使用,请重新发起授权");
|
|
61
62
|
default:
|
|
62
63
|
throw new Error(`未知授权状态: ${poll?.status || "empty"}`);
|
|
63
64
|
}
|
|
@@ -74,9 +75,10 @@ function secondsToMs(value, fallback) {
|
|
|
74
75
|
return seconds * 1000;
|
|
75
76
|
}
|
|
76
77
|
|
|
77
|
-
|
|
78
|
+
// 安全要求:device_code 是可换取 cli_token 的轮询凭证,只随 CLI 轮询发送,
|
|
79
|
+
// 绝不拼进浏览器 URL(避免进入浏览器历史 / 服务端访问日志)。
|
|
80
|
+
function withAuthCodes(url, { userCode }) {
|
|
78
81
|
const parsed = new URL(url);
|
|
79
82
|
parsed.searchParams.set("user_code", userCode);
|
|
80
|
-
parsed.searchParams.set("device_code", deviceCode);
|
|
81
83
|
return parsed.toString();
|
|
82
84
|
}
|
package/src/banner.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export const BRAND_BASE_URL = "https://claude360.xyz";
|
|
2
|
+
|
|
3
|
+
const LOGO_LINES = [
|
|
4
|
+
" ██████╗██╗ █████╗ ██╗ ██╗██████╗ ███████╗██████╗ ██████╗ ██████╗ ",
|
|
5
|
+
"██╔════╝██║ ██╔══██╗██║ ██║██╔══██╗██╔════╝╚════██╗██╔════╝ ██╔═████╗",
|
|
6
|
+
"██║ ██║ ███████║██║ ██║██║ ██║█████╗ █████╔╝███████╗ ██║██╔██║",
|
|
7
|
+
"██║ ██║ ██╔══██║██║ ██║██║ ██║██╔══╝ ╚═══██╗██╔═══██╗████╔╝██║",
|
|
8
|
+
"╚██████╗███████╗██║ ██║╚██████╔╝██████╔╝███████╗██████╔╝╚██████╔╝╚██████╔╝",
|
|
9
|
+
" ╚═════╝╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚══════╝╚═════╝ ╚═════╝ ╚═════╝ ",
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
export function renderBanner({ version = "", baseUrl = BRAND_BASE_URL } = {}) {
|
|
13
|
+
const lines = [
|
|
14
|
+
"",
|
|
15
|
+
...LOGO_LINES,
|
|
16
|
+
"",
|
|
17
|
+
" Claude360 模型站接入助手",
|
|
18
|
+
" 一键配置 Claude Code / Codex / API Key / 充值",
|
|
19
|
+
"",
|
|
20
|
+
`版本:${version || "-"} | 官网:${baseUrl}`,
|
|
21
|
+
"",
|
|
22
|
+
];
|
|
23
|
+
return lines.join("\n");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const CHECK_OK = "✓";
|
|
27
|
+
export const CHECK_WARN = "!";
|
|
28
|
+
export const CHECK_FAIL = "×";
|
|
29
|
+
|
|
30
|
+
export function formatCheckLine(status, text) {
|
|
31
|
+
const symbols = { ok: CHECK_OK, warn: CHECK_WARN, fail: CHECK_FAIL };
|
|
32
|
+
const symbol = symbols[status] || CHECK_WARN;
|
|
33
|
+
return `${symbol} ${text}`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function renderEnvironmentChecks(checks = []) {
|
|
37
|
+
return [
|
|
38
|
+
"正在检查运行环境...",
|
|
39
|
+
"",
|
|
40
|
+
...checks.map((check) => formatCheckLine(check.status, check.text)),
|
|
41
|
+
].join("\n");
|
|
42
|
+
}
|