@tsa-group/claude-usage 0.4.5 → 0.4.6
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 -2
- package/dist/install.js +75 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -340,8 +340,13 @@ Client 對 server 只用兩個端點,皆為 `application/json`:
|
|
|
340
340
|
`oauth:tokenCacheV2`,以 Chromium 的 `v10` 方案(DPAPI + AES-256-GCM)加密。
|
|
341
341
|
這是**未公開的內部格式**,Anthropic 改版就可能失效 —— 與 `/api/oauth/usage` 同一個
|
|
342
342
|
風險類別。失效時會在 `claude-usage doctor` 顯示卡在哪一步,不會靜默。
|
|
343
|
-
- **Windows
|
|
344
|
-
|
|
343
|
+
- **Windows 筆電請確認電源設定**:`schtasks` 建立的工作預設「只在接上電源時執行」、
|
|
344
|
+
「拔電就中斷」、且「錯過的排程不補跑」。v0.4.6 起 `install` 會自動放寬這三項並讀回
|
|
345
|
+
驗證;**v0.4.5 以前註冊的工作不會自動修正**,請重跑一次 `claude-usage install`。
|
|
346
|
+
`claude-usage status` 會在設定不對時明講。
|
|
347
|
+
(實測:2026-08-29 四台 Win11 因此全數斷線 10.7–18.1 小時,其中一台累計只跑過 7 次。)
|
|
348
|
+
- **重疊的 tick 尚未互斥**:VBS 以非阻塞方式啟動 node,工作排程器認為任務瞬間完成,
|
|
349
|
+
所以 `MultipleInstances` 擋不住重疊的 node 行程。極少數情況下會競寫 `health.json`。
|
|
345
350
|
- **PowerShell 執行原則**:預設 `Restricted` 會同時擋掉 `npm.ps1`(安裝時)與
|
|
346
351
|
`claude-usage.ps1`(執行時)。處理方式見上面 **Windows → 第 0 步**。
|
|
347
352
|
- **Linux 背景任務未實作**(systemd --user timer)。其餘指令可用。
|
package/dist/install.js
CHANGED
|
@@ -90,9 +90,57 @@ function macStatus() {
|
|
|
90
90
|
return 0;
|
|
91
91
|
}
|
|
92
92
|
// ────────────────────────────── Windows ──────────────────────────────
|
|
93
|
-
//
|
|
93
|
+
// 實機狀態(2026-08-29,四台 Win11 10.0.26200):
|
|
94
|
+
// ✅ 已驗證:工作註冊成功、VBS 隱藏視窗可用、tick 會執行並成功上傳
|
|
95
|
+
// ✅ 已修正:電源預設值害它在電池下不執行(見 winHardenTask)
|
|
96
|
+
// ⚠️ 未驗證:winHardenTask 本身 —— 寫完當下沒有 Windows 機器可測,所以它**讀回設定
|
|
97
|
+
// 並印出結果**,讓第一個安裝的人一眼看得出有沒有生效。
|
|
98
|
+
//
|
|
99
|
+
// ⚠️ 已知且**本次未解決**:VBS 用 s.Run(..., 0, False) 非阻塞啟動 node,wscript 立刻
|
|
100
|
+
// 返回,所以工作排程器認為任務瞬間完成。這代表 -MultipleInstances IgnoreNew
|
|
101
|
+
// 擋不住重疊的 **node** 行程(它擋的是重疊的「工作」,而工作早就結束了)。
|
|
102
|
+
// 重疊的 tick 會競寫 health.json —— 那正是先前 consecutive_failures 卡在 152 的成因。
|
|
103
|
+
// 要真正解決得在 daemon 內加檔案鎖,不屬於這次的範圍。
|
|
94
104
|
/** 隱藏視窗的啟動器:node 沒有 pythonw 那種無 console 的變體,用 VBS 包一層是標準做法。 */
|
|
95
105
|
const VBS_PATH = () => join(stateDir(), "run-hidden.vbs");
|
|
106
|
+
/**
|
|
107
|
+
* schtasks.exe 建立的工作有三個**預設值會讓它在筆電上幾乎不執行**,而 schtasks 沒有
|
|
108
|
+
* 任何旗標可以覆蓋它們 —— 只能事後用 PowerShell 的 ScheduledTasks cmdlet 改。
|
|
109
|
+
*
|
|
110
|
+
* DisallowStartIfOnBatteries = True → 靠電池時**根本不啟動**
|
|
111
|
+
* StopIfGoingOnBatteries = True → 執行中拔掉電源就中斷
|
|
112
|
+
* StartWhenAvailable = False → 錯過的排程不補跑,開機後不會自己追上
|
|
113
|
+
*
|
|
114
|
+
* ★ 這不是臆測,是實機資料逼出來的:2026-08-29 四台 Windows 全數斷線 10.7–18.1
|
|
115
|
+
* 小時,其中一台安裝後累計只跑過 7 次 tick(約 35 分鐘)。同一時間唯一存活的是
|
|
116
|
+
* macOS 那台(launchd 沒有這些預設)。症狀完全吻合「大部分時間用電池」。
|
|
117
|
+
*
|
|
118
|
+
* ★ 為什麼要讀回來驗證:我們沒有 Windows 機器可以測這段。`Set-ScheduledTask` 失敗
|
|
119
|
+
* 時未必回非零,所以不能只看 rc —— 讓它自己把結果印出來,成不成一眼可見。
|
|
120
|
+
*
|
|
121
|
+
* 失敗不是致命的:工作本身還在,只是接上電源時才會跑。所以回 boolean 由呼叫端決定
|
|
122
|
+
* 要不要當成錯誤,而不是在這裡 return 1 把整個 install 判定成失敗。
|
|
123
|
+
*/
|
|
124
|
+
function winHardenTask() {
|
|
125
|
+
const cmd = `$ErrorActionPreference='Stop';` +
|
|
126
|
+
`Set-ScheduledTask -TaskName '${WIN_TASK}' -Settings (New-ScheduledTaskSettingsSet ` +
|
|
127
|
+
`-AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable ` +
|
|
128
|
+
`-MultipleInstances IgnoreNew -ExecutionTimeLimit (New-TimeSpan -Minutes 10)) | Out-Null;` +
|
|
129
|
+
`$s=(Get-ScheduledTask -TaskName '${WIN_TASK}').Settings;` +
|
|
130
|
+
`"$($s.DisallowStartIfOnBatteries)|$($s.StopIfGoingOnBatteries)|$($s.StartWhenAvailable)"`;
|
|
131
|
+
const r = spawnSync("powershell.exe", ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", cmd], { encoding: "utf8", timeout: 20_000, windowsHide: true });
|
|
132
|
+
const got = (r.stdout ?? "").trim();
|
|
133
|
+
// 期望 False|False|True。任何其他值都代表沒真的套用上去。
|
|
134
|
+
if (r.status === 0 && got === "False|False|True") {
|
|
135
|
+
console.log("schtasks: 已放寬電源限制(電池下照跑、錯過會補跑)");
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
console.error(`! 電源設定沒有套用成功(rc=${r.status}, 讀回 "${got || "無輸出"}")。\n` +
|
|
139
|
+
" 工作仍然存在,但**只有接上電源時才會執行**。手動補上(單行,別換行):\n" +
|
|
140
|
+
` Set-ScheduledTask -TaskName ${WIN_TASK} -Settings (New-ScheduledTaskSettingsSet ` +
|
|
141
|
+
"-AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable)");
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
96
144
|
function winInstall() {
|
|
97
145
|
const vbs = `Set s = CreateObject("Wscript.Shell")\r\n` +
|
|
98
146
|
`s.Run """${NODE}"" ""${CLI}"" daemon-tick", 0, False\r\n`;
|
|
@@ -110,6 +158,7 @@ function winInstall() {
|
|
|
110
158
|
console.log(`wrote ${VBS_PATH()}`);
|
|
111
159
|
if (r.status === 0) {
|
|
112
160
|
console.log(`schtasks: 已建立工作 "${WIN_TASK}",每 ${Math.max(1, Math.floor(BASE_INTERVAL_SEC / 60))} 分鐘執行一次`);
|
|
161
|
+
winHardenTask(); // 建立完才能改設定;失敗只警告,工作本身仍可用
|
|
113
162
|
}
|
|
114
163
|
else {
|
|
115
164
|
// 失敗才印原始輸出。用 latin1 至少保證位元組不失真,並告訴使用者怎麼看到真訊息。
|
|
@@ -119,9 +168,8 @@ function winInstall() {
|
|
|
119
168
|
console.error(` 手動執行這行可看到正確訊息: schtasks ${args.map((a) => (a.includes(" ") ? `"${a}"` : a)).join(" ")}`);
|
|
120
169
|
}
|
|
121
170
|
console.log(`uninstall: schtasks /Delete /TN ${WIN_TASK} /F`);
|
|
122
|
-
console.log("!
|
|
123
|
-
"
|
|
124
|
-
" 裝完請跑 claude-usage status 確認有心跳。");
|
|
171
|
+
console.log("! 裝完請跑 claude-usage status 確認有心跳,並確認電源設定那一行沒有警告。\n" +
|
|
172
|
+
" 若你是筆電使用者,這一步特別重要 —— 舊版的排程在電池下不會執行。");
|
|
125
173
|
return r.status ?? 1;
|
|
126
174
|
}
|
|
127
175
|
function winUninstall() {
|
|
@@ -150,18 +198,39 @@ function winStatus() {
|
|
|
150
198
|
`$i=Get-ScheduledTaskInfo -TaskName '${WIN_TASK}';` +
|
|
151
199
|
`$last=if($i.LastRunTime){$i.LastRunTime.ToString('yyyy-MM-dd HH:mm')}else{'-'};` +
|
|
152
200
|
`$next=if($i.NextRunTime){$i.NextRunTime.ToString('yyyy-MM-dd HH:mm')}else{'-'};` +
|
|
153
|
-
|
|
201
|
+
// 電源設定一併帶回來:已經裝好的機器不會再跑 install,若不在 status 顯示,
|
|
202
|
+
// 那些工作會永遠停在「只有插電才跑」而沒有任何地方看得出來。
|
|
203
|
+
`$s=$t.Settings;` +
|
|
204
|
+
`"$($t.State)|$($i.LastTaskResult)|$last|$next` +
|
|
205
|
+
`|$($s.DisallowStartIfOnBatteries)|$($s.StopIfGoingOnBatteries)|$($s.StartWhenAvailable)"}`;
|
|
154
206
|
const r = spawnSync("powershell.exe", ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", cmd], { encoding: "utf8", timeout: 15_000, windowsHide: true });
|
|
155
207
|
const line = (r.stdout ?? "").trim();
|
|
156
208
|
if (r.status !== 0 || !line || line === "MISSING") {
|
|
157
209
|
console.log(`bg-task: NOT installed —— 跑 claude-usage install 重新註冊`);
|
|
158
210
|
return 1;
|
|
159
211
|
}
|
|
160
|
-
const [state, resultRaw, last, next] = line.split("|");
|
|
212
|
+
const [state, resultRaw, last, next, noBatt, stopBatt, catchUp] = line.split("|");
|
|
161
213
|
const result = Number(resultRaw);
|
|
162
214
|
const meaning = TASK_RESULT[result] ?? `結束碼 ${resultRaw}`;
|
|
163
215
|
console.log(`bg-task: 已註冊 "${WIN_TASK}" 狀態=${state} 上次=${last} 下次=${next}`);
|
|
164
216
|
console.log(` ${meaning}`);
|
|
217
|
+
// 舊版 client 註冊的工作沒有放寬電源限制 —— 那會表現成「白天在辦公室有心跳、
|
|
218
|
+
// 拔線後整段消失」。這裡把它講成可行動的一句話,而不是留給人去猜為什麼會斷。
|
|
219
|
+
// 欄位可能不存在(極舊的 PowerShell 或未來格式變動)→ 缺欄位就安靜跳過。
|
|
220
|
+
if (noBatt !== undefined) {
|
|
221
|
+
const bad = [];
|
|
222
|
+
if (noBatt === "True")
|
|
223
|
+
bad.push("靠電池時不啟動");
|
|
224
|
+
if (stopBatt === "True")
|
|
225
|
+
bad.push("拔電源就中斷");
|
|
226
|
+
if (catchUp === "False")
|
|
227
|
+
bad.push("錯過的排程不補跑");
|
|
228
|
+
if (bad.length) {
|
|
229
|
+
console.log(` ! 電源設定會讓它在筆電上幾乎不執行:${bad.join("、")}`);
|
|
230
|
+
console.log(` 修正: claude-usage install (會就地重新註冊並放寬限制)`);
|
|
231
|
+
return 1;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
165
234
|
// 只有「成功」與「還沒跑過」算正常;其餘代表背景任務其實沒在做事。
|
|
166
235
|
if (result !== 0 && result !== 267011 && result !== 267009) {
|
|
167
236
|
console.log(` ! 背景任務上次沒有正常結束 —— 細節: schtasks /Query /TN ${WIN_TASK} /V /FO LIST`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tsa-group/claude-usage",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.6",
|
|
4
4
|
"description": "Per-user Claude usage collector — measures Claude Code token detail and account-level rate-limit utilization locally, reports to your own ingest server.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|