@yhong91/vibetime 0.1.46 → 0.1.48

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 +249 -15
  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.46" : "0.1.1";
2050
+ var PACKAGE_VERSION = true ? "0.1.48" : "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;
@@ -5157,7 +5157,7 @@ async function parseCodexSessionFile(filePath, options) {
5157
5157
  let sessionId = sessionIdFromFilePath(filePath, "codex");
5158
5158
  let cwd;
5159
5159
  let project;
5160
- let model;
5160
+ let model = firstTurnContextModel(lines);
5161
5161
  let sessionStartEmitted = false;
5162
5162
  let serviceTier;
5163
5163
  let reasoningEffort;
@@ -5191,7 +5191,6 @@ async function parseCodexSessionFile(filePath, options) {
5191
5191
  sessionId = stringField(payload, "id") || sessionId;
5192
5192
  cwd = inherited?.cwd || stringField(payload, "cwd") || cwd;
5193
5193
  project = inherited?.project || (cwd ? path11.basename(cwd) : project);
5194
- model = stringField(payload, "model_provider") || model;
5195
5194
  events.push(withBackfillRefs({
5196
5195
  schemaVersion: AGENT_TIME_SCHEMA_VERSION,
5197
5196
  ts,
@@ -5599,6 +5598,21 @@ async function parseCodexSessionFile(filePath, options) {
5599
5598
  }
5600
5599
  return events.filter((event) => validateCanonicalEvent(event).valid);
5601
5600
  }
5601
+ function firstTurnContextModel(lines) {
5602
+ for (const line of lines) {
5603
+ if (!line.includes("turn_context")) {
5604
+ continue;
5605
+ }
5606
+ const raw = parseJsonLine(line);
5607
+ if (raw && stringField(raw, "type") === "turn_context") {
5608
+ const model = stringField(objectField(raw, "payload"), "model");
5609
+ if (model) {
5610
+ return model;
5611
+ }
5612
+ }
5613
+ }
5614
+ return void 0;
5615
+ }
5602
5616
  function rewriteCodexModelForTier(model, serviceTier) {
5603
5617
  if (!model) {
5604
5618
  return model;
@@ -6222,6 +6236,7 @@ function createCopilotAdapter() {
6222
6236
  import { readdir as readdir6, readFile as readFile8, stat as stat7 } from "node:fs/promises";
6223
6237
  import path13 from "node:path";
6224
6238
  init_fs();
6239
+ var GROK_COST_TICKS_PER_USD = 1e9;
6225
6240
  var SOURCE_ID = "grok-build";
6226
6241
  var AGENT_NAME = "grok-build";
6227
6242
  var HOOK_COMMAND = `vibetime hook --agent ${SOURCE_ID}`;
@@ -6488,18 +6503,76 @@ async function parseGrokSessionFile(filePath, options) {
6488
6503
  }), lineNumber, type, "permission");
6489
6504
  }
6490
6505
  }
6506
+ let nextLine = lineNumber + 1;
6507
+ const turnUsages = await loadTurnUsagesFromUpdates(updatesPath);
6508
+ const hasTurnUsage = turnUsages.length > 0;
6509
+ for (const turn of turnUsages) {
6510
+ for (const entry of turn.entries) {
6511
+ push(base({
6512
+ ts: turn.ts,
6513
+ type: "model.usage",
6514
+ model: entry.model || model,
6515
+ confidence: "exact",
6516
+ metrics: entry.metrics,
6517
+ refs: stringRefs({
6518
+ sourceId: `${sessionId}:usage:${turn.promptId}:${entry.model || "model"}`,
6519
+ promptId: turn.promptId
6520
+ })
6521
+ }), nextLine, "updates", "model.usage");
6522
+ nextLine += 1;
6523
+ }
6524
+ }
6525
+ const hunkPath = path13.join(sessionDir, "hunk_records.jsonl");
6526
+ const hunkActivities = await loadHunkFileActivities(hunkPath, cwd);
6527
+ if (hunkActivities.length > 0) {
6528
+ for (const event of events) {
6529
+ for (const file of event.fileActivities || []) {
6530
+ delete file.linesAdded;
6531
+ delete file.linesRemoved;
6532
+ }
6533
+ }
6534
+ }
6535
+ let hasFileLineData = hunkActivities.length > 0;
6536
+ for (const activity of hunkActivities) {
6537
+ push(base({
6538
+ ts: activity.ts,
6539
+ type: "file.changed",
6540
+ confidence: "exact",
6541
+ fileActivities: [activity],
6542
+ metrics: {
6543
+ linesAdded: activity.linesAdded,
6544
+ linesRemoved: activity.linesRemoved,
6545
+ filesChanged: 1
6546
+ },
6547
+ refs: stringRefs({
6548
+ sourceId: `${sessionId}:hunk:${activity.fileActivityId || activity.path}`
6549
+ })
6550
+ }), nextLine, "hunk_records", "file.changed");
6551
+ nextLine += 1;
6552
+ }
6553
+ if (!hasFileLineData) {
6554
+ hasFileLineData = events.some(
6555
+ (event) => (event.fileActivities || []).some(
6556
+ (file) => (file.linesAdded || 0) > 0 || (file.linesRemoved || 0) > 0
6557
+ )
6558
+ );
6559
+ }
6491
6560
  const signals = await readJsonIfExists(signalsPath);
6492
6561
  if (isPlainObject(signals) && updatedAt) {
6493
- const metrics = metricsFromSignals(signals);
6562
+ const metrics = metricsFromSignals(signals, {
6563
+ includeLines: !hasFileLineData,
6564
+ includeModelCalls: !hasTurnUsage
6565
+ });
6494
6566
  if (metrics) {
6495
6567
  push(base({
6496
6568
  ts: updatedAt,
6497
- type: "model.usage",
6569
+ type: hasTurnUsage ? "agent.operation" : "model.usage",
6498
6570
  model: stringField(signals, "primaryModelId") || model,
6499
6571
  confidence: "partial",
6500
6572
  metrics,
6501
- refs: stringRefs({ sourceId: `${sessionId}:usage:signals` })
6502
- }), lineNumber + 1, "signals", "model.usage");
6573
+ refs: stringRefs({ sourceId: `${sessionId}:signals` })
6574
+ }), nextLine, "signals", hasTurnUsage ? "agent.operation" : "model.usage");
6575
+ nextLine += 1;
6503
6576
  }
6504
6577
  }
