@yhong91/vibetime 0.1.45 → 0.1.47

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 +256 -14
  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.45" : "0.1.1";
2050
+ var PACKAGE_VERSION = true ? "0.1.47" : "0.1.1";
2051
2051
  var DEFAULT_API_URL = "http://121.196.224.82:3001";
2052
2052
  var DEFAULT_BACKFILL_BATCH_SIZE = 50;
2053
2053
  var DEFAULT_BACKFILL_BATCH_BYTES = 800 * 1024;
@@ -6222,6 +6222,7 @@ function createCopilotAdapter() {
6222
6222
  import { readdir as readdir6, readFile as readFile8, stat as stat7 } from "node:fs/promises";
6223
6223
  import path13 from "node:path";
6224
6224
  init_fs();
6225
+ var GROK_COST_TICKS_PER_USD = 1e9;
6225
6226
  var SOURCE_ID = "grok-build";
6226
6227
  var AGENT_NAME = "grok-build";
6227
6228
  var HOOK_COMMAND = `vibetime hook --agent ${SOURCE_ID}`;
@@ -6488,18 +6489,76 @@ async function parseGrokSessionFile(filePath, options) {
6488
6489
  }), lineNumber, type, "permission");
6489
6490
  }
6490
6491
  }
6492
+ let nextLine = lineNumber + 1;
6493
+ const turnUsages = await loadTurnUsagesFromUpdates(updatesPath);
6494
+ const hasTurnUsage = turnUsages.length > 0;
6495
+ for (const turn of turnUsages) {
6496
+ for (const entry of turn.entries) {
6497
+ push(base({
6498
+ ts: turn.ts,
6499
+ type: "model.usage",
6500
+ model: entry.model || model,
6501
+ confidence: "exact",
6502
+ metrics: entry.metrics,
6503
+ refs: stringRefs({
6504
+ sourceId: `${sessionId}:usage:${turn.promptId}:${entry.model || "model"}`,
6505
+ promptId: turn.promptId
6506
+ })
6507
+ }), nextLine, "updates", "model.usage");
6508
+ nextLine += 1;
6509
+ }
6510
+ }
6511
+ const hunkPath = path13.join(sessionDir, "hunk_records.jsonl");
6512
+ const hunkActivities = await loadHunkFileActivities(hunkPath, cwd);
6513
+ if (hunkActivities.length > 0) {
6514
+ for (const event of events) {
6515
+ for (const file of event.fileActivities || []) {
6516
+ delete file.linesAdded;
6517
+ delete file.linesRemoved;
6518
+ }
6519
+ }
6520
+ }
6521
+ let hasFileLineData = hunkActivities.length > 0;
6522
+ for (const activity of hunkActivities) {
6523
+ push(base({
6524
+ ts: activity.ts,
6525
+ type: "file.changed",
6526
+ confidence: "exact",
6527
+ fileActivities: [activity],
6528
+ metrics: {
6529
+ linesAdded: activity.linesAdded,
6530
+ linesRemoved: activity.linesRemoved,
6531
+ filesChanged: 1
6532
+ },
6533
+ refs: stringRefs({
6534
+ sourceId: `${sessionId}:hunk:${activity.fileActivityId || activity.path}`
6535
+ })
6536
+ }), nextLine, "hunk_records", "file.changed");
6537
+ nextLine += 1;
6538
+ }
6539
+ if (!hasFileLineData) {
6540
+ hasFileLineData = events.some(
6541
+ (event) => (event.fileActivities || []).some(
6542
+ (file) => (file.linesAdded || 0) > 0 || (file.linesRemoved || 0) > 0
6543
+ )
6544
+ );
6545
+ }
6491
6546
  const signals = await readJsonIfExists(signalsPath);
6492
6547
  if (isPlainObject(signals) && updatedAt) {
6493
- const metrics = metricsFromSignals(signals);
6548
+ const metrics = metricsFromSignals(signals, {
6549
+ includeLines: !hasFileLineData,
6550
+ includeModelCalls: !hasTurnUsage
6551
+ });
6494
6552
  if (metrics) {
6495
6553
  push(base({
6496
6554
  ts: updatedAt,
6497
- type: "model.usage",
6555
+ type: hasTurnUsage ? "agent.operation" : "model.usage",
6498
6556
  model: stringField(signals, "primaryModelId") || model,
6499
6557
  confidence: "partial",
6500
6558
  metrics,
6501
- refs: stringRefs({ sourceId: `${sessionId}:usage:signals` })
6502
- }), lineNumber + 1, "signals", "model.usage");
6559
+ refs: stringRefs({ sourceId: `${sessionId}:signals` })
6560
+ }), nextLine, "signals", hasTurnUsage ? "agent.operation" : "model.usage");
6561
+ nextLine += 1;
6503
6562
  }
