@yhong91/vibetime 0.1.58 → 0.1.59

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 +550 -36
  2. package/package.json +1 -1
package/bin/vibetime.mjs CHANGED
@@ -884,7 +884,7 @@ 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 mkdir5, open, rm, stat as stat16, writeFile as writeFile4 } from "node:fs/promises";
887
+ import { mkdir as mkdir6, open, rm, stat as stat16, writeFile as writeFile5 } from "node:fs/promises";
888
888
  import os13 from "node:os";
889
889
  import path26 from "node:path";
890
890
  import { fileURLToPath } from "node:url";
@@ -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.58" : "0.1.1";
2050
+ var PACKAGE_VERSION = true ? "0.1.59" : "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;
@@ -6292,7 +6292,7 @@ function createCopilotAdapter() {
6292
6292
  }
6293
6293
 
6294
6294
  // src/adapters/cursor.ts
6295
- import { copyFile, readdir as readdir6, readFile as readFile8, stat as stat8 } from "node:fs/promises";
6295
+ import { copyFile, mkdir as mkdir4, readdir as readdir6, readFile as readFile8, stat as stat8, writeFile as writeFile4 } from "node:fs/promises";
6296
6296
  import os6 from "node:os";
6297
6297
  import path13 from "node:path";
6298
6298
 