6505
6578
  if (updatedAt) {
@@ -6508,7 +6581,7 @@ async function parseGrokSessionFile(filePath, options) {
6508
6581
  type: "session.ended",
6509
6582
  confidence: "derived",
6510
6583
  refs: stringRefs({ sourceId: `${sessionId}:ended` })
6511
- }), lineNumber + 2, "summary", "session.ended");
6584
+ }), nextLine, "summary", "session.ended");
6512
6585
  }
6513
6586
  return events;
6514
6587
  }
@@ -6566,16 +6639,36 @@ function fileActivitiesFromTool(toolName, input, ts, cwd) {
6566
6639
  confidence: "exact"
6567
6640
  }];
6568
6641
  }
6569
- if (lower === "search_replace" || lower === "write" || lower.endsWith("__search_replace") || lower.endsWith("__write")) {
6642
+ if (lower === "search_replace" || lower.endsWith("__search_replace")) {
6570
6643
  const target = stringField(input, "file_path") || stringField(input, "target_file") || stringField(input, "path");
6571
6644
  if (!target) {
6572
6645
  return [];
6573
6646
  }
6647
+ const oldString = stringField(input, "old_string");
6648
+ const newString = stringField(input, "new_string");
6574
6649
  return [{
6575
6650
  ts,
6576
6651
  path: displayPath(target, cwd),
6577
6652
  operation: "edit",
6578
- confidence: "exact"
6653
+ confidence: "exact",
6654
+ linesAdded: countTextLines(newString),
6655
+ linesRemoved: countTextLines(oldString),
6656
+ charsWritten: newString?.length
6657
+ }];
6658
+ }
6659
+ if (lower === "write" || lower.endsWith("__write")) {
6660
+ const target = stringField(input, "file_path") || stringField(input, "target_file") || stringField(input, "path");
6661
+ if (!target) {
6662
+ return [];
6663
+ }
6664
+ const content = stringField(input, "content");
6665
+ return [{
6666
+ ts,
6667
+ path: displayPath(target, cwd),
6668
+ operation: "write",
6669
+ confidence: "exact",
6670
+ linesAdded: countTextLines(content),
6671
+ charsWritten: content?.length
6579
6672
  }];
6580
6673
  }
6581
6674
  return [];
@@ -6625,20 +6718,23 @@ async function loadToolInputsFromUpdates(updatesPath) {
6625
6718
  }
6626
6719
  return map;
6627
6720
  }
