@indigoai-us/hq-cli 5.105.0 → 5.105.1
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/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.105.1] — 2026-08-31
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Token usage reports now automatically include Claude activity stored in
|
|
10
|
+
named profiles such as `.claude-ridge`, with no extra setting required.
|
|
11
|
+
|
|
5
12
|
## [5.105.0] — 2026-08-31
|
|
6
13
|
|
|
7
14
|
### Changed
|
|
@@ -2,6 +2,8 @@ import { type UtilityIo } from "./common.js";
|
|
|
2
2
|
export type TokenUsageReportOptions = UtilityIo & {
|
|
3
3
|
projectDir?: string;
|
|
4
4
|
now?: Date;
|
|
5
|
+
homeDir?: string;
|
|
6
|
+
menubarPath?: string;
|
|
5
7
|
};
|
|
6
8
|
/** Print token usage totals from Claude session JSONL files. */
|
|
7
9
|
export declare function tokenUsageReport(args?: string[], options?: TokenUsageReportOptions): number;
|
|
@@ -11,12 +11,27 @@ const parseJsonl = (file) => fs.readFileSync(file, "utf8").split(/\r?\n/).flatMa
|
|
|
11
11
|
catch {
|
|
12
12
|
return [];
|
|
13
13
|
} });
|
|
14
|
-
const listJsonl = (dir) => {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
14
|
+
const listJsonl = (dir) => {
|
|
15
|
+
const found = [];
|
|
16
|
+
const visit = (current, relative) => {
|
|
17
|
+
let entries;
|
|
18
|
+
try {
|
|
19
|
+
entries = fs.readdirSync(current, { withFileTypes: true });
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
for (const entry of entries) {
|
|
25
|
+
const nextRelative = path.join(relative, entry.name);
|
|
26
|
+
if (entry.isDirectory() && entry.name !== "subagents")
|
|
27
|
+
visit(path.join(current, entry.name), nextRelative);
|
|
28
|
+
else if (entry.isFile() && entry.name.endsWith(".jsonl"))
|
|
29
|
+
found.push(nextRelative);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
visit(dir, "");
|
|
33
|
+
return found.sort();
|
|
34
|
+
};
|
|
20
35
|
const readNumber = (value) => typeof value === "number" ? value : 0;
|
|
21
36
|
function firstUser(record) { const content = record.message?.content; if (typeof content === "string")
|
|
22
37
|
return content.slice(0, 120); if (Array.isArray(content) && content.length) {
|
|
@@ -24,10 +39,42 @@ function firstUser(record) { const content = record.message?.content; if (typeof
|
|
|
24
39
|
if (first && typeof first === "object" && typeof first.text === "string")
|
|
25
40
|
return first.text.slice(0, 120);
|
|
26
41
|
} return ""; }
|
|
27
|
-
function
|
|
42
|
+
function savedClaudeProjectsDir(menubarPath) {
|
|
43
|
+
try {
|
|
44
|
+
const value = JSON.parse(fs.readFileSync(menubarPath, "utf8")).claudeProjectsDir;
|
|
45
|
+
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function resolveProjectDirs(options) {
|
|
52
|
+
if (options.projectDir)
|
|
53
|
+
return [path.resolve(options.projectDir)];
|
|
54
|
+
const projectsEnv = process.env.CLAUDE_PROJECTS_DIR?.trim();
|
|
55
|
+
if (projectsEnv)
|
|
56
|
+
return [path.resolve(projectsEnv)];
|
|
57
|
+
const home = options.homeDir ?? os.homedir();
|
|
58
|
+
const configEnv = process.env.CLAUDE_CONFIG_DIR?.trim();
|
|
59
|
+
if (configEnv)
|
|
60
|
+
return [path.resolve(configEnv, "projects")];
|
|
61
|
+
const roots = [
|
|
62
|
+
path.join(home, ".claude", "projects"),
|
|
63
|
+
savedClaudeProjectsDir(options.menubarPath ?? path.join(home, ".hq", "menubar.json")),
|
|
64
|
+
];
|
|
65
|
+
try {
|
|
66
|
+
for (const entry of fs.readdirSync(home, { withFileTypes: true })) {
|
|
67
|
+
if (entry.isDirectory() && (entry.name === ".claude" || entry.name.startsWith(".claude-")))
|
|
68
|
+
roots.push(path.join(home, entry.name, "projects"));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
catch { /* standard and saved fallbacks still apply */ }
|
|
72
|
+
return [...new Set(roots.filter((value) => Boolean(value)).map((value) => path.resolve(value)))];
|
|
73
|
+
}
|
|
74
|
+
function comparison(projectDirs, before, after, minHours, json, stdout) {
|
|
28
75
|
const parseRange = (range) => range.split(":").map((value) => new Date(`${value}T00:00:00Z`));
|
|
29
76
|
const [bStart, bEnd] = parseRange(before), [aStart, aEnd] = parseRange(after);
|
|
30
|
-
const collect = (start, end) => listJsonl(projectDir).flatMap((name) => {
|
|
77
|
+
const collect = (start, end) => projectDirs.flatMap((projectDir) => listJsonl(projectDir).flatMap((name) => {
|
|
31
78
|
const rows = parseJsonl(path.join(projectDir, name));
|
|
32
79
|
let cr = 0, first, last;
|
|
33
80
|
for (const row of rows) {
|
|
@@ -47,7 +94,7 @@ function comparison(projectDir, before, after, minHours, json, stdout) {
|
|
|
47
94
|
if (hours < minHours)
|
|
48
95
|
return [];
|
|
49
96
|
return [{ sid: name.replace(/\.jsonl$/, "").slice(0, 8), hours: Math.round(hours * 10) / 10, cr, cr_per_hour: Math.trunc(cr / hours) }];
|
|
50
|
-
});
|
|
97
|
+
}));
|
|
51
98
|
const bRows = collect(bStart, bEnd), aRows = collect(aStart, aEnd);
|
|
52
99
|
const median = (rows) => { if (!rows.length)
|
|
53
100
|
return 0; const values = rows.map((row) => row.cr_per_hour).sort((a, b) => a - b); const mid = Math.floor(values.length / 2); return Math.trunc(values.length % 2 ? values[mid] : (values[mid - 1] + values[mid]) / 2); };
|
|
@@ -111,62 +158,60 @@ export function tokenUsageReport(args = [], options = {}) {
|
|
|
111
158
|
return 1;
|
|
112
159
|
}
|
|
113
160
|
}
|
|
114
|
-
|
|
115
|
-
// the `}` in `{your-name}` terminates `${CLAUDE_PROJECTS_DIR:-…}` early, so
|
|
116
|
-
// its literal `-Documents-HQ}` suffix remains even when the environment
|
|
117
|
-
// variable is set. `projectDir` is the explicit native test/integration seam.
|
|
118
|
-
const projectDir = options.projectDir ?? `${process.env.CLAUDE_PROJECTS_DIR ?? path.join(os.homedir(), ".claude/projects/-Users-{your-name")}-Documents-HQ}`;
|
|
161
|
+
const projectDirs = resolveProjectDirs(options).filter((dir) => fs.existsSync(dir) && fs.statSync(dir).isDirectory());
|
|
119
162
|
if (before && after) {
|
|
120
|
-
comparison(
|
|
163
|
+
comparison(projectDirs, before, after, minHours, json, stdout);
|
|
121
164
|
return 0;
|
|
122
165
|
}
|
|
123
|
-
if (!
|
|
124
|
-
line(stderr, `
|
|
166
|
+
if (!projectDirs.length) {
|
|
167
|
+
line(stderr, `Claude activity folders not found`);
|
|
125
168
|
return 1;
|
|
126
169
|
}
|
|
127
170
|
const now = options.now ?? new Date();
|
|
128
171
|
const cutoff = since || dayOf(new Date(now.getTime() - (lastDays - 1) * 86_400_000));
|
|
129
172
|
const days = new Map(), sessions = new Map();
|
|
130
|
-
for (const
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
if (
|
|
145
|
-
firstTs
|
|
146
|
-
|
|
173
|
+
for (const projectDir of projectDirs)
|
|
174
|
+
for (const name of listJsonl(projectDir)) {
|
|
175
|
+
const file = path.join(projectDir, name);
|
|
176
|
+
const stat = fs.statSync(file);
|
|
177
|
+
const mtime = dayOf(stat.mtime);
|
|
178
|
+
if (mtime < cutoff)
|
|
179
|
+
continue;
|
|
180
|
+
let inp = 0, out = 0, cc = 0, cr = 0, firstTs = "", lastTs = "", first = "";
|
|
181
|
+
for (const record of parseJsonl(file)) {
|
|
182
|
+
const usage = record.message?.usage;
|
|
183
|
+
inp += readNumber(usage?.input_tokens);
|
|
184
|
+
out += readNumber(usage?.output_tokens);
|
|
185
|
+
cc += readNumber(usage?.cache_creation_input_tokens);
|
|
186
|
+
cr += readNumber(usage?.cache_read_input_tokens);
|
|
187
|
+
if (record.timestamp) {
|
|
188
|
+
if (!firstTs)
|
|
189
|
+
firstTs = record.timestamp;
|
|
190
|
+
lastTs = record.timestamp;
|
|
191
|
+
}
|
|
192
|
+
if (!first && record.type === "user")
|
|
193
|
+
first = firstUser(record);
|
|
147
194
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
195
|
+
const localSid = name.replace(/\.jsonl$/, "");
|
|
196
|
+
const sid = `${path.basename(path.dirname(projectDir))}/${localSid}`;
|
|
197
|
+
const subagents = (() => { try {
|
|
198
|
+
return fs.readdirSync(path.join(projectDir, localSid, "subagents")).filter((entry) => entry.endsWith(".jsonl")).length;
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
return 0;
|
|
202
|
+
} })();
|
|
203
|
+
const day = (firstTs || lastTs || "").slice(0, 10) || mtime;
|
|
204
|
+
if (day < cutoff)
|
|
205
|
+
continue;
|
|
206
|
+
const current = days.get(day) ?? { sessions: new Set(), inp: 0, out: 0, cc: 0, cr: 0 };
|
|
207
|
+
current.sessions.add(sid);
|
|
208
|
+
current.inp += inp;
|
|
209
|
+
current.out += out;
|
|
210
|
+
current.cc += cc;
|
|
211
|
+
current.cr += cr;
|
|
212
|
+
days.set(day, current);
|
|
213
|
+
sessions.set(sid, { day, inp, out, cc, cr, eff: Math.trunc(inp + 5 * out + 1.25 * cc + .1 * cr), subagents, first_user: first.replaceAll("\n", " ") });
|
|
154
214
|
}
|
|
155
|
-
catch {
|
|
156
|
-
return 0;
|
|
157
|
-
} })();
|
|
158
|
-
const day = (firstTs || lastTs || "").slice(0, 10) || mtime;
|
|
159
|
-
if (day < cutoff)
|
|
160
|
-
continue;
|
|
161
|
-
const current = days.get(day) ?? { sessions: new Set(), inp: 0, out: 0, cc: 0, cr: 0 };
|
|
162
|
-
current.sessions.add(sid);
|
|
163
|
-
current.inp += inp;
|
|
164
|
-
current.out += out;
|
|
165
|
-
current.cc += cc;
|
|
166
|
-
current.cr += cr;
|
|
167
|
-
days.set(day, current);
|
|
168
|
-
sessions.set(sid, { day, inp, out, cc, cr, eff: Math.trunc(inp + 5 * out + 1.25 * cc + .1 * cr), subagents, first_user: first.replaceAll("\n", " ") });
|
|
169
|
-
}
|
|
170
215
|
const sorted = [...days.keys()].sort();
|
|
171
216
|
const dayRows = sorted.map((day) => { const value = days.get(day); return { date: day, sessions: value.sessions.size, input: value.inp, output: value.out, cache_create: value.cc, cache_read: value.cr, effective: Math.trunc(value.inp + 5 * value.out + 1.25 * value.cc + .1 * value.cr) }; });
|
|
172
217
|
const recent = sorted.at(-1);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@indigoai-us/hq-cli",
|
|
3
|
-
"version": "5.105.
|
|
3
|
+
"version": "5.105.1",
|
|
4
4
|
"description": "HQ by Indigo management CLI — modules and cloud sync",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@aws-sdk/client-iot-data-plane": "^3.1096.0",
|
|
32
32
|
"@aws-sdk/client-s3": "^3.1049.0",
|
|
33
|
-
"@indigoai-us/hq-cloud": "~6.16.
|
|
33
|
+
"@indigoai-us/hq-cloud": "~6.16.1",
|
|
34
34
|
"@indigoai-us/hq-onboarding": "^0.1.0",
|
|
35
35
|
"@sentry/node": "^10.49.0",
|
|
36
36
|
"@tobilu/qmd": "2.5.3",
|