@yhong91/vibetime 0.1.58 → 0.1.60

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 +563 -38
  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.60" : "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;
@@ -2065,8 +2065,15 @@ function parseJsonLine(line) {
2065
2065
  }
2066
2066
  }
2067
2067
  function timestampFrom(value) {
2068
- if (typeof value === "string" && !Number.isNaN(Date.parse(value))) {
2069
- return value;
2068
+ if (typeof value === "string") {
2069
+ const trimmed = value.trim();
2070
+ if (/^\d+(\.\d+)?$/.test(trimmed)) {
2071
+ return timestampFrom(Number(trimmed));
2072
+ }
2073
+ if (!Number.isNaN(Date.parse(trimmed))) {
2074
+ return trimmed;
2075
+ }
2076
+ return void 0;
2070
2077
  }
2071
2078
  if (typeof value !== "number" || !Number.isFinite(value)) {
2072
2079
  return void 0;
@@ -6292,7 +6299,7 @@ function createCopilotAdapter() {
6292
6299
  }
6293
6300
 
6294
6301
  // src/adapters/cursor.ts
6295
- import { copyFile, readdir as readdir6, readFile as readFile8, stat as stat8 } from "node:fs/promises";
6302
+ import { copyFile, mkdir as mkdir4, readdir as readdir6, readFile as readFile8, stat as stat8, writeFile as writeFile4 } from "node:fs/promises";
6296
6303
  import os6 from "node:os";
6297
6304
  import path13 from "node:path";
6298
6305
 
@@ -6400,6 +6407,226 @@ function collectCursorHookCommands(content) {
6400
6407
 
6401
6408
  // src/adapters/cursor.ts
6402
6409
  init_fs();
6410
+
6411
+ // src/adapters/cursor-cloud-usage.ts
6412
+ var CURSOR_CLOUD_USAGE_GENERATION_ID = "cursor-cloud";
6413
+ var CURSOR_CLOUD_USAGE_FILENAME = "cursor-cloud-usage.json";
6414
+ var CURSOR_CLOUD_AGENT_PROJECT = "Cloud Agent";
6415
+ var CURSOR_DASHBOARD_USAGE_URL = "https://cursor.com/api/dashboard/get-filtered-usage-events";
6416
+ var CURSOR_CLOUD_USAGE_CACHE_VERSION = 1;
6417
+ var DEFAULT_WINDOW_DAYS = 90;
6418
+ var PAGE_SIZE = 1e3;
6419
+ function cursorCloudUsageDisabled(env = process.env) {
6420
+ const value = env.VIBETIME_CURSOR_CLOUD_USAGE;
6421
+ return value === "0" || value === "false";
6422
+ }
6423
+ function cursorSessionCookie(accessToken) {
6424
+ const token = accessToken.trim();
6425
+ if (!token) {
6426
+ return void 0;
6427
+ }
6428
+ const payload = jwtPayload(token);
6429
+ const userId = String(payload?.sub || "").split("|").at(-1)?.trim();
6430
+ if (!userId) {
6431
+ return void 0;
6432
+ }
6433
+ return `WorkosCursorSessionToken=${userId}%3A%3A${token}`;
6434
+ }
6435
+ function cursorCloudEventFromDashboard(event) {
6436
+ if (!isPlainObject(event)) {
6437
+ return void 0;
6438
+ }
6439
+ const conversationId = stringField(event, "conversationId")?.trim();
6440
+ if (!conversationId) {
6441
+ return void 0;
6442
+ }
6443
+ const tokenUsage = objectField(event, "tokenUsage");
6444
+ const tokensInput = numberField(tokenUsage, "inputTokens") || 0;
6445
+ const tokensOutput = numberField(tokenUsage, "outputTokens") || 0;
6446
+ const tokensCacheReadInput = numberField(tokenUsage, "cacheReadTokens") || 0;
6447
+ const tokensCacheCreationInput = numberField(tokenUsage, "cacheWriteTokens") || 0;
6448
+ if (tokensInput <= 0 && tokensOutput <= 0 && tokensCacheReadInput <= 0 && tokensCacheCreationInput <= 0) {
6449
+ return void 0;
6450
+ }
6451
+ const ts = timestampFrom(event.timestamp);
6452
+ if (!ts) {
6453
+ return void 0;
6454
+ }
6455
+ return {
6456
+ conversationId,
6457
+ model: stringField(event, "model") || void 0,
6458
+ ts,
6459
+ tokensInput,
6460
+ tokensOutput,
6461
+ tokensCacheReadInput,
6462
+ tokensCacheCreationInput
6463
+ };
6464
+ }
6465
+ function groupCursorUsageEvents(events) {
6466
+ const byConversation = /* @__PURE__ */ new Map();
6467
+ for (const event of events) {
6468
+ const parsed = cursorCloudEventFromDashboard(event);
6469
+ if (!parsed) {
6470
+ continue;
6471
+ }
6472
+ const current = byConversation.get(parsed.conversationId);
6473
+ if (current) {
6474
+ current.push(parsed);
6475
+ } else {
6476
+ byConversation.set(parsed.conversationId, [parsed]);
6477
+ }
6478
+ }
6479
+ return byConversation;
6480
+ }
6481
+ function sumCursorCloudEvents(conversationId, events) {
6482
+ if (events.length === 0) {
6483
+ return void 0;
6484
+ }
6485
+ const first = events[0];
6486
+ const firstStart = first.startedAt || first.ts;
6487
+ const summed = {
6488
+ conversationId,
6489
+ model: first.model,
6490
+ ts: first.ts,
6491
+ startedAt: firstStart,
6492
+ tokensInput: 0,
6493
+ tokensOutput: 0,
6494
+ tokensCacheReadInput: 0,
6495
+ tokensCacheCreationInput: 0
6496
+ };
6497
+ for (const event of events) {
6498
+ summed.tokensInput += event.tokensInput;
6499
+ summed.tokensOutput += event.tokensOutput;
6500
+ summed.tokensCacheReadInput += event.tokensCacheReadInput;
6501
+ summed.tokensCacheCreationInput += event.tokensCacheCreationInput;
6502
+ const eventStart = event.startedAt || event.ts;
6503
+ if (Date.parse(eventStart) < Date.parse(summed.startedAt)) {
6504
+ summed.startedAt = eventStart;
6505
+ }
6506
+ if (Date.parse(event.ts) >= Date.parse(summed.ts)) {
6507
+ summed.ts = event.ts;
6508
+ if (event.model) {
6509
+ summed.model = event.model;
6510
+ }
6511
+ }
6512
+ }
6513
+ return summed;
6514
+ }
6515
+ function hookUncachedInputTokens(turn) {
6516
+ return Math.max(0, (turn.tokensInput || 0) - (turn.tokensCacheReadInput || 0));
6517
+ }
6518
+ function cloudEventMatchesHookTurn(event, turn) {
6519
+ return event.tokensInput === hookUncachedInputTokens(turn) && event.tokensOutput === (turn.tokensOutput || 0) && event.tokensCacheReadInput === (turn.tokensCacheReadInput || 0);
6520
+ }
6521
+ function unmatchedCloudEvents(events, turns) {
6522
+ const used = /* @__PURE__ */ new Set();
6523
+ for (const turn of turns) {
6524
+ const index = events.findIndex((event, i) => !used.has(i) && cloudEventMatchesHookTurn(event, turn));
6525
+ if (index >= 0) {
6526
+ used.add(index);
6527
+ }
6528
+ }
6529
+ return events.filter((_, i) => !used.has(i));
6530
+ }
6531
+ async function fetchCursorDashboardUsage(args) {
6532
+ const cookie = cursorSessionCookie(args.accessToken);
6533
+ if (!cookie) {
6534
+ return /* @__PURE__ */ new Map();
6535
+ }
6536
+ const fetchImpl = args.fetchImpl || fetch;
6537
+ const now = args.now || /* @__PURE__ */ new Date();
6538
+ const windowDays = args.windowDays ?? DEFAULT_WINDOW_DAYS;
6539
+ const pageSize = args.pageSize ?? PAGE_SIZE;
6540
+ const start = new Date(now.getTime() - windowDays * 24 * 60 * 60 * 1e3);
6541
+ const events = [];
6542
+ let page = 1;
6543
+ let total = Number.POSITIVE_INFINITY;
6544
+ while ((page - 1) * pageSize < total) {
6545
+ const body = JSON.stringify({
6546
+ page,
6547
+ pageSize,
6548
+ startDate: String(start.getTime()),
6549
+ endDate: String(now.getTime())
6550
+ });
6551
+ const response = await fetchImpl(CURSOR_DASHBOARD_USAGE_URL, {
6552
+ method: "POST",
6553
+ headers: {
6554
+ "Content-Type": "application/json",
6555
+ Accept: "application/json",
6556
+ Cookie: cookie,
6557
+ Origin: "https://cursor.com",
6558
+ Referer: "https://cursor.com/settings",
6559
+ "User-Agent": "vibetime-cli"
6560
+ },
6561
+ body,
6562
+ signal: AbortSignal.timeout(2e4)
6563
+ });
6564
+ if (!response.ok) {
6565
+ return /* @__PURE__ */ new Map();
6566
+ }
6567
+ const payload = await response.json();
6568
+ if (!isPlainObject(payload)) {
6569
+ return /* @__PURE__ */ new Map();
6570
+ }
6571
+ const pageEvents = Array.isArray(payload.usageEventsDisplay) ? payload.usageEventsDisplay : [];
6572
+ events.push(...pageEvents);
6573
+ const reported = numberField(payload, "totalUsageEventsCount");
6574
+ total = reported ?? pageEvents.length;
6575
+ if (pageEvents.length < pageSize) {
6576
+ break;
6577
+ }
6578
+ page += 1;
6579
+ }
6580
+ return groupCursorUsageEvents(events);
6581
+ }
6582
+ function serializeCursorCloudUsageCache(sessions) {
6583
+ const sorted = [...sessions].sort((a, b) => a.conversationId.localeCompare(b.conversationId));
6584
+ return `${JSON.stringify({ version: CURSOR_CLOUD_USAGE_CACHE_VERSION, sessions: sorted }, null, 2)}
6585
+ `;
6586
+ }
6587
+ function parseCursorCloudUsageCache(raw) {
6588
+ if (!isPlainObject(raw) || raw.version !== CURSOR_CLOUD_USAGE_CACHE_VERSION || !Array.isArray(raw.sessions)) {
6589
+ return [];
6590
+ }
6591
+ const sessions = [];
6592
+ for (const item of raw.sessions) {
6593
+ if (!isPlainObject(item)) {
6594
+ continue;
6595
+ }
6596
+ const conversationId = stringField(item, "conversationId")?.trim();
6597
+ const ts = stringField(item, "ts");
6598
+ const startedAt = stringField(item, "startedAt") || ts;
6599
+ if (!conversationId || !ts || !startedAt) {
6600
+ continue;
6601
+ }
6602
+ sessions.push({
6603
+ conversationId,
6604
+ model: stringField(item, "model"),
6605
+ ts,
6606
+ startedAt,
6607
+ tokensInput: numberField(item, "tokensInput") || 0,
6608
+ tokensOutput: numberField(item, "tokensOutput") || 0,
6609
+ tokensCacheReadInput: numberField(item, "tokensCacheReadInput") || 0,
6610
+ tokensCacheCreationInput: numberField(item, "tokensCacheCreationInput") || 0
6611
+ });
6612
+ }
6613
+ return sessions;
6614
+ }
6615
+ function jwtPayload(token) {
6616
+ const segment = token.split(".")[1];
6617
+ if (!segment) {
6618
+ return void 0;
6619
+ }
6620
+ try {
6621
+ const json = Buffer.from(segment, "base64url").toString("utf8");
6622
+ const parsed = JSON.parse(json);
6623
+ return isPlainObject(parsed) ? parsed : void 0;
6624
+ } catch {
6625
+ return void 0;
6626
+ }
6627
+ }
6628
+
6629
+ // src/adapters/cursor.ts
6403
6630
  var SOURCE_ID = "cursor";
6404
6631
  var AGENT_NAME = "cursor";
6405
6632
  var HOOK_COMMAND = `vibetime hook --agent ${SOURCE_ID}`;
@@ -6468,6 +6695,297 @@ function cursorStateDbCandidates(home, env) {
6468
6695
  }
6469
6696
  return candidates;
6470
6697
  }
6698
+ var cursorCloudUsageByOptions = /* @__PURE__ */ new WeakMap();
6699
+ function isCursorStateDbPath(filePath) {
6700
+ if (!filePath) {
6701
+ return false;
6702
+ }
6703
+ const base = path13.basename(filePath);
6704
+ return base === "state.vscdb" || base.endsWith(".vscdb");
6705
+ }
6706
+ function injectedCloudEvent(conversationId, item) {
6707
+ const tokensInput = numberField(item, "tokensInput") || numberField(item, "inputTokens") || 0;
6708
+ const tokensOutput = numberField(item, "tokensOutput") || numberField(item, "outputTokens") || 0;
6709
+ const tokensCacheReadInput = numberField(item, "tokensCacheReadInput") || numberField(item, "cacheReadTokens") || 0;
6710
+ const tokensCacheCreationInput = numberField(item, "tokensCacheCreationInput") || numberField(item, "cacheWriteTokens") || 0;
6711
+ if (tokensInput <= 0 && tokensOutput <= 0 && tokensCacheReadInput <= 0 && tokensCacheCreationInput <= 0) {
6712
+ return void 0;
6713
+ }
6714
+ const ts = stringField(item, "ts") || (/* @__PURE__ */ new Date(0)).toISOString();
6715
+ return {
6716
+ conversationId,
6717
+ model: stringField(item, "model"),
6718
+ ts,
6719
+ startedAt: stringField(item, "startedAt") || void 0,
6720
+ tokensInput,
6721
+ tokensOutput,
6722
+ tokensCacheReadInput,
6723
+ tokensCacheCreationInput
6724
+ };
6725
+ }
6726
+ function injectedCursorCloudUsage(options) {
6727
+ if (!Object.prototype.hasOwnProperty.call(options, "cursorCloudUsage")) {
6728
+ return void 0;
6729
+ }
6730
+ const raw = options.cursorCloudUsage;
6731
+ const map = /* @__PURE__ */ new Map();
6732
+ if (!isPlainObject(raw)) {
6733
+ return map;
6734
+ }
6735
+ for (const [conversationId, item] of Object.entries(raw)) {
6736
+ if (!conversationId) {
6737
+ continue;
6738
+ }
6739
+ const events = [];
6740
+ if (Array.isArray(item)) {
6741
+ for (const entry of item) {
6742
+ if (isPlainObject(entry)) {
6743
+ const parsed = injectedCloudEvent(conversationId, entry);
6744
+ if (parsed) {
6745
+ events.push(parsed);
6746
+ }
6747
+ }
6748
+ }
6749
+ } else if (isPlainObject(item) && Array.isArray(item.events)) {
6750
+ for (const entry of item.events) {
6751
+ if (isPlainObject(entry)) {
6752
+ const parsed = injectedCloudEvent(conversationId, entry);
6753
+ if (parsed) {
6754
+ events.push(parsed);
6755
+ }
6756
+ }
6757
+ }
6758
+ } else if (isPlainObject(item)) {
6759
+ const parsed = injectedCloudEvent(conversationId, item);
6760
+ if (parsed) {
6761
+ events.push(parsed);
6762
+ }
6763
+ }
6764
+ if (events.length > 0) {
6765
+ map.set(conversationId, events);
6766
+ }
6767
+ }
6768
+ return map;
6769
+ }
6770
+ async function readCursorAccessToken(dbPath) {
6771
+ if (!isCursorStateDbPath(dbPath)) {
6772
+ return void 0;
6773
+ }
6774
+ const info = await stat8(dbPath).catch(() => null);
6775
+ if (!info) {
6776
+ return void 0;
6777
+ }
6778
+ try {
6779
+ const opened = await openCursorDb(dbPath);
6780
+ try {
6781
+ const row = opened.db.prepare(
6782
+ "SELECT value FROM ItemTable WHERE key = 'cursorAuth/accessToken'"
6783
+ ).get();
6784
+ const token = decodeKv(row?.value)?.trim();
6785
+ return token || void 0;
6786
+ } finally {
6787
+ opened.db.close();
6788
+ await opened.cleanup();
6789
+ }
6790
+ } catch {
6791
+ return void 0;
6792
+ }
6793
+ }
6794
+ async function loadCursorCloudUsageMap(options, filePath) {
6795
+ const injected = injectedCursorCloudUsage(options);
6796
+ if (injected) {
6797
+ return injected;
6798
+ }
6799
+ if (cursorCloudUsageDisabled()) {
6800
+ return /* @__PURE__ */ new Map();
6801
+ }
6802
+ const cached = cursorCloudUsageByOptions.get(options);
6803
+ if (cached) {
6804
+ return cached;
6805
+ }
6806
+ const pending = (async () => {
6807
+ try {
6808
+ const token = isCursorStateDbPath(filePath) ? await readCursorAccessToken(filePath) : await readCursorAccessTokenFromHome(options);
6809
+ if (!token) {
6810
+ return /* @__PURE__ */ new Map();
6811
+ }
6812
+ const fetchImpl = typeof options.cursorCloudFetch === "function" ? options.cursorCloudFetch : void 0;
6813
+ return await fetchCursorDashboardUsage({ accessToken: token, fetchImpl });
6814
+ } catch {
6815
+ return /* @__PURE__ */ new Map();
6816
+ }
6817
+ })();
6818
+ cursorCloudUsageByOptions.set(options, pending);
6819
+ return pending;
6820
+ }
6821
+ async function readCursorAccessTokenFromHome(options) {
6822
+ const home = stringOption(options.home) || os6.homedir();
6823
+ const env = {
6824
+ CURSOR_HOME: process.env.CURSOR_HOME,
6825
+ CURSOR_USER_DIR: process.env.CURSOR_USER_DIR
6826
+ };
6827
+ for (const candidate of cursorStateDbCandidates(home, env)) {
6828
+ const token = await readCursorAccessToken(candidate);
6829
+ if (token) {
6830
+ return token;
6831
+ }
6832
+ }
6833
+ return void 0;
6834
+ }
6835
+ function cloudUsageToPersisted(row, index = 0) {
6836
+ return {
6837
+ generationId: `${CURSOR_CLOUD_USAGE_GENERATION_ID}:${index}:${row.tokensInput}:${row.tokensOutput}:${row.tokensCacheReadInput}`,
6838
+ ts: row.ts,
6839
+ model: row.model,
6840
+ tokensInput: row.tokensInput || void 0,
6841
+ tokensOutput: row.tokensOutput || void 0,
6842
+ tokensCacheReadInput: row.tokensCacheReadInput || void 0,
6843
+ tokensCacheCreationInput: row.tokensCacheCreationInput || void 0
6844
+ };
6845
+ }
6846
+ async function resolveCursorSessionUsage(options, sessionId, filePath) {
6847
+ const persisted = await readPersistedSessionContextFromOptions(options, sessionId);
6848
+ const cloudEvents = (await loadCursorCloudUsageMap(options, filePath)).get(sessionId) || [];
6849
+ if (persisted?.usage?.length) {
6850
+ return [
6851
+ ...persisted.usage,
6852
+ ...unmatchedCloudEvents(cloudEvents, persisted.usage).map((event, index) => cloudUsageToPersisted(event, index))
6853
+ ];
6854
+ }
6855
+ return cloudEvents.map((event, index) => cloudUsageToPersisted(event, index));
6856
+ }
6857
+ function cursorCloudUsageCachePath(home) {
6858
+ return path13.join(home, ".vibetime", CURSOR_CLOUD_USAGE_FILENAME);
6859
+ }
6860
+ async function listLocalCursorSessionIds(home, env) {
6861
+ const ids = /* @__PURE__ */ new Set();
6862
+ for (const dbPath of cursorStateDbCandidates(home, env)) {
6863
+ const composerIds = await listCursorComposerIds(dbPath);
6864
+ for (const id of composerIds) {
6865
+ ids.add(id);
6866
+ }
6867
+ if (composerIds.size > 0 || await stat8(dbPath).catch(() => null)) {
6868
+ break;
6869
+ }
6870
+ }
6871
+ const projects = cursorProjectsDir(home, env);
6872
+ for (const filePath of await listJsonlFiles(projects)) {
6873
+ if (!isCursorTranscriptPath(filePath) && path13.basename(path13.dirname(filePath)) !== "agent-transcripts") {
6874
+ continue;
6875
+ }
6876
+ const sessionId = path13.basename(filePath, ".jsonl");
6877
+ if (sessionId) {
6878
+ ids.add(sessionId);
6879
+ }
6880
+ }
6881
+ return ids;
6882
+ }
6883
+ async function writeCursorCloudUsageCache(cachePath, sessions) {
6884
+ const next = serializeCursorCloudUsageCache(sessions);
6885
+ const existing = await readFile8(cachePath, "utf8").catch(() => "");
6886
+ if (existing === next) {
6887
+ const info2 = await stat8(cachePath);
6888
+ return info2.mtime.toISOString();
6889
+ }
6890
+ await mkdir4(path13.dirname(cachePath), { recursive: true });
6891
+ await writeFile4(cachePath, next, "utf8");
6892
+ const info = await stat8(cachePath);
6893
+ return info.mtime.toISOString();
6894
+ }
6895
+ async function appendCursorCloudAgentSource(files, home, env, options) {
6896
+ const dbPath = files.find((file) => isCursorStateDbPath(file.path))?.path;
6897
+ const map = await loadCursorCloudUsageMap(options, dbPath);
6898
+ const localIds = await listLocalCursorSessionIds(home, env);
6899
+ const cloudOnly = [];
6900
+ for (const [conversationId, events] of map) {
6901
+ if (localIds.has(conversationId)) {
6902
+ continue;
6903
+ }
6904
+ const summed = sumCursorCloudEvents(conversationId, events);
6905
+ if (summed) {
6906
+ cloudOnly.push(summed);
6907
+ }
6908
+ }
6909
+ if (cloudOnly.length === 0) {
6910
+ return;
6911
+ }
6912
+ const cachePath = cursorCloudUsageCachePath(home);
6913
+ files.push({
6914
+ path: cachePath,
6915
+ modifiedAt: await writeCursorCloudUsageCache(cachePath, cloudOnly)
6916
+ });
6917
+ }
6918
+ function parseCursorCloudAgentSessions(filePath, options, sessions, localIds) {
6919
+ const events = [];
6920
+ const sourcePathHash = `sha256:${createStableHash(filePath)}`;
6921
+ const project = CURSOR_CLOUD_AGENT_PROJECT;
6922
+ const workspaceId = createWorkspaceId({ projectName: project });
6923
+ let lineNumber = 0;
6924
+ for (const row of sessions) {
6925
+ if (localIds.has(row.conversationId) || !row.startedAt) {
6926
+ continue;
6927
+ }
6928
+ const sessionId = row.conversationId;
6929
+ const model = row.model;
6930
+ const endedAt = row.ts || row.startedAt;
6931
+ const push = (partial, topType) => {
6932
+ lineNumber += 1;
6933
+ const event = {
6934
+ schemaVersion: AGENT_TIME_SCHEMA_VERSION,
6935
+ source: SOURCE_ID,
6936
+ agent: AGENT_NAME,
6937
+ workspaceId,
6938
+ project,
6939
+ model,
6940
+ sessionId,
6941
+ ...partial
6942
+ };
6943
+ events.push(withBackfillRefs(event, {
6944
+ filePath,
6945
+ sourcePathHash,
6946
+ lineNumber,
6947
+ topType,
6948
+ payloadType: event.type,
6949
+ options
6950
+ }));
6951
+ };
6952
+ push({
6953
+ ts: row.startedAt,
6954
+ type: "session.started",
6955
+ confidence: "partial",
6956
+ refs: stringRefs({ sourceId: `${sessionId}:started` })
6957
+ }, "cloud-agent");
6958
+ emitPersistedCursorUsage(
6959
+ push,
6960
+ [cloudUsageToPersisted(row)],
6961
+ endedAt,
6962
+ void 0,
6963
+ model,
6964
+ sessionId
6965
+ );
6966
+ push({
6967
+ ts: endedAt,
6968
+ type: "session.ended",
6969
+ confidence: "partial",
6970
+ refs: stringRefs({ sourceId: `${sessionId}:ended` })
6971
+ }, "cloud-agent");
6972
+ }
6973
+ return events;
6974
+ }
6975
+ async function parseCursorCloudAgentFile(filePath, options) {
6976
+ const home = stringOption(options.home) || os6.homedir();
6977
+ const env = {
6978
+ CURSOR_HOME: process.env.CURSOR_HOME,
6979
+ CURSOR_USER_DIR: process.env.CURSOR_USER_DIR
6980
+ };
6981
+ const injected = injectedCursorCloudUsage(options);
6982
+ const sessions = injected ? [...injected.entries()].flatMap(([conversationId, events]) => {
6983
+ const summed = sumCursorCloudEvents(conversationId, events);
6984
+ return summed ? [summed] : [];
6985
+ }) : parseCursorCloudUsageCache(await readJsonIfExists(filePath));
6986
+ const localIds = await listLocalCursorSessionIds(home, env);
6987
+ return parseCursorCloudAgentSessions(filePath, options, sessions, localIds);
6988
+ }
6471
6989
  function decodeKv(value) {
6472
6990
  if (typeof value === "string") {
6473
6991
  return value;
@@ -7133,6 +7651,7 @@ async function parseCursorTranscriptFile(filePath, options) {
7133
7651
  push,
7134
7652
  sessionId,
7135
7653
  options,
7654
+ filePath,
7136
7655
  fallbackTs: endedAt || lastTs,
7137
7656
  lastTurnId,
7138
7657
  model,
@@ -7144,10 +7663,9 @@ async function appendPersistedCursorUsage(args) {
7144
7663
  if (args.skip) {
7145
7664
  return;
7146
7665
  }
7147
- const persisted = await readPersistedSessionContextFromOptions(args.options, args.sessionId);
7148
7666
  emitPersistedCursorUsage(
7149
7667
  args.push,
7150
- persisted?.usage,
7668
+ await resolveCursorSessionUsage(args.options, args.sessionId, args.filePath),
7151
7669
  args.fallbackTs,
7152
7670
  args.lastTurnId,
7153
7671
  args.model,
@@ -7168,15 +7686,16 @@ function emitPersistedCursorUsage(push, usage, fallbackTs, lastTurnId, model, se
7168
7686
  if (!metrics) {
7169
7687
  continue;
7170
7688
  }
7689
+ const fromCloud = item.generationId.startsWith(CURSOR_CLOUD_USAGE_GENERATION_ID);
7171
7690
  push({
7172
7691
  ts: timestampFrom(item.ts) || fallbackTs,
7173
7692
  type: "model.usage",
7174
7693
  turnId: lastTurnId,
7175
7694
  model: item.model || model,
7176
- confidence: "exact",
7695
+ confidence: fromCloud ? "partial" : "exact",
7177
7696
  metrics,
7178
7697
  refs: stringRefs({ sourceId: `${sessionId}:${item.generationId}:usage` })
7179
- }, "hook-usage");
7698
+ }, fromCloud ? "cloud-usage" : "hook-usage");
7180
7699
  }
7181
7700
  }
7182
7701
  async function listCursorComposerIds(dbPath) {
@@ -7235,6 +7754,9 @@ async function collectCursorTranscriptFiles(root, home, skipSessionIds) {
7235
7754
  }
7236
7755
  async function parseCursorSessionFile(filePath, options) {
7237
7756
  const base = path13.basename(filePath);
7757
+ if (base === CURSOR_CLOUD_USAGE_FILENAME) {
7758
+ return parseCursorCloudAgentFile(filePath, options);
7759
+ }
7238
7760
  if (base.endsWith(".jsonl")) {
7239
7761
  return parseCursorTranscriptFile(filePath, options);
7240
7762
  }
@@ -7250,8 +7772,8 @@ async function parseCursorSessionFile(filePath, options) {
7250
7772
  try {
7251
7773
  const composers = listComposers(opened.db);
7252
7774
  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));
7775
+ const usage = await resolveCursorSessionUsage(options, composer.composerId, filePath);
7776
+ events.push(...parseComposer(opened.db, composer, filePath, sourcePathHash, options, usage));
7255
7777
  }
7256
7778
  } finally {
7257
7779
  opened.db.close();
@@ -7472,7 +7994,7 @@ function parseComposer(db, composer, filePath, sourcePathHash, options, persiste
7472
7994
  emitPersistedCursorUsage(push, persistedUsage, endedAt || startedAt, currentTurnId, model, sessionId, events.some((event) => event.type === "model.usage"));
7473
7995
  return events;
7474
7996
  }
7475
- async function cursorBackfillFiles(sourceRoot, home = os6.homedir(), env) {
7997
+ async function cursorBackfillFiles(sourceRoot, home = os6.homedir(), env, options) {
7476
7998
  if (sourceRoot) {
7477
7999
  const info = await stat8(sourceRoot).catch(() => null);
7478
8000
  if (!info) {
@@ -7539,6 +8061,9 @@ async function cursorBackfillFiles(sourceRoot, home = os6.homedir(), env) {
7539
8061
  break;
7540
8062
  }
7541
8063
  files.push(...await collectCursorTranscriptFiles(cursorProjectsDir(home, env), home, skipIds));
8064
+ if (options) {
8065
+ await appendCursorCloudAgentSource(files, home, env, options);
8066
+ }
7542
8067
  return files;
7543
8068
  }
7544
8069
  function cursorHookConfig() {
@@ -13984,7 +14509,7 @@ async function uninstallEntry(entry, options) {
13984
14509
  await uninstallGeneratedFile(entry.path, options);
13985
14510
  }
13986
14511
  async function mergeHooksJson(filePath, content, { dryRun, force, onWrite }) {
13987
- const { mkdir: mkdir6, writeFile: writeFile5 } = await import("node:fs/promises");
14512
+ const { mkdir: mkdir7, writeFile: writeFile6 } = await import("node:fs/promises");
13988
14513
  const pathMod = await import("node:path");
13989
14514
  if (dryRun) {
13990
14515
  onWrite(`Would merge ${filePath}`);
@@ -14005,12 +14530,12 @@ async function mergeHooksJson(filePath, content, { dryRun, force, onWrite }) {
14005
14530
  onWrite(`Already installed ${filePath}`);
14006
14531
  return;
14007
14532
  }
14008
- await mkdir6(pathMod.dirname(filePath), { recursive: true });
14009
- await writeFile5(filePath, nextText, "utf8");
14533
+ await mkdir7(pathMod.dirname(filePath), { recursive: true });
14534
+ await writeFile6(filePath, nextText, "utf8");
14010
14535
  onWrite(`Installed ${filePath}`);
14011
14536
  }
14012
14537
  async function mergeCursorHooksFile(filePath, content, { dryRun, force, onWrite }) {
14013
- const { mkdir: mkdir6, writeFile: writeFile5 } = await import("node:fs/promises");
14538
+ const { mkdir: mkdir7, writeFile: writeFile6 } = await import("node:fs/promises");
14014
14539
  const pathMod = await import("node:path");
14015
14540
  if (dryRun) {
14016
14541
  onWrite(`Would merge ${filePath}`);
@@ -14030,8 +14555,8 @@ async function mergeCursorHooksFile(filePath, content, { dryRun, force, onWrite
14030
14555
  onWrite(`Already installed ${filePath}`);
14031
14556
  return;
14032
14557
  }
14033
- await mkdir6(pathMod.dirname(filePath), { recursive: true });
14034
- await writeFile5(filePath, nextText, "utf8");
14558
+ await mkdir7(pathMod.dirname(filePath), { recursive: true });
14559
+ await writeFile6(filePath, nextText, "utf8");
14035
14560
  onWrite(`Installed ${filePath}`);
14036
14561
  }
14037
14562
  async function uninstallCursorHooksFile(filePath, content, { dryRun, onWrite }) {
@@ -14065,8 +14590,8 @@ async function uninstallCursorHooksFile(filePath, content, { dryRun, onWrite })
14065
14590
  onWrite(`Would uninstall ${filePath}`);
14066
14591
  return;
14067
14592
  }
14068
- const { writeFile: writeFile5 } = await import("node:fs/promises");
14069
- await writeFile5(filePath, `${JSON.stringify(next, null, 2)}
14593
+ const { writeFile: writeFile6 } = await import("node:fs/promises");
14594
+ await writeFile6(filePath, `${JSON.stringify(next, null, 2)}
14070
14595
  `, "utf8");
14071
14596
  onWrite(`Uninstalled ${filePath}`);
14072
14597
  }
@@ -14141,7 +14666,7 @@ function hookCommandFromGroup(group) {
14141
14666
  return isPlainObject(hook) && typeof hook.command === "string" ? hook.command : void 0;
14142
14667
  }
14143
14668
  async function mergeHooksToml(filePath, content, { dryRun, force, onWrite }) {
14144
- const { mkdir: mkdir6, writeFile: writeFile5 } = await import("node:fs/promises");
14669
+ const { mkdir: mkdir7, writeFile: writeFile6 } = await import("node:fs/promises");
14145
14670
  const pathMod = await import("node:path");
14146
14671
  if (dryRun) {
14147
14672
  onWrite(`Would merge ${filePath}`);
@@ -14169,8 +14694,8 @@ async function mergeHooksToml(filePath, content, { dryRun, force, onWrite }) {
14169
14694
  return;
14170
14695
  }
14171
14696
  }
14172
- await mkdir6(pathMod.dirname(filePath), { recursive: true });
14173
- await writeFile5(filePath, nextText, "utf8");
14697
+ await mkdir7(pathMod.dirname(filePath), { recursive: true });
14698
+ await writeFile6(filePath, nextText, "utf8");
14174
14699
  onWrite(`Installed ${filePath}`);
14175
14700
  }
14176
14701
  async function uninstallHooksToml(filePath, content, { dryRun, onWrite }) {
@@ -14195,8 +14720,8 @@ async function uninstallHooksToml(filePath, content, { dryRun, onWrite }) {
14195
14720
  onWrite(`Would uninstall ${filePath}`);
14196
14721
  return;
14197
14722
  }
14198
- const { writeFile: writeFile5 } = await import("node:fs/promises");
14199
- await writeFile5(filePath, nextText, "utf8");
14723
+ const { writeFile: writeFile6 } = await import("node:fs/promises");
14724
+ await writeFile6(filePath, nextText, "utf8");
14200
14725
  onWrite(`Uninstalled ${filePath}`);
14201
14726
  }
14202
14727
  function collectHookCommandsFromJsonContent(content) {
@@ -14329,8 +14854,8 @@ async function uninstallHooksJson(filePath, content, { dryRun, onWrite }) {
14329
14854
  onWrite(`Would uninstall ${filePath}`);
14330
14855
  return;
14331
14856
  }
14332
- const { writeFile: writeFile5 } = await import("node:fs/promises");
14333
- await writeFile5(filePath, `${JSON.stringify(next, null, 2)}
14857
+ const { writeFile: writeFile6 } = await import("node:fs/promises");
14858
+ await writeFile6(filePath, `${JSON.stringify(next, null, 2)}
14334
14859
  `, "utf8");
14335
14860
  onWrite(`Uninstalled ${filePath}`);
14336
14861
  }
@@ -14412,7 +14937,7 @@ function defaultMachineName() {
14412
14937
  init_fs();
14413
14938
 
14414
14939
  // src/lib/logger.ts
14415
- import { appendFile, mkdir as mkdir4, rename, stat as stat15 } from "node:fs/promises";
14940
+ import { appendFile, mkdir as mkdir5, rename, stat as stat15 } from "node:fs/promises";
14416
14941
  import { homedir as homedir2 } from "node:os";
14417
14942
  import path25 from "node:path";
14418
14943
  var MAX_BYTES = 1 * 1024 * 1024;
@@ -14441,7 +14966,7 @@ async function rotateIfNeeded(file) {
14441
14966
  async function writeLog(entry, home = homedir2(), fileName = "cli.log") {
14442
14967
  try {
14443
14968
  const dir = logDir(home);
14444
- await mkdir4(dir, { recursive: true });
14969
+ await mkdir5(dir, { recursive: true });
14445
14970
  const file = logPath(home, fileName);
14446
14971
  await rotateIfNeeded(file);
14447
14972
  const record = {
@@ -14666,7 +15191,7 @@ async function deleteMachine(remote, id) {
14666
15191
  }
14667
15192
 
14668
15193
  // src/lib/types.ts
14669
- var BACKFILL_STATE_SCHEMA_VERSION = 7;
15194
+ var BACKFILL_STATE_SCHEMA_VERSION = 9;
14670
15195
 
14671
15196
  // src/cli.ts
14672
15197
  function createRegistry() {
@@ -15245,7 +15770,7 @@ async function listBackfillSourceFiles(source, options, ctx) {
15245
15770
  return zedBackfillFiles(stringOption(options["source-root"]), resolveHome3(options, ctx), ctx.env);
15246
15771
  }
15247
15772
  if (source.id === "cursor") {
15248
- return cursorBackfillFiles(stringOption(options["source-root"]), resolveHome3(options, ctx), ctx.env);
15773
+ return cursorBackfillFiles(stringOption(options["source-root"]), resolveHome3(options, ctx), ctx.env, options);
15249
15774
  }
15250
15775
  if (source.id === "pi") {
15251
15776
  return piBackfillFiles(stringOption(options["source-root"]), resolveHome3(options, ctx), ctx.env);
@@ -15654,8 +16179,8 @@ async function readBackfillIncrementalStateFile(home, ctx) {
15654
16179
  }
15655
16180
  async function writeBackfillIncrementalStateFile(home, file) {
15656
16181
  const statePath = backfillIncrementalStatePath(home);
15657
- await mkdir5(path26.dirname(statePath), { recursive: true });
15658
- await writeFile4(statePath, `${JSON.stringify(file, null, 2)}
16182
+ await mkdir6(path26.dirname(statePath), { recursive: true });
16183
+ await writeFile5(statePath, `${JSON.stringify(file, null, 2)}
15659
16184
  `, "utf8");
15660
16185
  }
15661
16186
  async function readBackfillIncrementalState(home, remoteKey, ctx) {
@@ -15703,8 +16228,8 @@ async function readSyncLocalTriggerState(statePath) {
15703
16228
  return nextState;
15704
16229
  }
15705
16230
  async function writeSyncLocalTriggerState(statePath, state) {
15706
- await mkdir5(path26.dirname(statePath), { recursive: true });
15707
- await writeFile4(statePath, `${JSON.stringify(state, null, 2)}
16231
+ await mkdir6(path26.dirname(statePath), { recursive: true });
16232
+ await writeFile5(statePath, `${JSON.stringify(state, null, 2)}
15708
16233
  `, "utf8");
15709
16234
  }
15710
16235
  async function readSyncLocalLock(lockPath) {
@@ -15718,12 +16243,12 @@ async function readSyncLocalLock(lockPath) {
15718
16243
  return { pid: lock.pid, startedAt: lock.startedAt };
15719
16244
  }
15720
16245
  async function writeSyncLocalLock(lockPath, lock) {
15721
- await mkdir5(path26.dirname(lockPath), { recursive: true });
15722
- await writeFile4(lockPath, `${JSON.stringify(lock, null, 2)}
16246
+ await mkdir6(path26.dirname(lockPath), { recursive: true });
16247
+ await writeFile5(lockPath, `${JSON.stringify(lock, null, 2)}
15723
16248
  `, "utf8");
15724
16249
  }
15725
16250
  async function acquireSyncLocalLock(lockPath, lock) {
15726
- await mkdir5(path26.dirname(lockPath), { recursive: true });
16251
+ await mkdir6(path26.dirname(lockPath), { recursive: true });
15727
16252
  try {
15728
16253
  const handle = await open(lockPath, "wx");
15729
16254
  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.60",
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": {