@@ -6400,6 +6400,222 @@ function collectCursorHookCommands(content) {
6400
6400
 
6401
6401
  // src/adapters/cursor.ts
6402
6402
  init_fs();
6403
+
6404
+ // src/adapters/cursor-cloud-usage.ts
6405
+ var CURSOR_CLOUD_USAGE_GENERATION_ID = "cursor-cloud";
6406
+ var CURSOR_CLOUD_USAGE_FILENAME = "cursor-cloud-usage.json";
6407
+ var CURSOR_CLOUD_AGENT_PROJECT = "Cloud Agent";
6408
+ var CURSOR_DASHBOARD_USAGE_URL = "https://cursor.com/api/dashboard/get-filtered-usage-events";
6409
+ var CURSOR_CLOUD_USAGE_CACHE_VERSION = 1;
6410
+ var DEFAULT_WINDOW_DAYS = 90;
6411
+ var PAGE_SIZE = 1e3;
6412
+ function cursorCloudUsageDisabled(env = process.env) {
6413
+ const value = env.VIBETIME_CURSOR_CLOUD_USAGE;
6414
+ return value === "0" || value === "false";
6415
+ }
6416
+ function cursorSessionCookie(accessToken) {
6417
+ const token = accessToken.trim();
6418
+ if (!token) {
6419
+ return void 0;
6420
+ }
6421
+ const payload = jwtPayload(token);
6422
+ const userId = String(payload?.sub || "").split("|").at(-1)?.trim();
6423
+ if (!userId) {
6424
+ return void 0;
6425
+ }
6426
+ return `WorkosCursorSessionToken=${userId}%3A%3A${token}`;
6427
+ }
6428
+ function cursorCloudEventFromDashboard(event) {
6429
+ if (!isPlainObject(event)) {
6430
+ return void 0;
6431
+ }
6432
+ const conversationId = stringField(event, "conversationId")?.trim();
6433
+ if (!conversationId) {
6434
+ return void 0;
6435
+ }
6436
+ const tokenUsage = objectField(event, "tokenUsage");
6437
+ const tokensInput = numberField(tokenUsage, "inputTokens") || 0;
6438
+ const tokensOutput = numberField(tokenUsage, "outputTokens") || 0;
6439
+ const tokensCacheReadInput = numberField(tokenUsage, "cacheReadTokens") || 0;
6440
+ const tokensCacheCreationInput = numberField(tokenUsage, "cacheWriteTokens") || 0;
6441
+ if (tokensInput <= 0 && tokensOutput <= 0 && tokensCacheReadInput <= 0 && tokensCacheCreationInput <= 0) {
6442
+ return void 0;
6443
+ }
6444
+ return {
6445
+ conversationId,
6446
+ model: stringField(event, "model") || void 0,
6447
+ ts: timestampFrom(event.timestamp) || (/* @__PURE__ */ new Date(0)).toISOString(),
6448
+ tokensInput,
6449
+ tokensOutput,
6450
+ tokensCacheReadInput,
6451
+ tokensCacheCreationInput
6452
+ };
6453
+ }
6454
+ function groupCursorUsageEvents(events) {
6455
+ const byConversation = /* @__PURE__ */ new Map();
6456
+ for (const event of events) {
6457
+ const parsed = cursorCloudEventFromDashboard(event);
6458
+ if (!parsed) {
6459
+ continue;
6460
+ }
6461
+ const current = byConversation.get(parsed.conversationId);
6462
+ if (current) {
6463
+ current.push(parsed);
6464
+ } else {
6465
+ byConversation.set(parsed.conversationId, [parsed]);
6466
+ }
6467
+ }
6468
+ return byConversation;
6469
+ }
6470
+ function sumCursorCloudEvents(conversationId, events) {
6471
+ if (events.length === 0) {
6472
+ return void 0;
6473
+ }
6474
+ const first = events[0];
6475
+ const firstStart = first.startedAt || first.ts;
6476
+ const summed = {
6477
+ conversationId,
6478
+ model: first.model,
6479
+ ts: first.ts,
6480
+ startedAt: firstStart,
6481
+ tokensInput: 0,
6482
+ tokensOutput: 0,
6483
+ tokensCacheReadInput: 0,
6484
+ tokensCacheCreationInput: 0
6485
+ };
6486
+ for (const event of events) {
6487
+ summed.tokensInput += event.tokensInput;
6488
+ summed.tokensOutput += event.tokensOutput;
6489
+ summed.tokensCacheReadInput += event.tokensCacheReadInput;
6490
+ summed.tokensCacheCreationInput += event.tokensCacheCreationInput;
6491
+ const eventStart = event.startedAt || event.ts;
6492
+ if (Date.parse(eventStart) < Date.parse(summed.startedAt)) {
6493
+ summed.startedAt = eventStart;
6494
+ }
6495
+ if (Date.parse(event.ts) >= Date.parse(summed.ts)) {
6496
+ summed.ts = event.ts;
6497
+ if (event.model) {
6498
+ summed.model = event.model;
6499
+ }
6500
+ }
6501
+ }
6502
+ return summed;
6503
+ }
6504
+ function hookUncachedInputTokens(turn) {
6505
+ return Math.max(0, (turn.tokensInput || 0) - (turn.tokensCacheReadInput || 0));
6506
+ }
6507
+ function cloudEventMatchesHookTurn(event, turn) {
6508
+ return event.tokensInput === hookUncachedInputTokens(turn) && event.tokensOutput === (turn.tokensOutput || 0) && event.tokensCacheReadInput === (turn.tokensCacheReadInput || 0);
6509
+ }
6510
+ function unmatchedCloudEvents(events, turns) {
6511
+ const used = /* @__PURE__ */ new Set();
6512
+ for (const turn of turns) {
6513
+ const index = events.findIndex((event, i) => !used.has(i) && cloudEventMatchesHookTurn(event, turn));
6514
+ if (index >= 0) {
6515
+ used.add(index);
6516
+ }
6517
+ }
6518
+ return events.filter((_, i) => !used.has(i));
6519
+ }
6520
+ async function fetchCursorDashboardUsage(args) {
6521
+ const cookie = cursorSessionCookie(args.accessToken);
6522
+ if (!cookie) {
6523
+ return /* @__PURE__ */ new Map();
6524
+ }
6525
+ const fetchImpl = args.fetchImpl || fetch;
6526
+ const now = args.now || /* @__PURE__ */ new Date();
6527
+ const windowDays = args.windowDays ?? DEFAULT_WINDOW_DAYS;
6528
+ const pageSize = args.pageSize ?? PAGE_SIZE;
6529
+ const start = new Date(now.getTime() - windowDays * 24 * 60 * 60 * 1e3);
6530
+ const events = [];
6531
+ let page = 1;
6532
+ let total = Number.POSITIVE_INFINITY;
6533
+ while ((page - 1) * pageSize < total) {
6534
+ const body = JSON.stringify({
6535
+ page,
6536
+ pageSize,
6537
+ startDate: String(start.getTime()),
6538
+ endDate: String(now.getTime())
6539
+ });
6540
+ const response = await fetchImpl(CURSOR_DASHBOARD_USAGE_URL, {
6541
+ method: "POST",
6542
+ headers: {
6543
+ "Content-Type": "application/json",
6544
+ Accept: "application/json",
6545
+ Cookie: cookie,
6546
+ Origin: "https://cursor.com",
6547
+ Referer: "https://cursor.com/settings",
6548
+ "User-Agent": "vibetime-cli"
6549
+ },
6550
+ body,
6551
+ signal: AbortSignal.timeout(2e4)
6552
+ });
6553
+ if (!response.ok) {
6554
+ return /* @__PURE__ */ new Map();
6555
+ }
6556
+ const payload = await response.json();
6557
+ if (!isPlainObject(payload)) {
6558
+ return /* @__PURE__ */ new Map();
6559
+ }
6560
+ const pageEvents = Array.isArray(payload.usageEventsDisplay) ? payload.usageEventsDisplay : [];
6561
+ events.push(...pageEvents);
6562
+ const reported = numberField(payload, "totalUsageEventsCount");
6563
+ total = reported ?? pageEvents.length;
6564
+ if (pageEvents.length < pageSize) {
6565
+ break;
6566
+ }
6567
+ page += 1;
6568
+ }
6569
+ return groupCursorUsageEvents(events);
6570
+ }
6571
+ function serializeCursorCloudUsageCache(sessions) {
6572
+ const sorted = [...sessions].sort((a, b) => a.conversationId.localeCompare(b.conversationId));
6573
+ return `${JSON.stringify({ version: CURSOR_CLOUD_USAGE_CACHE_VERSION, sessions: sorted }, null, 2)}
6574
+ `;
6575
+ }
6576
+ function parseCursorCloudUsageCache(raw) {
6577
+ if (!isPlainObject(raw) || raw.version !== CURSOR_CLOUD_USAGE_CACHE_VERSION || !Array.isArray(raw.sessions)) {
6578
+ return [];
6579
+ }
6580
+ const sessions = [];
6581
+ for (const item of raw.sessions) {
6582
+ if (!isPlainObject(item)) {
6583
+ continue;
6584
+ }
6585
+ const conversationId = stringField(item, "conversationId")?.trim();
6586
+ const ts = stringField(item, "ts");
6587
+ const startedAt = stringField(item, "startedAt") || ts;
6588
+ if (!conversationId || !ts || !startedAt) {
6589
+ continue;
6590
+ }
6591
+ sessions.push({
6592
+ conversationId,
6593
+ model: stringField(item, "model"),
6594
+ ts,
6595
+ startedAt,
6596
+ tokensInput: numberField(item, "tokensInput") || 0,
6597
+ tokensOutput: numberField(item, "tokensOutput") || 0,
6598
+ tokensCacheReadInput: numberField(item, "tokensCacheReadInput") || 0,
6599
+ tokensCacheCreationInput: numberField(item, "tokensCacheCreationInput") || 0
6600
+ });
6601
+ }
6602
+ return sessions;
6603
+ }
6604
+ function jwtPayload(token) {
6605
+ const segment = token.split(".")[1];
6606
+ if (!segment) {
6607
+ return void 0;
6608
+ }
6609
+ try {
6610
+ const json = Buffer.from(segment, "base64url").toString("utf8");
6611
+ const parsed = JSON.parse(json);
6612
+ return isPlainObject(parsed) ? parsed : void 0;
6613
+ } catch {
6614
+ return void 0;
6615
+ }
6616
+ }
6617
+
6618
+ // src/adapters/cursor.ts
6403
6619
  var SOURCE_ID = "cursor";
6404
6620
  var AGENT_NAME = "cursor";
6405
6621
  var HOOK_COMMAND = `vibetime hook --agent ${SOURCE_ID}`;
@@ -6468,6 +6684,297 @@ function cursorStateDbCandidates(home, env) {
6468
6684
  }
6469
6685
  return candidates;
6470
6686
  }
