@yhong91/vibetime 0.1.64 → 0.1.65

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 +82 -16
  2. package/package.json +1 -1
package/bin/vibetime.mjs CHANGED
@@ -2047,7 +2047,7 @@ function claudeStyleFileMetrics(tool, input) {
2047
2047
  }
2048
2048
 
2049
2049
  // src/lib/constants.ts
2050
- var PACKAGE_VERSION = true ? "0.1.64" : "0.1.1";
2050
+ var PACKAGE_VERSION = true ? "0.1.65" : "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;
@@ -6334,10 +6334,18 @@ var CURSOR_DASHBOARD_USAGE_URL = "https://cursor.com/api/dashboard/get-filtered-
6334
6334
  var CURSOR_CLOUD_USAGE_CACHE_VERSION = 2;
6335
6335
  var DEFAULT_WINDOW_DAYS = 90;
6336
6336
  var PAGE_SIZE = 1e3;
6337
+ var DEFAULT_CURSOR_CLOUD_FETCH_INTERVAL_SECONDS = 3600;
6337
6338
  function cursorCloudUsageDisabled(env = process.env) {
6338
6339
  const value = env.VIBETIME_CURSOR_CLOUD_USAGE;
6339
6340
  return value === "0" || value === "false";
6340
6341
  }
6342
+ function cursorCloudFetchIntervalSeconds(env = process.env) {
6343
+ const parsed = Number.parseInt(env.VIBETIME_CURSOR_CLOUD_FETCH_INTERVAL_SECONDS || "", 10);
6344
+ if (Number.isNaN(parsed) || parsed < 0) {
6345
+ return DEFAULT_CURSOR_CLOUD_FETCH_INTERVAL_SECONDS;
6346
+ }
6347
+ return parsed;
6348
+ }
6341
6349
  function cursorSessionCookie(accessToken) {
6342
6350
  const token = accessToken.trim();
6343
6351
  if (!token) {
@@ -6739,6 +6747,14 @@ async function loadCursorCloudUsageMap(options, filePath) {
6739
6747
  if (cached) {
6740
6748
  return cached;
6741
6749
  }
6750
+ const home = stringOption(options.home) || os6.homedir();
6751
+ const intervalSeconds = cursorCloudFetchIntervalSeconds();
6752
+ if (intervalSeconds > 0 && !options.force) {
6753
+ const lastFetchedMs = await readLastCloudFetchAt(home);
6754
+ if (lastFetchedMs !== void 0 && Date.now() - lastFetchedMs < intervalSeconds * 1e3) {
6755
+ return /* @__PURE__ */ new Map();
6756
+ }
6757
+ }
6742
6758
  const pending = (async () => {
6743
6759
  try {
6744
6760
  const token = isCursorStateDbPath(filePath) ? await readCursorAccessToken(filePath) : await readCursorAccessTokenFromHome(options);
@@ -6746,7 +6762,10 @@ async function loadCursorCloudUsageMap(options, filePath) {
6746
6762
  return /* @__PURE__ */ new Map();
6747
6763
  }
6748
6764
  const fetchImpl = typeof options.cursorCloudFetch === "function" ? options.cursorCloudFetch : void 0;
6749
- return await fetchCursorDashboardUsage({ accessToken: token, fetchImpl });
6765
+ const map = await fetchCursorDashboardUsage({ accessToken: token, fetchImpl });
6766
+ await writeLastCloudFetchAt(home, (/* @__PURE__ */ new Date()).toISOString()).catch(() => {
6767
+ });
6768
+ return map;
6750
6769
  } catch {
6751
6770
  return /* @__PURE__ */ new Map();
6752
6771
  }
@@ -6787,6 +6806,25 @@ async function resolveCursorSessionUsage(options, sessionId) {
6787
6806
  function cursorCloudUsageCachePath(home) {
6788
6807
  return path13.join(home, ".vibetime", CURSOR_CLOUD_USAGE_FILENAME);
6789
6808
  }
6809
+ function cursorCloudFetchStatePath(home) {
6810
+ return path13.join(home, ".vibetime", "cursor-cloud-usage-fetch.json");
6811
+ }
6812
+ async function readLastCloudFetchAt(home) {
6813
+ try {
6814
+ const raw = JSON.parse(await readFile8(cursorCloudFetchStatePath(home), "utf8"));
6815
+ const ts = isPlainObject(raw) ? stringField(raw, "fetchedAt") : void 0;
6816
+ const ms = ts ? Date.parse(ts) : Number.NaN;
6817
+ return Number.isNaN(ms) ? void 0 : ms;
6818
+ } catch {
6819
+ return void 0;
6820
+ }
6821
+ }
6822
+ async function writeLastCloudFetchAt(home, fetchedAt) {
6823
+ const statePath = cursorCloudFetchStatePath(home);
6824
+ await mkdir4(path13.dirname(statePath), { recursive: true });
6825
+ await writeFile4(statePath, `${JSON.stringify({ fetchedAt }, null, 2)}
6826
+ `, "utf8");
6827
+ }
6790
6828
  async function listLocalCursorSessionIds(home, env) {
6791
6829
  const ids = /* @__PURE__ */ new Set();
6792
6830
  for (const dbPath of cursorStateDbCandidates(home, env)) {
@@ -8040,7 +8078,12 @@ async function grokBotBackfillFiles(sourceRoot, home = os7.homedir(), env, optio
8040
8078
  if (!options) {
8041
8079
  return [];
8042
8080
  }
8081
+ const cachePath = cursorCloudUsageCachePath(home);
8043
8082
  const map = await loadCursorCloudUsageMap(options);
8083
+ if (map.size === 0) {
8084
+ const info = await stat9(cachePath).catch(() => null);
8085
+ return info ? [{ path: cachePath, modifiedAt: info.mtime.toISOString() }] : [];
8086
+ }
8044
8087
  const localIds = await listLocalCursorSessionIds(home, env);
8045
8088
  const cloudOnly = [];
8046
8089
  for (const [conversationId, events] of map) {
@@ -8055,7 +8098,6 @@ async function grokBotBackfillFiles(sourceRoot, home = os7.homedir(), env, optio
8055
8098
  if (cloudOnly.length === 0) {
8056
8099
  return [];
8057
8100
  }
8058
- const cachePath = cursorCloudUsageCachePath(home);
8059
8101
  return [{
8060
8102
  path: cachePath,
8061
8103
  modifiedAt: await writeCursorCloudUsageCache(cachePath, cloudOnly)
@@ -8799,6 +8841,36 @@ import path15 from "node:path";
8799
8841
 
8800
8842
  // src/lib/toml-hooks.ts
8801
8843
  init_fs();
8844
+ var KIMI_CODE_HOOK_EVENTS = /* @__PURE__ */ new Set([
8845
+ "PreToolUse",
8846
+ "PostToolUse",
8847
+ "PostToolUseFailure",
8848
+ "PermissionRequest",
8849
+ "PermissionResult",
8850
+ "UserPromptSubmit",
8851
+ "Stop",
8852
+ "StopFailure",
8853
+ "Interrupt",
8854
+ "SessionStart",
8855
+ "SessionEnd",
8856
+ "SubagentStart",
8857
+ "SubagentStop",
8858
+ "PreCompact",
8859
+ "PostCompact",
8860
+ "Notification"
8861
+ ]);
8862
+ function isValidTomlHookRule(rule) {
8863
+ if (!KIMI_CODE_HOOK_EVENTS.has(rule.event)) {
8864
+ return false;
8865
+ }
8866
+ if (typeof rule.command !== "string" || rule.command.trim() === "") {
8867
+ return false;
8868
+ }
8869
+ if (rule.timeout !== void 0 && (!Number.isInteger(rule.timeout) || rule.timeout < 1 || rule.timeout > 600)) {
8870
+ return false;
8871
+ }
8872
+ return true;
8873
+ }
8802
8874
  async function hasTomlHookCommand(filePath, command) {
8803
8875
  const text = await readTextIfExists(filePath);
8804
8876
  if (!text) {
@@ -8830,8 +8902,9 @@ function parseTomlHookRules(text) {
8830
8902
  return rules;
8831
8903
  }
8832
8904
  function mergeTomlHookRules(existingText, desired) {
8905
+ const valid = desired.filter(isValidTomlHookRule);
8833
8906
  const existingKeys = new Set(parseTomlHookRules(existingText).map(ruleKey));
8834
- const toAppend = desired.filter((rule) => !existingKeys.has(ruleKey(rule)));
8907
+ const toAppend = valid.filter((rule) => !existingKeys.has(ruleKey(rule)));
8835
8908
  if (toAppend.length === 0) {
8836
8909
  return existingText;
8837
8910
  }
@@ -8962,7 +9035,6 @@ function kimiHookRules() {
8962
9035
  "SessionStart",
8963
9036
  "SessionEnd",
8964
9037
  "UserPromptSubmit",
8965
- "TurnStarted",
8966
9038
  "PostToolUse",
8967
9039
  "PostToolUseFailure",
8968
9040
  "Stop",
@@ -14727,21 +14799,15 @@ async function mergeHooksToml(filePath, content, { dryRun, force, onWrite }) {
14727
14799
  );
14728
14800
  }
14729
14801
  const desired = Array.isArray(content.hooks) ? content.hooks : [];
14730
- const nextText = mergeTomlHookRules(existingText ?? "", desired);
14802
+ const commands = [...new Set(
14803
+ desired.map((rule) => rule.command).filter((cmd) => typeof cmd === "string" && cmd.length > 0)
14804
+ )];
14805
+ const stripped = removeTomlHookRulesByCommand(existingText ?? "", commands);
14806
+ const nextText = mergeTomlHookRules(stripped, desired);
14731
14807
  if (existingText === nextText || existingText === null && nextText === "") {
14732
14808
  onWrite(`Already installed ${filePath}`);
14733
14809
  return;
14734
14810
  }
14735
- if (existingText !== null) {
14736
- const existingKeys = new Set(
14737
- parseTomlHookRules(existingText).map((rule) => `${rule.event}\0${rule.command}`)
14738
- );
14739
- const allPresent = desired.every((rule) => existingKeys.has(`${rule.event}\0${rule.command}`));
14740
- if (allPresent && desired.length > 0) {
14741
- onWrite(`Already installed ${filePath}`);
14742
- return;
14743
- }
14744
- }
14745
14811
  await mkdir7(pathMod.dirname(filePath), { recursive: true });
14746
14812
  await writeFile6(filePath, nextText, "utf8");
14747
14813
  onWrite(`Installed ${filePath}`);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@yhong91/vibetime",
3
3
  "type": "module",
4
- "version": "0.1.64",
4
+ "version": "0.1.65",
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": {