stepfun-status 1.0.3 → 1.0.5

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 CHANGED
@@ -1,174 +1,190 @@
1
- # stepfun-status
2
-
3
- > StepFun Token-Plan 使用状态监控工具,支持 Claude Code 状态栏常驻显示。
4
-
5
- [![npm version](https://img.shields.io/npm/v/stepfun-status)](https://www.npmjs.com/package/stepfun-status)
6
- [![Node >=16](https://img.shields.io/node/v/stepfun-status)](https://nodejs.org)
7
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
8
-
9
- ## 效果预览
10
-
11
- Claude Code 底部状态栏:
12
-
13
- ```
14
- my-app │ main │ step-3.5-flash │ ██████░░░░ 60% (40/100) │ ⏱ 1h30m │ W ███░░ 58% │ 到期 12天
15
- ```
16
-
17
- 终端详细视图:
18
-
19
- ```
20
- ┌──────────────────────────────────────────────┐
21
- │ StepFun Claude Code 使用状态 │
22
-
23
- 当前模型: step-3.5-flash
24
- 套餐: Mini
25
-
26
- 5小时用量: ██████████████████░░░░░░░░░░ 60%
27
- 剩余: 40%
28
- 重置: 1 小时 30 分钟后重置
29
-
30
- 每周用量: ████████░░░░░░░ 58%
31
- 剩余: 42%
32
- 重置: 3 小时 12 分钟后重置
33
-
34
- 套餐到期: 2025/6/1 00:00:00 (还剩 12 天)
35
-
36
- 状态: ⚡ 注意使用
37
- └──────────────────────────────────────────────┘
38
- ```
39
-
40
- ## 安装
41
-
42
- ```bash
43
- npm install -g stepfun-status
44
- ```
45
-
46
- ## 快速开始
47
-
48
- ### 1. 获取 Cookie 并认证
49
-
50
- 运行下方命令,会自动打印获取步骤:
51
-
52
- ```bash
53
- stepfun auth
54
- ```
55
-
56
- 按提示操作:
57
- 1. 登录 [https://platform.stepfun.com/plan-subscribe](https://platform.stepfun.com/plan-subscribe)
58
- 2. F12 → Network 标签 → 刷新页面
59
- 3. 点击任意 API 请求(如 `GetStepPlanStatus`)
60
- 4. 滚动到 Request Headers → 找到 `Cookie` 行
61
- 5. **右键Copy value**(必须用此方式,直接框选会截断)
62
- 6. 粘贴到终端并回车
63
-
64
- > **提示**:Cookie 较长,建议使用交互式 `stepfun auth`(无参数),避免命令行参数截断问题。
65
-
66
- ### 2. 验证连接
67
-
68
- ```bash
69
- stepfun health
70
- ```
71
-
72
- ### 3. 查看状态
73
-
74
- ```bash
75
- stepfun status
76
- ```
77
-
78
- ## Claude Code 状态栏集成
79
-
80
- ### 配置
81
-
82
- 编辑 `~/.claude/settings.json`,添加:
83
-
84
- ```json
85
- {
86
- "statusLine": {
87
- "type": "command",
88
- "command": "stepfun statusline"
89
- }
90
- }
91
- ```
92
-
93
- 重启 Claude Code 后生效。
94
-
95
-
96
- ### 状态栏格式
97
-
98
- ```
99
- 目录 │ 分支 │ 模型 │ 5小时用量进度条(剩余) │ ⏱ 倒计时 │ W 周用量 │ 到期天数
100
- ```
101
-
102
- **颜色说明**:
103
-
104
- | 指标 | 绿色 | 黄色 | 红色 |
105
- |------|------|------|------|
106
- | 用量 | < 60% | 60–85% | ≥ 85% |
107
- | 到期 | > 7 | 7 天 | 3 |
108
- | 分支 | main/master | | 有未提交改动(`*`) |
109
-
110
- ## 命令参考
111
-
112
- | 命令 | 选项 | 说明 |
113
- |------|------|------|
114
- | `stepfun auth [cookie]` | — | 设置认证 Cookie(推荐无参数交互式输入) |
115
- | `stepfun health` | — | 检查配置文件、Cookie 和 API 连通性 |
116
- | `stepfun status` | — | 详细模式显示当前使用状态 |
117
- | `stepfun status` | `-c, --compact` | 紧凑单行模式 |
118
- | `stepfun status` | `-w, --watch` | 实时监控,每 10 秒刷新 |
119
- | `stepfun status` | `-c -w` | 紧凑模式 + 实时监控 |
120
- | `stepfun statusline` | | 输出状态行(供 Claude Code 调用) |
121
- | `stepfun statusline` | `-w, --watch` | 持续刷新状态行输出 |
122
-
123
- ## 配置文件
124
-
125
- | 项目 | 说明 |
126
- |------|------|
127
- | 路径 | `~/.stepfun-config.json` |
128
- | 内容 | `{ "cookie": "..." }` |
129
- | 权限 | Unix/Mac 下自动设置为 `0600`(仅所有者可读写) |
130
-
131
- > **Windows 注意**:不支持 Unix 文件权限位,Cookie 以明文存储,请妥善保管配置文件,避免他人访问。
132
-
133
- > **macOS / Linux**:配置文件权限已自动设为 `0600`,只有当前用户可读写,无需额外操作。
134
-
135
- ## 常见问题
136
-
137
- **Q: 状态栏显示 ❌**
138
- A: Cookie 未配置或已过期,运行 `stepfun health` 诊断,再用 `stepfun auth` 重新设置。
139
-
140
- **Q: Cookie 粘贴后提示"不能为空"**
141
- A: 可能复制到了空内容,请确保在 Network 面板使用"右键 → Copy value"而非框选复制。
142
-
143
- **Q: 状态栏不显示**
144
- A: 确认 `~/.claude/settings.json` `statusLine.command` `stepfun statusline`,且 `stepfun-status` 已全局安装(`npm install -g stepfun-status`)。
145
-
146
- **Q: macOS / Linux 上 `stepfun` 命令找不到**
147
- A: 确认 npm 全局 bin 目录已加入 `PATH`。可运行 `npm bin -g` 查看路径,并将其添加到 `~/.zshrc` 或 `~/.bashrc`:
148
- ```bash
149
- export PATH="$(npm bin -g):$PATH"
150
- ```
151
-
152
- ## 开发
153
-
154
- ```bash
155
- git clone https://github.com/Daiyimo/stepfun-status.git
156
- cd stepfun-status
157
- npm install
158
- npm link # 本地全局链接,方便调试
159
-
160
- npm start # 查看当前状态
161
- npm test # 检查连通性
162
- npm run lint # 语法检查
163
- ```
164
-
165
- ## 许可证
166
-
167
- [MIT](LICENSE)
168
-
169
- ## 相关链接
170
-
171
- - [StepFun 开放平台](https://platform.stepfun.com)
172
- - [StepFun 接口密钥](https://platform.stepfun.com/interface-key)
173
- - [StepFun 订阅情况](https://platform.stepfun.com/plan-subscribe)
174
- - [StepFun 接入指南](https://platform.stepfun.com/docs/zh/step-plan/integrations)
1
+ # stepfun-status
2
+
3
+ > StepFun Token-Plan 使用状态监控工具,支持 Claude Code 状态栏常驻显示。
4
+
5
+ [![npm version](https://img.shields.io/npm/v/stepfun-status)](https://www.npmjs.com/package/stepfun-status)
6
+ [![npm downloads](https://img.shields.io/npm/dm/stepfun-status)](https://www.npmjs.com/package/stepfun-status?activeTab=downloads)
7
+ [![Node >=16](https://img.shields.io/node/v/stepfun-status)](https://nodejs.org)
8
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
9
+
10
+ ## 效果预览
11
+
12
+ Claude Code 底部状态栏:
13
+
14
+ ```
15
+ my-app │ main │ step-3.5-flash │ ██████░░░░ 60% (40/100) │ ⏱ 1h30m │ W ███░░ 58% │ 到期 12天
16
+ ```
17
+
18
+ 终端详细视图:
19
+
20
+ ```
21
+ ┌──────────────────────────────────────────────┐
22
+ StepFun Claude Code 使用状态
23
+
24
+ 当前模型: step-3.5-flash
25
+ 套餐: Mini
26
+
27
+ 5小时用量: ██████████████████░░░░░░░░░░ 60%
28
+ 剩余: 40%
29
+ 重置: 1 小时 30 分钟后重置
30
+
31
+ 每周用量: ████████░░░░░░░ 58%
32
+ 剩余: 42%
33
+ 重置: 3 小时 12 分钟后重置
34
+
35
+ 套餐到期: 2025/6/1 00:00:00 (还剩 12 天)
36
+
37
+ │ 状态: ⚡ 注意使用 │
38
+ └──────────────────────────────────────────────┘
39
+ ```
40
+
41
+ ## 安装
42
+
43
+ ```bash
44
+ npm install -g stepfun-status
45
+ ```
46
+
47
+ ## 快速开始
48
+
49
+ ### 1. 获取 Cookie 并认证
50
+
51
+ 运行下方命令,会自动打印获取步骤:
52
+
53
+ ```bash
54
+ stepfun auth
55
+ ```
56
+
57
+ 按提示操作:
58
+ 1. 登录 [https://platform.stepfun.com/plan-subscribe](https://platform.stepfun.com/plan-subscribe)
59
+ 2. F12 Network 标签 → 刷新页面
60
+ 3. 点击任意 API 请求(如 `GetStepPlanStatus`)
61
+ 4. 滚动到 Request Headers 找到 `Cookie` 行
62
+ 5. **右键 → Copy value**(必须用此方式,直接框选会截断)
63
+ 6. 粘贴到终端并回车
64
+
65
+ > **提示**:Cookie 较长,建议使用交互式 `stepfun auth`(无参数),避免命令行参数截断问题。
66
+
67
+ ### 2. 验证连接
68
+
69
+ ```bash
70
+ stepfun health
71
+ ```
72
+
73
+ ### 3. 查看状态
74
+
75
+ ```bash
76
+ stepfun status
77
+ ```
78
+
79
+ ## Claude Code 状态栏集成
80
+
81
+ ### 配置
82
+
83
+ 编辑 `~/.claude/settings.json`,添加:
84
+
85
+ ```json
86
+ {
87
+ "statusLine": {
88
+ "type": "command",
89
+ "command": "stepfun statusline"
90
+ }
91
+ }
92
+ ```
93
+
94
+ 重启 Claude Code 后生效。
95
+
96
+
97
+ ### 状态栏格式
98
+
99
+ ```
100
+ 目录 │ 分支 │ 模型 │ 5小时用量进度条(剩余) │ ⏱ 倒计时 │ W 周用量 │ 到期天数
101
+ ```
102
+
103
+ **颜色说明**:
104
+
105
+ | 指标 | 绿色 | 黄色 | 红色 |
106
+ |------|------|------|------|
107
+ | 用量 | < 60% | 60–85% | 85% |
108
+ | 到期 | > 7 天 | 7 天 | 3 天 |
109
+ | 分支 | main/master | — | 有未提交改动(`*`) |
110
+
111
+ ## 命令参考
112
+
113
+ | 命令 | 选项 | 说明 |
114
+ |------|------|------|
115
+ | `stepfun auth [cookie]` | — | 设置认证 Cookie(推荐无参数交互式输入) |
116
+ | `stepfun health` | — | 检查配置文件、Cookie 和 API 连通性 |
117
+ | `stepfun status` | | 详细模式显示当前使用状态 |
118
+ | `stepfun status` | `-c, --compact` | 紧凑单行模式 |
119
+ | `stepfun status` | `-w, --watch` | 实时监控,每 10 秒刷新 |
120
+ | `stepfun status` | `-c -w` | 紧凑模式 + 实时监控 |
121
+ | `stepfun statusline` | | 输出状态行(供 Claude Code 调用) |
122
+ | `stepfun statusline` | `-w, --watch` | 持续刷新状态行输出 |
123
+
124
+ ## 卸载 / 更新
125
+
126
+ ```bash
127
+ # 一键更新到最新版
128
+ npm update -g stepfun-status
129
+
130
+ # 完全卸载
131
+ npm uninstall -g stepfun-status
132
+ # 可选:同时删除配置文件
133
+ # macOS / Linux
134
+ rm -f ~/.stepfun-config.json
135
+ # Windows (PowerShell)
136
+ Remove-Item ~\.stepfun-config.json -ErrorAction SilentlyContinue
137
+ ```
138
+
139
+ ## 配置文件
140
+
141
+ | 项目 | 说明 |
142
+ |------|------|
143
+ | 路径 | `~/.stepfun-config.json` |
144
+ | 内容 | `{ "cookie": "..." }` |
145
+ | 权限 | Unix/Mac 下自动设置为 `0600`(仅所有者可读写) |
146
+
147
+ > **Windows 注意**:不支持 Unix 文件权限位,Cookie 以明文存储,请妥善保管配置文件,避免他人访问。
148
+
149
+ > **macOS / Linux**:配置文件权限已自动设为 `0600`,只有当前用户可读写,无需额外操作。
150
+
151
+ ## 常见问题
152
+
153
+ **Q: 状态栏显示 ❌**
154
+ A: Cookie 未配置或已过期,运行 `stepfun health` 诊断,再用 `stepfun auth` 重新设置。
155
+
156
+ **Q: Cookie 粘贴后提示"不能为空"**
157
+ A: 可能复制到了空内容,请确保在 Network 面板使用"右键 → Copy value"而非框选复制。
158
+
159
+ **Q: 状态栏不显示**
160
+ A: 确认 `~/.claude/settings.json` 中 `statusLine.command` 为 `stepfun statusline`,且 `stepfun-status` 已全局安装(`npm install -g stepfun-status`)。
161
+
162
+ **Q: macOS / Linux 上 `stepfun` 命令找不到**
163
+ A: 确认 npm 全局 bin 目录已加入 `PATH`。可运行 `npm bin -g` 查看路径,并将其添加到 `~/.zshrc` 或 `~/.bashrc`:
164
+ ```bash
165
+ export PATH="$(npm bin -g):$PATH"
166
+ ```
167
+
168
+ ## 开发
169
+
170
+ ```bash
171
+ git clone https://github.com/Daiyimo/stepfun-status.git
172
+ cd stepfun-status
173
+ npm install
174
+ npm link # 本地全局链接,方便调试
175
+
176
+ npm start # 查看当前状态
177
+ npm test # 检查连通性
178
+ npm run lint # 语法检查
179
+ ```
180
+
181
+ ## 许可证
182
+
183
+ [MIT](LICENSE)
184
+
185
+ ## 相关链接
186
+
187
+ - [StepFun 开放平台](https://platform.stepfun.com)
188
+ - [StepFun 接口密钥](https://platform.stepfun.com/interface-key)
189
+ - [StepFun 订阅情况](https://platform.stepfun.com/plan-subscribe)
190
+ - [StepFun 接入指南](https://platform.stepfun.com/docs/zh/step-plan/integrations)
package/cli/api.js CHANGED
@@ -1,345 +1,408 @@
1
- const https = require("https");
2
- const fs = require("fs");
3
- const path = require("path");
4
- const os = require("os");
5
-
6
- // ─── 常量 ─────────────────────────────────────────────────────────────────────
7
-
8
- const STEPFUN_HOST = "platform.stepfun.com";
9
- const STEPFUN_ORIGIN = `https://${STEPFUN_HOST}`;
10
-
11
- const CONFIG = {
12
- CACHE_TIMEOUT: 30000, // 缓存超时:30 秒
13
- REQUEST_TIMEOUT: 8000, // 网络请求超时:8 秒
14
- CONFIG_FILENAME: ".stepfun-config.json",
15
- };
16
-
17
- // ─── 跨平台 User-Agent ────────────────────────────────────────────────────────
18
-
19
- /**
20
- * 根据当前操作系统返回对应的浏览器 User-Agent 字符串,
21
- * 避免 macOS/Linux 下使用 Windows UA 被服务端检测。
22
- */
23
- function getPlatformUserAgent() {
24
- const platform = os.platform();
25
- if (platform === "darwin") {
26
- return "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36";
27
- }
28
- if (platform === "linux") {
29
- return "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36";
30
- }
31
- // Windows(默认)
32
- return "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36";
33
- }
34
-
35
- // 配置文件路径(模块级常量,供外部共享,避免各处重复计算)
36
- const CONFIG_PATH = path.join(
37
- process.env.HOME || process.env.USERPROFILE,
38
- CONFIG.CONFIG_FILENAME
39
- );
40
-
41
- // ─── 模块级纯函数(提升出 parseUsageData,避免每次调用重新创建) ──────────────
42
-
43
- /**
44
- * 将 Unix 时间戳转换为剩余时间对象
45
- * @param {string|number|null} timestamp Unix 秒级时间戳
46
- * @returns {{ ts: number, date: string, hoursUntil: number, minutesUntil: number } | null}
47
- */
48
- function formatResetTime(timestamp) {
49
- if (!timestamp) return null;
50
- const ts = parseInt(timestamp, 10);
51
- const date = new Date(ts * 1000);
52
- const diffMs = date.getTime() - Date.now();
53
- return {
54
- ts,
55
- date: date.toLocaleString(),
56
- hoursUntil: Math.max(0, Math.floor(diffMs / (1000 * 60 * 60))),
57
- minutesUntil: Math.max(0, Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60))),
58
- };
59
- }
60
-
61
- /**
62
- * 将剩余时间对象格式化为可读文本
63
- * @param {{ hoursUntil: number, minutesUntil: number } | null} resetInfo
64
- * @returns {string}
65
- */
66
- function calcRemainingText(resetInfo) {
67
- if (!resetInfo) return "未知";
68
- const { hoursUntil, minutesUntil } = resetInfo;
69
- return hoursUntil > 0
70
- ? `${hoursUntil} 小时 ${minutesUntil} 分钟后重置`
71
- : `${minutesUntil} 分钟后重置`;
72
- }
73
-
74
- // ─── StepFunAPI ────────────────────────────────────────────────────────────
75
-
76
- class StepFunAPI {
77
- constructor() {
78
- this.cookie = null;
79
- this.cache = {
80
- statusData: null,
81
- quotaData: null,
82
- statusTimestamp: 0,
83
- quotaTimestamp: 0,
84
- };
85
- this.loadConfig();
86
- }
87
-
88
- /** 是否已配置凭据 */
89
- isAuthenticated() {
90
- return Boolean(this.cookie);
91
- }
92
-
93
- loadConfig() {
94
- try {
95
- if (fs.existsSync(CONFIG_PATH)) {
96
- const config = JSON.parse(fs.readFileSync(CONFIG_PATH, "utf8"));
97
- this.cookie = config.cookie || null;
98
- }
99
- } catch (error) {
100
- console.error("Failed to load config:", error.message);
101
- }
102
- }
103
-
104
- saveConfig() {
105
- try {
106
- const config = { cookie: this.cookie };
107
- // 0o600:仅所有者可读写,在 Unix/Mac 上防止其他用户读取 Cookie
108
- fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), { mode: 0o600 });
109
- } catch (error) {
110
- console.error("Failed to save config:", error.message);
111
- }
112
- }
113
-
114
- setCredentials(cookie) {
115
- if (!cookie || typeof cookie !== "string" || !cookie.trim()) {
116
- throw new Error("Cookie 不能为空");
117
- }
118
- this.cookie = cookie.trim();
119
- this.saveConfig();
120
- }
121
-
122
- /** 从 Cookie 字符串中提取指定字段 */
123
- extractFromCookie() {
124
- if (!this.cookie) return { oasisWebid: null };
125
-
126
- const cookies = {};
127
- this.cookie.split(/;\s*/).forEach((c) => {
128
- const idx = c.indexOf("=");
129
- if (idx === -1) return;
130
- const key = c.slice(0, idx).trim();
131
- const val = c.slice(idx + 1);
132
- if (!key) return;
133
- try {
134
- cookies[key] = decodeURIComponent(val);
135
- } catch {
136
- cookies[key] = val;
137
- }
138
- });
139
-
140
- return { oasisWebid: cookies["Oasis-Webid"] || null };
141
- }
142
-
143
- async makePlatformRequest(endpoint, data = {}) {
144
- if (!this.cookie) {
145
- throw new Error(
146
- 'Missing cookie. Please run "stepfun auth <cookie>" first.\n' +
147
- "获取方式:\n" +
148
- "1. 登录 https://platform.stepfun.com/plan-subscribe\n" +
149
- "2. F12 -> Network -> 刷新页面\n" +
150
- "3. 点击任意 API 请求 -> Copy -> Copy request headers -> 复制 Cookie 行"
151
- );
152
- }
153
-
154
- const { oasisWebid } = this.extractFromCookie();
155
- const postData = JSON.stringify(data);
156
-
157
- const headers = {
158
- accept: "*/*",
159
- "content-type": "application/json",
160
- "oasis-appid": "10300",
161
- "oasis-platform": "web",
162
- "oasis-webid": oasisWebid || "",
163
- origin: STEPFUN_ORIGIN,
164
- referer: `${STEPFUN_ORIGIN}/plan-subscribe`,
165
- "user-agent": getPlatformUserAgent(),
166
- "content-length": Buffer.byteLength(postData),
167
- Cookie: this.cookie,
168
- };
169
-
170
- return new Promise((resolve, reject) => {
171
- const req = https.request(
172
- {
173
- hostname: STEPFUN_HOST, // STEPFUN_ORIGIN 保持同一数据源
174
- port: 443,
175
- path: endpoint,
176
- method: "POST",
177
- headers,
178
- rejectUnauthorized: true, // 显式启用 SSL 证书验证
179
- },
180
- (res) => {
181
- let body = "";
182
- res.on("data", (chunk) => { body += chunk; });
183
- res.on("end", () => {
184
- if (res.statusCode === 200) {
185
- try {
186
- resolve(JSON.parse(body));
187
- } catch {
188
- reject(new Error("Failed to parse response: invalid JSON"));
189
- }
190
- } else if (res.statusCode === 401) {
191
- reject(new Error("Cookie 已失效或过期,请重新运行 auth 命令"));
192
- } else {
193
- reject(new Error(`API error: HTTP ${res.statusCode}`));
194
- }
195
- });
196
- }
197
- );
198
-
199
- // 超时保护
200
- req.setTimeout(CONFIG.REQUEST_TIMEOUT, () => {
201
- req.destroy();
202
- reject(new Error(`Request timeout after ${CONFIG.REQUEST_TIMEOUT / 1000}s`));
203
- });
204
-
205
- req.on("error", (e) => {
206
- reject(new Error(`Network error: ${e.message}`));
207
- });
208
-
209
- req.write(postData);
210
- req.end();
211
- });
212
- }
213
-
214
- /**
215
- * 带缓存的请求封装,消除 getSubscriptionStatus / getQuotaInfo 的重复逻辑
216
- * @param {'statusData'|'quotaData'} dataKey 缓存数据字段名
217
- * @param {'statusTimestamp'|'quotaTimestamp'} tsKey 缓存时间戳字段名
218
- * @param {string} endpoint API 路径
219
- * @param {boolean} forceRefresh 是否强制跳过缓存
220
- */
221
- async _cachedRequest(dataKey, tsKey, endpoint, forceRefresh) {
222
- const now = Date.now();
223
- if (
224
- !forceRefresh &&
225
- this.cache[dataKey] &&
226
- now - this.cache[tsKey] < CONFIG.CACHE_TIMEOUT
227
- ) {
228
- return this.cache[dataKey];
229
- }
230
- const data = await this.makePlatformRequest(endpoint);
231
- this.cache[dataKey] = data;
232
- this.cache[tsKey] = now;
233
- return data;
234
- }
235
-
236
- async getSubscriptionStatus(forceRefresh = false) {
237
- return this._cachedRequest(
238
- "statusData",
239
- "statusTimestamp",
240
- "/api/step.openapi.devcenter.Dashboard/GetStepPlanStatus",
241
- forceRefresh
242
- );
243
- }
244
-
245
- async getQuotaInfo(forceRefresh = false) {
246
- return this._cachedRequest(
247
- "quotaData",
248
- "quotaTimestamp",
249
- "/api/step.openapi.devcenter.Dashboard/QueryStepPlanRateLimit",
250
- forceRefresh
251
- );
252
- }
253
-
254
- // ── 预留扩展区 ──────────────────────────────────────────────────────────────
255
- // 后续新增接口时,只需:
256
- // 1. this.cache 里加对应的 data/timestamp 字段
257
- // 2. 仿照下方模板写一个 get 方法
258
- // 3. 在 getUsageStatus / parseUsageData 里消费新数据
259
- //
260
- // 模板(等 Token 用量明细接口开放后接入):
261
- //
262
- // async getTokenUsage(forceRefresh = false) {
263
- // return this._cachedRequest(
264
- // "tokenData",
265
- // "tokenTimestamp",
266
- // "/api/step.openapi.devcenter.Dashboard/QueryTokenUsage", // 待确认
267
- // forceRefresh
268
- // );
269
- // }
270
- // ────────────────────────────────────────────────────────────────────────────
271
-
272
- async getUsageStatus(forceRefresh = false) {
273
- const [statusData, quotaData] = await Promise.all([
274
- this.getSubscriptionStatus(forceRefresh),
275
- this.getQuotaInfo(forceRefresh),
276
- ]);
277
- return this.parseUsageData(statusData, quotaData);
278
- }
279
-
280
- parseUsageData(statusData, quotaData) {
281
- const subscription = statusData.subscription || {};
282
- const planDefinition = statusData.plan_definition || {};
283
-
284
- const fiveHourLeftRate = quotaData.five_hour_usage_left_rate || 0;
285
- const weeklyLeftRate = quotaData.weekly_usage_left_rate || 0;
286
-
287
- const fiveHourUsedPercent = Math.round((1 - fiveHourLeftRate) * 100);
288
- const weeklyUsedPercent = Math.round((1 - weeklyLeftRate) * 100);
289
-
290
- const fiveHourReset = formatResetTime(quotaData.five_hour_usage_reset_time);
291
- const weeklyReset = formatResetTime(quotaData.weekly_usage_reset_time);
292
-
293
- let expiry = null;
294
- if (subscription.expired_at) {
295
- const expiryDate = new Date(parseInt(subscription.expired_at, 10) * 1000);
296
- const daysRemaining = Math.ceil(
297
- (expiryDate.getTime() - Date.now()) / (1000 * 3600 * 24)
298
- );
299
- expiry = {
300
- date: expiryDate.toLocaleString(),
301
- daysRemaining,
302
- text:
303
- daysRemaining > 0
304
- ? `还剩 ${daysRemaining} 天`
305
- : daysRemaining === 0
306
- ? "今天到期"
307
- : `已过期 ${Math.abs(daysRemaining)} 天`,
308
- };
309
- }
310
-
311
- return {
312
- modelName: planDefinition.support_models?.[0] || "step-3.5-flash",
313
- planName: subscription.name || "Mini",
314
- remaining: {
315
- hours: fiveHourReset?.hoursUntil || 0,
316
- minutes: fiveHourReset?.minutesUntil || 0,
317
- text: calcRemainingText(fiveHourReset),
318
- },
319
- usage: {
320
- percentage: fiveHourUsedPercent,
321
- remaining: Math.round(fiveHourLeftRate * 100),
322
- },
323
- weekly: {
324
- percentage: weeklyUsedPercent,
325
- remaining: Math.round(weeklyLeftRate * 100),
326
- text: calcRemainingText(weeklyReset),
327
- daysUntilReset: weeklyReset?.hoursUntil
328
- ? Math.floor(weeklyReset.hoursUntil / 24)
329
- : 0,
330
- },
331
- expiry,
332
- };
333
- }
334
-
335
- clearCache() {
336
- this.cache = {
337
- statusData: null,
338
- quotaData: null,
339
- statusTimestamp: 0,
340
- quotaTimestamp: 0,
341
- };
342
- }
343
- }
344
-
345
- module.exports = { StepFunAPI, CONFIG_PATH };
1
+ const https = require("https");
2
+ const fs = require("fs");
3
+ const path = require("path");
4
+ const os = require("os");
5
+
6
+ // ─── 常量 ─────────────────────────────────────────────────────────────────────
7
+
8
+ const STEPFUN_HOST = "platform.stepfun.com";
9
+ const STEPFUN_ORIGIN = `https://${STEPFUN_HOST}`;
10
+
11
+ const CONFIG = {
12
+ CACHE_TIMEOUT: 30000, // 缓存超时:30 秒
13
+ REQUEST_TIMEOUT: 8000, // 网络请求超时:8 秒
14
+ CONFIG_FILENAME: ".stepfun-config.json",
15
+ };
16
+
17
+ // ─── 跨平台 User-Agent ────────────────────────────────────────────────────────
18
+
19
+ /**
20
+ * 根据当前操作系统返回对应的浏览器 User-Agent 字符串,
21
+ * 避免 macOS/Linux 下使用 Windows UA 被服务端检测。
22
+ */
23
+ function getPlatformUserAgent() {
24
+ const platform = os.platform();
25
+ if (platform === "darwin") {
26
+ return "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36";
27
+ }
28
+ if (platform === "linux") {
29
+ return "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36";
30
+ }
31
+ // Windows(默认)
32
+ return "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36";
33
+ }
34
+
35
+ // 配置文件路径(模块级常量,供外部共享,避免各处重复计算)
36
+ const CONFIG_PATH = path.join(
37
+ process.env.HOME || process.env.USERPROFILE,
38
+ CONFIG.CONFIG_FILENAME
39
+ );
40
+
41
+ // ─── 模块级纯函数(提升出 parseUsageData,避免每次调用重新创建) ──────────────
42
+
43
+ /**
44
+ * 将 Unix 时间戳转换为剩余时间对象
45
+ * @param {string|number|null} timestamp Unix 秒级时间戳
46
+ * @returns {{ ts: number, date: string, hoursUntil: number, minutesUntil: number } | null}
47
+ */
48
+ function formatResetTime(timestamp) {
49
+ if (!timestamp) return null;
50
+ const ts = parseInt(timestamp, 10);
51
+ const date = new Date(ts * 1000);
52
+ const diffMs = date.getTime() - Date.now();
53
+ return {
54
+ ts,
55
+ date: date.toLocaleString(),
56
+ hoursUntil: Math.max(0, Math.floor(diffMs / (1000 * 60 * 60))),
57
+ minutesUntil: Math.max(0, Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60))),
58
+ };
59
+ }
60
+
61
+ /**
62
+ * 将剩余时间对象格式化为可读文本
63
+ * @param {{ hoursUntil: number, minutesUntil: number } | null} resetInfo
64
+ * @returns {string}
65
+ */
66
+ function calcRemainingText(resetInfo) {
67
+ if (!resetInfo) return "未知";
68
+ const { hoursUntil, minutesUntil } = resetInfo;
69
+ return hoursUntil > 0
70
+ ? `${hoursUntil} 小时 ${minutesUntil} 分钟后重置`
71
+ : `${minutesUntil} 分钟后重置`;
72
+ }
73
+
74
+ // ─── Cookie 合并工具函数 ──────────────────────────────────────────────────────
75
+
76
+ /**
77
+ * 将 Set-Cookie 响应头合并到现有 Cookie 字符串中(模拟浏览器行为)。
78
+ * 新值覆盖同名旧值,从而自动延长会话有效期。
79
+ *
80
+ * @param {string} existingCookie 当前 Cookie 字符串("k1=v1; k2=v2" 格式)
81
+ * @param {string[]} setCookieHeaders 响应头中的 Set-Cookie 数组
82
+ * @returns {string} 合并后的 Cookie 字符串
83
+ */
84
+ function mergeCookies(existingCookie, setCookieHeaders) {
85
+ // 1. 解析现有 cookie 为 Map(保持插入顺序)
86
+ const cookieMap = new Map();
87
+ if (existingCookie) {
88
+ existingCookie.split(/;\s*/).forEach((c) => {
89
+ const idx = c.indexOf("=");
90
+ if (idx === -1) return;
91
+ const key = c.slice(0, idx).trim();
92
+ if (key) cookieMap.set(key, c.slice(idx + 1));
93
+ });
94
+ }
95
+
96
+ // 2. 从 Set-Cookie 头中提取 name=value(忽略 Path/Domain/Expires 等属性)
97
+ let updated = false;
98
+ for (const header of setCookieHeaders) {
99
+ // Set-Cookie 格式: "name=value; Path=/; Domain=...; Expires=..."
100
+ const firstPart = header.split(";")[0].trim();
101
+ const idx = firstPart.indexOf("=");
102
+ if (idx === -1) continue;
103
+ const key = firstPart.slice(0, idx).trim();
104
+ const val = firstPart.slice(idx + 1);
105
+ if (!key) continue;
106
+ const oldVal = cookieMap.get(key);
107
+ if (oldVal !== val) {
108
+ cookieMap.set(key, val);
109
+ updated = true;
110
+ }
111
+ }
112
+
113
+ if (!updated) return existingCookie;
114
+
115
+ // 3. 重新拼接
116
+ return Array.from(cookieMap.entries())
117
+ .map(([k, v]) => `${k}=${v}`)
118
+ .join("; ");
119
+ }
120
+
121
+ // ─── StepFunAPI 类 ────────────────────────────────────────────────────────────
122
+
123
+ class StepFunAPI {
124
+ constructor() {
125
+ this.cookie = null;
126
+ this.cache = {
127
+ statusData: null,
128
+ quotaData: null,
129
+ statusTimestamp: 0,
130
+ quotaTimestamp: 0,
131
+ };
132
+ this.loadConfig();
133
+ }
134
+
135
+ /** 是否已配置凭据 */
136
+ isAuthenticated() {
137
+ return Boolean(this.cookie);
138
+ }
139
+
140
+ loadConfig() {
141
+ try {
142
+ if (fs.existsSync(CONFIG_PATH)) {
143
+ const config = JSON.parse(fs.readFileSync(CONFIG_PATH, "utf8"));
144
+ this.cookie = config.cookie || null;
145
+ }
146
+ } catch (error) {
147
+ console.error("Failed to load config:", error.message);
148
+ }
149
+ }
150
+
151
+ saveConfig() {
152
+ try {
153
+ const config = { cookie: this.cookie };
154
+ // 0o600:仅所有者可读写,在 Unix/Mac 上防止其他用户读取 Cookie
155
+ fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), { mode: 0o600 });
156
+ } catch (error) {
157
+ console.error("Failed to save config:", error.message);
158
+ }
159
+ }
160
+
161
+ setCredentials(cookie) {
162
+ if (!cookie || typeof cookie !== "string" || !cookie.trim()) {
163
+ throw new Error("Cookie 不能为空");
164
+ }
165
+ this.cookie = cookie.trim();
166
+ this.saveConfig();
167
+ }
168
+
169
+ /** 从 Cookie 字符串中提取指定字段 */
170
+ extractFromCookie() {
171
+ if (!this.cookie) return { oasisWebid: null };
172
+
173
+ const cookies = {};
174
+ this.cookie.split(/;\s*/).forEach((c) => {
175
+ const idx = c.indexOf("=");
176
+ if (idx === -1) return;
177
+ const key = c.slice(0, idx).trim();
178
+ const val = c.slice(idx + 1);
179
+ if (!key) return;
180
+ try {
181
+ cookies[key] = decodeURIComponent(val);
182
+ } catch {
183
+ cookies[key] = val;
184
+ }
185
+ });
186
+
187
+ return { oasisWebid: cookies["Oasis-Webid"] || null };
188
+ }
189
+
190
+ async makePlatformRequest(endpoint, data = {}) {
191
+ if (!this.cookie) {
192
+ throw new Error(
193
+ 'Missing cookie. Please run "stepfun auth <cookie>" first.\n' +
194
+ "获取方式:\n" +
195
+ "1. 登录 https://platform.stepfun.com/plan-subscribe\n" +
196
+ "2. F12 -> Network -> 刷新页面\n" +
197
+ "3. 点击任意 API 请求 -> Copy -> Copy request headers -> 复制 Cookie 行"
198
+ );
199
+ }
200
+
201
+ const { oasisWebid } = this.extractFromCookie();
202
+ const postData = JSON.stringify(data);
203
+
204
+ const headers = {
205
+ accept: "*/*",
206
+ "content-type": "application/json",
207
+ "oasis-appid": "10300",
208
+ "oasis-platform": "web",
209
+ "oasis-webid": oasisWebid || "",
210
+ origin: STEPFUN_ORIGIN,
211
+ referer: `${STEPFUN_ORIGIN}/plan-subscribe`,
212
+ "user-agent": getPlatformUserAgent(),
213
+ "content-length": Buffer.byteLength(postData),
214
+ Cookie: this.cookie,
215
+ };
216
+
217
+ return new Promise((resolve, reject) => {
218
+ const req = https.request(
219
+ {
220
+ hostname: STEPFUN_HOST, // 与 STEPFUN_ORIGIN 保持同一数据源
221
+ port: 443,
222
+ path: endpoint,
223
+ method: "POST",
224
+ headers,
225
+ rejectUnauthorized: true, // 显式启用 SSL 证书验证
226
+ },
227
+ (res) => {
228
+ let body = "";
229
+ res.on("data", (chunk) => { body += chunk; });
230
+ res.on("end", () => {
231
+ // 自动捕获 Set-Cookie 响应头,合并更新本地 Cookie(模拟浏览器保活)
232
+ const setCookieHeaders = res.headers["set-cookie"];
233
+ if (setCookieHeaders && setCookieHeaders.length > 0 && this.cookie) {
234
+ const merged = mergeCookies(this.cookie, setCookieHeaders);
235
+ if (merged !== this.cookie) {
236
+ this.cookie = merged;
237
+ this.saveConfig();
238
+ }
239
+ }
240
+
241
+ if (res.statusCode === 200) {
242
+ try {
243
+ resolve(JSON.parse(body));
244
+ } catch {
245
+ reject(new Error("Failed to parse response: invalid JSON"));
246
+ }
247
+ } else if (res.statusCode === 401) {
248
+ this.clearCache();
249
+ reject(
250
+ new Error(
251
+ "Cookie 已失效或过期,请重新运行 auth 命令\n" +
252
+ "获取方式: 登录 https://platform.stepfun.com/plan-subscribe → F12 → Network → 复制 Cookie"
253
+ )
254
+ );
255
+ } else {
256
+ reject(new Error(`API error: HTTP ${res.statusCode}`));
257
+ }
258
+ });
259
+ }
260
+ );
261
+
262
+ // 超时保护
263
+ req.setTimeout(CONFIG.REQUEST_TIMEOUT, () => {
264
+ req.destroy();
265
+ reject(new Error(`Request timeout after ${CONFIG.REQUEST_TIMEOUT / 1000}s`));
266
+ });
267
+
268
+ req.on("error", (e) => {
269
+ reject(new Error(`Network error: ${e.message}`));
270
+ });
271
+
272
+ req.write(postData);
273
+ req.end();
274
+ });
275
+ }
276
+
277
+ /**
278
+ * 带缓存的请求封装,消除 getSubscriptionStatus / getQuotaInfo 的重复逻辑
279
+ * @param {'statusData'|'quotaData'} dataKey 缓存数据字段名
280
+ * @param {'statusTimestamp'|'quotaTimestamp'} tsKey 缓存时间戳字段名
281
+ * @param {string} endpoint API 路径
282
+ * @param {boolean} forceRefresh 是否强制跳过缓存
283
+ */
284
+ async _cachedRequest(dataKey, tsKey, endpoint, forceRefresh) {
285
+ const now = Date.now();
286
+ if (
287
+ !forceRefresh &&
288
+ this.cache[dataKey] &&
289
+ now - this.cache[tsKey] < CONFIG.CACHE_TIMEOUT
290
+ ) {
291
+ return this.cache[dataKey];
292
+ }
293
+ const data = await this.makePlatformRequest(endpoint);
294
+ this.cache[dataKey] = data;
295
+ this.cache[tsKey] = now;
296
+ return data;
297
+ }
298
+
299
+ async getSubscriptionStatus(forceRefresh = false) {
300
+ return this._cachedRequest(
301
+ "statusData",
302
+ "statusTimestamp",
303
+ "/api/step.openapi.devcenter.Dashboard/GetStepPlanStatus",
304
+ forceRefresh
305
+ );
306
+ }
307
+
308
+ async getQuotaInfo(forceRefresh = false) {
309
+ return this._cachedRequest(
310
+ "quotaData",
311
+ "quotaTimestamp",
312
+ "/api/step.openapi.devcenter.Dashboard/QueryStepPlanRateLimit",
313
+ forceRefresh
314
+ );
315
+ }
316
+
317
+ // ── 预留扩展区 ──────────────────────────────────────────────────────────────
318
+ // 后续新增接口时,只需:
319
+ // 1. 在 this.cache 里加对应的 data/timestamp 字段
320
+ // 2. 仿照下方模板写一个 get 方法
321
+ // 3. getUsageStatus / parseUsageData 里消费新数据
322
+ //
323
+ // 模板(等 Token 用量明细接口开放后接入):
324
+ //
325
+ // async getTokenUsage(forceRefresh = false) {
326
+ // return this._cachedRequest(
327
+ // "tokenData",
328
+ // "tokenTimestamp",
329
+ // "/api/step.openapi.devcenter.Dashboard/QueryTokenUsage", // 待确认
330
+ // forceRefresh
331
+ // );
332
+ // }
333
+ // ────────────────────────────────────────────────────────────────────────────
334
+
335
+ async getUsageStatus(forceRefresh = false) {
336
+ const [statusData, quotaData] = await Promise.all([
337
+ this.getSubscriptionStatus(forceRefresh),
338
+ this.getQuotaInfo(forceRefresh),
339
+ ]);
340
+ return this.parseUsageData(statusData, quotaData);
341
+ }
342
+
343
+ parseUsageData(statusData, quotaData) {
344
+ const subscription = statusData.subscription || {};
345
+ const planDefinition = statusData.plan_definition || {};
346
+
347
+ const fiveHourLeftRate = quotaData.five_hour_usage_left_rate || 0;
348
+ const weeklyLeftRate = quotaData.weekly_usage_left_rate || 0;
349
+
350
+ const fiveHourUsedPercent = Math.round((1 - fiveHourLeftRate) * 100);
351
+ const weeklyUsedPercent = Math.round((1 - weeklyLeftRate) * 100);
352
+
353
+ const fiveHourReset = formatResetTime(quotaData.five_hour_usage_reset_time);
354
+ const weeklyReset = formatResetTime(quotaData.weekly_usage_reset_time);
355
+
356
+ let expiry = null;
357
+ if (subscription.expired_at) {
358
+ const expiryDate = new Date(parseInt(subscription.expired_at, 10) * 1000);
359
+ const daysRemaining = Math.ceil(
360
+ (expiryDate.getTime() - Date.now()) / (1000 * 3600 * 24)
361
+ );
362
+ expiry = {
363
+ date: expiryDate.toLocaleString(),
364
+ daysRemaining,
365
+ text:
366
+ daysRemaining > 0
367
+ ? `还剩 ${daysRemaining} 天`
368
+ : daysRemaining === 0
369
+ ? "今天到期"
370
+ : `已过期 ${Math.abs(daysRemaining)} 天`,
371
+ };
372
+ }
373
+
374
+ return {
375
+ modelName: planDefinition.support_models?.[0] || "step-3.5-flash",
376
+ planName: subscription.name || "Mini",
377
+ remaining: {
378
+ hours: fiveHourReset?.hoursUntil || 0,
379
+ minutes: fiveHourReset?.minutesUntil || 0,
380
+ text: calcRemainingText(fiveHourReset),
381
+ },
382
+ usage: {
383
+ percentage: fiveHourUsedPercent,
384
+ remaining: Math.round(fiveHourLeftRate * 100),
385
+ },
386
+ weekly: {
387
+ percentage: weeklyUsedPercent,
388
+ remaining: Math.round(weeklyLeftRate * 100),
389
+ text: calcRemainingText(weeklyReset),
390
+ daysUntilReset: weeklyReset?.hoursUntil
391
+ ? Math.floor(weeklyReset.hoursUntil / 24)
392
+ : 0,
393
+ },
394
+ expiry,
395
+ };
396
+ }
397
+
398
+ clearCache() {
399
+ this.cache = {
400
+ statusData: null,
401
+ quotaData: null,
402
+ statusTimestamp: 0,
403
+ quotaTimestamp: 0,
404
+ };
405
+ }
406
+ }
407
+
408
+ module.exports = { StepFunAPI, CONFIG_PATH };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stepfun-status",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "description": "StepFun Token-Plan 使用状态监控工具,支持 Claude Code 状态栏集成",
5
5
  "main": "cli/index.js",
6
6
  "bin": {
package/ARCHITECTURE.md DELETED
@@ -1,175 +0,0 @@
1
- # StepFun Status 项目架构文档
2
-
3
- ## 概述
4
-
5
- StepFun Status 是一个 StepFun Token-Plan 使用状态监控工具,支持 CLI 命令和 Claude Code 状态栏集成。
6
-
7
- **注意**: 此工具需要 StepFun 官方开放用量查询 API 后才能显示完整额度信息。
8
-
9
- ---
10
-
11
- ## API 接口汇总
12
-
13
- ### 1. Platform API (需要浏览器 Cookie)
14
-
15
- **Base URL**: `https://platform.stepfun.com`
16
-
17
- **认证方式**: 完整 Cookie 字符串(包含 `Oasis-Token` 和 `Oasis-Webid`)
18
-
19
- **获取方式**:
20
- 1. 登录 https://platform.stepfun.com/plan-subscribe
21
- 2. F12 → Network 标签
22
- 3. 刷新页面
23
- 4. 点击任意 API 请求(如 `GetStepPlanStatus`)
24
- 5. 滚动到 Request Headers → 找到 `Cookie` 行
25
- 6. 右键 → Copy value
26
-
27
- ---
28
-
29
- #### 1.1 GetStepPlanStatus - 获取订阅信息
30
-
31
- **Endpoint**: `POST /api/step.openapi.devcenter.Dashboard/GetStepPlanStatus`
32
-
33
- **请求体**: `{}`
34
-
35
- **响应格式**:
36
- ```json
37
- {
38
- "status": 1,
39
- "desc": "",
40
- "subscription": {
41
- "plan_type": 4,
42
- "name": "Mini",
43
- "status": 1,
44
- "pay_channel": 1,
45
- "activated_at": "1774255868",
46
- "expired_at": "1776847868",
47
- "auto_renew": false,
48
- "plan_id": "5"
49
- },
50
- "plan_definition": {
51
- "type": 4,
52
- "price": "2500",
53
- "support_models": ["step-3.5-flash"],
54
- "available": true,
55
- "original_price": "4900",
56
- "plan_id": "5"
57
- }
58
- }
59
- ```
60
-
61
- **字段说明**:
62
- - `subscription.name`: 套餐名称 (Mini/Pro等)
63
- - `subscription.activated_at`: 激活时间戳(秒)
64
- - `subscription.expired_at`: 到期时间戳(秒)
65
- - `plan_definition.support_models`: 支持的模型列表
66
-
67
- ---
68
-
69
- #### 1.2 QueryStepPlanRateLimit - 获取用量信息
70
-
71
- **Endpoint**: `POST /api/step.openapi.devcenter.Dashboard/QueryStepPlanRateLimit`
72
-
73
- **请求体**: `{}`
74
-
75
- **响应格式**:
76
- ```json
77
- {
78
- "status": 1,
79
- "desc": "",
80
- "five_hour_usage_left_rate": 0.9999867,
81
- "five_hour_usage_reset_time": "1774324800",
82
- "weekly_usage_left_rate": 0.9103905,
83
- "weekly_usage_reset_time": "1774857600"
84
- }
85
- ```
86
-
87
- **字段说明**:
88
- - `five_hour_usage_left_rate`: 5小时窗口剩余比例 (0-1)
89
- - `five_hour_usage_reset_time`: 5小时窗口重置时间戳(秒)
90
- - `weekly_usage_left_rate`: 每周剩余比例 (0-1)
91
- - `weekly_usage_reset_time`: 每周重置时间戳(秒)
92
-
93
- ---
94
-
95
- ### 2. Developer API (API Key 方式) - [待开放]
96
-
97
- **Base URL**: `https://api.stepfun.com`
98
-
99
- **认证方式**: `Authorization: Bearer <API_KEY>`
100
-
101
- **API Key 获取**: https://platform.stepfun.com/interface-key
102
-
103
- #### 2.1 Models - 模型列表
104
-
105
- **Endpoint**: `GET /v1/models`
106
-
107
- **响应格式**:
108
- ```json
109
- {
110
- "data": [
111
- {
112
- "id": "step-3.5-flash",
113
- "object": "model",
114
- "created": 1769580037,
115
- "owned_by": "stepai"
116
- }
117
- ],
118
- "object": "list"
119
- }
120
- ```
121
-
122
- #### 2.2 Chat Completions - 对话
123
-
124
- **Endpoint**: `POST /v1/chat/completions`
125
-
126
- **请求示例**:
127
- ```json
128
- {
129
- "model": "step-3.5-flash",
130
- "messages": [{"role": "user", "content": "hi"}]
131
- }
132
- ```
133
-
134
- #### 2.3 Usage API - [待 StepFun 官方开放]
135
-
136
- **Endpoint**: `GET /v1/usage` (待确认)
137
-
138
- 此接口将允许使用 API Key 查询账户用量,无需浏览器 Cookie。
139
-
140
- ---
141
-
142
- ## 配置文件
143
-
144
- **路径**: `~/.stepfun-config.json`
145
-
146
- **格式**:
147
- ```json
148
- {
149
- "cookie": "Oasis-Webid=xxx; Oasis-Token=xxx; ..."
150
- }
151
- ```
152
-
153
- ---
154
-
155
- ## 与 MiniMax 的对比
156
-
157
- | 功能 | MiniMax | StepFun |
158
- |------|---------|---------|
159
- | AI API | ✅ OpenAI Compatible | ✅ OpenAI Compatible |
160
- | 用量查询 API | ✅ 有 | ⏳ 待开放 |
161
- | 认证方式 | API Key | Cookie (当前) / API Key (待开放) |
162
- | 配置文件 | `~/.minimax-config.json` | `~/.stepfun-config.json` |
163
-
164
- ---
165
-
166
- ## 页面显示数据来源
167
-
168
- 页面 https://platform.stepfun.com/plan-subscribe 显示的数据:
169
-
170
- ```
171
- 5小时用量: 剩余 100%, 重置时间: 2026-03-24 12:00:00
172
- 每周用量: 剩余 91%, 重置时间: 2026-03-30 16:00:00
173
- ```
174
-
175
- 这些数据来自 `QueryStepPlanRateLimit` 接口。
Binary file