6504
6563
  }
6505
6564
  if (updatedAt) {
@@ -6508,7 +6567,7 @@ async function parseGrokSessionFile(filePath, options) {
6508
6567
  type: "session.ended",
6509
6568
  confidence: "derived",
6510
6569
  refs: stringRefs({ sourceId: `${sessionId}:ended` })
6511
- }), lineNumber + 2, "summary", "session.ended");
6570
+ }), nextLine, "summary", "session.ended");
6512
6571
  }
6513
6572
  return events;
6514
6573
  }
@@ -6566,16 +6625,36 @@ function fileActivitiesFromTool(toolName, input, ts, cwd) {
6566
6625
  confidence: "exact"
6567
6626
  }];
6568
6627
  }
6569
- if (lower === "search_replace" || lower === "write" || lower.endsWith("__search_replace") || lower.endsWith("__write")) {
6628
+ if (lower === "search_replace" || lower.endsWith("__search_replace")) {
6570
6629
  const target = stringField(input, "file_path") || stringField(input, "target_file") || stringField(input, "path");
6571
6630
  if (!target) {
6572
6631
  return [];
6573
6632
  }
6633
+ const oldString = stringField(input, "old_string");
6634
+ const newString = stringField(input, "new_string");
6574
6635
  return [{
6575
6636
  ts,
6576
6637
  path: displayPath(target, cwd),
6577
6638
  operation: "edit",
6578
- confidence: "exact"
6639
+ confidence: "exact",
6640
+ linesAdded: countTextLines(newString),
6641
+ linesRemoved: countTextLines(oldString),
6642
+ charsWritten: newString?.length
6643
+ }];
6644
+ }
6645
+ if (lower === "write" || lower.endsWith("__write")) {
6646
+ const target = stringField(input, "file_path") || stringField(input, "target_file") || stringField(input, "path");
6647
+ if (!target) {
6648
+ return [];
6649
+ }
6650
+ const content = stringField(input, "content");
6651
+ return [{
6652
+ ts,
6653
+ path: displayPath(target, cwd),
6654
+ operation: "write",
6655
+ confidence: "exact",
6656
+ linesAdded: countTextLines(content),
6657
+ charsWritten: content?.length
6579
6658
  }];
6580
6659
  }
6581
6660
  return [];
@@ -6625,20 +6704,23 @@ async function loadToolInputsFromUpdates(updatesPath) {
6625
6704
  }
6626
6705
  return map;
6627
6706
  }
