@tsa-group/claude-usage 0.4.0 → 0.4.2
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 +146 -26
- package/dist/doctor.js +21 -0
- package/package.json +5 -3
- package/postinstall.mjs +59 -0
package/README.md
CHANGED
|
@@ -39,48 +39,168 @@ ingest server,彙整成團隊用量儀表。
|
|
|
39
39
|
|
|
40
40
|
## 需求
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
-
|
|
45
|
-
|
|
46
|
-
|
|
42
|
+
| | |
|
|
43
|
+
|---|---|
|
|
44
|
+
| **Node.js** | >= 20(`node -v` 確認) |
|
|
45
|
+
| **Claude 帳號登入** | 需要**帳號登入**才拿得到額度資料。用 API key / Bedrock / Vertex 時,token 明細仍可收集,但沒有 5 小時 / 每週額度 % |
|
|
46
|
+
| **平台** | macOS ✅ 完整支援 Windows ✅ CLI 與桌面版皆支援 Linux ⚠️ 指令可用,背景任務未實作 |
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
# 安裝
|
|
51
|
+
|
|
52
|
+
**三步都一樣**,只有平台細節不同:
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
1. configure 告訴它你們的 ingest server 在哪
|
|
56
|
+
2. install 取得身份 + 註冊 hook + 裝背景任務
|
|
57
|
+
3. status 確認真的在跑
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
管理者會給你 **ingest server 位址**。下面用 `https://<你們的 ingest host>` 代表它。
|
|
47
61
|
|
|
48
62
|
---
|
|
49
63
|
|
|
50
|
-
##
|
|
64
|
+
## macOS
|
|
51
65
|
|
|
52
66
|
```bash
|
|
53
67
|
npm i -g @tsa-group/claude-usage
|
|
54
68
|
|
|
55
|
-
# 1) 指向你們的 ingest server
|
|
56
69
|
claude-usage configure --server https://<你們的 ingest host>
|
|
70
|
+
claude-usage install --dry-run # 先看它會做什麼,不動任何設定
|
|
71
|
+
claude-usage install
|
|
72
|
+
claude-usage status
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
憑證走 **Keychain**(Claude Code 的主儲存)。首次執行可能跳出鑰匙圈授權,允許即可 ——
|
|
76
|
+
背景任務要能在沒有視窗的情況下讀到它。
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Windows
|
|
81
|
+
|
|
82
|
+
### 第 0 步:先確認 PowerShell 不會擋
|
|
83
|
+
|
|
84
|
+
多數 Windows 的執行原則預設是 `Restricted`,會擋掉 npm 產生的 `.ps1` 啟動器 ——
|
|
85
|
+
**安裝成功、一執行就被擋**:
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
因為這個系統上已停用指令碼執行,所以無法載入 ...\npm\claude-usage.ps1
|
|
89
|
+
+ FullyQualifiedErrorId : UnauthorizedAccess (PSSecurityException)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
這不是套件的問題(`cmd.exe` 與 Node 本身都不受影響)。先查現況:
|
|
93
|
+
|
|
94
|
+
```powershell
|
|
95
|
+
Get-ExecutionPolicy
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
| 結果 | 怎麼辦 |
|
|
99
|
+
|---|---|
|
|
100
|
+
| `RemoteSigned` / `Unrestricted` / `Bypass` | 不用處理,直接往下 |
|
|
101
|
+
| `Restricted` / `AllSigned` | 用下面 **A** 或 **B** |
|
|
102
|
+
|
|
103
|
+
**A. 改原則(推薦,免系統管理員、一次搞定)**
|
|
104
|
+
|
|
105
|
+
```powershell
|
|
106
|
+
Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
`RemoteSigned` = 本機與 npm 的腳本可跑,只擋網路下載的未簽章腳本。
|
|
110
|
+
|
|
111
|
+
**B. 不改原則,全程加 `.cmd`**(功能完全一樣)
|
|
112
|
+
|
|
113
|
+
```powershell
|
|
114
|
+
npm.cmd i -g @tsa-group/claude-usage
|
|
115
|
+
claude-usage.cmd configure --server https://<你們的 ingest host>
|
|
116
|
+
claude-usage.cmd install
|
|
117
|
+
claude-usage.cmd status
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
> 公司用 GPO 鎖了機器層級原則時,**A 會被覆寫** —— 直接用 B。
|
|
121
|
+
> 查誰在管:`Get-ExecutionPolicy -List`
|
|
122
|
+
|
|
123
|
+
### 第 1 步:安裝
|
|
124
|
+
|
|
125
|
+
```powershell
|
|
126
|
+
npm i -g @tsa-group/claude-usage
|
|
57
127
|
|
|
58
|
-
|
|
59
|
-
# 先加 --dry-run 可預覽會做什麼、不動任何系統設定
|
|
128
|
+
claude-usage configure --server https://<你們的 ingest host>
|
|
60
129
|
claude-usage install --dry-run
|
|
61
130
|
claude-usage install
|
|
131
|
+
claude-usage status
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
(走 B 方案的人全部加 `.cmd`。)
|
|
135
|
+
|
|
136
|
+
### 你是用終端機的 `claude`,還是桌面版 App?
|
|
137
|
+
|
|
138
|
+
**兩種都支援,不必改用哪一個** —— 但憑證存放位置完全不同,卡住時要知道差別:
|
|
139
|
+
|
|
140
|
+
| | 終端機 CLI | 桌面版 App |
|
|
141
|
+
|---|---|---|
|
|
142
|
+
| 憑證位置 | `%USERPROFILE%\.claude\.credentials.json` | 桌面版自己的加密儲存 |
|
|
143
|
+
| 形式 | 純文字 | DPAPI + AES-256-GCM 加密 |
|
|
144
|
+
| 需要什麼 | 跑過 `claude` 並完成登入 | 桌面版登入過 |
|
|
145
|
+
|
|
146
|
+
**只用桌面版、從沒登入過終端機 `claude` 的人也能用** —— 工具會自動去解桌面版的憑證庫
|
|
147
|
+
(見「隱私」段)。解密只在你自己的機器、你自己的 Windows 帳號下進行。
|
|
148
|
+
|
|
149
|
+
不想走那條路的話,在終端機跑一次 `claude` 登入即可,工具會優先用那份純文字憑證。
|
|
150
|
+
|
|
151
|
+
---
|
|
62
152
|
|
|
63
|
-
|
|
153
|
+
## 確認安裝成功
|
|
154
|
+
|
|
155
|
+
```bash
|
|
64
156
|
claude-usage status
|
|
65
157
|
```
|
|
66
158
|
|
|
67
|
-
|
|
159
|
+
成功長這樣(重點是標示的那三行):
|
|
160
|
+
|
|
161
|
+
```
|
|
162
|
+
state dir: /Users/you/.claude-usage
|
|
163
|
+
server: https://<你們的 ingest host>
|
|
164
|
+
enrolled: yes <-- 拿到身份了
|
|
165
|
+
hook: registered <-- 開 session 時會自動採樣
|
|
166
|
+
last_sample_status : ok
|
|
167
|
+
last_upload_state : uploaded
|
|
168
|
+
status : ok <-- 採集正常
|
|
169
|
+
state = not running (背景任務平常就是閒置,每 5 分鐘才醒一次)
|
|
170
|
+
runs = 12
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
`state = not running` 是**正常的** —— 背景任務不是常駐程式,每 5 分鐘被叫醒一次。
|
|
174
|
+
`runs` 有在累加就代表它真的有跑。
|
|
175
|
+
|
|
176
|
+
`status` 在採集不健康時會 **exit 1**,可以接監控。
|
|
177
|
+
|
|
178
|
+
**接著還有一步不在你這邊**:若你們的 server 開了人工核准,`enroll` 會回
|
|
179
|
+
`status: pending` —— 你這邊一切正常,但要等管理者按核准,資料才會開始進去。
|
|
180
|
+
**這是正常的,不是裝壞了。**
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## 卡住了?先跑這個
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
claude-usage doctor # Windows 走 B 方案的人:claude-usage.cmd doctor
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
它會印出**解析到的每個路徑,與那裡實際有什麼** —— 路徑對不對通常你一眼就看得出來
|
|
191
|
+
(「那不是我 Claude Code 裝的地方」),前提是有人把路徑印出來。
|
|
68
192
|
|
|
69
|
-
|
|
70
|
-
開始進來。`status` 會顯示已 enroll、心跳正常,但 server 端看不到你的用量。這是正常的。
|
|
71
|
-
- **server 要求 enroll 密語**(多數部署不需要)→ `install --enroll-secret <密語>`,或用
|
|
72
|
-
環境變數 `CLAUDE_USAGE_ENROLL_SECRET`。⚠️ 寫在指令列會留在你的 shell history 裡,
|
|
73
|
-
能用環境變數就用環境變數。
|
|
193
|
+
把整段輸出貼給管理者即可,裡面**不含任何 token 值**。
|
|
74
194
|
|
|
75
|
-
|
|
195
|
+
常見症狀對照:
|
|
76
196
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
197
|
+
| 症狀 | 多半是 |
|
|
198
|
+
|---|---|
|
|
199
|
+
| `UnauthorizedAccess` / `PSSecurityException` | PowerShell 執行原則,見 Windows 第 0 步 |
|
|
200
|
+
| `cannot read credentials (… file-no-oauth-key …)` | 只用桌面版、沒登入過終端機 `claude`。0.4.0 起會自動去讀桌面版憑證;若仍失敗,`doctor` 會指出卡在哪一步 |
|
|
201
|
+
| `access token EXPIRED` | 重新登入 Claude Code(終端機 `/login` 或桌面版)即可換發 |
|
|
202
|
+
| `status` 一切正常但 server 看不到我的用量 | 等管理者核准(見上) |
|
|
203
|
+
| `沒有任何 .jsonl` | 本工具與 Claude Code 不在同一個環境(WSL?另一個帳號?`CLAUDE_CONFIG_DIR`?) |
|
|
84
204
|
|
|
85
205
|
---
|
|
86
206
|
|
|
@@ -222,8 +342,8 @@ Client 對 server 只用兩個端點,皆為 `application/json`:
|
|
|
222
342
|
風險類別。失效時會在 `claude-usage doctor` 顯示卡在哪一步,不會靜默。
|
|
223
343
|
- **Windows 背景任務未完整實機驗證**:schtasks + VBS 隱藏視窗啟動器。裝完請用
|
|
224
344
|
`claude-usage status` 確認有心跳。
|
|
225
|
-
- **PowerShell
|
|
226
|
-
|
|
345
|
+
- **PowerShell 執行原則**:預設 `Restricted` 會同時擋掉 `npm.ps1`(安裝時)與
|
|
346
|
+
`claude-usage.ps1`(執行時)。處理方式見上面 **Windows → 第 0 步**。
|
|
227
347
|
- **Linux 背景任務未實作**(systemd --user timer)。其餘指令可用。
|
|
228
348
|
- **`session_id` 跨 compaction / resume 不穩定**:session **數**會高估。token 與成本不受影響
|
|
229
349
|
(那是 per-event 去重的,與 session 身份無關)。
|
package/dist/doctor.js
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* 路徑對不對是使用者一眼就能判斷的事(「那不是我的 Claude Code 裝的地方」),
|
|
10
10
|
* 但前提是我們得把路徑印出來。
|
|
11
11
|
*/
|
|
12
|
+
import { spawnSync } from "node:child_process";
|
|
12
13
|
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
13
14
|
import { homedir, platform, release, userInfo } from "node:os";
|
|
14
15
|
import { join } from "node:path";
|
|
@@ -87,6 +88,26 @@ export async function doctor() {
|
|
|
87
88
|
out.push(info(`憑證主儲存 ${isMac ? "macOS Keychain(檔案只是副本,可能過時)" : "檔案"}`));
|
|
88
89
|
out.push(info(`憑證檔 ${cp} -> ${existsSync(cp) ? `存在 ${statSync(cp).size} bytes` : "不存在"}` +
|
|
89
90
|
(!existsSync(cp) && !isMac ? " <- 這個平台只有檔案這條路,所以是問題" : "")));
|
|
91
|
+
// ── Windows:PowerShell 執行原則 ──
|
|
92
|
+
// 這個問題有個特別惡劣的性質:**它發生時我們的程式碼根本沒機會執行** ——
|
|
93
|
+
// PowerShell 在 Node 啟動前就拒絕載入 claude-usage.ps1。所以工具無法在出錯當下
|
|
94
|
+
// 自我診斷,只能在 postinstall 事先講、或在這裡(使用者改用 .cmd 跑得起來時)補講。
|
|
95
|
+
if (platform() === "win32") {
|
|
96
|
+
const r = spawnSync("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", "Get-ExecutionPolicy"], { encoding: "utf8", timeout: 10_000, windowsHide: true });
|
|
97
|
+
const policy = r.error || r.status !== 0 ? null : (r.stdout ?? "").trim();
|
|
98
|
+
if (!policy) {
|
|
99
|
+
out.push(info("執行原則 查不到(powershell.exe 不可用?)"));
|
|
100
|
+
}
|
|
101
|
+
else if (policy === "Restricted" || policy === "AllSigned") {
|
|
102
|
+
out.push(bad(`執行原則 ${policy} —— PowerShell 會擋掉 claude-usage.ps1 啟動器`));
|
|
103
|
+
problems.push(`PowerShell 執行原則是 ${policy}(錯誤含 UnauthorizedAccess / ExecutionPolicy)。` +
|
|
104
|
+
`擇一:Set-ExecutionPolicy -Scope CurrentUser RemoteSigned(免管理員),` +
|
|
105
|
+
`或直接用 claude-usage.cmd。公司 GPO 鎖機器層級時只能用後者`);
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
out.push(ok(`執行原則 ${policy}(不會擋 .ps1 啟動器)`));
|
|
109
|
+
}
|
|
110
|
+
}
|
|
90
111
|
// ── Windows 桌面版(MSIX)──
|
|
91
112
|
// 只用桌面版、沒登入過 CLI 的人,.credentials.json 裡根本不會有 claudeAiOauth。
|
|
92
113
|
// 這一段把「桌面版憑證庫在哪、有沒有東西」攤開,否則診斷又會退回「猜 + 手寫指令」。
|
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.2",
|
|
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": {
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
10
|
"dist",
|
|
11
|
-
"README.md"
|
|
11
|
+
"README.md",
|
|
12
|
+
"postinstall.mjs"
|
|
12
13
|
],
|
|
13
14
|
"engines": {
|
|
14
15
|
"node": ">=20"
|
|
@@ -17,7 +18,8 @@
|
|
|
17
18
|
"build": "tsc -p tsconfig.json",
|
|
18
19
|
"test": "node --test test/*.test.ts",
|
|
19
20
|
"prepublishOnly": "npm run build && npm test",
|
|
20
|
-
"check": "tsc -p tsconfig.json --noEmit"
|
|
21
|
+
"check": "tsc -p tsconfig.json --noEmit",
|
|
22
|
+
"postinstall": "node postinstall.mjs"
|
|
21
23
|
},
|
|
22
24
|
"keywords": [
|
|
23
25
|
"claude",
|
package/postinstall.mjs
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 安裝後提示:Windows 的 PowerShell 執行原則會擋掉 npm 產生的 .ps1 啟動器。
|
|
3
|
+
*
|
|
4
|
+
* ★ 為什麼一定要在這裡講:這個失敗發生時**我們的程式碼根本沒機會執行** ——
|
|
5
|
+
* PowerShell 在 Node 啟動前就拒絕載入 `claude-usage.ps1` 了。所以工具永遠無法在
|
|
6
|
+
* 出錯當下自我診斷,提示只能前置,而 postinstall 是唯一「撞牆之前還能說話」的
|
|
7
|
+
* 時機(它由 Node 執行,不受 .ps1 原則管制)。
|
|
8
|
+
*
|
|
9
|
+
* ★ 為什麼要先偵測而不是無條件印:無條件每次安裝都印一段警告,就是在訓練人忽略
|
|
10
|
+
* 它 —— 等真的出事那次也會被跳過。只在**實際會擋**的機器上印,訊息才有訊號價值。
|
|
11
|
+
*
|
|
12
|
+
* ★ 為什麼這支檔案刻意獨立、不 import dist/:postinstall 失敗會讓整個安裝失敗。
|
|
13
|
+
* 它必須在 dist 壞掉、Node 版本奇怪、或任何預期外的狀況下都安靜地成功。
|
|
14
|
+
* 與 doctor 裡那份偵測有少量重複,那是刻意的隔離。
|
|
15
|
+
*/
|
|
16
|
+
import { spawnSync } from "node:child_process";
|
|
17
|
+
|
|
18
|
+
// 這兩種原則會擋掉 .ps1 啟動器。RemoteSigned / Unrestricted / Bypass 都不會。
|
|
19
|
+
const BLOCKING = new Set(["Restricted", "AllSigned"]);
|
|
20
|
+
|
|
21
|
+
function effectivePolicy() {
|
|
22
|
+
const r = spawnSync("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", "Get-ExecutionPolicy"], {
|
|
23
|
+
encoding: "utf8",
|
|
24
|
+
timeout: 10_000,
|
|
25
|
+
windowsHide: true,
|
|
26
|
+
});
|
|
27
|
+
if (r.error || r.status !== 0) return null;
|
|
28
|
+
return (r.stdout ?? "").trim() || null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
if (process.platform === "win32") {
|
|
33
|
+
const policy = effectivePolicy();
|
|
34
|
+
if (policy && BLOCKING.has(policy)) {
|
|
35
|
+
const L = [
|
|
36
|
+
"",
|
|
37
|
+
` ⚠ PowerShell 執行原則是 ${policy} —— 在 PowerShell 直接打 claude-usage 會被擋`,
|
|
38
|
+
" (錯誤訊息含 UnauthorizedAccess / PSSecurityException / ExecutionPolicy)",
|
|
39
|
+
"",
|
|
40
|
+
" 擇一處理:",
|
|
41
|
+
" 1) 設定一次、永久生效、免系統管理員:",
|
|
42
|
+
" Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned",
|
|
43
|
+
" RemoteSigned = 本機與 npm 的腳本可跑,只擋網路下載的未簽章腳本。",
|
|
44
|
+
" 2) 不改原則,改叫 .cmd 啟動器(功能完全一樣):",
|
|
45
|
+
" claude-usage.cmd status",
|
|
46
|
+
"",
|
|
47
|
+
" 公司用 GPO 鎖了機器層級原則時,(1) 會被覆寫 —— 直接用 (2)。",
|
|
48
|
+
" 現況查詢: Get-ExecutionPolicy -List",
|
|
49
|
+
"",
|
|
50
|
+
];
|
|
51
|
+
console.log(L.join("\n"));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
} catch {
|
|
55
|
+
// 提示壞掉絕不能讓安裝失敗
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// 無論如何都成功收場
|
|
59
|
+
process.exitCode = 0;
|