6687
+ var cursorCloudUsageByOptions = /* @__PURE__ */ new WeakMap();
6688
+ function isCursorStateDbPath(filePath) {
6689
+ if (!filePath) {
6690
+ return false;
6691
+ }
6692
+ const base = path13.basename(filePath);
6693
+ return base === "state.vscdb" || base.endsWith(".vscdb");
6694
+ }
6695
+ function injectedCloudEvent(conversationId, item) {
6696
+ const tokensInput = numberField(item, "tokensInput") || numberField(item, "inputTokens") || 0;
6697
+ const tokensOutput = numberField(item, "tokensOutput") || numberField(item, "outputTokens") || 0;
6698
+ const tokensCacheReadInput = numberField(item, "tokensCacheReadInput") || numberField(item, "cacheReadTokens") || 0;
6699
+ const tokensCacheCreationInput = numberField(item, "tokensCacheCreationInput") || numberField(item, "cacheWriteTokens") || 0;
6700
+ if (tokensInput <= 0 && tokensOutput <= 0 && tokensCacheReadInput <= 0 && tokensCacheCreationInput <= 0) {
6701
+ return void 0;
6702
+ }
6703
+ const ts = stringField(item, "ts") || (/* @__PURE__ */ new Date(0)).toISOString();
6704
+ return {
6705
+ conversationId,
6706
+ model: stringField(item, "model"),
6707
+ ts,
6708
+ startedAt: stringField(item, "startedAt") || void 0,
6709
+ tokensInput,
6710
+ tokensOutput,
6711
+ tokensCacheReadInput,
6712
+ tokensCacheCreationInput
6713
+ };
6714
+ }
6715
+ function injectedCursorCloudUsage(options) {
6716
+ if (!Object.prototype.hasOwnProperty.call(options, "cursorCloudUsage")) {
6717
+ return void 0;
6718
+ }
6719
+ const raw = options.cursorCloudUsage;
6720
+ const map = /* @__PURE__ */ new Map();
6721
+ if (!isPlainObject(raw)) {
6722
+ return map;
6723
+ }
6724
+ for (const [conversationId, item] of Object.entries(raw)) {
6725
+ if (!conversationId) {
6726
+ continue;
6727
+ }
6728
+ const events = [];
6729
+ if (Array.isArray(item)) {
6730
+ for (const entry of item) {
6731
+ if (isPlainObject(entry)) {
6732
+ const parsed = injectedCloudEvent(conversationId, entry);
6733
+ if (parsed) {
6734
+ events.push(parsed);
6735
+ }
6736
+ }
6737
+ }
6738
+ } else if (isPlainObject(item) && Array.isArray(item.events)) {
6739
+ for (const entry of item.events) {
6740
+ if (isPlainObject(entry)) {
6741
+ const parsed = injectedCloudEvent(conversationId, entry);
6742
+ if (parsed) {
6743
+ events.push(parsed);
6744
+ }
6745
+ }
6746
+ }
6747
+ } else if (isPlainObject(item)) {
6748
+ const parsed = injectedCloudEvent(conversationId, item);
6749
+ if (parsed) {
6750
+ events.push(parsed);
6751
+ }
6752
+ }
6753
+ if (events.length > 0) {
6754
+ map.set(conversationId, events);
6755
+ }
6756
+ }
6757
+ return map;
6758
+ }
6759
+ async function readCursorAccessToken(dbPath) {
6760
+ if (!isCursorStateDbPath(dbPath)) {
6761
+ return void 0;
6762
+ }
6763
+ const info = await stat8(dbPath).catch(() => null);
6764
+ if (!info) {
6765
+ return void 0;
6766
+ }
6767
+ try {
6768
+ const opened = await openCursorDb(dbPath);
6769
+ try {
6770
+ const row = opened.db.prepare(
6771
+ "SELECT value FROM ItemTable WHERE key = 'cursorAuth/accessToken'"
6772
+ ).get();
6773
+ const token = decodeKv(row?.value)?.trim();
6774
+ return token || void 0;
6775
+ } finally {
6776
+ opened.db.close();
6777
+ await opened.cleanup();
6778
+ }
6779
+ } catch {
6780
+ return void 0;
6781
+ }
6782
+ }
6783
+ async function loadCursorCloudUsageMap(options, filePath) {
6784
+ const injected = injectedCursorCloudUsage(options);
6785
+ if (injected) {
6786
+ return injected;
6787
+ }
6788
+ if (cursorCloudUsageDisabled()) {
6789
+ return /* @__PURE__ */ new Map();
6790
+ }
6791
+ const cached = cursorCloudUsageByOptions.get(options);
6792
+ if (cached) {
6793
+ return cached;
6794
+ }
6795
+ const pending = (async () => {
6796
+ try {
6797
+ const token = isCursorStateDbPath(filePath) ? await readCursorAccessToken(filePath) : await readCursorAccessTokenFromHome(options);
6798
+ if (!token) {
6799
+ return /* @__PURE__ */ new Map();
6800
+ }
6801
+ const fetchImpl = typeof options.cursorCloudFetch === "function" ? options.cursorCloudFetch : void 0;
6802
+ return await fetchCursorDashboardUsage({ accessToken: token, fetchImpl });
6803
+ } catch {
6804
+ return /* @__PURE__ */ new Map();
6805
+ }
6806
+ })();
6807
+ cursorCloudUsageByOptions.set(options, pending);
6808
+ return pending;
6809
+ }
6810
+ async function readCursorAccessTokenFromHome(options) {
6811
+ const home = stringOption(options.home) || os6.homedir();
6812
+ const env = {
6813
+ CURSOR_HOME: process.env.CURSOR_HOME,
6814
+ CURSOR_USER_DIR: process.env.CURSOR_USER_DIR
6815
+ };
6816
+ for (const candidate of cursorStateDbCandidates(home, env)) {
6817
+ const token = await readCursorAccessToken(candidate);
6818
+ if (token) {
6819
+ return token;
6820
+ }
6821
+ }
6822
+ return void 0;
6823
+ }
6824
+ function cloudUsageToPersisted(row, index = 0) {
6825
+ return {
6826
+ generationId: `${CURSOR_CLOUD_USAGE_GENERATION_ID}:${index}:${row.tokensInput}:${row.tokensOutput}:${row.tokensCacheReadInput}`,
6827
+ ts: row.ts,
6828
+ model: row.model,
6829
+ tokensInput: row.tokensInput || void 0,
6830
+ tokensOutput: row.tokensOutput || void 0,
6831
+ tokensCacheReadInput: row.tokensCacheReadInput || void 0,
6832
+ tokensCacheCreationInput: row.tokensCacheCreationInput || void 0
6833
+ };
6834
+ }
6835
+ async function resolveCursorSessionUsage(options, sessionId, filePath) {
6836
+ const persisted = await readPersistedSessionContextFromOptions(options, sessionId);
6837
+ const cloudEvents = (await loadCursorCloudUsageMap(options, filePath)).get(sessionId) || [];
6838
+ if (persisted?.usage?.length) {
6839
+ return [
6840
+ ...persisted.usage,
6841
+ ...unmatchedCloudEvents(cloudEvents, persisted.usage).map((event, index) => cloudUsageToPersisted(event, index))
6842
+ ];
6843
+ }
6844
+ return cloudEvents.map((event, index) => cloudUsageToPersisted(event, index));
6845
+ }
6846
+ function cursorCloudUsageCachePath(home) {
6847
+ return path13.join(home, ".vibetime", CURSOR_CLOUD_USAGE_FILENAME);
6848
+ }
6849
+ async function listLocalCursorSessionIds(home, env) {
6850
+ const ids = /* @__PURE__ */ new Set();
6851
+ for (const dbPath of cursorStateDbCandidates(home, env)) {
6852
+ const composerIds = await listCursorComposerIds(dbPath);
6853
+ for (const id of composerIds) {
6854
+ ids.add(id);
6855
+ }
6856
+ if (composerIds.size > 0 || await stat8(dbPath).catch(() => null)) {
6857
+ break;
6858
+ }
6859
+ }
6860
+ const projects = cursorProjectsDir(home, env);
6861
+ for (const filePath of await listJsonlFiles(projects)) {
6862
+ if (!isCursorTranscriptPath(filePath) && path13.basename(path13.dirname(filePath)) !== "agent-transcripts") {
6863
+ continue;
6864
+ }
6865
+ const sessionId = path13.basename(filePath, ".jsonl");
6866
+ if (sessionId) {
6867
+ ids.add(sessionId);
6868
+ }
6869
+ }
6870
+ return ids;
6871
+ }
6872
+ async function writeCursorCloudUsageCache(cachePath, sessions) {
6873
+ const next = serializeCursorCloudUsageCache(sessions);
6874
+ const existing = await readFile8(cachePath, "utf8").catch(() => "");
6875
+ if (existing === next) {
6876
+ const info2 = await stat8(cachePath);
6877
+ return info2.mtime.toISOString();
6878
+ }
6879
+ await mkdir4(path13.dirname(cachePath), { recursive: true });
6880
+ await writeFile4(cachePath, next, "utf8");
6881
+ const info = await stat8(cachePath);
6882
+ return info.mtime.toISOString();
6883
+ }
6884
+ async function appendCursorCloudAgentSource(files, home, env, options) {
6885
+ const dbPath = files.find((file) => isCursorStateDbPath(file.path))?.path;
6886
+ const map = await loadCursorCloudUsageMap(options, dbPath);
6887
+ const localIds = await listLocalCursorSessionIds(home, env);
6888
+ const cloudOnly = [];
6889
+ for (const [conversationId, events] of map) {
6890
+ if (localIds.has(conversationId)) {
6891
+ continue;
6892
+ }
6893
+ const summed = sumCursorCloudEvents(conversationId, events);
6894
+ if (summed) {
6895
+ cloudOnly.push(summed);
6896
+ }
6897
+ }
6898
+ if (cloudOnly.length === 0) {
6899
+ return;
6900
+ }
6901
+ const cachePath = cursorCloudUsageCachePath(home);
6902
+ files.push({
6903
+ path: cachePath,
6904
+ modifiedAt: await writeCursorCloudUsageCache(cachePath, cloudOnly)
6905
+ });
6906
+ }
6907
+ function parseCursorCloudAgentSessions(filePath, options, sessions, localIds) {
6908
+ const events = [];
6909
+ const sourcePathHash = `sha256:${createStableHash(filePath)}`;
6910
+ const project = CURSOR_CLOUD_AGENT_PROJECT;
6911
+ const workspaceId = createWorkspaceId({ projectName: project });
6912
+ let lineNumber = 0;
6913
+ for (const row of sessions) {
6914
+ if (localIds.has(row.conversationId) || !row.startedAt) {
6915
+ continue;
6916
+ }
6917
+ const sessionId = row.conversationId;
6918
+ const model = row.model;
6919
+ const endedAt = row.ts || row.startedAt;
6920
+ const push = (partial, topType) => {
6921
+ lineNumber += 1;
6922
+ const event = {
6923
+ schemaVersion: AGENT_TIME_SCHEMA_VERSION,
6924
+ source: SOURCE_ID,
6925
+ agent: AGENT_NAME,
6926
+ workspaceId,
6927
+ project,
6928
+ model,
6929
+ sessionId,
6930
+ ...partial
6931
+ };
6932
+ events.push(withBackfillRefs(event, {
6933
+ filePath,
6934
+ sourcePathHash,
6935
+ lineNumber,
6936
+ topType,
6937
+ payloadType: event.type,
6938
+ options
6939
+ }));
6940
+ };
6941
+ push({
6942
+ ts: row.startedAt,
6943
+ type: "session.started",
6944
+ confidence: "partial",
6945
+ refs: stringRefs({ sourceId: `${sessionId}:started` })
6946
+ }, "cloud-agent");
6947
+ emitPersistedCursorUsage(
6948
+ push,
6949
+ [cloudUsageToPersisted(row)],
6950
+ endedAt,
6951
+ void 0,
6952
+ model,
6953
+ sessionId
6954
+ );
6955
+ push({
6956
+ ts: endedAt,
6957
+ type: "session.ended",
6958
+ confidence: "partial",
6959
+ refs: stringRefs({ sourceId: `${sessionId}:ended` })
6960
+ }, "cloud-agent");
6961
+ }
6962
+ return events;
6963
+ }
6964
+ async function parseCursorCloudAgentFile(filePath, options) {
6965
+ const home = stringOption(options.home) || os6.homedir();
6966
+ const env = {
6967
+ CURSOR_HOME: process.env.CURSOR_HOME,
6968
+ CURSOR_USER_DIR: process.env.CURSOR_USER_DIR
6969
+ };
6970
+ const injected = injectedCursorCloudUsage(options);
6971
+ const sessions = injected ? [...injected.entries()].flatMap(([conversationId, events]) => {
6972
+ const summed = sumCursorCloudEvents(conversationId, events);
6973
+ return summed ? [summed] : [];
6974
+ }) : parseCursorCloudUsageCache(await readJsonIfExists(filePath));
6975
+ const localIds = await listLocalCursorSessionIds(home, env);
6976
+ return parseCursorCloudAgentSessions(filePath, options, sessions, localIds);
6977
+ }
6471
6978
  function decodeKv(value) {
6472
6979
  if (typeof value === "string") {
6473
6980
  return value;
@@ -7133,6 +7640,7 @@ async function parseCursorTranscriptFile(filePath, options) {
7133
7640
  push,
7134
7641
  sessionId,
7135
7642
  options,
7643
+ filePath,
7136
7644
  fallbackTs: endedAt || lastTs,
7137
7645
  lastTurnId,
7138
7646
  model,
@@ -7144,10 +7652,9 @@ async function appendPersistedCursorUsage(args) {
7144
7652
  if (args.skip) {
7145
7653
  return;
7146
7654
  }
7147
- const persisted = await readPersistedSessionContextFromOptions(args.options, args.sessionId);
7148
7655
  emitPersistedCursorUsage(
7149
7656
  args.push,
7150
- persisted?.usage,
7657
+ await resolveCursorSessionUsage(args.options, args.sessionId, args.filePath),
7151
7658
  args.fallbackTs,
7152
7659
  args.lastTurnId,
7153
7660
  args.model,
@@ -7168,15 +7675,16 @@ function emitPersistedCursorUsage(push, usage, fallbackTs, lastTurnId, model, se
7168
7675
  if (!metrics) {
7169
7676
  continue;
7170
7677
  }
7678
+ const fromCloud = item.generationId.startsWith(CURSOR_CLOUD_USAGE_GENERATION_ID);
7171
7679
  push({
7172
7680
  ts: timestampFrom(item.ts) || fallbackTs,
7173
7681
  type: "model.usage",
7174
7682
  turnId: lastTurnId,
7175
7683
  model: item.model || model,
7176
- confidence: "exact",
7684
+ confidence: fromCloud ? "partial" : "exact",
7177
7685
  metrics,
7178
7686
  refs: stringRefs({ sourceId: `${sessionId}:${item.generationId}:usage` })
7179
- }, "hook-usage");
7687
+ }, fromCloud ? "cloud-usage" : "hook-usage");
7180
7688
  }
7181
7689
  }
7182
7690
  async function listCursorComposerIds(dbPath) {
@@ -7235,6 +7743,9 @@ async function collectCursorTranscriptFiles(root, home, skipSessionIds) {
7235
7743
  }
7236
7744
  async function parseCursorSessionFile(filePath, options) {
7237
7745
  const base = path13.basename(filePath);
7746
+ if (base === CURSOR_CLOUD_USAGE_FILENAME) {
7747
+ return parseCursorCloudAgentFile(filePath, options);
7748
+ }
7238
7749
  if (base.endsWith(".jsonl")) {
7239
7750
  return parseCursorTranscriptFile(filePath, options);
7240
7751
  }
@@ -7250,8 +7761,8 @@ async function parseCursorSessionFile(filePath, options) {
7250
7761
  try {
7251
7762
  const composers = listComposers(opened.db);
7252
7763
  for (const composer of composers) {
7253
- const persisted = await readPersistedSessionContextFromOptions(options, composer.composerId);
7254
- events.push(...parseComposer(opened.db, composer, filePath, sourcePathHash, options, persisted?.usage));
7764
+ const usage = await resolveCursorSessionUsage(options, composer.composerId, filePath);
7765
+ events.push(...parseComposer(opened.db, composer, filePath, sourcePathHash, options, usage));
7255
7766
  }
7256
7767
  } finally {
7257
7768
  opened.db.close();
@@ -7472,7 +7983,7 @@ function parseComposer(db, composer, filePath, sourcePathHash, options, persiste
7472
7983
  emitPersistedCursorUsage(push, persistedUsage, endedAt || startedAt, currentTurnId, model, sessionId, events.some((event) => event.type === "model.usage"));
7473
7984
  return events;
7474
7985
  }
7475
- async function cursorBackfillFiles(sourceRoot, home = os6.homedir(), env) {
7986
+ async function cursorBackfillFiles(sourceRoot, home = os6.homedir(), env, options) {
7476
7987
  if (sourceRoot) {
7477
7988
  const info = await stat8(sourceRoot).catch(() => null);
7478
7989
  if (!info) {
@@ -7539,6 +8050,9 @@ async function cursorBackfillFiles(sourceRoot, home = os6.homedir(), env) {
7539
8050
  break;
7540
8051
  }
7541
8052
  files.push(...await collectCursorTranscriptFiles(cursorProjectsDir(home, env), home, skipIds));
8053
+ if (options) {
8054
+ await appendCursorCloudAgentSource(files, home, env, options);
8055
+ }
7542
8056
  return files;
7543
8057
  }
7544
8058
  function cursorHookConfig() {
@@ -13984,7 +14498,7 @@ async function uninstallEntry(entry, options) {
13984
14498
  await uninstallGeneratedFile(entry.path, options);
13985
14499
  }
13986
14500
  async function mergeHooksJson(filePath, content, { dryRun, force, onWrite }) {
13987
- const { mkdir: mkdir6, writeFile: writeFile5 } = await import("node:fs/promises");
14501
+ const { mkdir: mkdir7, writeFile: writeFile6 } = await import("node:fs/promises");
13988
14502
  const pathMod = await import("node:path");
13989
14503
  if (dryRun) {
13990
14504
  onWrite(`Would merge ${filePath}`);
@@ -14005,12 +14519,12 @@ async function mergeHooksJson(filePath, content, { dryRun, force, onWrite }) {
14005
14519
  onWrite(`Already installed ${filePath}`);
14006
14520
  return;
14007
14521
  }
14008
- await mkdir6(pathMod.dirname(filePath), { recursive: true });
14009
- await writeFile5(filePath, nextText, "utf8");
14522
+ await mkdir7(pathMod.dirname(filePath), { recursive: true });
14523
+ await writeFile6(filePath, nextText, "utf8");
14010
14524
  onWrite(`Installed ${filePath}`);
14011
14525
  }
14012
14526
  async function mergeCursorHooksFile(filePath, content, { dryRun, force, onWrite }) {
14013
- const { mkdir: mkdir6, writeFile: writeFile5 } = await import("node:fs/promises");
14527
+ const { mkdir: mkdir7, writeFile: writeFile6 } = await import("node:fs/promises");
14014
14528
  const pathMod = await import("node:path");
14015
14529
  if (dryRun) {
14016
14530
  onWrite(`Would merge ${filePath}`);
@@ -14030,8 +14544,8 @@ async function mergeCursorHooksFile(filePath, content, { dryRun, force, onWrite
14030
14544
  onWrite(`Already installed ${filePath}`);
14031
14545
  return;
14032
14546
  }
14033
- await mkdir6(pathMod.dirname(filePath), { recursive: true });
14034
- await writeFile5(filePath, nextText, "utf8");
14547
+ await mkdir7(pathMod.dirname(filePath), { recursive: true });
14548
+ await writeFile6(filePath, nextText, "utf8");
14035
14549
  onWrite(`Installed ${filePath}`);
14036
14550
  }
14037
14551
  async function uninstallCursorHooksFile(filePath, content, { dryRun, onWrite }) {
@@ -14065,8 +14579,8 @@ async function uninstallCursorHooksFile(filePath, content, { dryRun, onWrite })
14065
14579
  onWrite(`Would uninstall ${filePath}`);
14066
14580
  return;
14067
14581
  }
14068
- const { writeFile: writeFile5 } = await import("node:fs/promises");
14069
- await writeFile5(filePath, `${JSON.stringify(next, null, 2)}
14582
+ const { writeFile: writeFile6 } = await import("node:fs/promises");
14583
+ await writeFile6(filePath, `${JSON.stringify(next, null, 2)}
14070
14584
  `, "utf8");
14071
14585
  onWrite(`Uninstalled ${filePath}`);
14072
14586
  }
@@ -14141,7 +14655,7 @@ function hookCommandFromGroup(group) {
14141
14655
  return isPlainObject(hook) && typeof hook.command === "string" ? hook.command : void 0;
14142
14656
  }
14143
14657
  async function mergeHooksToml(filePath, content, { dryRun, force, onWrite }) {
14144
- const { mkdir: mkdir6, writeFile: writeFile5 } = await import("node:fs/promises");
14658
+ const { mkdir: mkdir7, writeFile: writeFile6 } = await import("node:fs/promises");
14145
14659
  const pathMod = await import("node:path");
14146
14660
  if (dryRun) {
14147
14661
  onWrite(`Would merge ${filePath}`);
@@ -14169,8 +14683,8 @@ async function mergeHooksToml(filePath, content, { dryRun, force, onWrite }) {
14169
14683
  return;
14170
14684
  }
14171
14685
  }
14172
- await mkdir6(pathMod.dirname(filePath), { recursive: true });
14173
- await writeFile5(filePath, nextText, "utf8");
14686
+ await mkdir7(pathMod.dirname(filePath), { recursive: true });
14687
+ await writeFile6(filePath, nextText, "utf8");
14174
14688
  onWrite(`Installed ${filePath}`);
14175
14689
  }
14176
14690
  async function uninstallHooksToml(filePath, content, { dryRun, onWrite }) {
@@ -14195,8 +14709,8 @@ async function uninstallHooksToml(filePath, content, { dryRun, onWrite }) {
14195
14709
  onWrite(`Would uninstall ${filePath}`);
14196
14710
  return;
14197
14711
  }
14198
- const { writeFile: writeFile5 } = await import("node:fs/promises");
14199
- await writeFile5(filePath, nextText, "utf8");
14712
+ const { writeFile: writeFile6 } = await import("node:fs/promises");
14713
+ await writeFile6(filePath, nextText, "utf8");
14200
14714
  onWrite(`Uninstalled ${filePath}`);
14201
14715
  }
14202
14716
  function collectHookCommandsFromJsonContent(content) {
@@ -14329,8 +14843,8 @@ async function uninstallHooksJson(filePath, content, { dryRun, onWrite }) {
14329
14843
  onWrite(`Would uninstall ${filePath}`);
14330
14844
  return;
14331
14845
  }
14332
- const { writeFile: writeFile5 } = await import("node:fs/promises");
14333
- await writeFile5(filePath, `${JSON.stringify(next, null, 2)}
14846
+ const { writeFile: writeFile6 } = await import("node:fs/promises");
14847
+ await writeFile6(filePath, `${JSON.stringify(next, null, 2)}
14334
14848
  `, "utf8");
14335
14849
  onWrite(`Uninstalled ${filePath}`);
14336
14850
  }
@@ -14412,7 +14926,7 @@ function defaultMachineName() {
14412
14926
  init_fs();
14413
14927
 
14414
14928
  // src/lib/logger.ts
14415
- import { appendFile, mkdir as mkdir4, rename, stat as stat15 } from "node:fs/promises";
14929
+ import { appendFile, mkdir as mkdir5, rename, stat as stat15 } from "node:fs/promises";
14416
14930
  import { homedir as homedir2 } from "node:os";
14417
14931
  import path25 from "node:path";
14418
14932
  var MAX_BYTES = 1 * 1024 * 1024;
@@ -14441,7 +14955,7 @@ async function rotateIfNeeded(file) {
14441
14955
  async function writeLog(entry, home = homedir2(), fileName = "cli.log") {
14442
14956
  try {
14443
14957
  const dir = logDir(home);
14444
- await mkdir4(dir, { recursive: true });
14958
+ await mkdir5(dir, { recursive: true });
14445
14959
  const file = logPath(home, fileName);
14446
14960
  await rotateIfNeeded(file);
14447
14961
  const record = {
@@ -14666,7 +15180,7 @@ async function deleteMachine(remote, id) {
14666
15180
  }
14667
15181
 
14668
15182
  // src/lib/types.ts
14669
- var BACKFILL_STATE_SCHEMA_VERSION = 7;
15183
+ var BACKFILL_STATE_SCHEMA_VERSION = 9;
14670
15184
 
14671
15185
  // src/cli.ts
14672
15186
  function createRegistry() {
@@ -15245,7 +15759,7 @@ async function listBackfillSourceFiles(source, options, ctx) {
15245
15759
  return zedBackfillFiles(stringOption(options["source-root"]), resolveHome3(options, ctx), ctx.env);
15246
15760
  }
15247
15761
  if (source.id === "cursor") {
15248
- return cursorBackfillFiles(stringOption(options["source-root"]), resolveHome3(options, ctx), ctx.env);
15762
+ return cursorBackfillFiles(stringOption(options["source-root"]), resolveHome3(options, ctx), ctx.env, options);
15249
15763
  }
15250
15764
  if (source.id === "pi") {
15251
15765
  return piBackfillFiles(stringOption(options["source-root"]), resolveHome3(options, ctx), ctx.env);
@@ -15654,8 +16168,8 @@ async function readBackfillIncrementalStateFile(home, ctx) {
15654
16168
  }
15655
16169
  async function writeBackfillIncrementalStateFile(home, file) {
15656
16170
  const statePath = backfillIncrementalStatePath(home);
15657
- await mkdir5(path26.dirname(statePath), { recursive: true });
15658
- await writeFile4(statePath, `${JSON.stringify(file, null, 2)}
16171
+ await mkdir6(path26.dirname(statePath), { recursive: true });
16172
+ await writeFile5(statePath, `${JSON.stringify(file, null, 2)}
15659
16173
  `, "utf8");
15660
16174
  }
15661
16175
  async function readBackfillIncrementalState(home, remoteKey, ctx) {
@@ -15703,8 +16217,8 @@ async function readSyncLocalTriggerState(statePath) {
15703
16217
  return nextState;
15704
16218
  }
15705
16219
  async function writeSyncLocalTriggerState(statePath, state) {
15706
- await mkdir5(path26.dirname(statePath), { recursive: true });
15707
- await writeFile4(statePath, `${JSON.stringify(state, null, 2)}
16220
+ await mkdir6(path26.dirname(statePath), { recursive: true });
16221
+ await writeFile5(statePath, `${JSON.stringify(state, null, 2)}
15708
16222
  `, "utf8");
15709
16223
  }
15710
16224
  async function readSyncLocalLock(lockPath) {
@@ -15718,12 +16232,12 @@ async function readSyncLocalLock(lockPath) {
15718
16232
  return { pid: lock.pid, startedAt: lock.startedAt };
15719
16233
  }
15720
16234
  async function writeSyncLocalLock(lockPath, lock) {
15721
- await mkdir5(path26.dirname(lockPath), { recursive: true });
15722
- await writeFile4(lockPath, `${JSON.stringify(lock, null, 2)}
16235
+ await mkdir6(path26.dirname(lockPath), { recursive: true });
16236
+ await writeFile5(lockPath, `${JSON.stringify(lock, null, 2)}
15723
16237
  `, "utf8");
15724
16238
  }
15725
16239
  async function acquireSyncLocalLock(lockPath, lock) {
15726
- await mkdir5(path26.dirname(lockPath), { recursive: true });
16240
+ await mkdir6(path26.dirname(lockPath), { recursive: true });
15727
16241
  try {
15728
16242
  const handle = await open(lockPath, "wx");
15729
16243
  try {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@yhong91/vibetime",
3
3
  "type": "module",
4
- "version": "0.1.58",
4
+ "version": "0.1.59",
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": {