@yhong91/vibetime 0.1.62 → 0.1.64

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 +247 -175
  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 stat16, writeFile as writeFile5 } from "node:fs/promises";
888
- import os13 from "node:os";
887
+ import { mkdir as mkdir6, open, rm, stat as stat17, writeFile as writeFile5 } from "node:fs/promises";
888
+ import os14 from "node:os";
889
889
  import path26 from "node:path";
890
890
  import { fileURLToPath } from "node:url";
891
891
 
@@ -924,7 +924,7 @@ var TELEMETRY_EVENT_TYPES = [
924
924
  "agent.operation"
925
925
  ];
926
926
  var FILE_ACTIVITY_OPERATIONS = ["read", "search", "create", "write", "edit", "delete"];
927
- var BACKFILL_SOURCE_IDS = ["codex", "claude-code", "claude-cowork", "copilot", "opencode", "pi", "agy", "codebuddy", "qoder", "qoder-cn", "workbuddy", "zcode", "grok-build", "zed", "kimi-code", "cursor"];
927
+ var BACKFILL_SOURCE_IDS = ["codex", "claude-code", "claude-cowork", "copilot", "opencode", "pi", "agy", "codebuddy", "qoder", "qoder-cn", "workbuddy", "zcode", "grok-build", "zed", "kimi-code", "cursor", "grok-bot"];
928
928
  function createWorkspaceId(input) {
929
929
  const basis = input.repoUrl || input.repoRoot || input.projectName || "unknown";
930
930
  return `workspace_${fnv1a(basis)}`;
@@ -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.62" : "0.1.1";
2050
+ var PACKAGE_VERSION = true ? "0.1.64" : "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;
@@ -4936,8 +4936,8 @@ async function codebuddyBackfillFiles(sourceRoot, home, env) {
4936
4936
  }
4937
4937
  const filePath = path9.join(traceDir, entry);
4938
4938
  try {
4939
- const stat17 = await import("node:fs/promises").then((fs) => fs.stat(filePath));
4940
- files.push({ path: filePath, modifiedAt: stat17.mtime.toISOString(), groupId: pidDir.name });
4939
+ const stat18 = await import("node:fs/promises").then((fs) => fs.stat(filePath));
4940
+ files.push({ path: filePath, modifiedAt: stat18.mtime.toISOString(), groupId: pidDir.name });
4941
4941
  } catch {
4942
4942
  }
4943
4943
  }
@@ -6331,7 +6331,7 @@ var CURSOR_CLOUD_USAGE_GENERATION_ID = "cursor-cloud";
6331
6331
  var CURSOR_CLOUD_USAGE_FILENAME = "cursor-cloud-usage.json";
6332
6332
  var CURSOR_CLOUD_AGENT_PROJECT = "Cloud Agent";
6333
6333
  var CURSOR_DASHBOARD_USAGE_URL = "https://cursor.com/api/dashboard/get-filtered-usage-events";
6334
- var CURSOR_CLOUD_USAGE_CACHE_VERSION = 1;
6334
+ var CURSOR_CLOUD_USAGE_CACHE_VERSION = 2;
6335
6335
  var DEFAULT_WINDOW_DAYS = 90;
6336
6336
  var PAGE_SIZE = 1e3;
6337
6337
  function cursorCloudUsageDisabled(env = process.env) {
@@ -6396,55 +6396,25 @@ function groupCursorUsageEvents(events) {
6396
6396
  }
6397
6397
  return byConversation;
6398
6398
  }
