@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) => { try {
15
- return fs.readdirSync(dir).filter((name) => name.endsWith(".jsonl")).sort();
16
- }
17
- catch {
18
- return [];
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 comparison(projectDir, before, after, minHours, json, stdout) {
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
- // Preserve the bundled shell asset's historical parameter-expansion parsing:
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(projectDir, before, after, minHours, json, stdout);
163
+ comparison(projectDirs, before, after, minHours, json, stdout);
121
164
  return 0;
122
165
  }
123
- if (!fs.existsSync(projectDir) || !fs.statSync(projectDir).isDirectory()) {
124
- line(stderr, `Project dir not found: ${projectDir}`);
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 name of listJsonl(projectDir)) {
131
- const file = path.join(projectDir, name);
132
- const stat = fs.statSync(file);
133
- const mtime = dayOf(stat.mtime);
134
- if (mtime < cutoff)
135
- continue;
136
- let inp = 0, out = 0, cc = 0, cr = 0, firstTs = "", lastTs = "", first = "";
137
- for (const record of parseJsonl(file)) {
138
- const usage = record.message?.usage;
139
- inp += readNumber(usage?.input_tokens);
140
- out += readNumber(usage?.output_tokens);
141
- cc += readNumber(usage?.cache_creation_input_tokens);
142
- cr += readNumber(usage?.cache_read_input_tokens);
143
- if (record.timestamp) {
144
- if (!firstTs)
145
- firstTs = record.timestamp;
146
- lastTs = record.timestamp;
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
- if (!first && record.type === "user")
149
- first = firstUser(record);
150
- }
151
- const sid = name.replace(/\.jsonl$/, "");
152
- const subagents = (() => { try {
153
- return fs.readdirSync(path.join(projectDir, sid, "subagents")).filter((entry) => entry.endsWith(".jsonl")).length;
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.0",
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.0",
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",