@yhong91/vibetime 0.1.41 → 0.1.43

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 +72 -12
  2. package/package.json +1 -1
package/bin/vibetime.mjs CHANGED
@@ -1928,7 +1928,7 @@ function countTextLines(text) {
1928
1928
  }
1929
1929
 
1930
1930
  // src/lib/constants.ts
1931
- var PACKAGE_VERSION = true ? "0.1.41" : "0.1.1";
1931
+ var PACKAGE_VERSION = true ? "0.1.43" : "0.1.1";
1932
1932
  var DEFAULT_API_URL = "http://121.196.224.82:3001";
1933
1933
  var DEFAULT_BACKFILL_BATCH_SIZE = 50;
1934
1934
  var DEFAULT_BACKFILL_BATCH_BYTES = 800 * 1024;
@@ -5815,6 +5815,18 @@ async function parseCopilotSessionFile(filePath, options) {
5815
5815
  }
5816
5816
  const msgTurnId = stringField(data, "turnId");
5817
5817
  const turnId = msgTurnId !== void 0 ? `turn_${createStableHash([sessionId, msgTurnId]).slice(0, 24)}` : currentTurnId;
5818
+ events.push(baseCopilotEvent({
5819
+ ts,
5820
+ type: "agent.operation",
5821
+ operation: "model call",
5822
+ sessionId,
5823
+ turnId,
5824
+ cwd,
5825
+ project,
5826
+ model,
5827
+ confidence: "exact",
5828
+ metrics: { modelCalls: 1 }
5829
+ }));
5818
5830
  const outputTokens = numberField(data, "outputTokens");
