@yhong91/vibetime 0.1.74 → 0.1.76

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.
Files changed (2) hide show
  1. package/bin/vibetime.mjs +191 -154
  2. package/package.json +1 -1
package/bin/vibetime.mjs CHANGED
@@ -884,8 +884,8 @@ var init_esm = __esm({
884
884
 
885
885
  // src/cli.ts
886
886
  import { spawn as spawn2, spawnSync } from "node:child_process";
887
- import { mkdir as mkdir6, open, rm, stat as stat18, writeFile as writeFile5 } from "node:fs/promises";
888
- import os14 from "node:os";
887
+ import { mkdir as mkdir6, open, rm, stat as stat17, writeFile as writeFile5 } from "node:fs/promises";
888
+ import os13 from "node:os";
889
889
  import path27 from "node:path";
890
890
  import { fileURLToPath } from "node:url";
891
891
 
@@ -2047,7 +2047,7 @@ function claudeStyleFileMetrics(tool, input) {
2047
2047
  }
2048
2048
 
2049
2049
  // src/lib/constants.ts
2050
- var PACKAGE_VERSION = true ? "0.1.74" : "0.1.1";
2050
+ var PACKAGE_VERSION = true ? "0.1.76" : "0.1.1";
2051
2051
  var GENERATED_MARKER = "Generated by vibetime.";
2052
2052
  var DEFAULT_API_URL = "http://121.196.224.82:3001";
2053
2053
  var DEFAULT_BACKFILL_BATCH_SIZE = 50;
@@ -2103,6 +2103,9 @@ function msToIso(ms) {
2103
2103
  }
2104
2104
 
2105
2105
  // src/lib/backfill.ts
2106
+ function rollupKeyForSourceSession(source, sessionId) {
2107
+ return createImportKey(["rollup", source, sessionId]);
2108
+ }
2106
2109
  function withBackfillRefs(event, context) {
2107
2110
  const importKey = createImportKey([
2108
2111
  event.source,
@@ -4936,8 +4939,8 @@ async function codebuddyBackfillFiles(sourceRoot, home, env) {
4936
4939
  }
4937
4940
  const filePath = path9.join(traceDir, entry);
4938
4941
  try {
4939
- const stat19 = await import("node:fs/promises").then((fs) => fs.stat(filePath));
4940
- files.push({ path: filePath, modifiedAt: stat19.mtime.toISOString(), groupId: pidDir.name });
4942
+ const stat18 = await import("node:fs/promises").then((fs) => fs.stat(filePath));
4943
+ files.push({ path: filePath, modifiedAt: stat18.mtime.toISOString(), groupId: pidDir.name });
4941
4944
  } catch {
4942
4945
  }
4943
4946
  }
@@ -6500,10 +6503,10 @@ function cloudAgentSessionFromEvents(conversationId, events) {
6500
6503
  };
6501
6504
  }
6502
6505
  function hookUncachedInputTokens(turn) {
6503
- return Math.max(0, (turn.tokensInput || 0) - (turn.tokensCacheReadInput || 0));
6506
+ return Math.max(0, (turn.tokensInput || 0) - (turn.tokensCacheReadInput || 0) - (turn.tokensCacheCreationInput || 0));
6504
6507
  }
6505
6508
  function cloudEventMatchesHookTurn(event, turn) {
6506
- return event.tokensInput === hookUncachedInputTokens(turn) && event.tokensOutput === (turn.tokensOutput || 0) && event.tokensCacheReadInput === (turn.tokensCacheReadInput || 0);
6509
+ return event.tokensInput === hookUncachedInputTokens(turn) && event.tokensOutput === (turn.tokensOutput || 0) && event.tokensCacheReadInput === (turn.tokensCacheReadInput || 0) && event.tokensCacheCreationInput === (turn.tokensCacheCreationInput || 0);
6507
6510
  }
6508
6511
  function unmatchedCloudEvents(events, turns) {
6509
6512
  const used = /* @__PURE__ */ new Set();
@@ -6846,6 +6849,11 @@ async function loadCursorCloudUsageMap(options, filePath) {
6846
6849
  return cached;
6847
6850
  }
6848
6851
  const home = stringOption(options.home) || os6.homedir();
6852
+ if (options.skipCursorCloud) {
6853
+ return cloudUsageMapFromSessions(
6854
+ parseCursorCloudUsageCache(await readJsonIfExists(cursorCloudUsageCachePath(home)))
6855
+ );
6856
+ }
6849
6857
  const intervalSeconds = cursorCloudFetchIntervalSeconds();
6850
6858
  if (intervalSeconds > 0 && !options.force) {
6851
6859
  const lastFetchedMs = await readLastCloudFetchAt(home);
@@ -6967,7 +6975,7 @@ async function writeCursorCloudUsageCache(cachePath, sessions) {
6967
6975
  const info = await stat8(cachePath);
6968
6976
  return info.mtime.toISOString();
6969
6977
  }
6970
- function parseCursorCloudAgentSessions(filePath, options, sessions, localIds, identity) {
6978
+ function parseCursorCloudAgentSessions(filePath, options, sessions, localIds) {
6971
6979
  const events = [];
6972
6980
  const sourcePathHash = `sha256:${createStableHash(filePath)}`;
6973
6981
  const project = CURSOR_CLOUD_AGENT_PROJECT;
@@ -6984,8 +6992,8 @@ function parseCursorCloudAgentSessions(filePath, options, sessions, localIds, id
6984
6992
  lineNumber += 1;
6985
6993
  const event = {
6986
6994
  schemaVersion: AGENT_TIME_SCHEMA_VERSION,
6987
- source: identity.source,
6988
- agent: identity.agent,
6995
+ source: SOURCE_ID,
6996
+ agent: AGENT_NAME,
6989
6997
  workspaceId,
6990
6998
  project,
6991
6999
  model: lastModel,
@@ -7042,7 +7050,7 @@ function parseCursorCloudAgentSessions(filePath, options, sessions, localIds, id
7042
7050
  }
7043
7051
  return events;
7044
7052
  }
7045
- async function parseCursorCloudAgentFile(filePath, options, identity) {
7053
+ async function parseCursorCloudAgentFile(filePath, options) {
7046
7054
  const home = stringOption(options.home) || os6.homedir();
7047
7055
  const env = {
7048
7056
  CURSOR_HOME: process.env.CURSOR_HOME,
@@ -7054,7 +7062,7 @@ async function parseCursorCloudAgentFile(filePath, options, identity) {
7054
7062
  return session ? [session] : [];
7055
7063
  }) : parseCursorCloudUsageCache(await readJsonIfExists(filePath));
7056
7064
  const localIds = await listLocalCursorSessionIds(home, env);
7057
- return parseCursorCloudAgentSessions(filePath, options, sessions, localIds, identity);
7065
+ return parseCursorCloudAgentSessions(filePath, options, sessions, localIds);
7058
7066
  }
7059
7067
  function decodeKv(value) {
7060
7068
  if (typeof value === "string") {
@@ -7913,6 +7921,9 @@ async function parseCursorSessionFile(filePath, options) {
7913
7921
  if (base.endsWith(".jsonl")) {
7914
7922
  return parseCursorTranscriptFile(filePath, options);
7915
7923
  }
7924
+ if (base === CURSOR_CLOUD_USAGE_FILENAME) {
7925
+ return parseCursorCloudAgentFile(filePath, options);
7926
+ }
7916
7927
  if (base === "store.db") {
7917
7928
  return [];
7918
7929
  }
@@ -8147,7 +8158,36 @@ function parseComposer(db, composer, filePath, sourcePathHash, options, persiste
8147
8158
  emitPersistedCursorUsage(push, persistedUsage, endedAt || startedAt, currentTurnId, model, sessionId, events.some((event) => event.type === "model.usage"));
8148
8159
  return events;
8149
8160
  }
8150
- async function cursorBackfillFiles(sourceRoot, home = os6.homedir(), env) {
8161
+ async function cursorCloudUsageBackfillFiles(home, options) {
8162
+ if (options?.skipCursorCloud || cursorCloudUsageDisabled()) {
8163
+ return [];
8164
+ }
8165
+ const cachePath = cursorCloudUsageCachePath(home);
8166
+ if (!options) {
8167
+ const info = await stat8(cachePath).catch(() => null);
8168
+ return info ? [{ path: cachePath, modifiedAt: info.mtime.toISOString() }] : [];
8169
+ }
8170
+ const map = await loadCursorCloudUsageMap(options);
8171
+ if (map.size === 0) {
8172
+ const info = await stat8(cachePath).catch(() => null);
8173
+ return info ? [{ path: cachePath, modifiedAt: info.mtime.toISOString() }] : [];
8174
+ }
8175
+ const sessions = [];
8176
+ for (const [conversationId, events] of map) {
8177
+ const session = cloudAgentSessionFromEvents(conversationId, events);
8178
+ if (session) {
8179
+ sessions.push(session);
8180
+ }
8181
+ }
8182
+ if (sessions.length === 0) {
8183
+ return [];
8184
+ }
8185
+ return [{
8186
+ path: cachePath,
8187
+ modifiedAt: await writeCursorCloudUsageCache(cachePath, sessions)
8188
+ }];
8189
+ }
8190
+ async function cursorBackfillFiles(sourceRoot, home = os6.homedir(), env, options) {
8151
8191
  if (sourceRoot) {
8152
8192
  const info = await stat8(sourceRoot).catch(() => null);
8153
8193
  if (!info) {
@@ -8177,6 +8217,9 @@ async function cursorBackfillFiles(sourceRoot, home = os6.homedir(), env) {
8177
8217
  files2.push(...await collectCursorTranscriptFiles(sourceRoot, home, skipIds2));
8178
8218
  return files2;
8179
8219
  }
8220
+ if (path13.basename(sourceRoot) === CURSOR_CLOUD_USAGE_FILENAME) {
8221
+ return [{ path: sourceRoot, modifiedAt: info.mtime.toISOString() }];
8222
+ }
8180
8223
  if (path13.basename(sourceRoot) === "store.db") {
8181
8224
  return [];
8182
8225
  }
@@ -8214,6 +8257,7 @@ async function cursorBackfillFiles(sourceRoot, home = os6.homedir(), env) {
8214
8257
  break;
8215
8258
  }
8216
8259
  files.push(...await collectCursorTranscriptFiles(cursorProjectsDir(home, env), home, skipIds));
8260
+ files.push(...await cursorCloudUsageBackfillFiles(home, options));
8217
8261
  return files;
8218
8262
  }
8219
8263
  function cursorHookConfig() {
@@ -8251,85 +8295,22 @@ function createCursorAdapter() {
8251
8295
  return [
8252
8296
  ...cursorStateDbCandidates(home, env),
8253
8297
  cursorProjectsDir(home, env),
8254
- cursorChatsDir(home, env)
8298
+ cursorChatsDir(home, env),
8299
+ cursorCloudUsageCachePath(home)
8255
8300
  ];
8256
8301
  },
8257
8302
  parseSessionFile: parseCursorSessionFile
8258
8303
  };
8259
8304
  }
8260
8305
 
8261
- // src/adapters/grok-bot.ts
8262
- import { stat as stat9 } from "node:fs/promises";
8263
- import os7 from "node:os";
8264
- var SOURCE_ID2 = "grok-bot";
8265
- var AGENT_NAME2 = "grok-bot";
8266
- var IDENTITY = { source: SOURCE_ID2, agent: AGENT_NAME2 };
8267
- async function grokBotBackfillFiles(sourceRoot, home = os7.homedir(), _env, options) {
8268
- if (sourceRoot) {
8269
- const info = await stat9(sourceRoot).catch(() => null);
8270
- return info ? [{ path: sourceRoot, modifiedAt: info.mtime.toISOString() }] : [];
8271
- }
8272
- if (!options) {
8273
- return [];
8274
- }
8275
- const cachePath = cursorCloudUsageCachePath(home);
8276
- const map = await loadCursorCloudUsageMap(options);
8277
- if (map.size === 0) {
8278
- const info = await stat9(cachePath).catch(() => null);
8279
- return info ? [{ path: cachePath, modifiedAt: info.mtime.toISOString() }] : [];
8280
- }
8281
- const sessions = [];
8282
- for (const [conversationId, events] of map) {
8283
- const session = cloudAgentSessionFromEvents(conversationId, events);
8284
- if (session) {
8285
- sessions.push(session);
8286
- }
8287
- }
8288
- if (sessions.length === 0) {
8289
- return [];
8290
- }
8291
- return [{
8292
- path: cachePath,
8293
- modifiedAt: await writeCursorCloudUsageCache(cachePath, sessions)
8294
- }];
8295
- }
8296
- function createGrokBotAdapter() {
8297
- return {
8298
- id: SOURCE_ID2,
8299
- label: "Grok Bot",
8300
- agentName: AGENT_NAME2,
8301
- kind: "agent",
8302
- // No hooks to install — data arrives via the Cursor dashboard API.
8303
- // The cache file stands in as the detect/install marker.
8304
- detectPath(home) {
8305
- return cursorCloudUsageCachePath(home);
8306
- },
8307
- installedPath(home) {
8308
- return cursorCloudUsageCachePath(home);
8309
- },
8310
- async isInstalled() {
8311
- return false;
8312
- },
8313
- installEntries() {
8314
- return [];
8315
- },
8316
- sourcePaths(home) {
8317
- return [cursorCloudUsageCachePath(home)];
8318
- },
8319
- parseSessionFile(filePath, options) {
8320
- return parseCursorCloudAgentFile(filePath, options, IDENTITY);
8321
- }
8322
- };
8323
- }
8324
-
8325
8306
  // src/adapters/grok-build.ts
8326
- import { readdir as readdir7, readFile as readFile9, stat as stat10 } from "node:fs/promises";
8307
+ import { readdir as readdir7, readFile as readFile9, stat as stat9 } from "node:fs/promises";
8327
8308
  import path14 from "node:path";
8328
8309
  init_fs();
8329
8310
  var GROK_COST_TICKS_PER_USD = 1e9;
8330
- var SOURCE_ID3 = "grok-build";
8331
- var AGENT_NAME3 = "grok-build";
8332
- var HOOK_COMMAND2 = `vibetime hook --agent ${SOURCE_ID3}`;
8311
+ var SOURCE_ID2 = "grok-build";
8312
+ var AGENT_NAME2 = "grok-build";
8313
+ var HOOK_COMMAND2 = `vibetime hook --agent ${SOURCE_ID2}`;
8333
8314
  function grokHome(home, env) {
8334
8315
  const override = env?.GROK_HOME;
8335
8316
  if (override && override.trim()) {
@@ -8363,7 +8344,7 @@ async function grokBackfillFiles(sourceRoot, home, env) {
8363
8344
  continue;
8364
8345
  }
8365
8346
  try {
8366
- const info = await stat10(entryPath);
8347
+ const info = await stat9(entryPath);
8367
8348
  files.push({ path: entryPath, modifiedAt: info.mtime.toISOString() });
8368
8349
  } catch {
8369
8350
  }
@@ -8415,8 +8396,8 @@ async function parseGrokSessionFile(filePath, options) {
8415
8396
  };
8416
8397
  const base = (partial) => ({
8417
8398
  schemaVersion: AGENT_TIME_SCHEMA_VERSION,
8418
- source: SOURCE_ID3,
8419
- agent: AGENT_NAME3,
8399
+ source: SOURCE_ID2,
8400
+ agent: AGENT_NAME2,
8420
8401
  workspaceId,
8421
8402
  project,
8422
8403
  cwd,
@@ -8976,7 +8957,7 @@ async function loadHunkFileActivities(hunkPath, cwd) {
8976
8957
  }
8977
8958
  return activities;
8978
8959
  }
8979
- var grokHandler = (msg) => hookHandler(SOURCE_ID3, msg);
8960
+ var grokHandler = (msg) => hookHandler(SOURCE_ID2, msg);
8980
8961
  function hookConfig5() {
8981
8962
  const anyTool = [{ matcher: ".*", hooks: [grokHandler("Reporting tool activity")] }];
8982
8963
  return {
@@ -8998,9 +8979,9 @@ function hookConfig5() {
8998
8979
  }
8999
8980
  function createGrokBuildAdapter() {
9000
8981
  return {
9001
- id: SOURCE_ID3,
8982
+ id: SOURCE_ID2,
9002
8983
  label: "Grok Build",
9003
- agentName: AGENT_NAME3,
8984
+ agentName: AGENT_NAME2,
9004
8985
  kind: "agent",
9005
8986
  detectPath(home, env) {
9006
8987
  return grokHome(home, env);
@@ -9026,7 +9007,7 @@ function createGrokBuildAdapter() {
9026
9007
  }
9027
9008
 
9028
9009
  // src/adapters/hermes.ts
9029
- import { readdir as readdir8, stat as stat11 } from "node:fs/promises";
9010
+ import { readdir as readdir8, stat as stat10 } from "node:fs/promises";
9030
9011
  import path15 from "node:path";
9031
9012
  init_fs();
9032
9013
  var SOURCE = "hermes";
@@ -9319,11 +9300,11 @@ function hermesToolAlias(tool) {
9319
9300
  }
9320
9301
  async function sqliteMtime(filePath) {
9321
9302
  try {
9322
- const info = await stat11(filePath);
9303
+ const info = await stat10(filePath);
9323
9304
  let modifiedMs = info.mtimeMs;
9324
9305
  for (const suffix of ["-wal", "-shm"]) {
9325
9306
  try {
9326
- const sidecar = await stat11(`${filePath}${suffix}`);
9307
+ const sidecar = await stat10(`${filePath}${suffix}`);
9327
9308
  modifiedMs = Math.max(modifiedMs, sidecar.mtimeMs);
9328
9309
  } catch {
9329
9310
  }
@@ -10616,7 +10597,7 @@ function createKimiCodeAdapter() {
10616
10597
  }
10617
10598
 
10618
10599
  // src/adapters/opencode.ts
10619
- import os8 from "node:os";
10600
+ import os7 from "node:os";
10620
10601
  import path17 from "node:path";
10621
10602
  async function parseOpenCodeSessionFile(dbPath, options) {
10622
10603
  const { DatabaseSync } = await import("node:sqlite");
@@ -11071,20 +11052,20 @@ function opencodeDataCandidates(home, env) {
11071
11052
  const primary = xdgData && xdgData.trim() ? path17.join(path17.resolve(xdgData), "opencode", "opencode.db") : path17.join(home, ".local", "share", "opencode", "opencode.db");
11072
11053
  return [primary, path17.join(home, ".opencode", "opencode.db")];
11073
11054
  }
11074
- async function opencodeBackfillFiles(sourceRoot, home = os8.homedir(), env) {
11075
- const { stat: stat19 } = await import("node:fs/promises");
11055
+ async function opencodeBackfillFiles(sourceRoot, home = os7.homedir(), env) {
11056
+ const { stat: stat18 } = await import("node:fs/promises");
11076
11057
  if (sourceRoot) {
11077
11058
  if (!sourceRoot.endsWith(".db")) {
11078
11059
  return [];
11079
11060
  }
11080
- const info = await stat19(sourceRoot).catch(() => null);
11061
+ const info = await stat18(sourceRoot).catch(() => null);
11081
11062
  if (!info) {
11082
11063
  return [];
11083
11064
  }
11084
11065
  return [{ path: sourceRoot, modifiedAt: info.mtime.toISOString() }];
11085
11066
  }
11086
11067
  for (const candidatePath of opencodeDataCandidates(home, env)) {
11087
- const info = await stat19(candidatePath).catch(() => null);
11068
+ const info = await stat18(candidatePath).catch(() => null);
11088
11069
  if (info) {
11089
11070
  return [{ path: candidatePath, modifiedAt: info.mtime.toISOString() }];
11090
11071
  }
@@ -11184,8 +11165,8 @@ function createOpenCodeAdapter() {
11184
11165
  }
11185
11166
 
11186
11167
  // src/adapters/pi.ts
11187
- import { readdir as readdir9, readFile as readFile11, stat as stat12 } from "node:fs/promises";
11188
- import os9 from "node:os";
11168
+ import { readdir as readdir9, readFile as readFile11, stat as stat11 } from "node:fs/promises";
11169
+ import os8 from "node:os";
11189
11170
  import path18 from "node:path";
11190
11171
  init_fs();
11191
11172
  function piHostSessionIdFromBasename(basename) {
@@ -11596,7 +11577,7 @@ async function parsePiWorkflowRunFile(filePath, options) {
11596
11577
  return state.events.filter((event) => matchesBackfillFilters(event, options));
11597
11578
  }
11598
11579
  async function findPiSessionFileById(sessionId, options) {
11599
- const sessionsDir = piSessionDir(path18.resolve(stringOption(options.home) || os9.homedir()));
11580
+ const sessionsDir = piSessionDir(path18.resolve(stringOption(options.home) || os8.homedir()));
11600
11581
  try {
11601
11582
  const projectDirs = await readdir9(sessionsDir, { withFileTypes: true });
11602
11583
  for (const dir of projectDirs) {
@@ -11873,14 +11854,14 @@ async function workflowRunGroupId(filePath) {
11873
11854
  return void 0;
11874
11855
  }
11875
11856
  }
11876
- async function piBackfillFiles(sourceRoot, home = os9.homedir(), env) {
11857
+ async function piBackfillFiles(sourceRoot, home = os8.homedir(), env) {
11877
11858
  const lists = sourceRoot ? [await listFilesByExtensions(sourceRoot, [".jsonl", ".json"])] : await Promise.all([
11878
11859
  listFilesByExtensions(piSessionDir(home, env), [".jsonl"]),
11879
11860
  listFilesByExtensions(piWorkflowProjectsDir(home), [".json"])
11880
11861
  ]);
11881
11862
  const files = lists.flat().sort();
11882
11863
  return Promise.all(files.map(async (filePath) => {
11883
- const info = await stat12(filePath);
11864
+ const info = await stat11(filePath);
11884
11865
  let groupId = resolvePiBackfillGroupId(filePath);
11885
11866
  if (!groupId && filePath.endsWith(".json")) {
11886
11867
  groupId = await workflowRunGroupId(filePath);
@@ -11927,13 +11908,13 @@ function createPiAdapter() {
11927
11908
  }
11928
11909
 
11929
11910
  // src/adapters/qoder-cn.ts
11930
- import { readdir as readdir10, readFile as readFile12, stat as stat13 } from "node:fs/promises";
11931
- import os11 from "node:os";
11911
+ import { readdir as readdir10, readFile as readFile12, stat as stat12 } from "node:fs/promises";
11912
+ import os10 from "node:os";
11932
11913
  import path20 from "node:path";
11933
11914
 
11934
11915
  // src/adapters/qoder-local-db.ts
11935
11916
  import { access } from "node:fs/promises";
11936
- import os10 from "node:os";
11917
+ import os9 from "node:os";
11937
11918
  import path19 from "node:path";
11938
11919
  function takeQoderDbModelCall(calls, requestId, blockStart) {
11939
11920
  if (requestId) {
@@ -11951,7 +11932,7 @@ function takeQoderDbModelCall(calls, requestId, blockStart) {
11951
11932
  }
11952
11933
  return calls.ordered.shift();
11953
11934
  }
11954
- function appDataRoot(appDirName, home = os10.homedir()) {
11935
+ function appDataRoot(appDirName, home = os9.homedir()) {
11955
11936
  if (process.platform === "darwin") {
11956
11937
  return path19.join(home, "Library", "Application Support", appDirName);
11957
11938
  }
@@ -12058,7 +12039,7 @@ async function loadQoderDbModelCalls(appDirName, sessionId, modelMap) {
12058
12039
  const modelInfo = parseJsonLine(stringField(row, "model_info") || "") || {};
12059
12040
  const modelKey = stringField(modelInfo, "model_key");
12060
12041
  const call = {
12061
- model: modelKey ? modelMap[modelKey] || modelKey : preferredModel,
12042
+ model: mappedQoderModel(modelKey, modelMap) || preferredModel,
12062
12043
  inputTokens: numberField(usage, "prompt_tokens") || 0,
12063
12044
  outputTokens: numberField(usage, "completion_tokens") || 0,
12064
12045
  cachedTokens: numberField(usage, "cached_tokens") || 0
@@ -12094,7 +12075,14 @@ function resolveRootSessionId(db, sessionId) {
12094
12075
  }
12095
12076
  return current === sessionId ? void 0 : current;
12096
12077
  }
12078
+ function mappedQoderModel(raw, modelMap) {
12079
+ if (!raw) {
12080
+ return void 0;
12081
+ }
12082
+ return modelMap[raw] || raw;
12083
+ }
12097
12084
  function resolveSessionPreferredModel(db, sessionId, modelMap) {
12085
+ const chain = [];
12098
12086
  let current = sessionId;
12099
12087
  for (let depth = 0; depth < 5 && current; depth++) {
12100
12088
  let row;
@@ -12106,15 +12094,36 @@ function resolveSessionPreferredModel(db, sessionId, modelMap) {
12106
12094
  return void 0;
12107
12095
  }
12108
12096
  if (!row) {
12109
- return void 0;
12097
+ break;
12110
12098
  }
12099
+ chain.push(current);
12111
12100
  const info = parseJsonLine(stringField(row, "preferred_model_info") || "") || {};
12112
- const preferred = stringField(info, "preferred_model");
12101
+ const preferred = mappedQoderModel(stringField(info, "preferred_model"), modelMap);
12113
12102
  if (preferred) {
12114
- return modelMap[preferred] || preferred;
12103
+ return preferred;
12115
12104
  }
12116
12105
  current = stringField(row, "parent_session_id");
12117
12106
  }
12107
+ return resolveModelKeyFromAncestorMessages(db, chain, modelMap);
12108
+ }
12109
+ function resolveModelKeyFromAncestorMessages(db, sessionIds, modelMap) {
12110
+ for (const sid of sessionIds) {
12111
+ let rows;
12112
+ try {
12113
+ rows = db.prepare(
12114
+ `select model_info from chat_message where session_id = ? and role = 'assistant' and instr(model_info, 'model_key') > 0 order by gmt_create desc limit 20`
12115
+ ).all(sid);
12116
+ } catch {
12117
+ continue;
12118
+ }
12119
+ for (const row of rows) {
12120
+ const info = parseJsonLine(stringField(row, "model_info") || "") || {};
12121
+ const mapped = mappedQoderModel(stringField(info, "model_key"), modelMap);
12122
+ if (mapped) {
12123
+ return mapped;
12124
+ }
12125
+ }
12126
+ }
12118
12127
  return void 0;
12119
12128
  }
12120
12129
 
@@ -12227,8 +12236,8 @@ async function loadQoderCnSegmentModelCalls(filePath, isSubagentSession, modelMa
12227
12236
  }
12228
12237
  if (type === "model.response.completed") {
12229
12238
  const ts = stringField(raw, "ts") || "";
12230
- const rawModel = stringField(data, "model") || "unknown";
12231
- const modelName = modelMap[rawModel] || rawModel;
12239
+ const rawModel = stringField(data, "model");
12240
+ const modelName = rawModel ? modelMap[rawModel] || rawModel : void 0;
12232
12241
  modelCalls.push({
12233
12242
  ts,
12234
12243
  isSubagent: currentTurnIsSubagent,
@@ -12260,7 +12269,7 @@ async function parseQoderCnSessionFile(filePath, options) {
12260
12269
  let cwd;
12261
12270
  let project = projectContext.project;
12262
12271
  let model;
12263
- const home = path20.resolve(stringOption(options.home) || os11.homedir());
12272
+ const home = path20.resolve(stringOption(options.home) || os10.homedir());
12264
12273
  const modelMap = await loadQoderCnModelNames(configDir2, home);
12265
12274
  const isSubagentSession = filePath.includes("subagents");
12266
12275
  const segmentModelCalls = await loadQoderCnSegmentModelCalls(filePath, isSubagentSession, modelMap);
@@ -12830,7 +12839,7 @@ async function gitRootFromCwds2(cwds) {
12830
12839
  while (!seen.has(current)) {
12831
12840
  seen.add(current);
12832
12841
  try {
12833
- await stat13(path20.join(current, ".git"));
12842
+ await stat12(path20.join(current, ".git"));
12834
12843
  return current;
12835
12844
  } catch {
12836
12845
  }
@@ -12864,7 +12873,7 @@ function encodeQoderCnProjectPath(value) {
12864
12873
  }
12865
12874
  async function qoderCnProjectFromFilePath(filePath, options) {
12866
12875
  const projectDir = path20.basename(path20.dirname(filePath));
12867
- const home = options ? path20.resolve(stringOption(options.home) || os11.homedir()) : os11.homedir();
12876
+ const home = options ? path20.resolve(stringOption(options.home) || os10.homedir()) : os10.homedir();
12868
12877
  const resolved = await resolveQoderCnProjectPath(projectDir, home);
12869
12878
  if (resolved) {
12870
12879
  return path20.basename(resolved);
@@ -12980,8 +12989,8 @@ function createQoderCnAdapter() {
12980
12989
 
12981
12990
  // src/adapters/qoder.ts
12982
12991
  import { existsSync } from "node:fs";
12983
- import { readdir as readdir11, readFile as readFile13, stat as stat14 } from "node:fs/promises";
12984
- import os12 from "node:os";
12992
+ import { readdir as readdir11, readFile as readFile13, stat as stat13 } from "node:fs/promises";
12993
+ import os11 from "node:os";
12985
12994
  import path21 from "node:path";
12986
12995
  function parseQoderPaths(filePath) {
12987
12996
  const parts = filePath.split(path21.sep);
@@ -13113,8 +13122,8 @@ async function loadQoderSegmentModelCalls(filePath, isSubagentSession, modelMap)
13113
13122
  }
13114
13123
  if (type === "model.response.completed") {
13115
13124
  const ts = stringField(raw, "ts") || "";
13116
- const rawModel = stringField(data, "model") || "unknown";
13117
- const modelName = modelMap[rawModel] || rawModel;
13125
+ const rawModel = stringField(data, "model");
13126
+ const modelName = rawModel ? modelMap[rawModel] || rawModel : void 0;
13118
13127
  modelCalls.push({
13119
13128
  ts,
13120
13129
  isSubagent: currentTurnIsSubagent,
@@ -13146,7 +13155,7 @@ async function parseQoderSessionFile(filePath, options) {
13146
13155
  let cwd;
13147
13156
  let project = projectContext.project;
13148
13157
  let model;
13149
- const home = path21.resolve(stringOption(options.home) || os12.homedir());
13158
+ const home = path21.resolve(stringOption(options.home) || os11.homedir());
13150
13159
  const modelMap = await loadQoderModelNames(configDir2, home);
13151
13160
  const qwenworkRoot = isQwenworkConfigRoot(configDir2);
13152
13161
  const isSubagentSession = filePath.includes("subagents");
@@ -13682,7 +13691,7 @@ async function gitRootFromCwds3(cwds) {
13682
13691
  while (!seen.has(current)) {
13683
13692
  seen.add(current);
13684
13693
  try {
13685
- await stat14(path21.join(current, ".git"));
13694
+ await stat13(path21.join(current, ".git"));
13686
13695
  return current;
13687
13696
  } catch {
13688
13697
  }
@@ -13734,7 +13743,7 @@ function qoderEncodedProjectSuffix(projectDir, home) {
13734
13743
  }
13735
13744
  async function qoderProjectFromFilePath(filePath, options) {
13736
13745
  const projectDir = path21.basename(path21.dirname(filePath));
13737
- const home = options ? path21.resolve(stringOption(options.home) || os12.homedir()) : os12.homedir();
13746
+ const home = options ? path21.resolve(stringOption(options.home) || os11.homedir()) : os11.homedir();
13738
13747
  const resolved = await resolveQoderProjectPath(projectDir, home);
13739
13748
  if (resolved) {
13740
13749
  return path21.basename(resolved);
@@ -13886,11 +13895,14 @@ function normalizeId(id) {
13886
13895
  if (id === "claude") {
13887
13896
  return "claude-code";
13888
13897
  }
13898
+ if (id === "grok-bot") {
13899
+ return "cursor";
13900
+ }
13889
13901
  return id;
13890
13902
  }
13891
13903
 
13892
13904
  // src/adapters/workbuddy.ts
13893
- import { readdir as readdir12, readFile as readFile14, stat as stat15 } from "node:fs/promises";
13905
+ import { readdir as readdir12, readFile as readFile14, stat as stat14 } from "node:fs/promises";
13894
13906
  import path22 from "node:path";
13895
13907
  function workbuddyProjectsDir(home, env) {
13896
13908
  const override = env?.WORKBUDDY_PROJECTS_DIR || env?.WORKBUDDY_HOME;
@@ -14343,7 +14355,7 @@ async function workbuddyBackfillFiles(sourceRoot, home, env) {
14343
14355
  for (const entry of entries) {
14344
14356
  if (entry.isFile() && entry.name.endsWith(".jsonl")) {
14345
14357
  const filePath = path22.join(projectDir, entry.name);
14346
- const info = await stat15(filePath);
14358
+ const info = await stat14(filePath);
14347
14359
  files.push({ path: filePath, modifiedAt: info.mtime.toISOString() });
14348
14360
  }
14349
14361
  }
@@ -14404,7 +14416,7 @@ function createWorkbuddyAdapter() {
14404
14416
 
14405
14417
  // src/adapters/zcode.ts
14406
14418
  import { execFile } from "node:child_process";
14407
- import { readFile as readFile15, stat as stat16 } from "node:fs/promises";
14419
+ import { readFile as readFile15, stat as stat15 } from "node:fs/promises";
14408
14420
  import path23 from "node:path";
14409
14421
  import { promisify as promisify2 } from "node:util";
14410
14422
  var execFileAsync = promisify2(execFile);
@@ -14443,7 +14455,7 @@ var providerNameCache = null;
14443
14455
  async function loadProviderNames(configPath2) {
14444
14456
  let fileMtime = 0;
14445
14457
  try {
14446
- const info = await stat16(configPath2);
14458
+ const info = await stat15(configPath2);
14447
14459
  fileMtime = info.mtimeMs;
14448
14460
  } catch {
14449
14461
  return /* @__PURE__ */ new Map();
@@ -14671,7 +14683,7 @@ async function parseZCodeDb(filePath, options) {
14671
14683
  for (let i = 0; i < 12; i++) {
14672
14684
  const probe = path23.join(candidate, ".zcode", "v2", "config.json");
14673
14685
  try {
14674
- await stat16(probe);
14686
+ await stat15(probe);
14675
14687
  configPath2 = probe;
14676
14688
  break;
14677
14689
  } catch {
@@ -14905,11 +14917,11 @@ async function zcodeBackfillFiles(sourceRoot, home, env) {
14905
14917
  const candidate = sourceRoot || zcodeDbPath(home, env);
14906
14918
  const filePath = candidate.endsWith(".sqlite") ? candidate : path23.join(candidate, "db", "db.sqlite");
14907
14919
  try {
14908
- const info = await stat16(filePath);
14920
+ const info = await stat15(filePath);
14909
14921
  let modifiedMs = info.mtimeMs;
14910
14922
  for (const suffix of ["-wal", "-shm"]) {
14911
14923
  try {
14912
- const sidecar = await stat16(`${filePath}${suffix}`);
14924
+ const sidecar = await stat15(`${filePath}${suffix}`);
14913
14925
  modifiedMs = Math.max(modifiedMs, sidecar.mtimeMs);
14914
14926
  } catch {
14915
14927
  }
@@ -14952,7 +14964,7 @@ function createZCodeAdapter() {
14952
14964
  }
14953
14965
 
14954
14966
  // src/adapters/zed.ts
14955
- import os13 from "node:os";
14967
+ import os12 from "node:os";
14956
14968
  import path24 from "node:path";
14957
14969
  function zedThreadsCandidates(home, env) {
14958
14970
  const candidates = [];
@@ -15304,20 +15316,20 @@ async function parseZedSessionFile(dbPath, options) {
15304
15316
  }
15305
15317
  return events.filter((event) => matchesBackfillFilters(event, options));
15306
15318
  }
15307
- async function zedBackfillFiles(sourceRoot, home = os13.homedir(), env) {
15308
- const { stat: stat19 } = await import("node:fs/promises");
15319
+ async function zedBackfillFiles(sourceRoot, home = os12.homedir(), env) {
15320
+ const { stat: stat18 } = await import("node:fs/promises");
15309
15321
  if (sourceRoot) {
15310
15322
  if (!sourceRoot.endsWith(".db")) {
15311
15323
  return [];
15312
15324
  }
15313
- const info = await stat19(sourceRoot).catch(() => null);
15325
+ const info = await stat18(sourceRoot).catch(() => null);
15314
15326
  if (!info) {
15315
15327
  return [];
15316
15328
  }
15317
15329
  return [{ path: sourceRoot, modifiedAt: info.mtime.toISOString() }];
15318
15330
  }
15319
15331
  for (const candidatePath of zedThreadsCandidates(home, env)) {
15320
- const info = await stat19(candidatePath).catch(() => null);
15332
+ const info = await stat18(candidatePath).catch(() => null);
15321
15333
  if (info) {
15322
15334
  return [{ path: candidatePath, modifiedAt: info.mtime.toISOString() }];
15323
15335
  }
@@ -16208,7 +16220,7 @@ function defaultMachineName() {
16208
16220
  init_fs();
16209
16221
 
16210
16222
  // src/lib/logger.ts
16211
- import { appendFile, mkdir as mkdir5, rename as rename2, stat as stat17 } from "node:fs/promises";
16223
+ import { appendFile, mkdir as mkdir5, rename as rename2, stat as stat16 } from "node:fs/promises";
16212
16224
  import { homedir as homedir2 } from "node:os";
16213
16225
  import path26 from "node:path";
16214
16226
  var MAX_BYTES = 1 * 1024 * 1024;
@@ -16226,7 +16238,7 @@ function serializeError(error) {
16226
16238
  }
16227
16239
  async function rotateIfNeeded(file) {
16228
16240
  try {
16229
- const info = await stat17(file);
16241
+ const info = await stat16(file);
16230
16242
  if (info.size > MAX_BYTES) {
16231
16243
  await rename2(file, `${file}.1`).catch(() => {
16232
16244
  });
@@ -16462,7 +16474,7 @@ async function deleteMachine(remote, id) {
16462
16474
  }
16463
16475
 
16464
16476
  // src/lib/types.ts
16465
- var BACKFILL_STATE_SCHEMA_VERSION = 10;
16477
+ var BACKFILL_STATE_SCHEMA_VERSION = 11;
16466
16478
 
16467
16479
  // src/cli.ts
16468
16480
  function createRegistry() {
@@ -16483,7 +16495,6 @@ function createRegistry() {
16483
16495
  registry.register(createZedAdapter());
16484
16496
  registry.register(createKimiCodeAdapter());
16485
16497
  registry.register(createCursorAdapter());
16486
- registry.register(createGrokBotAdapter());
16487
16498
  registry.register(createHermesAdapter());
16488
16499
  return registry;
16489
16500
  }
@@ -17044,10 +17055,14 @@ async function listBackfillSourceFiles(source, options, ctx) {
17044
17055
  return zedBackfillFiles(stringOption(options["source-root"]), resolveHome3(options, ctx), ctx.env);
17045
17056
  }
17046
17057
  if (source.id === "cursor") {
17047
- return cursorBackfillFiles(stringOption(options["source-root"]), resolveHome3(options, ctx), ctx.env);
17048
- }
17049
- if (source.id === "grok-bot") {
17050
- return grokBotBackfillFiles(stringOption(options["source-root"]), resolveHome3(options, ctx), ctx.env, options);
17058
+ const requested = normalizeBackfillSource(stringOption(options.source) || "all");
17059
+ const skipCursorCloud = requested === "all" && disabledBackfillSourceSet(readConfig(resolveHome3(options, ctx))).has("grok-bot");
17060
+ return cursorBackfillFiles(
17061
+ stringOption(options["source-root"]),
17062
+ resolveHome3(options, ctx),
17063
+ ctx.env,
17064
+ { ...options, skipCursorCloud }
17065
+ );
17051
17066
  }
17052
17067
  if (source.id === "hermes") {
17053
17068
  return hermesBackfillFiles(stringOption(options["source-root"]), resolveHome3(options, ctx), ctx.env);
@@ -17059,7 +17074,7 @@ async function listBackfillSourceFiles(source, options, ctx) {
17059
17074
  const fileLists = await Promise.all(roots.map((r) => listJsonlFiles(r)));
17060
17075
  const files = fileLists.flat().sort().slice(0, numberOption(options.limit) || void 0);
17061
17076
  return Promise.all(files.map(async (filePath) => {
17062
- const info = await stat18(filePath);
17077
+ const info = await stat17(filePath);
17063
17078
  return { path: filePath, modifiedAt: info.mtime.toISOString() };
17064
17079
  }));
17065
17080
  }
@@ -17310,20 +17325,36 @@ async function sendSessionRollupBatch(rollups, options, ctx) {
17310
17325
  machine,
17311
17326
  allowHistoricalRewrite: options.force === true
17312
17327
  });
17313
- if (options.force && result.conflicts === 0) {
17314
- for (const rollup of rollups) {
17315
- for (const sessionId of rollup.supersededSessionIds ?? []) {
17316
- try {
17317
- await deleteRollupsBySource(remote, rollup.source, machine, {
17318
- rollupKey: createImportKey(["rollup", rollup.source, sessionId]),
17319
- allowHistoricalRewrite: options.force === true
17320
- });
17321
- } catch (error) {
17322
- debug(ctx, `Failed to delete superseded ${rollup.source} session ${sessionId}: ${error.message}
17328
+ if (result.conflicts === 0) {
17329
+ if (options.force) {
17330
+ for (const rollup of rollups) {
17331
+ for (const sessionId of rollup.supersededSessionIds ?? []) {
17332
+ try {
17333
+ await deleteRollupsBySource(remote, rollup.source, machine, {
17334
+ rollupKey: rollupKeyForSourceSession(rollup.source, sessionId),
17335
+ allowHistoricalRewrite: true
17336
+ });
17337
+ } catch (error) {
17338
+ debug(ctx, `Failed to delete superseded ${rollup.source} session ${sessionId}: ${error.message}
17323
17339
  `);
17340
+ }
17324
17341
  }
17325
17342
  }
17326
17343
  }
17344
+ for (const rollup of rollups) {
17345
+ if (rollup.source !== "cursor") {
17346
+ continue;
17347
+ }
17348
+ try {
17349
+ await deleteRollupsBySource(remote, "grok-bot", machine, {
17350
+ rollupKey: rollupKeyForSourceSession("grok-bot", rollup.sessionId),
17351
+ allowHistoricalRewrite: true
17352
+ });
17353
+ } catch (error) {
17354
+ debug(ctx, `Failed to delete grok-bot twin of cursor session ${rollup.sessionId}: ${error.message}
17355
+ `);
17356
+ }
17357
+ }
17327
17358
  }
17328
17359
  return result;
17329
17360
  }
@@ -17611,7 +17642,7 @@ function syncLocalRunnerEntryArgs(cliPath) {
17611
17642
  return [path27.resolve(path27.dirname(cliPath), "../bin/vibetime.mjs")];
17612
17643
  }
17613
17644
  function resolveHome3(options, ctx) {
17614
- return path27.resolve(stringOption(options.home) || ctx.env.HOME || os14.homedir());
17645
+ return path27.resolve(stringOption(options.home) || ctx.env.HOME || os13.homedir());
17615
17646
  }
17616
17647
  function requestedTargets(options) {
17617
17648
  const value = options.target || options.targets;
@@ -17621,7 +17652,13 @@ function requestedTargets(options) {
17621
17652
  return valuesOption(value).flatMap((item) => item.split(",")).map((t) => t.trim()).filter(Boolean);
17622
17653
  }
17623
17654
  function normalizeBackfillSource(source) {
17624
- return source === "claude" ? "claude-code" : source;
17655
+ if (source === "claude") {
17656
+ return "claude-code";
17657
+ }
17658
+ if (source === "grok-bot") {
17659
+ return "cursor";
17660
+ }
17661
+ return source;
17625
17662
  }
17626
17663
  function requiredOption(options, name) {
17627
17664
  const value = stringOption(options[name]);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@yhong91/vibetime",
3
3
  "type": "module",
4
- "version": "0.1.74",
4
+ "version": "0.1.76",
5
5
  "description": "vibetime CLI — install AI-agent hooks (Claude Code, Codex, OpenCode, Pi, Cursor) and report activity to vibetime.",
6
6
  "license": "MIT",
7
7
  "publishConfig": {