@yhong91/vibetime 0.1.68 → 0.1.70

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 +93 -48
  2. package/package.json +1 -1
package/bin/vibetime.mjs CHANGED
@@ -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.68" : "0.1.1";
2050
+ var PACKAGE_VERSION = true ? "0.1.70" : "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;
@@ -5024,7 +5024,7 @@ function fileActivitiesFromPatchChanges(changes, ts, cwd, displayFilePath3) {
5024
5024
  }
5025
5025
 
5026
5026
  // src/lib/session-context.ts
5027
- import { mkdir as mkdir3, stat as stat6, writeFile as writeFile3 } from "node:fs/promises";
5027
+ import { mkdir as mkdir3, rename, stat as stat6, unlink, writeFile as writeFile3 } from "node:fs/promises";
5028
5028
  import os4 from "node:os";
5029
5029
  import path10 from "node:path";
5030
5030
  init_fs();
@@ -5039,7 +5039,12 @@ function resolveHome2(options) {
5039
5039
  return options ? path10.resolve(stringOption(options.home) || os4.homedir()) : os4.homedir();
5040
5040
  }
5041
5041
  async function readPersistedSessionContext(home, sessionId) {
5042
- const raw = await readJsonIfExists(sessionContextPath(home, sessionId));
5042
+ let raw;
5043
+ try {
5044
+ raw = await readJsonIfExists(sessionContextPath(home, sessionId));
5045
+ } catch {
5046
+ return void 0;
5047
+ }
5043
5048
  if (!isPlainObject(raw)) {
5044
5049
  return void 0;
5045
5050
  }
@@ -5086,8 +5091,17 @@ async function persistHookSessionContext(home, payload) {
5086
5091
  usage: nextUsage
5087
5092
  };
5088
5093
  await mkdir3(sessionContextDir(home), { recursive: true });
5089
- await writeFile3(sessionContextPath(home, sessionId), `${JSON.stringify(context, null, 2)}
5094
+ const dest = sessionContextPath(home, sessionId);
5095
+ const tmp = `${dest}.${process.pid}.tmp`;
5096
+ await writeFile3(tmp, `${JSON.stringify(context, null, 2)}
5090
5097
  `, "utf8");
5098
+ try {
5099
+ await rename(tmp, dest);
5100
+ } catch (error) {
5101
+ await unlink(tmp).catch(() => {
5102
+ });
5103
+ throw error;
5104
+ }
5091
5105
  }
5092
5106
  function hookSessionId(payload) {
5093
5107
  for (const key of ["session_id", "sessionId", "conversation_id", "conversationId"]) {
@@ -6499,6 +6513,22 @@ function cloudAgentSessionFromEvents(conversationId, events) {
6499
6513
  events: sorted.map((event) => ({ ...event, conversationId }))
6500
6514
  };
6501
6515
  }
6516
+ function hookUncachedInputTokens(turn) {
6517
+ return Math.max(0, (turn.tokensInput || 0) - (turn.tokensCacheReadInput || 0));
6518
+ }
6519
+ function cloudEventMatchesHookTurn(event, turn) {
6520
+ return event.tokensInput === hookUncachedInputTokens(turn) && event.tokensOutput === (turn.tokensOutput || 0) && event.tokensCacheReadInput === (turn.tokensCacheReadInput || 0);
6521
+ }
6522
+ function unmatchedCloudEvents(events, turns) {
6523
+ const used = /* @__PURE__ */ new Set();
6524
+ for (const turn of turns) {
6525
+ const index = events.findIndex((event, i) => !used.has(i) && cloudEventMatchesHookTurn(event, turn));
6526
+ if (index >= 0) {
6527
+ used.add(index);
6528
+ }
6529
+ }
6530
+ return events.filter((_, i) => !used.has(i));
6531
+ }
6502
6532
  async function fetchCursorDashboardUsage(args) {
6503
6533
  const cookie = cursorSessionCookie(args.accessToken);
6504
6534
  if (!cookie) {
@@ -6742,6 +6772,13 @@ function injectedCloudEvent(conversationId, item) {
6742
6772
  tokensCacheCreationInput
6743
6773
  };
6744
6774
  }
6775
+ function cloudUsageMapFromSessions(sessions) {
6776
+ const map = /* @__PURE__ */ new Map();
6777
+ for (const session of sessions) {
6778
+ map.set(session.conversationId, session.events);
6779
+ }
6780
+ return map;
6781
+ }
6745
6782
  function injectedCursorCloudUsage(options) {
6746
6783
  if (!Object.prototype.hasOwnProperty.call(options, "cursorCloudUsage")) {
6747
6784
  return void 0;
@@ -6827,7 +6864,9 @@ async function loadCursorCloudUsageMap(options, filePath) {
6827
6864
  if (intervalSeconds > 0 && !options.force) {
6828
6865
  const lastFetchedMs = await readLastCloudFetchAt(home);
6829
6866
  if (lastFetchedMs !== void 0 && Date.now() - lastFetchedMs < intervalSeconds * 1e3) {
6830
- return /* @__PURE__ */ new Map();
6867
+ return cloudUsageMapFromSessions(
6868
+ parseCursorCloudUsageCache(await readJsonIfExists(cursorCloudUsageCachePath(home)))
6869
+ );
6831
6870
  }
6832
6871
  }
6833
6872
  const pending = (async () => {
@@ -6877,15 +6916,13 @@ function cloudUsageToPersisted(row, index = 0) {
6877
6916
  async function resolveCursorSessionUsage(options, sessionId, filePath) {
6878
6917
  const persisted = await readPersistedSessionContextFromOptions(options, sessionId);
6879
6918
  const hookUsage = persisted?.usage ?? [];
6880
- if (hookUsage.length > 0) {
6881
- return hookUsage;
6882
- }
6883
6919
  const cloudMap = await loadCursorCloudUsageMap(options, filePath);
6884
- const cloudEvents = cloudMap.get(sessionId);
6885
- if (!cloudEvents?.length) {
6886
- return [];
6887
- }
6888
- return cloudEvents.map((event, index) => cloudUsageToPersisted(event, index));
6920
+ const cloudEvents = cloudMap.get(sessionId) ?? [];
6921
+ const leftover = unmatchedCloudEvents(cloudEvents, hookUsage);
6922
+ return [
6923
+ ...hookUsage,
6924
+ ...leftover.map((event, index) => cloudUsageToPersisted(event, index))
6925
+ ];
6889
6926
  }
6890
6927
  function cursorCloudUsageCachePath(home) {
6891
6928
  return path13.join(home, ".vibetime", CURSOR_CLOUD_USAGE_FILENAME);
@@ -7082,10 +7119,10 @@ async function openCursorDb(dbPath) {
7082
7119
  return {
7083
7120
  db: new DatabaseSync(tmp, { readOnly: true }),
7084
7121
  cleanup: async () => {
7085
- const { unlink } = await import("node:fs/promises");
7086
- await unlink(tmp).catch(() => void 0);
7087
- await unlink(`${tmp}-wal`).catch(() => void 0);
7088
- await unlink(`${tmp}-shm`).catch(() => void 0);
7122
+ const { unlink: unlink2 } = await import("node:fs/promises");
7123
+ await unlink2(tmp).catch(() => void 0);
7124
+ await unlink2(`${tmp}-wal`).catch(() => void 0);
7125
+ await unlink2(`${tmp}-shm`).catch(() => void 0);
7089
7126
  }
7090
7127
  };
7091
7128
  }
@@ -8241,7 +8278,7 @@ import os7 from "node:os";
8241
8278
  var SOURCE_ID2 = "grok-bot";
8242
8279
  var AGENT_NAME2 = "grok-bot";
8243
8280
  var IDENTITY = { source: SOURCE_ID2, agent: AGENT_NAME2 };
8244
- async function grokBotBackfillFiles(sourceRoot, home = os7.homedir(), env, options) {
8281
+ async function grokBotBackfillFiles(sourceRoot, home = os7.homedir(), _env, options) {
8245
8282
  if (sourceRoot) {
8246
8283
  const info = await stat9(sourceRoot).catch(() => null);
8247
8284
  return info ? [{ path: sourceRoot, modifiedAt: info.mtime.toISOString() }] : [];
@@ -8255,29 +8292,25 @@ async function grokBotBackfillFiles(sourceRoot, home = os7.homedir(), env, optio
8255
8292
  const info = await stat9(cachePath).catch(() => null);
8256
8293
  return info ? [{ path: cachePath, modifiedAt: info.mtime.toISOString() }] : [];
8257
8294
  }
8258
- const localIds = await listLocalCursorSessionIds(home, env);
8259
- const cloudOnly = [];
8295
+ const sessions = [];
8260
8296
  for (const [conversationId, events] of map) {
8261
- if (localIds.has(conversationId)) {
8262
- continue;
8263
- }
8264
8297
  const session = cloudAgentSessionFromEvents(conversationId, events);
8265
8298
  if (session) {
8266
- cloudOnly.push(session);
8299
+ sessions.push(session);
8267
8300
  }
8268
8301
  }
8269
- if (cloudOnly.length === 0) {
8302
+ if (sessions.length === 0) {
8270
8303
  return [];
8271
8304
  }
8272
8305
  return [{
8273
8306
  path: cachePath,
8274
- modifiedAt: await writeCursorCloudUsageCache(cachePath, cloudOnly)
8307
+ modifiedAt: await writeCursorCloudUsageCache(cachePath, sessions)
8275
8308
  }];
8276
8309
  }
8277
8310
  function createGrokBotAdapter() {
8278
8311
  return {
8279
8312
  id: SOURCE_ID2,
8280
- label: "Cloud Agent",
8313
+ label: "Grok Bot",
8281
8314
  agentName: AGENT_NAME2,
8282
8315
  kind: "agent",
8283
8316
  // No hooks to install — data arrives via the Cursor dashboard API.
@@ -11241,19 +11274,25 @@ async function parseModelNamesFromDynamicTexts(dynamicTextsPath) {
11241
11274
  }
11242
11275
  }
11243
11276
  async function loadQoderCnModelNames(configDir2, home) {
11244
- const map = await parseModelNamesFromDynamicTexts(path19.join(configDir2, ".auth", "dynamic-texts.json"));
11245
- const siblingConfigDir = configDir2.replace(/\.qoder-cn$/, ".qoder");
11246
- if (siblingConfigDir !== configDir2) {
11247
- const siblingMap = await parseModelNamesFromDynamicTexts(path19.join(siblingConfigDir, ".auth", "dynamic-texts.json"));
11248
- for (const [key, val] of Object.entries(siblingMap)) {
11277
+ const map = {};
11278
+ for (const appDirName of ["QoderCN", "Qoder"]) {
11279
+ const ideMap = await loadQoderIdeModelCatalog(appDirName, home);
11280
+ for (const [key, val] of Object.entries(ideMap)) {
11249
11281
  if (!(key in map)) {
11250
11282
  map[key] = val;
11251
11283
  }
11252
11284
  }
11253
11285
  }
11254
- for (const appDirName of ["QoderCN", "Qoder"]) {
11255
- const ideMap = await loadQoderIdeModelCatalog(appDirName, home);
11256
- for (const [key, val] of Object.entries(ideMap)) {
11286
+ const textsMap = await parseModelNamesFromDynamicTexts(path19.join(configDir2, ".auth", "dynamic-texts.json"));
11287
+ for (const [key, val] of Object.entries(textsMap)) {
11288
+ if (!(key in map)) {
11289
+ map[key] = val;
11290
+ }
11291
+ }
11292
+ const siblingConfigDir = configDir2.replace(/\.qoder-cn$/, ".qoder");
11293
+ if (siblingConfigDir !== configDir2) {
11294
+ const siblingMap = await parseModelNamesFromDynamicTexts(path19.join(siblingConfigDir, ".auth", "dynamic-texts.json"));
11295
+ for (const [key, val] of Object.entries(siblingMap)) {
11257
11296
  if (!(key in map)) {
11258
11297
  map[key] = val;
11259
11298
  }
@@ -12099,7 +12138,21 @@ async function parseModelNamesFromDynamicTexts2(dynamicTextsPath) {
12099
12138
  }
12100
12139
  }
12101
12140
  async function loadQoderModelNames(configDir2, home) {
12102
- const map = await parseModelNamesFromDynamicTexts2(path20.join(configDir2, ".auth", "dynamic-texts.json"));
12141
+ const map = {};
12142
+ for (const appDirName of ["Qoder", "QoderCN"]) {
12143
+ const ideMap = await loadQoderIdeModelCatalog(appDirName, home);
12144
+ for (const [key, val] of Object.entries(ideMap)) {
12145
+ if (!(key in map)) {
12146
+ map[key] = val;
12147
+ }
12148
+ }
12149
+ }
12150
+ const textsMap = await parseModelNamesFromDynamicTexts2(path20.join(configDir2, ".auth", "dynamic-texts.json"));
12151
+ for (const [key, val] of Object.entries(textsMap)) {
12152
+ if (!(key in map)) {
12153
+ map[key] = val;
12154
+ }
12155
+ }
12103
12156
  const siblingConfigDir = configDir2.replace(/\.qoder$/, ".qoder-cn");
12104
12157
  if (siblingConfigDir !== configDir2) {
12105
12158
  const siblingMap = await parseModelNamesFromDynamicTexts2(path20.join(siblingConfigDir, ".auth", "dynamic-texts.json"));
@@ -12122,14 +12175,6 @@ async function loadQoderModelNames(configDir2, home) {
12122
12175
  }
12123
12176
  }
12124
12177
  }
12125
- for (const appDirName of ["Qoder", "QoderCN"]) {
12126
- const ideMap = await loadQoderIdeModelCatalog(appDirName, home);
12127
- for (const [key, val] of Object.entries(ideMap)) {
12128
- if (!(key in map)) {
12129
- map[key] = val;
12130
- }
12131
- }
12132
- }
12133
12178
  return map;
12134
12179
  }
12135
12180
  function isQwenworkConfigRoot(configDir2) {
@@ -15190,8 +15235,8 @@ async function uninstallGeneratedFile(filePath, { dryRun, onWrite }) {
15190
15235
  onWrite(`Would uninstall ${filePath}`);
15191
15236
  return;
15192
15237
  }
15193
- const { unlink } = await import("node:fs/promises");
15194
- await unlink(filePath);
15238
+ const { unlink: unlink2 } = await import("node:fs/promises");
15239
+ await unlink2(filePath);
15195
15240
  onWrite(`Uninstalled ${filePath}`);
15196
15241
  }
15197
15242
 
@@ -15258,7 +15303,7 @@ function defaultMachineName() {
15258
15303
  init_fs();
15259
15304
 
15260
15305
  // src/lib/logger.ts
15261
- import { appendFile, mkdir as mkdir5, rename, stat as stat16 } from "node:fs/promises";
15306
+ import { appendFile, mkdir as mkdir5, rename as rename2, stat as stat16 } from "node:fs/promises";
15262
15307
  import { homedir as homedir2 } from "node:os";
15263
15308
  import path25 from "node:path";
15264
15309
  var MAX_BYTES = 1 * 1024 * 1024;
@@ -15278,7 +15323,7 @@ async function rotateIfNeeded(file) {
15278
15323
  try {
15279
15324
  const info = await stat16(file);
15280
15325
  if (info.size > MAX_BYTES) {
15281
- await rename(file, `${file}.1`).catch(() => {
15326
+ await rename2(file, `${file}.1`).catch(() => {
15282
15327
  });
15283
15328
  }
15284
15329
  } catch {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@yhong91/vibetime",
3
3
  "type": "module",
4
- "version": "0.1.68",
4
+ "version": "0.1.70",
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": {