6628
- function metricsFromSignals(signals) {
6721
+ function metricsFromSignals(signals, options = {}) {
6722
+ const includeLines = options.includeLines !== false;
6723
+ const includeModelCalls = options.includeModelCalls !== false;
6629
6724
  const toolCallCount = numberField(signals, "toolCallCount");
6630
6725
  const turnCount = numberField(signals, "turnCount");
6631
6726
  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) {
6727
+ const linesAdded = includeLines ? numberField(signals, "agentLinesAdded") : void 0;
6728
+ const linesRemoved = includeLines ? numberField(signals, "agentLinesRemoved") : void 0;
6729
+ const filesTouched = includeLines ? numberField(signals, "agentFilesTouched") : void 0;
6730
+ if (toolCallCount === void 0 && (!includeModelCalls || turnCount === void 0) && durationSec === void 0 && linesAdded === void 0 && linesRemoved === void 0) {
6635
6731
  return void 0;
6636
6732
  }
6637
6733
  const metrics = {};
6638
6734
  if (toolCallCount !== void 0) {
6639
6735
  metrics.toolCalls = toolCallCount;
6640
6736
  }
6641
- if (turnCount !== void 0) {
6737
+ if (includeModelCalls && turnCount !== void 0) {
6642
6738
  metrics.modelCalls = turnCount;
6643
6739
  }
6644
6740
  if (durationSec !== void 0) {
@@ -6650,8 +6746,146 @@ function metricsFromSignals(signals) {
6650
6746
  if (linesRemoved !== void 0) {
6651
6747
  metrics.linesRemoved = linesRemoved;
6652
6748
  }
6749
+ if (filesTouched !== void 0) {
6750
+ metrics.filesChanged = filesTouched;
6751
+ }
6752
+ return metrics;
6753
+ }
6754
+ async function loadTurnUsagesFromUpdates(updatesPath) {
6755
+ const turns = [];
6756
+ let text = "";
6757
+ try {
6758
+ text = await readFile8(updatesPath, "utf8");
6759
+ } catch {
6760
+ return turns;
6761
+ }
6762
+ for (const line of text.split("\n")) {
6763
+ if (!line.trim()) {
6764
+ continue;
6765
+ }
6766
+ const raw = parseJsonLine(line);
6767
+ if (!raw) {
6768
+ continue;
6769
+ }
6770
+ const params = objectField(raw, "params");
6771
+ const update = objectField(params, "update");
6772
+ if (stringField(update, "sessionUpdate") !== "turn_completed") {
6773
+ continue;
6774
+ }
6775
+ const usage = objectField(update, "usage");
6776
+ if (Object.keys(usage).length === 0) {
6777
+ continue;
6778
+ }
6779
+ const meta = objectField(params, "_meta");
6780
+ const ts = timestampFrom(meta.agentTimestampMs) || timestampFrom(raw.timestamp) || timestampFrom(raw.ts);
6781
+ if (!ts) {
6782
+ continue;
6783
+ }
6784
+ const promptId = stringField(update, "prompt_id") || stringField(meta, "eventId") || `turn_${turns.length + 1}`;
6785
+ const entries = [];
6786
+ const modelUsage = objectField(usage, "modelUsage");
6787
+ const modelKeys = Object.keys(modelUsage);
6788
+ if (modelKeys.length > 0) {
6789
+ for (const modelName of modelKeys) {
6790
+ const perModel = objectField(modelUsage, modelName);
6791
+ const metrics = metricsFromGrokUsage(
6792
+ Object.keys(perModel).length > 0 ? perModel : usage
6793
+ );
6794
+ if (!hasTokenMetrics(metrics)) {
6795
+ continue;
6796
+ }
6797
+ entries.push({ model: modelName, metrics });
6798
+ }
6799
+ } else {
6800
+ const metrics = metricsFromGrokUsage(usage);
6801
+ if (hasTokenMetrics(metrics)) {
6802
+ entries.push({ metrics });
6803
+ }
6804
+ }
6805
+ if (entries.length > 0) {
6806
+ turns.push({ ts, promptId, entries });
6807
+ }
6808
+ }
6809
+ return turns;
6810
+ }
6811
+ function metricsFromGrokUsage(usage) {
6812
+ const inputTokens = numberField(usage, "inputTokens") || 0;
6813
+ const outputTokens = numberField(usage, "outputTokens") || 0;
6814
+ const cachedRead = numberField(usage, "cachedReadTokens") || 0;
6815
+ const cacheCreation = numberField(usage, "cacheCreationTokens") || 0;
6816
+ const reasoning = numberField(usage, "reasoningTokens") || 0;
6817
+ const totalTokens = numberField(usage, "totalTokens") || inputTokens + outputTokens;
6818
+ const modelCalls = numberField(usage, "modelCalls");
6819
+ const apiDurationMs = numberField(usage, "apiDurationMs");
6820
+ const costTicks = numberField(usage, "costUsdTicks");
6821
+ const metrics = {
6822
+ tokensInput: inputTokens || void 0,
6823
+ tokensOutput: outputTokens || void 0,
6824
+ tokensCachedInput: cachedRead + cacheCreation || void 0,
6825
+ tokensCacheReadInput: cachedRead || void 0,
6826
+ tokensCacheCreationInput: cacheCreation || void 0,
6827
+ tokensReasoningOutput: reasoning || void 0,
6828
+ tokensTotal: totalTokens || void 0,
6829
+ modelCalls: modelCalls || void 0,
6830
+ modelDurationMs: apiDurationMs || void 0,
6831
+ durationMs: apiDurationMs || void 0
6832
+ };
6833
+ if (costTicks !== void 0 && costTicks > 0) {
6834
+ metrics.costUsd = costTicks / GROK_COST_TICKS_PER_USD;
6835
+ }
6653
6836
  return metrics;
6654
6837
  }
6838
+ function hasTokenMetrics(metrics) {
6839
+ return Boolean(
6840
+ metrics.tokensInput || metrics.tokensOutput || metrics.tokensCachedInput || metrics.tokensTotal
6841
+ );
6842
+ }
6843
+ async function loadHunkFileActivities(hunkPath, cwd) {
6844
+ const activities = [];
6845
+ let text = "";
6846
+ try {
6847
+ text = await readFile8(hunkPath, "utf8");
6848
+ } catch {
6849
+ return activities;
6850
+ }
6851
+ for (const line of text.split("\n")) {
6852
+ if (!line.trim()) {
6853
+ continue;
6854
+ }
6855
+ const raw = parseJsonLine(line);
6856
+ if (!raw) {
6857
+ continue;
6858
+ }
6859
+ const authorType = stringField(raw, "authorType");
6860
+ if (authorType && authorType !== "agent") {
6861
+ continue;
6862
+ }
6863
+ const filePath = stringField(raw, "filePath") || stringField(raw, "path");
6864
+ if (!filePath) {
6865
+ continue;
6866
+ }
6867
+ const ts = timestampFrom(raw.timestamp) || timestampFrom(raw.ts);
6868
+ if (!ts) {
6869
+ continue;
6870
+ }
6871
+ const linesAdded = numberField(raw, "linesAdded");
6872
+ const linesRemoved = numberField(raw, "linesRemoved");
6873
+ if ((linesAdded || 0) <= 0 && (linesRemoved || 0) <= 0) {
6874
+ continue;
6875
+ }
6876
+ const hunkId = stringField(raw, "hunkId");
6877
+ activities.push({
6878
+ ts,
6879
+ path: displayPath(filePath, cwd),
6880
+ operation: "edit",
6881
+ confidence: "exact",
6882
+ fileActivityId: hunkId,
6883
+ linesAdded: linesAdded || void 0,
6884
+ linesRemoved: linesRemoved || void 0
6885
+ });
6886
+ }
6887
+ return activities;
6888
+ }
6655
6889
  var grokHandler = (msg) => hookHandler(SOURCE_ID, msg);
6656
6890
  function hookConfig5() {
6657
6891
  const anyTool = [{ matcher: ".*", hooks: [grokHandler("Reporting tool activity")] }];
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@yhong91/vibetime",
3
3
  "type": "module",
4
- "version": "0.1.46",
4
+ "version": "0.1.48",
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": {