tokenforbes-cli 0.1.2 → 0.1.3
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/bin.js +40 -19
- package/package.json +1 -1
package/bin.js
CHANGED
|
@@ -30,9 +30,8 @@ if (cmd === 'login') {
|
|
|
30
30
|
} else if (cmd === 'report') {
|
|
31
31
|
await report();
|
|
32
32
|
} else if (!cmd) {
|
|
33
|
-
//
|
|
34
|
-
|
|
35
|
-
if (key) await report(); else await link();
|
|
33
|
+
// 一条命令走完:report 内部会兜底——没密钥或密钥失效(服务器重置过)都自动带去登录再上报。
|
|
34
|
+
await report();
|
|
36
35
|
} else {
|
|
37
36
|
console.log('命令: link | register <昵称> | login <密钥> | report | daemon <install|status|uninstall>');
|
|
38
37
|
}
|
|
@@ -44,12 +43,12 @@ async function link() {
|
|
|
44
43
|
const device = detectDevice();
|
|
45
44
|
const s = await post(server + '/api/device/start', { device });
|
|
46
45
|
const url = `${server}/link.html?code=${s.user_code}`;
|
|
47
|
-
console.log('\n
|
|
48
|
-
console.log('
|
|
49
|
-
console.log('
|
|
46
|
+
console.log('\n正在打开浏览器让你确认登录…没弹出来就手动复制下面这条链接:');
|
|
47
|
+
console.log(' → ' + url);
|
|
48
|
+
console.log(' 验证码: ' + s.user_code + '(网页上核对一致再点确认)\n');
|
|
50
49
|
openBrowser(url);
|
|
51
50
|
|
|
52
|
-
process.stdout.write('
|
|
51
|
+
process.stdout.write('已在浏览器等你点确认…');
|
|
53
52
|
const until = Date.now() + s.expires_in * 1000;
|
|
54
53
|
while (Date.now() < until) {
|
|
55
54
|
await sleep(s.interval * 1000);
|
|
@@ -58,7 +57,7 @@ async function link() {
|
|
|
58
57
|
c.key = p.key; c.server = server; saveConfig(c);
|
|
59
58
|
console.log('\n✓ 已批准,密钥已存到 ' + CONFIG);
|
|
60
59
|
console.log('\n顺手把你的本地用量传上去:\n');
|
|
61
|
-
await report(); //
|
|
60
|
+
await report({ allowRelink: false }); // 登录即上报,一条命令闭环;刚拿到新密钥,别再触发二次登录
|
|
62
61
|
console.log('\n想让它每半小时自动后台上报? npm i -g tokenforbes-cli 后跑 tokenforbes daemon install');
|
|
63
62
|
return;
|
|
64
63
|
}
|
|
@@ -88,31 +87,53 @@ async function register(nickname) {
|
|
|
88
87
|
console.log('现在跑 tokenforbes report 上报你的用量。');
|
|
89
88
|
}
|
|
90
89
|
|
|
91
|
-
async function report() {
|
|
90
|
+
async function report({ allowRelink = true } = {}) {
|
|
92
91
|
const c = loadConfig();
|
|
93
|
-
|
|
92
|
+
// 没密钥:直接带去登录(登录成功会自动上报),不再让用户对着报错发愣。
|
|
93
|
+
if (!c.key) {
|
|
94
|
+
if (allowRelink) return link();
|
|
95
|
+
die('还没登录。先跑 npx tokenforbes-cli (浏览器一键登录),或 register / login');
|
|
96
|
+
}
|
|
94
97
|
const server = c.server || DEFAULT_SERVER;
|
|
95
|
-
console.log('
|
|
98
|
+
console.log('正在读取本地用量(ccusage)…');
|
|
96
99
|
const days = readUsage();
|
|
97
|
-
if (!days.length) return console.log('
|
|
100
|
+
if (!days.length) return console.log('没读到用量数据(这台电脑还没用过 Claude Code / Codex?)。');
|
|
98
101
|
const device = detectDevice();
|
|
99
102
|
const total = days.reduce((s, d) => s + d.input + d.output + (d.cacheCreation || 0) + (d.cacheRead || 0), 0);
|
|
100
103
|
console.log(`设备:${device}`);
|
|
101
|
-
console.log(`共 ${days.length} 天,合计约 ${Math.round(total / 1e4).toLocaleString()} 万 token
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
+
console.log(`共 ${days.length} 天,合计约 ${Math.round(total / 1e4).toLocaleString()} 万 token,正在上传…`);
|
|
105
|
+
const res = await postRaw(server + '/api/report', { key: c.key, device, days });
|
|
106
|
+
if (res.ok) {
|
|
107
|
+
console.log(`上报成功 ✓ 已记录 ${res.json.days} 天。去 ${server} 看你的排名。`);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
// 密钥失效(最常见:服务器重置过用户库,旧密钥查无此人):清掉旧密钥,自动带去重新登录再上报。
|
|
111
|
+
const authFailed = res.status === 401 || /密钥/.test(res.json?.error || '');
|
|
112
|
+
if (authFailed && allowRelink) {
|
|
113
|
+
console.log('\n本地密钥已失效(服务器可能重置过数据),带你重新登录一次…\n');
|
|
114
|
+
delete c.key; saveConfig(c);
|
|
115
|
+
return link();
|
|
116
|
+
}
|
|
117
|
+
die('上报失败:' + (res.json?.error || res.netError || res.status));
|
|
104
118
|
}
|
|
105
119
|
|
|
120
|
+
// 会抛错就 die 的版本:给登录/注册这类「失败就该停」的调用用。
|
|
106
121
|
async function post(url, body) {
|
|
122
|
+
const res = await postRaw(url, body);
|
|
123
|
+
if (!res.ok) die('失败:' + (res.json?.error || res.netError || res.status));
|
|
124
|
+
return res.json;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// 不 die、把结果原样返回:给 report 这类「失败要自己判断怎么办」的调用用。
|
|
128
|
+
async function postRaw(url, body) {
|
|
107
129
|
let res;
|
|
108
130
|
try {
|
|
109
131
|
res = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) });
|
|
110
132
|
} catch (e) {
|
|
111
|
-
|
|
133
|
+
return { ok: false, netError: `连不上服务器(${url}):${e.message}` };
|
|
112
134
|
}
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
return j;
|
|
135
|
+
const json = await res.json().catch(() => ({}));
|
|
136
|
+
return { ok: res.ok, status: res.status, json };
|
|
116
137
|
}
|
|
117
138
|
|
|
118
139
|
function die(msg) { console.error(msg); process.exit(1); }
|