5819
5831
  if (outputTokens && outputTokens > 0) {
5820
5832
  const key = turnId || "unknown";
@@ -10751,6 +10763,8 @@ function buildSessionRollup(rollupKey, events) {
10751
10763
  const toolRollups = /* @__PURE__ */ new Map();
10752
10764
  const fileRollups = /* @__PURE__ */ new Map();
10753
10765
  const turnRollups = /* @__PURE__ */ new Map();
10766
+ const turnTimings = /* @__PURE__ */ new Map();
10767
+ let activeTurnId;
10754
10768
  let promptCount = 0;
10755
10769
  let turnCount = 0;
10756
10770
  let toolCallCount = 0;
@@ -10766,6 +10780,22 @@ function buildSessionRollup(rollupKey, events) {
10766
10780
  let linesAdded = 0;
10767
10781
  let linesRemoved = 0;
10768
10782
  for (const event of ordered) {
10783
+ if (event.type === "turn.started" && event.turnId) {
10784
+ activeTurnId = event.turnId;
10785
+ const timing = turnTimings.get(event.turnId) || {};
10786
+ timing.taskStartedAt ??= event.ts;
10787
+ turnTimings.set(event.turnId, timing);
10788
+ }
10789
+ if (event.type === "model.usage" || (event.metrics?.modelCalls || 0) > 0) {
10790
+ const turnId = event.turnId || activeTurnId;
10791
+ if (turnId) {
10792
+ const timing = turnTimings.get(turnId) || {};
10793
+ if (!timing.lastCallAt || event.ts > timing.lastCallAt) {
10794
+ timing.lastCallAt = event.ts;
10795
+ }
10796
+ turnTimings.set(turnId, timing);
10797
+ }
10798
+ }
10769
10799
  const eventInputTokens = Math.max(0, event.metrics?.tokensInput || 0);
10770
10800
  const eventCachedInputTokens = Math.max(0, event.metrics?.tokensCachedInput || 0);
10771
10801
  const eventCacheCreationInputTokens = Math.max(0, event.metrics?.tokensCacheCreationInput || 0);
@@ -10969,6 +10999,9 @@ function buildSessionRollup(rollupKey, events) {
10969
10999
  fileRollups.set(pathHash, fileRollup);
10970
11000
  }
10971
11001
  timeBuckets.set(bucketTs, bucket);
11002
+ if (event.type === "turn.completed" && event.turnId === activeTurnId) {
11003
+ activeTurnId = void 0;
11004
+ }
10972
11005
  }
10973
11006
  const baseRollup = {
10974
11007
  rollupKey,
@@ -10999,10 +11032,15 @@ function buildSessionRollup(rollupKey, events) {
10999
11032
  modelRollups: [...modelRollups.values()].sort((a, b) => b.callCount - a.callCount || a.model.localeCompare(b.model)),
11000
11033
  toolRollups: [...toolRollups.values()].sort((a, b) => b.callCount - a.callCount || a.tool.localeCompare(b.tool)),
11001
11034
  fileRollups: [...fileRollups.values()].sort((a, b) => b.writes - a.writes || b.reads - a.reads || a.displayPath.localeCompare(b.displayPath)),
11002
- turnRollups: [...turnRollups.values()].map((rollup) => ({
11003
- ...rollup,
11004
- durationMs: Math.max(0, Date.parse(rollup.lastEventAt) - Date.parse(rollup.startedAt))
11005
- })).sort((a, b) => a.startedAt.localeCompare(b.startedAt))
11035
+ turnRollups: [...turnRollups.values()].map((rollup) => {
11036
+ const timing = turnTimings.get(rollup.turnId);
11037
+ const startedAt2 = timing?.taskStartedAt || rollup.startedAt;
11038
+ return {
11039
+ ...rollup,
11040
+ startedAt: startedAt2,
11041
+ durationMs: timing?.lastCallAt ? Math.max(0, Date.parse(timing.lastCallAt) - Date.parse(startedAt2)) : 0
11042
+ };
11043
+ }).sort((a, b) => a.startedAt.localeCompare(b.startedAt))
11006
11044
  };
11007
11045
  return {
11008
11046
  ...baseRollup,
@@ -11397,7 +11435,11 @@ async function postRollupBatch(remote, rollups, options = {}) {
11397
11435
  const response = await remote.fetchImpl(joinUrl(remote.baseUrl, "/v3/agent/ingest"), {
11398
11436
  method: "POST",
11399
11437
  headers: buildHeaders(remote.token, options.machine),
11400
- body: JSON.stringify({ rollups, replace: options.replace !== false })
11438
+ body: JSON.stringify({
11439
+ rollups,
11440
+ replace: options.replace !== false,
11441
+ allowHistoricalRewrite: options.allowHistoricalRewrite === true
11442
+ })
11401
11443
  });
11402
11444
  if (!response.ok) {
11403
11445
  const body = await response.text();
@@ -11412,7 +11454,7 @@ async function postRollupBatch(remote, rollups, options = {}) {
11412
11454
  };
11413
11455
  }
11414
11456
  async function deleteRollupsBySource(remote, source, machine, options = {}) {
11415
- const query = `source=${encodeURIComponent(source)}${options.preserveTokens ? "&preserveTokens=1" : ""}`;
11457
+ const query = `source=${encodeURIComponent(source)}${options.preserveTokens ? "&preserveTokens=1" : ""}${options.allowHistoricalRewrite ? "&allowHistoricalRewrite=1" : ""}`;
11416
11458
  const response = await remote.fetchImpl(
11417
11459
  joinUrl(remote.baseUrl, `/v3/agent/sessions?${query}`),
11418
11460
  { method: "DELETE", headers: buildHeaders(remote.token, machine) }
@@ -11460,6 +11502,7 @@ async function deleteMachine(remote, id) {
11460
11502
  var BACKFILL_STATE_SCHEMA_VERSION = 6;
11461
11503
 
11462
11504
  // src/cli.ts
11505
+ var SESSION_REWRITE_DAYS = 7;
11463
11506
  function createRegistry() {
11464
11507
  const registry = new AdapterRegistry();
11465
11508
  registry.register(createCodexAdapter());
@@ -11537,7 +11580,7 @@ function createCli(ctx, registry) {
11537
11580
  cli.command("hook", "Read agent hook JSON from stdin and report a throttled event").option("--agent <name>", "Agent name").option("--project <name>", "Project name").option("--min-interval <seconds>", "Minimum seconds between similar hook reports").action((options) => hookCommand(normalizeOptions(options), ctx));
11538
11581
  cli.command("sync-local-trigger", "Trigger one background local sync with throttle and locking").option("--min-interval <seconds>", "Minimum seconds between sync triggers").action((options) => syncLocalTriggerCommand(normalizeOptions(options), ctx, registry));
11539
11582
  cli.command("sync-local-runner", "Internal background local sync runner").option("--lock-file <path>", "Lock file for the active sync").option("--state-file <path>", "State file for trigger metadata").action((options) => syncLocalRunnerCommand(normalizeOptions(options), ctx, registry));
11540
- cli.command("backfill [action]", "Inspect local history import candidates").option("--source <source>", "Backfill source").option("--since <time>", "Only include history after this time").option("--until <time>", "Only include history before this time").option("--project <name>", "Project filter").option("--source-root <path>", "Override source history root").option("--include-source-path", "Include local source paths in output").option("--import-run <id>", "Import run id for verify/resume workflows").option("--limit <count>", "Maximum session files to parse").option("--batch-size <count>", "Max rollups per request (also bounded by --batch-bytes)").option("--batch-bytes <bytes>", "Soft byte cap for the JSON body of a single ingest POST").option("--replace", "Replace conflicting records during import (default)").option("--skip-conflicts", "Skip conflicting records instead of replacing them").option("--force", "Force full re-import: clear watermark and re-process all files (keeps server rollups that already have tokens)").option("--purge-all", "With --force: also delete token-bearing server rollups before re-importing (DANGEROUS \u2014 tokens whose local sources were rotated away are lost forever)").action((action, options) => backfillCommand({ ...normalizeOptions(options), action }, ctx, registry));
11583
+ cli.command("backfill [action]", "Inspect local history import candidates").option("--source <source>", "Backfill source").option("--since <time>", "Only include history after this time").option("--until <time>", "Only include history before this time").option("--project <name>", "Project filter").option("--source-root <path>", "Override source history root").option("--include-source-path", "Include local source paths in output").option("--import-run <id>", "Import run id for verify/resume workflows").option("--limit <count>", "Maximum session files to parse").option("--batch-size <count>", "Max rollups per request (also bounded by --batch-bytes)").option("--batch-bytes <bytes>", "Soft byte cap for the JSON body of a single ingest POST").option("--replace", "Replace conflicting records during import (default)").option("--skip-conflicts", "Skip conflicting records instead of replacing them").option("--force", "Force re-import of sessions active within the last 7 days").option("--purge-all", "With --force: unlock and replace history older than 7 days (DANGEROUS)").action((action, options) => backfillCommand({ ...normalizeOptions(options), action }, ctx, registry));
11541
11584
  cli.command("token [action] [value]", "Set, show, or clear the persisted API token").option("--remote <url>", "Override API base URL when setting a token").action((action, value, options) => tokenCommand(action, value, normalizeOptions(options), ctx));
11542
11585
  cli.command("machine [action]", "List or rename machines (requires login)").option("--name <name>", "New display name (used by `machine rename`)").option("--id <id>", "Machine id (defaults to current machine)").action((action, options) => machineCommand(action, normalizeOptions(options), ctx));
11543
11586
  return cli;
@@ -11827,6 +11870,10 @@ async function backfillCommand(options, ctx, registry) {
11827
11870
  if (action === "verify") {
11828
11871
  return backfillVerifyCommand(options, ctx);
11829
11872
  }
11873
+ if (options["purge-all"] && !options.force) {
11874
+ write(ctx.stderr, "--purge-all requires --force\n");
11875
+ return 1;
11876
+ }
11830
11877
  if (action === "import" && !options["dry-run"]) {
11831
11878
  const requested = normalizeBackfillSource(stringOption(options.source) || "all");
11832
11879
  const supported = /* @__PURE__ */ new Set(["all", ...BACKFILL_SOURCE_IDS]);
@@ -12102,7 +12149,7 @@ async function importBackfillPlan(plan, options, ctx, registry) {
12102
12149
  options,
12103
12150
  ctx
12104
12151
  );
12105
- const rollups = buildSessionRollups(canonicalEvents);
12152
+ const rollups = selectRollupsForUpload(buildSessionRollups(canonicalEvents), options);
12106
12153
  const counts = await uploadSessionRollups(rollups, canonicalEvents.length, options, ctx);
12107
12154
  const result = {
12108
12155
  importRunId: plan.importRun.importRunId,
@@ -12134,7 +12181,7 @@ async function purgeForcedSources(sourceDefs, home, remoteKey, options, ctx) {
12134
12181
  try {
12135
12182
  const deleted = await deleteSessionRollupsBySourceAPI(item.id, options, ctx, preserveTokens);
12136
12183
  if (!options.json) {
12137
- const suffix = preserveTokens ? " (token-bearing rollups kept)" : "";
12184
+ const suffix = preserveTokens ? " (token-bearing and sessions older than 7 days kept)" : "";
12138
12185
  write(ctx.stdout, `purged ${item.id}: ${deleted} old rollups${suffix}
12139
12186
  `);
12140
12187
  }
@@ -12261,7 +12308,8 @@ async function sendSessionRollupBatch(rollups, options, ctx) {
12261
12308
  };
12262
12309
  const result = await postRollupBatch(remote, rollups, {
12263
12310
  replace: options["skip-conflicts"] !== true,
12264
- machine
12311
+ machine,
12312
+ allowHistoricalRewrite: options["purge-all"] === true
12265
12313
  });
12266
12314
  return result;
12267
12315
  }
@@ -12275,7 +12323,17 @@ async function deleteSessionRollupsBySourceAPI(source, options, ctx, preserveTok
12275
12323
  id: ensureLocalMachineId(home),
12276
12324
  hostname: defaultMachineName(),
12277
12325
  platform: process.platform
12278
- }, { preserveTokens });
12326
+ }, {
12327
+ preserveTokens,
12328
+ allowHistoricalRewrite: options["purge-all"] === true
12329
+ });
12330
+ }
12331
+ function selectRollupsForUpload(rollups, options, now = /* @__PURE__ */ new Date()) {
12332
+ if (!options.force || options["purge-all"]) {
12333
+ return rollups;
12334
+ }
12335
+ const cutoff = now.getTime() - SESSION_REWRITE_DAYS * 24 * 60 * 60 * 1e3;
12336
+ return rollups.filter((rollup) => Date.parse(rollup.lastEventAt) >= cutoff);
12279
12337
  }
12280
12338
  function shouldUseIncrementalBackfill(options) {
12281
12339
  return !stringOption(options.since) && !stringOption(options.until) && !stringOption(options["source-root"]) && numberOption(options.limit) === void 0;
@@ -12767,8 +12825,10 @@ Environment:
12767
12825
  `;
12768
12826
  }
12769
12827
  export {
12828
+ SESSION_REWRITE_DAYS,
12770
12829
  run,
12771
12830
  selectBackfillFilesForImport,
12831
+ selectRollupsForUpload,
12772
12832
  syncLocalRunnerEntryArgs
12773
12833
  };
12774
12834
  const code = await run(process.argv.slice(2));process.exitCode = code;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@yhong91/vibetime",
3
3
  "type": "module",
4
- "version": "0.1.41",
4
+ "version": "0.1.43",
5
5
  "description": "vibetime CLI — install AI-agent hooks (Claude Code, Codex, OpenCode, Pi) and report activity to vibetime.",
6
6
  "license": "MIT",
7
7
  "publishConfig": {