@vibe-cafe/vibe-usage 0.10.19 → 0.10.21

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/src/pi-roots.js CHANGED
@@ -1,6 +1,7 @@
1
- import { existsSync, readdirSync } from 'node:fs';
2
- import { delimiter, join } from 'node:path';
1
+ import { existsSync, readdirSync, readFileSync } from 'node:fs';
2
+ import { delimiter, isAbsolute, join } from 'node:path';
3
3
  import { homedir } from 'node:os';
4
+ import { piSessionsDir } from './extra-roots.js';
4
5
 
5
6
  function expandHome(value) {
6
7
  const trimmed = value.trim();
@@ -41,18 +42,49 @@ export function looksLikeOmpAgentDir(agentDir) {
41
42
  || existsSync(join(agentDir, 'agent.db'));
42
43
  }
43
44
 
44
- export function getPiSessionDirs() {
45
+ // Pi resolves a session directory from `--session-dir`, then
46
+ // PI_CODING_AGENT_SESSION_DIR, then `sessionDir` in settings.json. Only the
47
+ // last two are discoverable after the fact, and both name the sessions
48
+ // directory itself (no `sessions` segment is appended).
49
+ function settingsSessionDir(agentDir) {
50
+ try {
51
+ const raw = readFileSync(join(agentDir, 'settings.json'), 'utf-8');
52
+ const value = JSON.parse(raw)?.sessionDir;
53
+ if (typeof value !== 'string') return null;
54
+ const trimmed = value.trim();
55
+ // Pi also accepts project-relative values. Those resolve against a cwd we
56
+ // do not have here, so only absolute and ~-anchored paths are scanned.
57
+ if (!trimmed || !(isAbsolute(trimmed) || trimmed.startsWith('~'))) return null;
58
+ return expandHome(trimmed);
59
+ } catch {
60
+ return null;
61
+ }
62
+ }
63
+
64
+ export function getPiSessionDirs(extraRoots = []) {
65
+ // Explicitly configured roots are user intent, not a fixture: they are always
66
+ // scanned, and they never replace the default store. A root whose shape no
67
+ // longer resolves drops out here; the parser reports that as skipped.
68
+ const extraDirs = extraRoots.map(piSessionsDir).filter(dir => dir !== null);
69
+
45
70
  const override = process.env.VIBE_USAGE_PI_SESSION_DIRS?.trim();
46
- if (override) return uniqueExistingDirs(override.split(delimiter));
71
+ if (override) return uniqueExistingDirs([...override.split(delimiter), ...extraDirs]);
47
72
 
48
- const agentDir = process.env.PI_CODING_AGENT_DIR?.trim();
49
- if (agentDir) {
50
- const expanded = expandHome(agentDir);
51
- // OMP inherits PI_CODING_AGENT_DIR from Pi. Do not parse an identifiable
52
- // OMP store again as source=pi-coding-agent.
53
- return looksLikeOmpAgentDir(expanded) ? [] : uniqueExistingDirs([join(expanded, 'sessions')]);
73
+ const envAgentDir = process.env.PI_CODING_AGENT_DIR?.trim();
74
+ const agentDir = envAgentDir ? expandHome(envAgentDir) : join(homedir(), '.pi', 'agent');
75
+ // OMP inherits PI_CODING_AGENT_DIR from Pi. Do not parse an identifiable
76
+ // OMP store again as source=pi-coding-agent.
77
+ const isOmpStore = Boolean(envAgentDir) && looksLikeOmpAgentDir(agentDir);
78
+
79
+ const dirs = [];
80
+ if (!isOmpStore) {
81
+ dirs.push(join(agentDir, 'sessions'));
82
+ const envSessionDir = process.env.PI_CODING_AGENT_SESSION_DIR?.trim();
83
+ if (envSessionDir) dirs.push(expandHome(envSessionDir));
84
+ const configured = settingsSessionDir(agentDir);
85
+ if (configured) dirs.push(configured);
54
86
  }
55
- return uniqueExistingDirs([join(homedir(), '.pi', 'agent', 'sessions')]);
87
+ return uniqueExistingDirs([...dirs, ...extraDirs]);
56
88
  }
57
89
 
58
90
  export function getOmpSessionDirs() {
package/src/sync.js CHANGED
@@ -9,6 +9,7 @@ import { createSyncClient, forBatch } from './client-meta.js';
9
9
  import { parsers } from './parsers/index.js';
10
10
  import { aggregateToBuckets } from './parsers/aggregate.js';
11
11
  import { normalizeParserResult } from './parsers/contract.js';
12
+ import { extraRootList } from './extra-roots.js';
12
13
  import { success, failure, warn, arrow, link, dim } from './output.js';
13
14
 
14
15
  const BATCH_SIZE = 100;
@@ -20,6 +21,12 @@ function formatBytes(bytes) {
20
21
  return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
21
22
  }
22
23
 
24
+ /** Hide only Cursor's intentional transient fetch soft-skip in quiet (daemon) syncs. */
25
+ export function shouldSuppressParserWarning(source, message, quiet) {
26
+ if (!quiet) return false;
27
+ return source === 'cursor' && message.startsWith('cursor: Cursor usage export skipped (');
28
+ }
29
+
23
30
  export function resolveUploadProjectSetting(settings) {
24
31
  if (typeof settings?.uploadProject !== 'boolean') {
25
32
  const error = new Error('SETTINGS_UNAVAILABLE');
@@ -147,9 +154,12 @@ export async function runSync({
147
154
  PARSER_CONCURRENCY,
148
155
  async ([source, parse]) => {
149
156
  try {
150
- const result = source === 'codex'
151
- ? await parse({ codexExtraHome: resolveCodexExtraHome(config.codexExtraHome, codexExtraHome) })
152
- : await parse();
157
+ const result = await parse({
158
+ extraRoots: extraRootList(config.extraRoots?.[source]),
159
+ ...(source === 'codex' ? {
160
+ codexExtraHome: resolveCodexExtraHome(config.codexExtraHome, codexExtraHome),
161
+ } : {}),
162
+ });
153
163
  return { source, result };
154
164
  } catch (err) {
155
165
  return { source, error: err };
@@ -175,6 +185,7 @@ export async function runSync({
175
185
  parserProgress.push({ source, ...indexing });
176
186
  }
177
187
  for (const message of warnings) {
188
+ if (shouldSuppressParserWarning(source, message, quiet)) continue;
178
189
  process.stderr.write(`${dim(` ${message}`)}\n`);
179
190
  }
180
191
  // A parser may deliberately suppress a transient error (Cursor network
package/src/tools.js CHANGED
@@ -1,9 +1,15 @@
1
1
  import { existsSync, readdirSync, statSync } from 'node:fs';
2
- import { isAbsolute, join, posix, resolve, win32 } from 'node:path';
2
+ import { dirname, isAbsolute, join, posix, resolve, win32 } from 'node:path';
3
3
  import { homedir } from 'node:os';
4
4
  import { findClaudeCodeDataDirs } from './claude-roots.js';
5
5
  import { findCindyDataDirs, getCindyDataRoots } from './cindy-roots.js';
6
6
  import { codexSessionDirs, resolveCodexHomes } from './codex-roots.js';
7
+ import {
8
+ antigravityConversationDirs,
9
+ discoverCodexHomes,
10
+ extraRootList,
11
+ grokSessionsDir,
12
+ } from './extra-roots.js';
7
13
  import { findClineDataDirs } from './cline-roots.js';
8
14
  import { findCraftDataDirs } from './craft-roots.js';
9
15
  import { findOmpDataDirs, findPiDataDirs } from './pi-roots.js';
@@ -106,8 +112,9 @@ function findOpenclawDataDirs() {
106
112
  // Codex keeps live sessions in ~/.codex/sessions and moves completed ones to
107
113
  // ~/.codex/archived_sessions. Detect Codex if either dir exists, so a user
108
114
  // whose sessions have all been archived is still recognized.
109
- export function findCodexDataDirs(codexExtraHome) {
110
- return resolveCodexHomes(codexExtraHome)
115
+ export function findCodexDataDirs(codexExtraHome, extraRoots = []) {
116
+ const configuredHomes = extraRoots.flatMap(root => discoverCodexHomes(root).homes);
117
+ return [...new Set([...resolveCodexHomes(codexExtraHome), ...configuredHomes])]
111
118
  .flatMap(codexSessionDirs)
112
119
  .filter(existsSync);
113
120
  }
@@ -143,6 +150,17 @@ export function findDshDataDirs() {
143
150
  return [getDshSessionsDir()].filter(existsSync);
144
151
  }
145
152
 
153
+ /** mcode runtime database: VIBE_USAGE_MCODE_DB wins, then MCODE_HOME, then ~/.minimax. */
154
+ export function getMcodeDbPath(env = process.env, home = homedir()) {
155
+ const override = env.VIBE_USAGE_MCODE_DB?.trim();
156
+ if (override) return isAbsolute(override) ? override : resolve(override);
157
+ if (env.MCODE_HOME && !isAbsolute(env.MCODE_HOME)) {
158
+ throw new Error(`MCODE_HOME must be an absolute path, got: ${JSON.stringify(env.MCODE_HOME)}`);
159
+ }
160
+ const root = env.MCODE_HOME || join(home, '.minimax');
161
+ return join(root, 'v2', 'sqlite', 'runtime-state.sqlite');
162
+ }
163
+
146
164
  export function getMimocodeDbPath(env = process.env) {
147
165
  if (env.MIMOCODE_HOME && !isAbsolute(env.MIMOCODE_HOME)) {
148
166
  throw new Error(`MIMOCODE_HOME must be an absolute path, got: ${JSON.stringify(env.MIMOCODE_HOME)}`);
@@ -154,11 +172,12 @@ export function getMimocodeDbPath(env = process.env) {
154
172
  return isAbsolute(env.MIMOCODE_DB) ? env.MIMOCODE_DB : join(dataDir, env.MIMOCODE_DB);
155
173
  }
156
174
 
157
- function findAntigravityDataDirs() {
158
- return [
175
+ export function findAntigravityDataDirs(extraRoots = []) {
176
+ return [...new Set([
159
177
  join(homedir(), '.gemini', 'antigravity'),
160
178
  join(homedir(), '.gemini', 'antigravity-cli'),
161
- ].filter(existsSync);
179
+ ...extraRoots.flatMap(root => antigravityConversationDirs(root).map(dirname)),
180
+ ])].filter(existsSync);
162
181
  }
163
182
 
164
183
  export function findTraeCliDataDirs() {
@@ -193,10 +212,13 @@ export function getGrokSessionsDir() {
193
212
  }
194
213
 
195
214
  // Detect Grok when sessions/ exists under GROK_HOME (or the test override).
196
- export function findGrokDataDirs() {
215
+ export function findGrokDataDirs(extraRoots = []) {
197
216
  const testDir = process.env.VIBE_USAGE_GROK_SESSIONS?.trim();
198
217
  if (testDir) return [testDir].filter(existsSync);
199
- return [join(getGrokHome(), 'sessions')].filter(existsSync);
218
+ return [...new Set([
219
+ join(getGrokHome(), 'sessions'),
220
+ ...extraRoots.map(grokSessionsDir),
221
+ ])].filter(existsSync);
200
222
  }
201
223
 
202
224
  export function getDimAgentDbPath() {
@@ -240,13 +262,15 @@ export const TOOLS = [
240
262
  name: 'Codex CLI',
241
263
  id: 'codex',
242
264
  dataDir: join(homedir(), '.codex', 'sessions'),
243
- detectDataDirs: ({ codexExtraHome } = {}) => findCodexDataDirs(codexExtraHome),
265
+ detectDataDirs: ({ codexExtraHome, extraRoots } = {}) => (
266
+ findCodexDataDirs(codexExtraHome, extraRootList(extraRoots?.codex))
267
+ ),
244
268
  },
245
269
  {
246
270
  name: 'Grok',
247
271
  id: 'grok',
248
272
  dataDir: join(homedir(), '.grok', 'sessions'),
249
- detectDataDirs: findGrokDataDirs,
273
+ detectDataDirs: ({ extraRoots } = {}) => findGrokDataDirs(extraRootList(extraRoots?.grok)),
250
274
  },
251
275
  {
252
276
  name: 'GitHub Copilot CLI',
@@ -296,7 +320,9 @@ export const TOOLS = [
296
320
  name: 'pi',
297
321
  id: 'pi-coding-agent',
298
322
  dataDir: join(homedir(), '.pi', 'agent', 'sessions'),
299
- detectDataDirs: findPiDataDirs,
323
+ detectDataDirs: ({ extraRoots } = {}) => (
324
+ findPiDataDirs(extraRootList(extraRoots?.['pi-coding-agent']))
325
+ ),
300
326
  },
301
327
  {
302
328
  name: 'Qwen Code',
@@ -311,6 +337,12 @@ export const TOOLS = [
311
337
  dataDir: join(homedir(), '.kimi-code', 'sessions'),
312
338
  detectDataDirs: findKimiCodeDataDirs,
313
339
  },
340
+ {
341
+ name: 'MiniMax Code',
342
+ id: 'mcode',
343
+ dataDir: join(homedir(), '.minimax', 'v2', 'sqlite', 'runtime-state.sqlite'),
344
+ detectDataDirs: () => [getMcodeDbPath()].filter(existsSync),
345
+ },
314
346
  {
315
347
  name: 'MiMoCode',
316
348
  id: 'mimocode',
@@ -337,7 +369,9 @@ export const TOOLS = [
337
369
  name: 'Antigravity',
338
370
  id: 'antigravity',
339
371
  dataDir: join(homedir(), '.gemini', 'antigravity'),
340
- detectDataDirs: findAntigravityDataDirs,
372
+ detectDataDirs: ({ extraRoots } = {}) => (
373
+ findAntigravityDataDirs(extraRootList(extraRoots?.antigravity))
374
+ ),
341
375
  },
342
376
  {
343
377
  name: 'Trae CLI',