6399
- function sumCursorCloudEvents(conversationId, events) {
6399
+ function cloudAgentSessionFromEvents(conversationId, events) {
6400
6400
  if (events.length === 0) {
6401
6401
  return void 0;
6402
6402
  }
6403
- const first = events[0];
6404
- const firstStart = first.startedAt || first.ts;
6405
- const summed = {
6403
+ const sorted = [...events].sort((a, b) => {
6404
+ const byTs = Date.parse(a.ts) - Date.parse(b.ts);
6405
+ if (byTs !== 0) {
6406
+ return byTs;
6407
+ }
6408
+ return a.tokensInput - b.tokensInput || a.tokensOutput - b.tokensOutput || a.tokensCacheReadInput - b.tokensCacheReadInput || (a.model || "").localeCompare(b.model || "");
6409
+ });
6410
+ const first = sorted[0];
6411
+ const last = sorted[sorted.length - 1];
6412
+ return {
6406
6413
  conversationId,
6407
- model: first.model,
6408
- ts: first.ts,
6409
- startedAt: firstStart,
6410
- tokensInput: 0,
6411
- tokensOutput: 0,
6412
- tokensCacheReadInput: 0,
6413
- tokensCacheCreationInput: 0
6414
+ startedAt: first.startedAt || first.ts,
6415
+ ts: last.ts,
6416
+ events: sorted.map((event) => ({ ...event, conversationId }))
6414
6417
  };
6415
- for (const event of events) {
6416
- summed.tokensInput += event.tokensInput;
6417
- summed.tokensOutput += event.tokensOutput;
6418
- summed.tokensCacheReadInput += event.tokensCacheReadInput;
6419
- summed.tokensCacheCreationInput += event.tokensCacheCreationInput;
6420
- const eventStart = event.startedAt || event.ts;
6421
- if (Date.parse(eventStart) < Date.parse(summed.startedAt)) {
6422
- summed.startedAt = eventStart;
6423
- }
6424
- if (Date.parse(event.ts) >= Date.parse(summed.ts)) {
6425
- summed.ts = event.ts;
6426
- if (event.model) {
6427
- summed.model = event.model;
6428
- }
6429
- }
6430
- }
6431
- return summed;
6432
- }
6433
- function hookUncachedInputTokens(turn) {
6434
- return Math.max(0, (turn.tokensInput || 0) - (turn.tokensCacheReadInput || 0));
6435
- }
6436
- function cloudEventMatchesHookTurn(event, turn) {
6437
- return event.tokensInput === hookUncachedInputTokens(turn) && event.tokensOutput === (turn.tokensOutput || 0) && event.tokensCacheReadInput === (turn.tokensCacheReadInput || 0);
6438
- }
6439
- function unmatchedCloudEvents(events, turns) {
6440
- const used = /* @__PURE__ */ new Set();
6441
- for (const turn of turns) {
6442
- const index = events.findIndex((event, i) => !used.has(i) && cloudEventMatchesHookTurn(event, turn));
6443
- if (index >= 0) {
6444
- used.add(index);
6445
- }
6446
- }
6447
- return events.filter((_, i) => !used.has(i));
6448
6418
  }
6449
6419
  async function fetchCursorDashboardUsage(args) {
6450
6420
  const cookie = cursorSessionCookie(args.accessToken);
@@ -6499,11 +6469,46 @@ async function fetchCursorDashboardUsage(args) {
6499
6469
  }
6500
6470
  function serializeCursorCloudUsageCache(sessions) {
6501
6471
  const sorted = [...sessions].sort((a, b) => a.conversationId.localeCompare(b.conversationId));
6502
- return `${JSON.stringify({ version: CURSOR_CLOUD_USAGE_CACHE_VERSION, sessions: sorted }, null, 2)}
6472
+ const payload = {
6473
+ version: CURSOR_CLOUD_USAGE_CACHE_VERSION,
6474
+ sessions: sorted.map((session) => ({
6475
+ conversationId: session.conversationId,
6476
+ startedAt: session.startedAt,
6477
+ ts: session.ts,
6478
+ events: session.events.map((event) => ({
6479
+ model: event.model,
6480
+ ts: event.ts,
6481
+ tokensInput: event.tokensInput,
6482
+ tokensOutput: event.tokensOutput,
6483
+ tokensCacheReadInput: event.tokensCacheReadInput,
6484
+ tokensCacheCreationInput: event.tokensCacheCreationInput
6485
+ }))
6486
+ }))
6487
+ };
6488
+ return `${JSON.stringify(payload, null, 2)}
6503
6489
  `;
6504
6490
  }
6491
+ function cacheEventFromObject(conversationId, item) {
6492
+ const ts = stringField(item, "ts");
6493
+ if (!ts) {
6494
+ return void 0;
6495
+ }
6496
+ return {
6497
+ conversationId,
6498
+ model: stringField(item, "model"),
6499
+ ts,
6500
+ tokensInput: numberField(item, "tokensInput") || 0,
6501
+ tokensOutput: numberField(item, "tokensOutput") || 0,
6502
+ tokensCacheReadInput: numberField(item, "tokensCacheReadInput") || 0,
6503
+ tokensCacheCreationInput: numberField(item, "tokensCacheCreationInput") || 0
6504
+ };
6505
+ }
6505
6506
  function parseCursorCloudUsageCache(raw) {
6506
- if (!isPlainObject(raw) || raw.version !== CURSOR_CLOUD_USAGE_CACHE_VERSION || !Array.isArray(raw.sessions)) {
6507
+ if (!isPlainObject(raw) || !Array.isArray(raw.sessions)) {
6508
+ return [];
6509
+ }
6510
+ const version = numberField(raw, "version");
6511
+ if (version !== 1 && version !== CURSOR_CLOUD_USAGE_CACHE_VERSION) {
6507
6512
  return [];
6508
6513
  }
6509
6514
  const sessions = [];
@@ -6517,16 +6522,29 @@ function parseCursorCloudUsageCache(raw) {
6517
6522
  if (!conversationId || !ts || !startedAt) {
6518
6523
  continue;
6519
6524
  }
6520
- sessions.push({
6521
- conversationId,
6522
- model: stringField(item, "model"),
6523
- ts,
6524
- startedAt,
6525
- tokensInput: numberField(item, "tokensInput") || 0,
6526
- tokensOutput: numberField(item, "tokensOutput") || 0,
6527
- tokensCacheReadInput: numberField(item, "tokensCacheReadInput") || 0,
6528
- tokensCacheCreationInput: numberField(item, "tokensCacheCreationInput") || 0
6529
- });
6525
+ const events = [];
6526
+ if (Array.isArray(item.events)) {
6527
+ for (const entry of item.events) {
6528
+ if (!isPlainObject(entry)) {
6529
+ continue;
6530
+ }
6531
+ const parsed = cacheEventFromObject(conversationId, entry);
6532
+ if (parsed) {
6533
+ events.push(parsed);
6534
+ }
6535
+ }
6536
+ } else {
6537
+ const parsed = cacheEventFromObject(conversationId, item);
6538
+ if (parsed) {
6539
+ events.push(parsed);
6540
+ }
6541
+ }
6542
+ const session = cloudAgentSessionFromEvents(conversationId, events);
6543
+ if (session) {
6544
+ session.startedAt = startedAt;
6545
+ session.ts = ts;
6546
+ sessions.push(session);
6547
+ }
6530
6548
  }
6531
6549
  return sessions;
6532
6550
  }
@@ -6753,7 +6771,7 @@ async function readCursorAccessTokenFromHome(options) {
6753
6771
  function cloudUsageToPersisted(row, index = 0) {
6754
6772
  const tokensInput = (row.tokensInput || 0) + (row.tokensCacheReadInput || 0) + (row.tokensCacheCreationInput || 0);
6755
6773
  return {
6756
- generationId: `${CURSOR_CLOUD_USAGE_GENERATION_ID}:${index}:${row.tokensInput}:${row.tokensOutput}:${row.tokensCacheReadInput}`,
6774
+ generationId: `${CURSOR_CLOUD_USAGE_GENERATION_ID}:${index}:${row.ts}:${row.tokensInput}:${row.tokensOutput}:${row.tokensCacheReadInput}`,
6757
6775
  ts: row.ts,
6758
6776
  model: row.model,
6759
6777
  tokensInput: tokensInput || void 0,
@@ -6762,16 +6780,9 @@ function cloudUsageToPersisted(row, index = 0) {
6762
6780
  tokensCacheCreationInput: row.tokensCacheCreationInput || void 0
6763
6781
  };
6764
6782
  }
6765
- async function resolveCursorSessionUsage(options, sessionId, filePath) {
6783
+ async function resolveCursorSessionUsage(options, sessionId) {
6766
6784
  const persisted = await readPersistedSessionContextFromOptions(options, sessionId);
6767
- const cloudEvents = (await loadCursorCloudUsageMap(options, filePath)).get(sessionId) || [];
6768
- if (persisted?.usage?.length) {
6769
- return [
6770
- ...persisted.usage,
6771
- ...unmatchedCloudEvents(cloudEvents, persisted.usage).map((event, index) => cloudUsageToPersisted(event, index))
6772
- ];
6773
- }
6774
- return cloudEvents.map((event, index) => cloudUsageToPersisted(event, index));
6785
+ return persisted?.usage ?? [];
6775
6786
  }
6776
6787
  function cursorCloudUsageCachePath(home) {
6777
6788
  return path13.join(home, ".vibetime", CURSOR_CLOUD_USAGE_FILENAME);
@@ -6811,30 +6822,7 @@ async function writeCursorCloudUsageCache(cachePath, sessions) {
6811
6822
  const info = await stat8(cachePath);
6812
6823
  return info.mtime.toISOString();
6813
6824
  }
6814
- async function appendCursorCloudAgentSource(files, home, env, options) {
6815
- const dbPath = files.find((file) => isCursorStateDbPath(file.path))?.path;
6816
- const map = await loadCursorCloudUsageMap(options, dbPath);
6817
- const localIds = await listLocalCursorSessionIds(home, env);
6818
- const cloudOnly = [];
6819
- for (const [conversationId, events] of map) {
6820
- if (localIds.has(conversationId)) {
6821
- continue;
6822
- }
6823
- const summed = sumCursorCloudEvents(conversationId, events);
6824
- if (summed) {
6825
- cloudOnly.push(summed);
6826
- }
6827
- }
6828
- if (cloudOnly.length === 0) {
6829
- return;
6830
- }
6831
- const cachePath = cursorCloudUsageCachePath(home);
6832
- files.push({
6833
- path: cachePath,
6834
- modifiedAt: await writeCursorCloudUsageCache(cachePath, cloudOnly)
6835
- });
6836
- }
6837
- function parseCursorCloudAgentSessions(filePath, options, sessions, localIds) {
6825
+ function parseCursorCloudAgentSessions(filePath, options, sessions, localIds, identity) {
6838
6826
  const events = [];
6839
6827
  const sourcePathHash = `sha256:${createStableHash(filePath)}`;
6840
6828
  const project = CURSOR_CLOUD_AGENT_PROJECT;
@@ -6845,17 +6833,17 @@ function parseCursorCloudAgentSessions(filePath, options, sessions, localIds) {
6845
6833
  continue;
6846
6834
  }
6847
6835
  const sessionId = row.conversationId;
6848
- const model = row.model;
6836
+ const lastModel = row.events.at(-1)?.model;
6849
6837
  const endedAt = row.ts || row.startedAt;
6850
6838
  const push = (partial, topType) => {
6851
6839
  lineNumber += 1;
6852
6840
  const event = {
6853
6841
  schemaVersion: AGENT_TIME_SCHEMA_VERSION,
6854
- source: SOURCE_ID,
6855
- agent: AGENT_NAME,
6842
+ source: identity.source,
6843
+ agent: identity.agent,
6856
6844
  workspaceId,
6857
6845
  project,
6858
- model,
6846
+ model: lastModel,
6859
6847
  sessionId,
6860
6848
  ...partial
6861
6849
  };
@@ -6874,14 +6862,32 @@ function parseCursorCloudAgentSessions(filePath, options, sessions, localIds) {
6874
6862
  confidence: "partial",
6875
6863
  refs: stringRefs({ sourceId: `${sessionId}:started` })
6876
6864
  }, "cloud-agent");
6877
- emitPersistedCursorUsage(
6878
- push,
6879
- [cloudUsageToPersisted(row)],
6880
- endedAt,
6881
- void 0,
6882
- model,
6883
- sessionId
6884
- );
6865
+ for (const [index, event] of row.events.entries()) {
6866
+ const item = cloudUsageToPersisted(event, index);
6867
+ const ts = timestampFrom(item.ts) || endedAt;
6868
+ const turnId = `${sessionId}:${item.generationId}`;
6869
+ const model = item.model || lastModel;
6870
+ const nextTs = timestampFrom(row.events[index + 1]?.ts) || endedAt;
6871
+ const completedAt = Date.parse(nextTs) > Date.parse(ts) ? nextTs : ts;
6872
+ push({
6873
+ ts,
6874
+ type: "turn.started",
6875
+ turnId,
6876
+ model,
6877
+ confidence: "partial",
6878
+ refs: stringRefs({ sourceId: `${turnId}:started` })
6879
+ }, "cloud-agent");
6880
+ emitPersistedCursorUsage(push, [item], ts, turnId, model, sessionId);
6881
+ push({
6882
+ ts: completedAt,
6883
+ type: "turn.completed",
6884
+ turnId,
6885
+ model,
6886
+ success: true,
6887
+ confidence: "partial",
6888
+ refs: stringRefs({ sourceId: `${turnId}:completed` })
6889
+ }, "cloud-agent");
6890
+ }
6885
6891
  push({
6886
6892
  ts: endedAt,
6887
6893
  type: "session.ended",
@@ -6891,7 +6897,7 @@ function parseCursorCloudAgentSessions(filePath, options, sessions, localIds) {
6891
6897
  }
6892
6898
  return events;
6893
6899
  }
6894
- async function parseCursorCloudAgentFile(filePath, options) {
6900
+ async function parseCursorCloudAgentFile(filePath, options, identity) {
6895
6901
  const home = stringOption(options.home) || os6.homedir();
6896
6902
  const env = {
6897
6903
  CURSOR_HOME: process.env.CURSOR_HOME,
@@ -6899,11 +6905,11 @@ async function parseCursorCloudAgentFile(filePath, options) {
6899
6905
  };
6900
6906
  const injected = injectedCursorCloudUsage(options);
6901
6907
  const sessions = injected ? [...injected.entries()].flatMap(([conversationId, events]) => {
6902
- const summed = sumCursorCloudEvents(conversationId, events);
6903
- return summed ? [summed] : [];
6908
+ const session = cloudAgentSessionFromEvents(conversationId, events);
6909
+ return session ? [session] : [];
6904
6910
  }) : parseCursorCloudUsageCache(await readJsonIfExists(filePath));
6905
6911
  const localIds = await listLocalCursorSessionIds(home, env);
6906
- return parseCursorCloudAgentSessions(filePath, options, sessions, localIds);
6912
+ return parseCursorCloudAgentSessions(filePath, options, sessions, localIds, identity);
6907
6913
  }
6908
6914
  function decodeKv(value) {
6909
6915
  if (typeof value === "string") {
@@ -7570,7 +7576,6 @@ async function parseCursorTranscriptFile(filePath, options) {
7570
7576
  push,
7571
7577
  sessionId,
7572
7578
  options,
7573
- filePath,
7574
7579
  fallbackTs: endedAt || lastTs,
7575
7580
  lastTurnId,
7576
7581
  model,
@@ -7584,7 +7589,7 @@ async function appendPersistedCursorUsage(args) {
7584
7589
  }
7585
7590
  emitPersistedCursorUsage(
7586
7591
  args.push,
7587
- await resolveCursorSessionUsage(args.options, args.sessionId, args.filePath),
7592
+ await resolveCursorSessionUsage(args.options, args.sessionId),
7588
7593
  args.fallbackTs,
7589
7594
  args.lastTurnId,
7590
7595
  args.model,
@@ -7673,9 +7678,6 @@ async function collectCursorTranscriptFiles(root, home, skipSessionIds) {
7673
7678
  }
7674
7679
  async function parseCursorSessionFile(filePath, options) {
7675
7680
  const base = path13.basename(filePath);
7676
- if (base === CURSOR_CLOUD_USAGE_FILENAME) {
7677
- return parseCursorCloudAgentFile(filePath, options);
7678
- }
7679
7681
  if (base.endsWith(".jsonl")) {
7680
7682
  return parseCursorTranscriptFile(filePath, options);
7681
7683
  }
@@ -7691,7 +7693,7 @@ async function parseCursorSessionFile(filePath, options) {
7691
7693
  try {
7692
7694
  const composers = listComposers(opened.db);
7693
7695
  for (const composer of composers) {
7694
- const usage = await resolveCursorSessionUsage(options, composer.composerId, filePath);
7696
+ const usage = await resolveCursorSessionUsage(options, composer.composerId);
7695
7697
  events.push(...parseComposer(opened.db, composer, filePath, sourcePathHash, options, usage));
7696
7698
  }
7697
7699
  } finally {
@@ -7913,7 +7915,7 @@ function parseComposer(db, composer, filePath, sourcePathHash, options, persiste
7913
7915
  emitPersistedCursorUsage(push, persistedUsage, endedAt || startedAt, currentTurnId, model, sessionId, events.some((event) => event.type === "model.usage"));
7914
7916
  return events;
7915
7917
  }
7916
- async function cursorBackfillFiles(sourceRoot, home = os6.homedir(), env, options) {
7918
+ async function cursorBackfillFiles(sourceRoot, home = os6.homedir(), env) {
7917
7919
  if (sourceRoot) {
7918
7920
  const info = await stat8(sourceRoot).catch(() => null);
7919
7921
  if (!info) {
@@ -7980,9 +7982,6 @@ async function cursorBackfillFiles(sourceRoot, home = os6.homedir(), env, option
7980
7982
  break;
7981
7983
  }
7982
7984
  files.push(...await collectCursorTranscriptFiles(cursorProjectsDir(home, env), home, skipIds));
7983
- if (options) {
7984
- await appendCursorCloudAgentSource(files, home, env, options);
7985
- }
7986
7985
  return files;
7987
7986
  }
7988
7987
  function cursorHookConfig() {
@@ -8027,14 +8026,78 @@ function createCursorAdapter() {
8027
8026
  };
8028
8027
  }
8029
8028
 
8029
+ // src/adapters/grok-bot.ts
8030
+ import { stat as stat9 } from "node:fs/promises";
8031
+ import os7 from "node:os";
8032
+ var SOURCE_ID2 = "grok-bot";
8033
+ var AGENT_NAME2 = "grok-bot";
8034
+ var IDENTITY = { source: SOURCE_ID2, agent: AGENT_NAME2 };
8035
+ async function grokBotBackfillFiles(sourceRoot, home = os7.homedir(), env, options) {
8036
+ if (sourceRoot) {
8037
+ const info = await stat9(sourceRoot).catch(() => null);
8038
+ return info ? [{ path: sourceRoot, modifiedAt: info.mtime.toISOString() }] : [];
8039
+ }
8040
+ if (!options) {
8041
+ return [];
8042
+ }
8043
+ const map = await loadCursorCloudUsageMap(options);
8044
+ const localIds = await listLocalCursorSessionIds(home, env);
8045
+ const cloudOnly = [];
8046
+ for (const [conversationId, events] of map) {
8047
+ if (localIds.has(conversationId)) {
8048
+ continue;
8049
+ }
8050
+ const session = cloudAgentSessionFromEvents(conversationId, events);
8051
+ if (session) {
8052
+ cloudOnly.push(session);
8053
+ }
8054
+ }
8055
+ if (cloudOnly.length === 0) {
8056
+ return [];
8057
+ }
8058
+ const cachePath = cursorCloudUsageCachePath(home);
8059
+ return [{
8060
+ path: cachePath,
8061
+ modifiedAt: await writeCursorCloudUsageCache(cachePath, cloudOnly)
8062
+ }];
8063
+ }
8064
+ function createGrokBotAdapter() {
8065
+ return {
8066
+ id: SOURCE_ID2,
8067
+ label: "Grok Bot",
8068
+ agentName: AGENT_NAME2,
8069
+ kind: "agent",
8070
+ // No hooks to install — data arrives via the Cursor dashboard API.
8071
+ // The cache file stands in as the detect/install marker.
8072
+ detectPath(home) {
8073
+ return cursorCloudUsageCachePath(home);
8074
+ },
8075
+ installedPath(home) {
8076
+ return cursorCloudUsageCachePath(home);
8077
+ },
8078
+ async isInstalled() {
8079
+ return false;
8080
+ },
8081
+ installEntries() {
8082
+ return [];
8083
+ },
8084
+ sourcePaths(home) {
8085
+ return [cursorCloudUsageCachePath(home)];
8086
+ },
8087
+ parseSessionFile(filePath, options) {
8088
+ return parseCursorCloudAgentFile(filePath, options, IDENTITY);
8089
+ }
8090
+ };
8091
+ }
8092
+
8030
8093
  // src/adapters/grok-build.ts
8031
- import { readdir as readdir7, readFile as readFile9, stat as stat9 } from "node:fs/promises";
8094
+ import { readdir as readdir7, readFile as readFile9, stat as stat10 } from "node:fs/promises";
8032
8095
  import path14 from "node:path";
8033
8096
  init_fs();
8034
8097
  var GROK_COST_TICKS_PER_USD = 1e9;
8035
- var SOURCE_ID2 = "grok-build";
8036
- var AGENT_NAME2 = "grok-build";
8037
- var HOOK_COMMAND2 = `vibetime hook --agent ${SOURCE_ID2}`;
8098
+ var SOURCE_ID3 = "grok-build";
8099
+ var AGENT_NAME3 = "grok-build";
8100
+ var HOOK_COMMAND2 = `vibetime hook --agent ${SOURCE_ID3}`;
8038
8101
  function grokHome(home, env) {
8039
8102
  const override = env?.GROK_HOME;
8040
8103
  if (override && override.trim()) {
@@ -8068,7 +8131,7 @@ async function grokBackfillFiles(sourceRoot, home, env) {
8068
8131
  continue;
8069
8132
  }
8070
8133
  try {
8071
- const info = await stat9(entryPath);
8134
+ const info = await stat10(entryPath);
8072
8135
  files.push({ path: entryPath, modifiedAt: info.mtime.toISOString() });
8073
8136
  } catch {
8074
8137
  }
@@ -8120,8 +8183,8 @@ async function parseGrokSessionFile(filePath, options) {
8120
8183
  };
8121
8184
  const base = (partial) => ({
8122
8185
  schemaVersion: AGENT_TIME_SCHEMA_VERSION,
8123
- source: SOURCE_ID2,
8124
- agent: AGENT_NAME2,
8186
+ source: SOURCE_ID3,
8187
+ agent: AGENT_NAME3,
8125
8188
  workspaceId,
8126
8189
  project,
8127
8190
  cwd,
@@ -8681,7 +8744,7 @@ async function loadHunkFileActivities(hunkPath, cwd) {
8681
8744
  }
8682
8745
  return activities;
8683
8746
  }
8684
- var grokHandler = (msg) => hookHandler(SOURCE_ID2, msg);
8747
+ var grokHandler = (msg) => hookHandler(SOURCE_ID3, msg);
8685
8748
  function hookConfig5() {
8686
8749
  const anyTool = [{ matcher: ".*", hooks: [grokHandler("Reporting tool activity")] }];
8687
8750
  return {
@@ -8703,9 +8766,9 @@ function hookConfig5() {
8703
8766
  }
8704
8767
  function createGrokBuildAdapter() {
8705
8768
  return {
8706
- id: SOURCE_ID2,
8769
+ id: SOURCE_ID3,
8707
8770
  label: "Grok Build",
8708
- agentName: AGENT_NAME2,
8771
+ agentName: AGENT_NAME3,
8709
8772
  kind: "agent",
8710
8773
  detectPath(home, env) {
8711
8774
  return grokHome(home, env);
@@ -9372,7 +9435,7 @@ function createKimiCodeAdapter() {
9372
9435
  }
9373
9436
 
9374
9437
  // src/adapters/opencode.ts
9375
- import os7 from "node:os";
9438
+ import os8 from "node:os";
9376
9439
  import path16 from "node:path";
9377
9440
  async function parseOpenCodeSessionFile(dbPath, options) {
9378
9441
  const { DatabaseSync } = await import("node:sqlite");
@@ -9827,20 +9890,20 @@ function opencodeDataCandidates(home, env) {
9827
9890
  const primary = xdgData && xdgData.trim() ? path16.join(path16.resolve(xdgData), "opencode", "opencode.db") : path16.join(home, ".local", "share", "opencode", "opencode.db");
9828
9891
  return [primary, path16.join(home, ".opencode", "opencode.db")];
9829
9892
  }
9830
- async function opencodeBackfillFiles(sourceRoot, home = os7.homedir(), env) {
9831
- const { stat: stat17 } = await import("node:fs/promises");
9893
+ async function opencodeBackfillFiles(sourceRoot, home = os8.homedir(), env) {
9894
+ const { stat: stat18 } = await import("node:fs/promises");
9832
9895
  if (sourceRoot) {
9833
9896
  if (!sourceRoot.endsWith(".db")) {
9834
9897
  return [];
9835
9898
  }
9836
- const info = await stat17(sourceRoot).catch(() => null);
9899
+ const info = await stat18(sourceRoot).catch(() => null);
9837
9900
  if (!info) {
9838
9901
  return [];
9839
9902
  }
9840
9903
  return [{ path: sourceRoot, modifiedAt: info.mtime.toISOString() }];
9841
9904
  }
9842
9905
  for (const candidatePath of opencodeDataCandidates(home, env)) {
9843
- const info = await stat17(candidatePath).catch(() => null);
9906
+ const info = await stat18(candidatePath).catch(() => null);
9844
9907
  if (info) {
9845
9908
  return [{ path: candidatePath, modifiedAt: info.mtime.toISOString() }];
9846
9909
  }
@@ -9940,8 +10003,8 @@ function createOpenCodeAdapter() {
9940
10003
  }
9941
10004
 
9942
10005
  // src/adapters/pi.ts
9943
- import { readdir as readdir8, readFile as readFile11, stat as stat10 } from "node:fs/promises";
9944
- import os8 from "node:os";
10006
+ import { readdir as readdir8, readFile as readFile11, stat as stat11 } from "node:fs/promises";
10007
+ import os9 from "node:os";
9945
10008
  import path17 from "node:path";
9946
10009
  init_fs();
9947
10010
  function piHostSessionIdFromBasename(basename) {
@@ -10352,7 +10415,7 @@ async function parsePiWorkflowRunFile(filePath, options) {
10352
10415
  return state.events.filter((event) => matchesBackfillFilters(event, options));
10353
10416
  }
10354
10417
  async function findPiSessionFileById(sessionId, options) {
10355
- const sessionsDir = piSessionDir(path17.resolve(stringOption(options.home) || os8.homedir()));
10418
+ const sessionsDir = piSessionDir(path17.resolve(stringOption(options.home) || os9.homedir()));
10356
10419
  try {
10357
10420
  const projectDirs = await readdir8(sessionsDir, { withFileTypes: true });
10358
10421
  for (const dir of projectDirs) {
@@ -10629,14 +10692,14 @@ async function workflowRunGroupId(filePath) {
10629
10692
  return void 0;
10630
10693
  }
10631
10694
  }
10632
- async function piBackfillFiles(sourceRoot, home = os8.homedir(), env) {
10695
+ async function piBackfillFiles(sourceRoot, home = os9.homedir(), env) {
10633
10696
  const lists = sourceRoot ? [await listFilesByExtensions(sourceRoot, [".jsonl", ".json"])] : await Promise.all([
10634
10697
  listFilesByExtensions(piSessionDir(home, env), [".jsonl"]),
10635
10698
  listFilesByExtensions(piWorkflowProjectsDir(home), [".json"])
10636
10699
  ]);
10637
10700
  const files = lists.flat().sort();
10638
10701
  return Promise.all(files.map(async (filePath) => {
10639
- const info = await stat10(filePath);
10702
+ const info = await stat11(filePath);
10640
10703
  let groupId = resolvePiBackfillGroupId(filePath);
10641
10704
  if (!groupId && filePath.endsWith(".json")) {
10642
10705
  groupId = await workflowRunGroupId(filePath);
@@ -10683,13 +10746,13 @@ function createPiAdapter() {
10683
10746
  }
10684
10747
 
10685
10748
  // src/adapters/qoder-cn.ts
10686
- import { readdir as readdir9, readFile as readFile12, stat as stat11 } from "node:fs/promises";
10687
- import os10 from "node:os";
10749
+ import { readdir as readdir9, readFile as readFile12, stat as stat12 } from "node:fs/promises";
10750
+ import os11 from "node:os";
10688
10751
  import path19 from "node:path";
10689
10752
 
10690
10753
  // src/adapters/qoder-local-db.ts
10691
10754
  import { access } from "node:fs/promises";
10692
- import os9 from "node:os";
10755
+ import os10 from "node:os";
10693
10756
  import path18 from "node:path";
10694
10757
  function takeQoderDbModelCall(calls, requestId, blockStart) {
10695
10758
  if (requestId) {
@@ -10707,7 +10770,7 @@ function takeQoderDbModelCall(calls, requestId, blockStart) {
10707
10770
  }
10708
10771
  return calls.ordered.shift();
10709
10772
  }
10710
- function appDataRoot(appDirName, home = os9.homedir()) {
10773
+ function appDataRoot(appDirName, home = os10.homedir()) {
10711
10774
  if (process.platform === "darwin") {
10712
10775
  return path18.join(home, "Library", "Application Support", appDirName);
10713
10776
  }
@@ -11010,7 +11073,7 @@ async function parseQoderCnSessionFile(filePath, options) {
11010
11073
  let cwd;
11011
11074
  let project = projectContext.project;
11012
11075
  let model;
11013
- const home = path19.resolve(stringOption(options.home) || os10.homedir());
11076
+ const home = path19.resolve(stringOption(options.home) || os11.homedir());
11014
11077
  const modelMap = await loadQoderCnModelNames(configDir2, home);
11015
11078
  const isSubagentSession = filePath.includes("subagents");
11016
11079
  const segmentModelCalls = await loadQoderCnSegmentModelCalls(filePath, isSubagentSession, modelMap);
@@ -11580,7 +11643,7 @@ async function gitRootFromCwds2(cwds) {
11580
11643
  while (!seen.has(current)) {
11581
11644
  seen.add(current);
11582
11645
  try {
11583
- await stat11(path19.join(current, ".git"));
11646
+ await stat12(path19.join(current, ".git"));
11584
11647
  return current;
11585
11648
  } catch {
11586
11649
  }
@@ -11614,7 +11677,7 @@ function encodeQoderCnProjectPath(value) {
11614
11677
  }
11615
11678
  async function qoderCnProjectFromFilePath(filePath, options) {
11616
11679
  const projectDir = path19.basename(path19.dirname(filePath));
11617
- const home = options ? path19.resolve(stringOption(options.home) || os10.homedir()) : os10.homedir();
11680
+ const home = options ? path19.resolve(stringOption(options.home) || os11.homedir()) : os11.homedir();
11618
11681
  const resolved = await resolveQoderCnProjectPath(projectDir, home);
11619
11682
  if (resolved) {
11620
11683
  return path19.basename(resolved);
@@ -11730,8 +11793,8 @@ function createQoderCnAdapter() {
11730
11793
 
11731
11794
  // src/adapters/qoder.ts
11732
11795
  import { existsSync } from "node:fs";
11733
- import { readdir as readdir10, readFile as readFile13, stat as stat12 } from "node:fs/promises";
11734
- import os11 from "node:os";
11796
+ import { readdir as readdir10, readFile as readFile13, stat as stat13 } from "node:fs/promises";
11797
+ import os12 from "node:os";
11735
11798
  import path20 from "node:path";
11736
11799
  function parseQoderPaths(filePath) {
11737
11800
  const parts = filePath.split(path20.sep);
@@ -11890,7 +11953,7 @@ async function parseQoderSessionFile(filePath, options) {
11890
11953
  let cwd;
11891
11954
  let project = projectContext.project;
11892
11955
  let model;
11893
- const home = path20.resolve(stringOption(options.home) || os11.homedir());
11956
+ const home = path20.resolve(stringOption(options.home) || os12.homedir());
11894
11957
  const modelMap = await loadQoderModelNames(configDir2, home);
11895
11958
  const qwenworkRoot = isQwenworkConfigRoot(configDir2);
11896
11959
  const isSubagentSession = filePath.includes("subagents");
@@ -12426,7 +12489,7 @@ async function gitRootFromCwds3(cwds) {
12426
12489
  while (!seen.has(current)) {
12427
12490
  seen.add(current);
12428
12491
  try {
12429
- await stat12(path20.join(current, ".git"));
12492
+ await stat13(path20.join(current, ".git"));
12430
12493
  return current;
12431
12494
  } catch {
12432
12495
  }
@@ -12478,7 +12541,7 @@ function qoderEncodedProjectSuffix(projectDir, home) {
12478
12541
  }
12479
12542
  async function qoderProjectFromFilePath(filePath, options) {
12480
12543
  const projectDir = path20.basename(path20.dirname(filePath));
12481
- const home = options ? path20.resolve(stringOption(options.home) || os11.homedir()) : os11.homedir();
12544
+ const home = options ? path20.resolve(stringOption(options.home) || os12.homedir()) : os12.homedir();
12482
12545
  const resolved = await resolveQoderProjectPath(projectDir, home);
12483
12546
  if (resolved) {
12484
12547
  return path20.basename(resolved);
@@ -12634,7 +12697,7 @@ function normalizeId(id) {
12634
12697
  }
12635
12698
 
12636
12699
  // src/adapters/workbuddy.ts
12637
- import { readdir as readdir11, readFile as readFile14, stat as stat13 } from "node:fs/promises";
12700
+ import { readdir as readdir11, readFile as readFile14, stat as stat14 } from "node:fs/promises";
12638
12701
  import path21 from "node:path";
12639
12702
  function workbuddyProjectsDir(home, env) {
12640
12703
  const override = env?.WORKBUDDY_PROJECTS_DIR || env?.WORKBUDDY_HOME;
@@ -13087,7 +13150,7 @@ async function workbuddyBackfillFiles(sourceRoot, home, env) {
13087
13150
  for (const entry of entries) {
13088
13151
  if (entry.isFile() && entry.name.endsWith(".jsonl")) {
13089
13152
  const filePath = path21.join(projectDir, entry.name);
13090
- const info = await stat13(filePath);
13153
+ const info = await stat14(filePath);
13091
13154
  files.push({ path: filePath, modifiedAt: info.mtime.toISOString() });
13092
13155
  }
13093
13156
  }
@@ -13148,7 +13211,7 @@ function createWorkbuddyAdapter() {
13148
13211
 
13149
13212
  // src/adapters/zcode.ts
13150
13213
  import { execFile } from "node:child_process";
13151
- import { readFile as readFile15, stat as stat14 } from "node:fs/promises";
13214
+ import { readFile as readFile15, stat as stat15 } from "node:fs/promises";
13152
13215
  import path22 from "node:path";
13153
13216
  import { promisify as promisify2 } from "node:util";
13154
13217
  var execFileAsync = promisify2(execFile);
@@ -13187,7 +13250,7 @@ var providerNameCache = null;
13187
13250
  async function loadProviderNames(configPath2) {
13188
13251
  let fileMtime = 0;
13189
13252
  try {
13190
- const info = await stat14(configPath2);
13253
+ const info = await stat15(configPath2);
13191
13254
  fileMtime = info.mtimeMs;
13192
13255
  } catch {
13193
13256
  return /* @__PURE__ */ new Map();
@@ -13415,7 +13478,7 @@ async function parseZCodeDb(filePath, options) {
13415
13478
  for (let i = 0; i < 12; i++) {
13416
13479
  const probe = path22.join(candidate, ".zcode", "v2", "config.json");
13417
13480
  try {
13418
- await stat14(probe);
13481
+ await stat15(probe);
13419
13482
  configPath2 = probe;
13420
13483
  break;
13421
13484
  } catch {
@@ -13649,11 +13712,11 @@ async function zcodeBackfillFiles(sourceRoot, home, env) {
13649
13712
  const candidate = sourceRoot || zcodeDbPath(home, env);
13650
13713
  const filePath = candidate.endsWith(".sqlite") ? candidate : path22.join(candidate, "db", "db.sqlite");
13651
13714
  try {
13652
- const info = await stat14(filePath);
13715
+ const info = await stat15(filePath);
13653
13716
  let modifiedMs = info.mtimeMs;
13654
13717
  for (const suffix of ["-wal", "-shm"]) {
13655
13718
  try {
13656
- const sidecar = await stat14(`${filePath}${suffix}`);
13719
+ const sidecar = await stat15(`${filePath}${suffix}`);
13657
13720
  modifiedMs = Math.max(modifiedMs, sidecar.mtimeMs);
13658
13721
  } catch {
13659
13722
  }
@@ -13696,7 +13759,7 @@ function createZCodeAdapter() {
13696
13759
  }
13697
13760
 
13698
13761
  // src/adapters/zed.ts
13699
- import os12 from "node:os";
13762
+ import os13 from "node:os";
13700
13763
  import path23 from "node:path";
13701
13764
  function zedThreadsCandidates(home, env) {
13702
13765
  const candidates = [];
@@ -14048,20 +14111,20 @@ async function parseZedSessionFile(dbPath, options) {
14048
14111
  }
14049
14112
  return events.filter((event) => matchesBackfillFilters(event, options));
14050
14113
  }
14051
- async function zedBackfillFiles(sourceRoot, home = os12.homedir(), env) {
14052
- const { stat: stat17 } = await import("node:fs/promises");
14114
+ async function zedBackfillFiles(sourceRoot, home = os13.homedir(), env) {
14115
+ const { stat: stat18 } = await import("node:fs/promises");
14053
14116
  if (sourceRoot) {
14054
14117
  if (!sourceRoot.endsWith(".db")) {
14055
14118
  return [];
14056
14119
  }
14057
- const info = await stat17(sourceRoot).catch(() => null);
14120
+ const info = await stat18(sourceRoot).catch(() => null);
14058
14121
  if (!info) {
14059
14122
  return [];
14060
14123
  }
14061
14124
  return [{ path: sourceRoot, modifiedAt: info.mtime.toISOString() }];
14062
14125
  }
14063
14126
  for (const candidatePath of zedThreadsCandidates(home, env)) {
14064
- const info = await stat17(candidatePath).catch(() => null);
14127
+ const info = await stat18(candidatePath).catch(() => null);
14065
14128
  if (info) {
14066
14129
  return [{ path: candidatePath, modifiedAt: info.mtime.toISOString() }];
14067
14130
  }
@@ -14900,6 +14963,10 @@ import { randomUUID } from "node:crypto";
14900
14963
  import { existsSync as existsSync2, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
14901
14964
  import { homedir, hostname } from "node:os";
14902
14965
  import path24 from "node:path";
14966
+ function disabledBackfillSourceSet(config) {
14967
+ const list = Array.isArray(config.disabledSources) ? config.disabledSources : [];
14968
+ return new Set(list.map((id) => String(id).trim()).filter(Boolean));
14969
+ }
14903
14970
  function configDir(home = homedir()) {
14904
14971
  return path24.join(home, ".vibetime");
14905
14972
  }
@@ -14954,7 +15021,7 @@ function defaultMachineName() {
14954
15021
  init_fs();
14955
15022
 
14956
15023
  // src/lib/logger.ts
14957
- import { appendFile, mkdir as mkdir5, rename, stat as stat15 } from "node:fs/promises";
15024
+ import { appendFile, mkdir as mkdir5, rename, stat as stat16 } from "node:fs/promises";
14958
15025
  import { homedir as homedir2 } from "node:os";
14959
15026
  import path25 from "node:path";
14960
15027
  var MAX_BYTES = 1 * 1024 * 1024;
@@ -14972,7 +15039,7 @@ function serializeError(error) {
14972
15039
  }
14973
15040
  async function rotateIfNeeded(file) {
14974
15041
  try {
14975
- const info = await stat15(file);
15042
+ const info = await stat16(file);
14976
15043
  if (info.size > MAX_BYTES) {
14977
15044
  await rename(file, `${file}.1`).catch(() => {
14978
15045
  });
@@ -15208,7 +15275,7 @@ async function deleteMachine(remote, id) {
15208
15275
  }
15209
15276
 
15210
15277
  // src/lib/types.ts
15211
- var BACKFILL_STATE_SCHEMA_VERSION = 9;
15278
+ var BACKFILL_STATE_SCHEMA_VERSION = 10;
15212
15279
 
15213
15280
  // src/cli.ts
15214
15281
  function createRegistry() {
@@ -15229,6 +15296,7 @@ function createRegistry() {
15229
15296
  registry.register(createZedAdapter());
15230
15297
  registry.register(createKimiCodeAdapter());
15231
15298
  registry.register(createCursorAdapter());
15299
+ registry.register(createGrokBotAdapter());
15232
15300
  return registry;
15233
15301
  }
15234
15302
  var defaultContext = {
@@ -15644,7 +15712,8 @@ async function createBackfillPlanFromOptions(options, ctx, action, registry) {
15644
15712
  const home = resolveHome3(options, ctx);
15645
15713
  const env = ctx.env;
15646
15714
  const source = normalizeBackfillSource(stringOption(options.source) || "all");
15647
- const sourceDefs = source === "all" ? registry.all().map((a) => ({ id: a.id, label: a.label, paths: a.sourcePaths(home, env) })) : (() => {
15715
+ const disabled = disabledBackfillSourceSet(readConfig(home));
15716
+ const sourceDefs = source === "all" ? registry.all().filter((a) => !disabled.has(a.id)).map((a) => ({ id: a.id, label: a.label, paths: a.sourcePaths(home, env) })) : (() => {
15648
15717
  const adapter = registry.get(source);
15649
15718
  if (!adapter) {
15650
15719
  return [];
@@ -15787,7 +15856,10 @@ async function listBackfillSourceFiles(source, options, ctx) {
15787
15856
  return zedBackfillFiles(stringOption(options["source-root"]), resolveHome3(options, ctx), ctx.env);
15788
15857
  }
15789
15858
  if (source.id === "cursor") {
15790
- return cursorBackfillFiles(stringOption(options["source-root"]), resolveHome3(options, ctx), ctx.env, options);
15859
+ return cursorBackfillFiles(stringOption(options["source-root"]), resolveHome3(options, ctx), ctx.env);
15860
+ }
15861
+ if (source.id === "grok-bot") {
15862
+ return grokBotBackfillFiles(stringOption(options["source-root"]), resolveHome3(options, ctx), ctx.env, options);
15791
15863
  }
15792
15864
  if (source.id === "pi") {
15793
15865
  return piBackfillFiles(stringOption(options["source-root"]), resolveHome3(options, ctx), ctx.env);
@@ -15796,7 +15868,7 @@ async function listBackfillSourceFiles(source, options, ctx) {
15796
15868
  const fileLists = await Promise.all(roots.map((r) => listJsonlFiles(r)));
15797
15869
  const files = fileLists.flat().sort().slice(0, numberOption(options.limit) || void 0);
15798
15870
  return Promise.all(files.map(async (filePath) => {
15799
- const info = await stat16(filePath);
15871
+ const info = await stat17(filePath);
15800
15872
  return { path: filePath, modifiedAt: info.mtime.toISOString() };
15801
15873
  }));
15802
15874
  }
@@ -15879,7 +15951,7 @@ async function importBackfillPlan(plan, options, ctx, registry) {
15879
15951
  `);
15880
15952
  return 1;
15881
15953
  }
15882
- const sourceDefs = registry.all().filter((a) => supportedSources.has(a.id) && (source === "all" || a.id === source)).map((a) => ({ id: a.id, label: a.label, paths: a.sourcePaths(home, ctx.env) }));
15954
+ const sourceDefs = registry.all().filter((a) => supportedSources.has(a.id) && (source === "all" ? !disabledBackfillSourceSet(readConfig(home)).has(a.id) : a.id === source)).map((a) => ({ id: a.id, label: a.label, paths: a.sourcePaths(home, ctx.env) }));
15883
15955
  const remote = resolveRemoteFromOptions(options, ctx);
15884
15956
  const remoteKey = backfillRemoteKey(remote?.baseUrl ?? DEFAULT_API_URL);
15885
15957
  if (options.force) {
@@ -16348,7 +16420,7 @@ function syncLocalRunnerEntryArgs(cliPath) {
16348
16420
  return [path26.resolve(path26.dirname(cliPath), "../bin/vibetime.mjs")];
16349
16421
  }
16350
16422
  function resolveHome3(options, ctx) {
16351
- return path26.resolve(stringOption(options.home) || ctx.env.HOME || os13.homedir());
16423
+ return path26.resolve(stringOption(options.home) || ctx.env.HOME || os14.homedir());
16352
16424
  }
16353
16425
  function requestedTargets(options) {
16354
16426
  const value = options.target || options.targets;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@yhong91/vibetime",
3
3
  "type": "module",
4
- "version": "0.1.62",
4
+ "version": "0.1.64",
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": {