6628
- function metricsFromSignals(signals) {
6707
+ function metricsFromSignals(signals, options = {}) {
6708
+ const includeLines = options.includeLines !== false;
6709
+ const includeModelCalls = options.includeModelCalls !== false;
6629
6710
  const toolCallCount = numberField(signals, "toolCallCount");
6630
6711
  const turnCount = numberField(signals, "turnCount");
6631
6712
  const durationSec = numberField(signals, "sessionDurationSeconds");
6632
- const linesAdded = numberField(signals, "agentLinesAdded");
6633
- const linesRemoved = numberField(signals, "agentLinesRemoved");
6634
- if (toolCallCount === void 0 && turnCount === void 0 && durationSec === void 0) {
6713
+ const linesAdded = includeLines ? numberField(signals, "agentLinesAdded") : void 0;
6714
+ const linesRemoved = includeLines ? numberField(signals, "agentLinesRemoved") : void 0;
6715
+ const filesTouched = includeLines ? numberField(signals, "agentFilesTouched") : void 0;
6716
+ if (toolCallCount === void 0 && (!includeModelCalls || turnCount === void 0) && durationSec === void 0 && linesAdded === void 0 && linesRemoved === void 0) {
6635
6717
  return void 0;
6636
6718
  }
6637
6719
  const metrics = {};
6638
6720
  if (toolCallCount !== void 0) {
6639
6721
  metrics.toolCalls = toolCallCount;
6640
6722
  }
6641
- if (turnCount !== void 0) {
6723
+ if (includeModelCalls && turnCount !== void 0) {
6642
6724
  metrics.modelCalls = turnCount;
6643
6725
  }
6644
6726
  if (durationSec !== void 0) {
@@ -6650,8 +6732,146 @@ function metricsFromSignals(signals) {
6650
6732
  if (linesRemoved !== void 0) {
6651
6733
  metrics.linesRemoved = linesRemoved;
6652
6734
  }
6735
+ if (filesTouched !== void 0) {
6736
+ metrics.filesChanged = filesTouched;
6737
+ }
6738
+ return metrics;
6739
+ }
6740
+ async function loadTurnUsagesFromUpdates(updatesPath) {
6741
+ const turns = [];
6742
+ let text = "";
6743
+ try {
6744
+ text = await readFile8(updatesPath, "utf8");
6745
+ } catch {
6746
+ return turns;
6747
+ }
6748
+ for (const line of text.split("\n")) {
6749
+ if (!line.trim()) {
6750
+ continue;
6751
+ }
6752
+ const raw = parseJsonLine(line);
6753
+ if (!raw) {
6754
+ continue;
6755
+ }
6756
+ const params = objectField(raw, "params");
6757
+ const update = objectField(params, "update");
6758
+ if (stringField(update, "sessionUpdate") !== "turn_completed") {
6759
+ continue;
6760
+ }
6761
+ const usage = objectField(update, "usage");
6762
+ if (Object.keys(usage).length === 0) {
6763
+ continue;
6764
+ }
6765
+ const meta = objectField(params, "_meta");
6766
+ const ts = timestampFrom(meta.agentTimestampMs) || timestampFrom(raw.timestamp) || timestampFrom(raw.ts);
6767
+ if (!ts) {
6768
+ continue;
6769
+ }
6770
+ const promptId = stringField(update, "prompt_id") || stringField(meta, "eventId") || `turn_${turns.length + 1}`;
6771
+ const entries = [];
6772
+ const modelUsage = objectField(usage, "modelUsage");
6773
+ const modelKeys = Object.keys(modelUsage);
6774
+ if (modelKeys.length > 0) {
6775
+ for (const modelName of modelKeys) {
6776
+ const perModel = objectField(modelUsage, modelName);
6777
+ const metrics = metricsFromGrokUsage(
6778
+ Object.keys(perModel).length > 0 ? perModel : usage
6779
+ );
6780
+ if (!hasTokenMetrics(metrics)) {
6781
+ continue;
6782
+ }
6783
+ entries.push({ model: modelName, metrics });
6784
+ }
6785
+ } else {
6786
+ const metrics = metricsFromGrokUsage(usage);
6787
+ if (hasTokenMetrics(metrics)) {
6788
+ entries.push({ metrics });
6789
+ }
6790
+ }
6791
+ if (entries.length > 0) {
6792
+ turns.push({ ts, promptId, entries });
6793
+ }
6794
+ }
6795
+ return turns;
6796
+ }
6797
+ function metricsFromGrokUsage(usage) {
6798
+ const inputTokens = numberField(usage, "inputTokens") || 0;
6799
+ const outputTokens = numberField(usage, "outputTokens") || 0;
6800
+ const cachedRead = numberField(usage, "cachedReadTokens") || 0;
6801
+ const cacheCreation = numberField(usage, "cacheCreationTokens") || 0;
6802
+ const reasoning = numberField(usage, "reasoningTokens") || 0;
6803
+ const totalTokens = numberField(usage, "totalTokens") || inputTokens + outputTokens;
6804
+ const modelCalls = numberField(usage, "modelCalls");
6805
+ const apiDurationMs = numberField(usage, "apiDurationMs");
6806
+ const costTicks = numberField(usage, "costUsdTicks");
6807
+ const metrics = {
6808
+ tokensInput: inputTokens || void 0,
6809
+ tokensOutput: outputTokens || void 0,
6810
+ tokensCachedInput: cachedRead + cacheCreation || void 0,
6811
+ tokensCacheReadInput: cachedRead || void 0,
6812
+ tokensCacheCreationInput: cacheCreation || void 0,
6813
+ tokensReasoningOutput: reasoning || void 0,
6814
+ tokensTotal: totalTokens || void 0,
6815
+ modelCalls: modelCalls || void 0,
6816
+ modelDurationMs: apiDurationMs || void 0,
6817
+ durationMs: apiDurationMs || void 0
6818
+ };
6819
+ if (costTicks !== void 0 && costTicks > 0) {
6820
+ metrics.costUsd = costTicks / GROK_COST_TICKS_PER_USD;
6821
+ }
6653
6822
  return metrics;
6654
6823
  }
6824
+ function hasTokenMetrics(metrics) {
6825
+ return Boolean(
6826
+ metrics.tokensInput || metrics.tokensOutput || metrics.tokensCachedInput || metrics.tokensTotal
6827
+ );
6828
+ }
6829
+ async function loadHunkFileActivities(hunkPath, cwd) {
6830
+ const activities = [];
6831
+ let text = "";
6832
+ try {
6833
+ text = await readFile8(hunkPath, "utf8");
6834
+ } catch {
6835
+ return activities;
6836
+ }
6837
+ for (const line of text.split("\n")) {
6838
+ if (!line.trim()) {
6839
+ continue;
6840
+ }
6841
+ const raw = parseJsonLine(line);
6842
+ if (!raw) {
6843
+ continue;
6844
+ }
6845
+ const authorType = stringField(raw, "authorType");
6846
+ if (authorType && authorType !== "agent") {
6847
+ continue;
6848
+ }
6849
+ const filePath = stringField(raw, "filePath") || stringField(raw, "path");
6850
+ if (!filePath) {
6851
+ continue;
6852
+ }
6853
+ const ts = timestampFrom(raw.timestamp) || timestampFrom(raw.ts);
6854
+ if (!ts) {
6855
+ continue;
6856
+ }
6857
+ const linesAdded = numberField(raw, "linesAdded");
6858
+ const linesRemoved = numberField(raw, "linesRemoved");
6859
+ if ((linesAdded || 0) <= 0 && (linesRemoved || 0) <= 0) {
6860
+ continue;
6861
+ }
6862
+ const hunkId = stringField(raw, "hunkId");
6863
+ activities.push({
6864
+ ts,
6865
+ path: displayPath(filePath, cwd),
6866
+ operation: "edit",
6867
+ confidence: "exact",
6868
+ fileActivityId: hunkId,
6869
+ linesAdded: linesAdded || void 0,
6870
+ linesRemoved: linesRemoved || void 0
6871
+ });
6872
+ }
6873
+ return activities;
6874
+ }
6655
6875
  var grokHandler = (msg) => hookHandler(SOURCE_ID, msg);
6656
6876
  function hookConfig5() {
6657
6877
  const anyTool = [{ matcher: ".*", hooks: [grokHandler("Reporting tool activity")] }];
@@ -6949,6 +7169,7 @@ async function parseOpenCodeSessionFile(dbPath, options) {
6949
7169
  }
6950
7170
  const filePath = stringField(stateInput, "file_path") || stringField(stateInput, "filePath");
6951
7171
  if (filePath) {
7172
+ const lineStats = opencodeLineStats(tool, state, stateInput);
6952
7173
  events.push(baseOpenCodeEvent({
6953
7174
  ts: msToIso(endMs || completedTs || createdTs),
6954
7175
  type: toolFileActivityType(tool),
@@ -6963,8 +7184,10 @@ async function parseOpenCodeSessionFile(dbPath, options) {
6963
7184
  fileActivities: [{
6964
7185
  ts: msToIso(endMs || completedTs || createdTs),
6965
7186
  path: filePath,
6966
- operation: operationForTool(tool)
7187
+ operation: operationForTool(tool),
7188
+ ...lineStats
6967
7189
  }],
7190
+ metrics: lineStats.linesAdded || lineStats.linesRemoved ? lineStats : void 0,
6968
7191
  refs: stringRefs({ sourceId: callId })
6969
7192
  }, assistantAgent));
6970
7193
  }
@@ -7087,6 +7310,25 @@ function baseOpenCodeEvent(event, agentName) {
7087
7310
  ...event
7088
7311
  };
7089
7312
  }
7313
+ function opencodeLineStats(tool, state, input) {
7314
+ const diff = stringField(objectField(state, "metadata"), "diff");
7315
+ if (diff) {
7316
+ return diffStats(diff);
7317
+ }
7318
+ const normalized = tool.toLowerCase();
7319
+ if (normalized === "write") {
7320
+ return { linesAdded: countTextLines(stringField(input, "content")) };
7321
+ }
7322
+ if (normalized === "edit" || normalized === "multiedit" || normalized === "patch") {
7323
+ const newString = stringField(input, "newString") || stringField(input, "new_string");
7324
+ const oldString = stringField(input, "oldString") || stringField(input, "old_string");
7325
+ return {
7326
+ linesAdded: countTextLines(newString),
7327
+ linesRemoved: countTextLines(oldString)
7328
+ };
7329
+ }
7330
+ return {};
7331
+ }
7090
7332
  function opencodeUsageFromInfo(info) {
7091
7333
  const tokensObj = objectField(info, "tokens");
7092
7334
  if (!tokensObj) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@yhong91/vibetime",
3
3
  "type": "module",
4
- "version": "0.1.45",
4
+ "version": "0.1.47",
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": {