@yhong91/vibetime 0.1.44 → 0.1.46

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 +344 -230
  2. package/package.json +1 -1
package/bin/vibetime.mjs CHANGED
@@ -1719,6 +1719,69 @@ import { promisify } from "node:util";
1719
1719
  // src/lib/activity.ts
1720
1720
  import path2 from "node:path";
1721
1721
 
1722
+ // src/lib/fields.ts
1723
+ function stringField(object, key) {
1724
+ if (!isPlainObject(object)) {
1725
+ return void 0;
1726
+ }
1727
+ const value = object[key];
1728
+ return typeof value === "string" ? value : void 0;
1729
+ }
1730
+ function numberField(object, key) {
1731
+ const value = object[key];
1732
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
1733
+ }
1734
+ function objectField(object, key) {
1735
+ if (!isPlainObject(object) || !isPlainObject(object[key])) {
1736
+ return {};
1737
+ }
1738
+ return object[key];
1739
+ }
1740
+ function arrayField(object, key) {
1741
+ if (!isPlainObject(object) || !Array.isArray(object[key])) {
1742
+ return [];
1743
+ }
1744
+ return object[key];
1745
+ }
1746
+ function isPlainObject(value) {
1747
+ return value !== null && typeof value === "object" && !Array.isArray(value);
1748
+ }
1749
+ function stringRefs(value) {
1750
+ const refs2 = {};
1751
+ for (const [key, item] of Object.entries(value || {})) {
1752
+ if (typeof item === "string" && item.length > 0) {
1753
+ refs2[key] = item;
1754
+ }
1755
+ }
1756
+ return refs2;
1757
+ }
1758
+ function stringOption(value) {
1759
+ if (Array.isArray(value)) {
1760
+ return value.at(-1);
1761
+ }
1762
+ return typeof value === "string" ? value : void 0;
1763
+ }
1764
+ function valuesOption(value) {
1765
+ if (Array.isArray(value)) {
1766
+ return value;
1767
+ }
1768
+ return typeof value === "string" ? [value] : [];
1769
+ }
1770
+ function numberOption(value) {
1771
+ if (typeof value === "number" && Number.isFinite(value)) {
1772
+ return value;
1773
+ }
1774
+ if (Array.isArray(value)) {
1775
+ return numberOption(value.at(-1));
1776
+ }
1777
+ const text = stringOption(value);
1778
+ if (!text) {
1779
+ return void 0;
1780
+ }
1781
+ const parsed = Number(text);
1782
+ return Number.isFinite(parsed) ? parsed : void 0;
1783
+ }
1784
+
1722
1785
  // src/lib/shell.ts
1723
1786
  import path from "node:path";
1724
1787
  function fileActivitiesFromShellCommand(command, ts, rootCwd, initialCwd) {
@@ -1926,78 +1989,71 @@ function countTextLines(text) {
1926
1989
  }
1927
1990
  return text.split(/\r\n|\r|\n/).length;
1928
1991
  }
1929
-
1930
- // src/lib/constants.ts
1931
- var PACKAGE_VERSION = true ? "0.1.44" : "0.1.1";
1932
- var DEFAULT_API_URL = "http://121.196.224.82:3001";
1933
- var DEFAULT_BACKFILL_BATCH_SIZE = 50;
1934
- var DEFAULT_BACKFILL_BATCH_BYTES = 800 * 1024;
1935
- var ROLLUP_BUCKET_MS = 15 * 60 * 1e3;
1936
- var DEFAULT_HOOK_SYNC_MIN_INTERVAL_SECONDS = 60;
1937
-
1938
- // src/lib/fields.ts
1939
- function stringField(object, key) {
1940
- if (!isPlainObject(object)) {
1941
- return void 0;
1942
- }
1943
- const value = object[key];
1944
- return typeof value === "string" ? value : void 0;
1945
- }
1946
- function numberField(object, key) {
1947
- const value = object[key];
1948
- return typeof value === "number" && Number.isFinite(value) ? value : void 0;
1949
- }
1950
- function objectField(object, key) {
1951
- if (!isPlainObject(object) || !isPlainObject(object[key])) {
1952
- return {};
1953
- }
1954
- return object[key];
1955
- }
1956
- function arrayField(object, key) {
1957
- if (!isPlainObject(object) || !Array.isArray(object[key])) {
1958
- return [];
1959
- }
1960
- return object[key];
1961
- }
1962
- function isPlainObject(value) {
1963
- return value !== null && typeof value === "object" && !Array.isArray(value);
1964
- }
1965
- function stringRefs(value) {
1966
- const refs2 = {};
1967
- for (const [key, item] of Object.entries(value || {})) {
1968
- if (typeof item === "string" && item.length > 0) {
1969
- refs2[key] = item;
1992
+ function claudeStyleToolFileActivities(tool, input, ts, cwd) {
1993
+ const changes = /* @__PURE__ */ new Map();
1994
+ const operation = operationForTool(tool);
1995
+ const workdir = stringField(input, "cwd") || cwd;
1996
+ for (const key of ["file_path", "path", "notebook_path"]) {
1997
+ const filePath = stringField(input, key);
1998
+ if (!filePath) {
1999
+ continue;
1970
2000
  }
2001
+ const metrics = claudeStyleFileMetrics(tool, input);
2002
+ addResolvedPathActivity(changes, filePath, operation, ts, cwd, workdir, metrics);
1971
2003
  }
1972
- return refs2;
1973
- }
1974
- function stringOption(value) {
1975
- if (Array.isArray(value)) {
1976
- return value.at(-1);
2004
+ for (const edit of [...arrayField(input, "edits"), ...arrayField(input, "replacements")]) {
2005
+ if (!isPlainObject(edit)) {
2006
+ continue;
2007
+ }
2008
+ const filePath = stringField(input, "file_path") || stringField(input, "path");
2009
+ if (!filePath) {
2010
+ continue;
2011
+ }
2012
+ addResolvedPathActivity(changes, filePath, "edit", ts, cwd, workdir, {
2013
+ linesAdded: countTextLines(stringField(edit, "new_string") || stringField(edit, "new_text")),
2014
+ linesRemoved: countTextLines(stringField(edit, "old_string") || stringField(edit, "original_text"))
2015
+ });
1977
2016
  }
1978
- return typeof value === "string" ? value : void 0;
1979
- }
1980
- function valuesOption(value) {
1981
- if (Array.isArray(value)) {
1982
- return value;
2017
+ const command = stringField(input, "command");
2018
+ if (command) {
2019
+ for (const item of fileActivitiesFromShellCommand(command, ts, cwd, workdir)) {
2020
+ changes.set(item.path, mergeFileActivity(changes.get(item.path), item));
2021
+ }
1983
2022
  }
1984
- return typeof value === "string" ? [value] : [];
2023
+ return [...changes.values()];
1985
2024
  }
1986
- function numberOption(value) {
1987
- if (typeof value === "number" && Number.isFinite(value)) {
1988
- return value;
2025
+ function claudeStyleFileMetrics(tool, input) {
2026
+ const normalized = tool.toLowerCase();
2027
+ if (normalized === "read") {
2028
+ return { linesRead: numberField(input, "limit") };
1989
2029
  }
1990
- if (Array.isArray(value)) {
1991
- return numberOption(value.at(-1));
2030
+ if (normalized === "write") {
2031
+ const content = stringField(input, "content") || stringField(input, "file_content");
2032
+ return {
2033
+ linesAdded: countTextLines(content),
2034
+ charsWritten: content?.length
2035
+ };
1992
2036
  }
1993
- const text = stringOption(value);
1994
- if (!text) {
1995
- return void 0;
2037
+ if (normalized === "edit" || normalized === "multiedit" || normalized === "searchreplace") {
2038
+ const oldString = stringField(input, "old_string");
2039
+ const newString = stringField(input, "new_string");
2040
+ return {
2041
+ linesAdded: countTextLines(newString),
2042
+ linesRemoved: countTextLines(oldString),
2043
+ charsWritten: newString?.length
2044
+ };
1996
2045
  }
1997
- const parsed = Number(text);
1998
- return Number.isFinite(parsed) ? parsed : void 0;
2046
+ return {};
1999
2047
  }
2000
2048
 
2049
+ // src/lib/constants.ts
2050
+ var PACKAGE_VERSION = true ? "0.1.46" : "0.1.1";
2051
+ var DEFAULT_API_URL = "http://121.196.224.82:3001";
2052
+ var DEFAULT_BACKFILL_BATCH_SIZE = 50;
2053
+ var DEFAULT_BACKFILL_BATCH_BYTES = 800 * 1024;
2054
+ var ROLLUP_BUCKET_MS = 15 * 60 * 1e3;
2055
+ var DEFAULT_HOOK_SYNC_MIN_INTERVAL_SECONDS = 60;
2056
+
2001
2057
  // src/lib/jsonl.ts
2002
2058
  function parseJsonLine(line) {
2003
2059
  try {
@@ -3868,14 +3924,14 @@ function claudeProjectRootFromCwds(projectDir, cwds) {
3868
3924
  return void 0;
3869
3925
  }
3870
3926
  function encodeClaudeProjectPath(value) {
3871
- return path7.resolve(value).split(path7.sep).join("-").replace(/_/g, "-");
3927
+ return path7.resolve(value).split(path7.sep).join("-").replaceAll("_", "-");
3872
3928
  }
3873
3929
  function rawClaudeProjectPath(value) {
3874
3930
  return path7.resolve(value).split(path7.sep).join("-");
3875
3931
  }
3876
3932
  function claudeEncodedVariants(value) {
3877
3933
  const raw = rawClaudeProjectPath(value);
3878
- const normalized = raw.replace(/_/g, "-");
3934
+ const normalized = raw.replaceAll("_", "-");
3879
3935
  return raw === normalized ? [raw] : [raw, normalized];
3880
3936
  }
3881
3937
  function claudeEncodedProjectSuffix(projectDir, home) {
@@ -4129,7 +4185,7 @@ async function discoverCoworkTranscripts(root) {
4129
4185
  function coworkMetaPathForTranscript(transcriptPath) {
4130
4186
  const marker = `${path8.sep}.claude${path8.sep}projects${path8.sep}`;
4131
4187
  const index = transcriptPath.indexOf(marker);
4132
- if (index < 0) {
4188
+ if (index === -1) {
4133
4189
  return void 0;
4134
4190
  }
4135
4191
  return `${transcriptPath.slice(0, index)}.json`;
@@ -4144,7 +4200,7 @@ function projectFromMeta(meta) {
4144
4200
  }
4145
4201
  const resolved = path8.resolve(folder);
4146
4202
  return {
4147
- project: path8.basename(resolved).replace(/-/g, "_") || "cowork",
4203
+ project: path8.basename(resolved).replaceAll("-", "_") || "cowork",
4148
4204
  cwd: resolved
4149
4205
  };
4150
4206
  }
@@ -4269,7 +4325,7 @@ function createClaudeCoworkAdapter() {
4269
4325
  }
4270
4326
 
4271
4327
  // src/adapters/codebuddy.ts
4272
- import { readFile as readFile5, readdir as readdir4 } from "node:fs/promises";
4328
+ import { readdir as readdir4, readFile as readFile5 } from "node:fs/promises";
4273
4329
  import path9 from "node:path";
4274
4330
  var ENDPOINT_MODEL_ID_RE = /^(?:ep|endpoint)-/i;
4275
4331
  async function parseCodebuddyTraceFile(filePath, options) {
@@ -4504,6 +4560,28 @@ async function parseCodebuddyTraceFile(filePath, options) {
4504
4560
  sourceId: span.spanId
4505
4561
  })
4506
4562
  }), index, "function");
4563
+ const toolInput = parseEmbeddedJson(span.toolInput);
4564
+ if (isPlainObject(toolInput)) {
4565
+ const fileActivities = claudeStyleToolFileActivities(tool, toolInput, ts, cwd);
4566
+ if (fileActivities.length > 0) {
4567
+ push(baseEvent({
4568
+ ts,
4569
+ type: eventTypeFromFileActivities(fileActivities),
4570
+ operation: `${tool} file activity`,
4571
+ sessionId,
4572
+ cwd,
4573
+ project,
4574
+ model,
4575
+ tool,
4576
+ confidence: "derived",
4577
+ fileActivities,
4578
+ metrics: summarizeFileActivities(fileActivities),
4579
+ refs: stringRefs({
4580
+ sourceId: span.spanId
4581
+ })
4582
+ }), index, "function");
4583
+ }
4584
+ }
4507
4585
  if (span.endedAt) {
4508
4586
  const durationMs = span.duration ?? void 0;
4509
4587
  push(baseEvent({
@@ -4527,30 +4605,28 @@ async function parseCodebuddyTraceFile(filePath, options) {
4527
4605
  })
4528
4606
  }), index, "function");
4529
4607
  }
4530
- if (tool === "Bash") {
4531
- if (span.endedAt) {
4532
- const durationMs = span.duration ?? void 0;
4533
- push(baseEvent({
4534
- ts: span.endedAt,
4535
- type: toolSuccess ? "command.completed" : "command.failed",
4536
- operation: "Bash completed",
4537
- sessionId,
4538
- cwd,
4539
- project,
4540
- model,
4541
- tool,
4542
- success: toolSuccess,
4543
- confidence: "derived",
4544
- metrics: {
4545
- commandCalls: 1,
4546
- commandDurationMs: durationMs,
4547
- durationMs
4548
- },
4549
- refs: stringRefs({
4550
- sourceId: span.spanId
4551
- })
4552
- }), index, "function");
4553
- }
4608
+ if (tool === "Bash" && span.endedAt) {
4609
+ const durationMs = span.duration ?? void 0;
4610
+ push(baseEvent({
4611
+ ts: span.endedAt,
4612
+ type: toolSuccess ? "command.completed" : "command.failed",
4613
+ operation: "Bash completed",
4614
+ sessionId,
4615
+ cwd,
4616
+ project,
4617
+ model,
4618
+ tool,
4619
+ success: toolSuccess,
4620
+ confidence: "derived",
4621
+ metrics: {
4622
+ commandCalls: 1,
4623
+ commandDurationMs: durationMs,
4624
+ durationMs
4625
+ },
4626
+ refs: stringRefs({
4627
+ sourceId: span.spanId
4628
+ })
4629
+ }), index, "function");
4554
4630
  }
4555
4631
  continue;
4556
4632
  }
@@ -4755,7 +4831,6 @@ async function resolveSessionIdFromSiblingTraces(filePath) {
4755
4831
  }
4756
4832
  } catch {
4757
4833
  }
4758
- return void 0;
4759
4834
  })();
4760
4835
  siblingSessionIdByDir.set(dir, cached);
4761
4836
  }
@@ -4949,6 +5024,33 @@ import { readFile as readFile6 } from "node:fs/promises";
4949
5024
  import path11 from "node:path";
4950
5025
 
4951
5026
  // src/lib/diff.ts
5027
+ function parseApplyPatch(patch, ts) {
5028
+ const changes = [];
5029
+ let current;
5030
+ for (const line of patch.split("\n")) {
5031
+ const fileMatch = line.match(/^\*\*\* (Add|Update|Delete) File: (.+)$/);
5032
+ if (fileMatch) {
5033
+ current = {
5034
+ ts,
5035
+ path: fileMatch[2].trim(),
5036
+ operation: fileMatch[1] === "Add" ? "create" : fileMatch[1] === "Delete" ? "delete" : "edit",
5037
+ linesAdded: 0,
5038
+ linesRemoved: 0
5039
+ };
5040
+ changes.push(current);
5041
+ continue;
5042
+ }
5043
+ if (!current) {
5044
+ continue;
5045
+ }
5046
+ if (line.startsWith("+") && !line.startsWith("+++")) {
5047
+ current.linesAdded = (current.linesAdded || 0) + 1;
5048
+ } else if (line.startsWith("-") && !line.startsWith("---")) {
5049
+ current.linesRemoved = (current.linesRemoved || 0) + 1;
5050
+ }
5051
+ }
5052
+ return changes;
5053
+ }
4952
5054
  function diffStats(diff) {
4953
5055
  let linesAdded = 0;
4954
5056
  let linesRemoved = 0;
@@ -4992,10 +5094,10 @@ function fileActivitiesFromPatchChanges(changes, ts, cwd, displayFilePath3) {
4992
5094
  }
4993
5095
 
4994
5096
  // src/lib/session-context.ts
4995
- init_fs();
4996
5097
  import { mkdir as mkdir3, writeFile as writeFile3 } from "node:fs/promises";
4997
5098
  import os4 from "node:os";
4998
5099
  import path10 from "node:path";
5100
+ init_fs();
4999
5101
  var SESSION_CONTEXT_VERSION = 1;
5000
5102
  function sessionContextDir(home) {
5001
5103
  return path10.join(home, ".vibetime", "session-context");
@@ -5814,7 +5916,7 @@ async function parseCopilotSessionFile(filePath, options) {
5814
5916
  model = msgModel;
5815
5917
  }
5816
5918
  const msgTurnId = stringField(data, "turnId");
5817
- const turnId = msgTurnId !== void 0 ? `turn_${createStableHash([sessionId, msgTurnId]).slice(0, 24)}` : currentTurnId;
5919
+ const turnId = msgTurnId === void 0 ? currentTurnId : `turn_${createStableHash([sessionId, msgTurnId]).slice(0, 24)}`;
5818
5920
  events.push(baseCopilotEvent({
5819
5921
  ts,
5820
5922
  type: "agent.operation",
@@ -5841,7 +5943,7 @@ async function parseCopilotSessionFile(filePath, options) {
5841
5943
  const toolCallId = stringField(data, "toolCallId");
5842
5944
  const toolName = stringField(data, "toolName") || "tool";
5843
5945
  const toolTurnId = stringField(data, "turnId");
5844
- const turnId = toolTurnId !== void 0 ? `turn_${createStableHash([sessionId, toolTurnId]).slice(0, 24)}` : currentTurnId;
5946
+ const turnId = toolTurnId === void 0 ? currentTurnId : `turn_${createStableHash([sessionId, toolTurnId]).slice(0, 24)}`;
5845
5947
  if (toolCallId) {
5846
5948
  pendingTools.set(toolCallId, { tool: toolName, startedAt: ts, turnId });
5847
5949
  }
@@ -5859,6 +5961,24 @@ async function parseCopilotSessionFile(filePath, options) {
5859
5961
  metrics: { toolCalls: 1 },
5860
5962
  refs: stringRefs({ sourceId: toolCallId })
5861
5963
  }));
5964
+ const fileActivities = copilotFileActivities(toolName, data.arguments, ts, cwd);
5965
+ if (fileActivities.length > 0) {
5966
+ events.push(baseCopilotEvent({
5967
+ ts,
5968
+ type: eventTypeFromFileActivities(fileActivities),
5969
+ operation: `${toolName} file activity`,
5970
+ sessionId,
5971
+ turnId,
5972
+ cwd,
5973
+ project,
5974
+ model,
5975
+ tool: toolName,
5976
+ confidence: "derived",
5977
+ fileActivities,
5978
+ metrics: summarizeFileActivities(fileActivities),
5979
+ refs: stringRefs({ sourceId: toolCallId })
5980
+ }));
5981
+ }
5862
5982
  break;
5863
5983
  }
5864
5984
  case "tool.execution_complete": {
@@ -5881,7 +6001,7 @@ async function parseCopilotSessionFile(filePath, options) {
5881
6001
  tool: pending?.tool,
5882
6002
  success,
5883
6003
  confidence: "exact",
5884
- metrics: durationMs !== void 0 ? { toolDurationMs: durationMs, durationMs } : void 0,
6004
+ metrics: durationMs === void 0 ? void 0 : { toolDurationMs: durationMs, durationMs },
5885
6005
  refs: stringRefs({ sourceId: toolCallId })
5886
6006
  }));
5887
6007
  const toolLower = (pending?.tool || "").toLowerCase();
@@ -5910,7 +6030,7 @@ async function parseCopilotSessionFile(filePath, options) {
5910
6030
  }
5911
6031
  case "assistant.turn_end": {
5912
6032
  const endTurnId = stringField(data, "turnId");
5913
- const turnId = endTurnId !== void 0 ? `turn_${createStableHash([sessionId, endTurnId]).slice(0, 24)}` : currentTurnId;
6033
+ const turnId = endTurnId === void 0 ? currentTurnId : `turn_${createStableHash([sessionId, endTurnId]).slice(0, 24)}`;
5914
6034
  const accum = turnId ? turnTokenAccum.get(turnId) : void 0;
5915
6035
  if (accum && accum.tokensOutput && accum.tokensOutput > 0) {
5916
6036
  accum.reasoningEffort = reasoningEffort;
@@ -5997,6 +6117,29 @@ async function parseCopilotSessionFile(filePath, options) {
5997
6117
  }
5998
6118
  return events.filter((event) => validateCanonicalEvent(event).valid);
5999
6119
  }
6120
+ function copilotFileActivities(tool, args, ts, cwd) {
6121
+ const normalized = tool.toLowerCase();
6122
+ if (normalized === "apply_patch" && typeof args === "string") {
6123
+ return parseApplyPatch(args, ts);
6124
+ }
6125
+ if (!isPlainObject(args)) {
6126
+ return [];
6127
+ }
6128
+ const changes = /* @__PURE__ */ new Map();
6129
+ const filePath = stringField(args, "path") || stringField(args, "file_path");
6130
+ if (filePath) {
6131
+ const metrics = {};
6132
+ if (normalized === "edit") {
6133
+ const oldStr = stringField(args, "old_str");
6134
+ const newStr = stringField(args, "new_str");
6135
+ metrics.linesAdded = countTextLines(newStr);
6136
+ metrics.linesRemoved = countTextLines(oldStr);
6137
+ metrics.charsWritten = newStr?.length;
6138
+ }
6139
+ addResolvedPathActivity(changes, filePath, operationForTool(normalized === "view" ? "read" : tool), ts, cwd, cwd, metrics);
6140
+ }
6141
+ return [...changes.values()];
6142
+ }
6000
6143
  function baseCopilotEvent(event) {
6001
6144
  return {
6002
6145
  schemaVersion: AGENT_TIME_SCHEMA_VERSION,
@@ -6205,7 +6348,7 @@ async function parseGrokSessionFile(filePath, options) {
6205
6348
  }
6206
6349
  if (type === "turn_started") {
6207
6350
  const turnNumber = numberField(raw, "turn_number");
6208
- currentTurnId = turnNumber !== void 0 ? `turn_${turnNumber}` : `turn_${createStableHash([sessionId, lineNumber]).slice(0, 24)}`;
6351
+ currentTurnId = turnNumber === void 0 ? `turn_${createStableHash([sessionId, lineNumber]).slice(0, 24)}` : `turn_${turnNumber}`;
6209
6352
  currentTurnStartedAt = ts;
6210
6353
  const turnModel = stringField(raw, "model_id") || model;
6211
6354
  push(base({
@@ -6229,7 +6372,7 @@ async function parseGrokSessionFile(filePath, options) {
6229
6372
  turnId,
6230
6373
  success,
6231
6374
  confidence: "exact",
6232
- metrics: durationMs !== void 0 ? { durationMs } : void 0,
6375
+ metrics: durationMs === void 0 ? void 0 : { durationMs },
6233
6376
  refs: stringRefs({
6234
6377
  sourceId: `${sessionId}:${turnId || "turn"}:ended`,
6235
6378
  outcome
@@ -6374,7 +6517,7 @@ function resolveSessionDir(filePath) {
6374
6517
  if (base === "summary.json" || base === "events.jsonl" || base === "signals.json") {
6375
6518
  return path13.dirname(filePath);
6376
6519
  }
6377
- if (base.match(/^[0-9a-f]{8}-[0-9a-f-]{27,}$/i)) {
6520
+ if (/^[0-9a-f]{8}-[0-9a-f-]{27,}$/i.test(base)) {
6378
6521
  return filePath;
6379
6522
  }
6380
6523
  return void 0;
@@ -6806,6 +6949,7 @@ async function parseOpenCodeSessionFile(dbPath, options) {
6806
6949
  }
6807
6950
  const filePath = stringField(stateInput, "file_path") || stringField(stateInput, "filePath");
6808
6951
  if (filePath) {
6952
+ const lineStats = opencodeLineStats(tool, state, stateInput);
6809
6953
  events.push(baseOpenCodeEvent({
6810
6954
  ts: msToIso(endMs || completedTs || createdTs),
6811
6955
  type: toolFileActivityType(tool),
@@ -6820,8 +6964,10 @@ async function parseOpenCodeSessionFile(dbPath, options) {
6820
6964
  fileActivities: [{
6821
6965
  ts: msToIso(endMs || completedTs || createdTs),
6822
6966
  path: filePath,
6823
- operation: operationForTool(tool)
6967
+ operation: operationForTool(tool),
6968
+ ...lineStats
6824
6969
  }],
6970
+ metrics: lineStats.linesAdded || lineStats.linesRemoved ? lineStats : void 0,
6825
6971
  refs: stringRefs({ sourceId: callId })
6826
6972
  }, assistantAgent));
6827
6973
  }
@@ -6944,6 +7090,25 @@ function baseOpenCodeEvent(event, agentName) {
6944
7090
  ...event
6945
7091
  };
6946
7092
  }
7093
+ function opencodeLineStats(tool, state, input) {
7094
+ const diff = stringField(objectField(state, "metadata"), "diff");
7095
+ if (diff) {
7096
+ return diffStats(diff);
7097
+ }
7098
+ const normalized = tool.toLowerCase();
7099
+ if (normalized === "write") {
7100
+ return { linesAdded: countTextLines(stringField(input, "content")) };
7101
+ }
7102
+ if (normalized === "edit" || normalized === "multiedit" || normalized === "patch") {
7103
+ const newString = stringField(input, "newString") || stringField(input, "new_string");
7104
+ const oldString = stringField(input, "oldString") || stringField(input, "old_string");
7105
+ return {
7106
+ linesAdded: countTextLines(newString),
7107
+ linesRemoved: countTextLines(oldString)
7108
+ };
7109
+ }
7110
+ return {};
7111
+ }
6947
7112
  function opencodeUsageFromInfo(info) {
6948
7113
  const tokensObj = objectField(info, "tokens");
6949
7114
  if (!tokensObj) {
@@ -7381,6 +7546,18 @@ function piFileActivitiesFromToolCall(tool, input, ts, cwd) {
7381
7546
  addResolvedPathActivity(changes, filePath, operation, ts, cwd, cwd, piToolFileMetrics(normalized, input));
7382
7547
  }
7383
7548
  }
7549
+ if (normalized === "edit") {
7550
+ const filePath = stringField(input, "path") || stringField(input, "file_path") || stringField(input, "filePath");
7551
+ for (const edit of Array.isArray(input.edits) ? input.edits : []) {
7552
+ if (!isPlainObject(edit) || !filePath) {
7553
+ continue;
7554
+ }
7555
+ addResolvedPathActivity(changes, filePath, "edit", ts, cwd, cwd, {
7556
+ linesAdded: countTextLines(stringField(edit, "newText") || stringField(edit, "new_text")),
7557
+ linesRemoved: countTextLines(stringField(edit, "oldText") || stringField(edit, "old_text"))
7558
+ });
7559
+ }
7560
+ }
7384
7561
  if (normalized === "bash" || normalized === "run") {
7385
7562
  const command = stringField(input, "command");
7386
7563
  if (command) {
@@ -8069,7 +8246,7 @@ async function parseQoderCnSessionFile(filePath, options) {
8069
8246
  const toolUseId = stringField(toolUse, "id") || `tool_${createStableHash([filePath, lineNumber, tool]).slice(0, 24)}`;
8070
8247
  const input = objectField(toolUse, "input");
8071
8248
  const command = stringField(input, "command");
8072
- const fileActivities = fileActivitiesFromQoderCnToolUse(tool, input, ts, cwd);
8249
+ const fileActivities = claudeStyleToolFileActivities(tool, input, ts, cwd);
8073
8250
  const agentInstanceId = tool === "Agent" ? `agent_${createStableHash([sessionId, toolUseId]).slice(0, 24)}` : void 0;
8074
8251
  pendingTools.set(toolUseId, {
8075
8252
  id: toolUseId,
@@ -8206,62 +8383,6 @@ function qoderCnSubagentMetrics(toolResult, fallbackDurationMs) {
8206
8383
  tokensTotal: numberField(toolResult, "totalTokens") || totalInputTokens + outputTokens || void 0
8207
8384
  };
8208
8385
  }
8209
- function fileActivitiesFromQoderCnToolUse(tool, input, ts, cwd) {
8210
- const changes = /* @__PURE__ */ new Map();
8211
- const operation = operationForTool(tool);
8212
- const workdir = stringField(input, "cwd") || cwd;
8213
- for (const key of ["file_path", "path", "notebook_path"]) {
8214
- const filePath = stringField(input, key);
8215
- if (!filePath) {
8216
- continue;
8217
- }
8218
- const metrics = fileMetricsFromQoderCnToolInput(tool, input);
8219
- addResolvedPathActivity(changes, filePath, operation, ts, cwd, workdir, metrics);
8220
- }
8221
- for (const edit of [...arrayField4(input, "edits"), ...arrayField4(input, "replacements")]) {
8222
- if (!isPlainObject(edit)) {
8223
- continue;
8224
- }
8225
- const filePath = stringField(input, "file_path") || stringField(input, "path");
8226
- if (!filePath) {
8227
- continue;
8228
- }
8229
- addResolvedPathActivity(changes, filePath, "edit", ts, cwd, workdir, {
8230
- linesAdded: countTextLines(stringField(edit, "new_string") || stringField(edit, "new_text")),
8231
- linesRemoved: countTextLines(stringField(edit, "old_string") || stringField(edit, "original_text"))
8232
- });
8233
- }
8234
- const command = stringField(input, "command");
8235
- if (command) {
8236
- for (const item of fileActivitiesFromShellCommand(command, ts, cwd, workdir)) {
8237
- changes.set(item.path, mergeFileActivity(changes.get(item.path), item));
8238
- }
8239
- }
8240
- return [...changes.values()];
8241
- }
8242
- function fileMetricsFromQoderCnToolInput(tool, input) {
8243
- const normalized = tool.toLowerCase();
8244
- if (normalized === "read") {
8245
- return { linesRead: numberField(input, "limit") };
8246
- }
8247
- if (normalized === "write") {
8248
- const content = stringField(input, "content") || stringField(input, "file_content");
8249
- return {
8250
- linesAdded: countTextLines(content),
8251
- charsWritten: content?.length
8252
- };
8253
- }
8254
- if (normalized === "edit" || normalized === "multiedit" || normalized === "searchreplace") {
8255
- const oldString = stringField(input, "old_string");
8256
- const newString = stringField(input, "new_string");
8257
- return {
8258
- linesAdded: countTextLines(newString),
8259
- linesRemoved: countTextLines(oldString),
8260
- charsWritten: newString?.length
8261
- };
8262
- }
8263
- return {};
8264
- }
8265
8386
  function arrayField4(object, key) {
8266
8387
  if (!isPlainObject(object) || !Array.isArray(object[key])) {
8267
8388
  return [];
@@ -8422,7 +8543,7 @@ function qoderCnProjectRootFromCwds(projectDir, cwds) {
8422
8543
  return void 0;
8423
8544
  }
8424
8545
  function encodeQoderCnProjectPath(value) {
8425
- return path17.resolve(value).split(path17.sep).join("-").replace(/_/g, "-");
8546
+ return path17.resolve(value).split(path17.sep).join("-").replaceAll("_", "-");
8426
8547
  }
8427
8548
  async function qoderCnProjectFromFilePath(filePath, options) {
8428
8549
  const projectDir = path17.basename(path17.dirname(filePath));
@@ -8903,7 +9024,7 @@ async function parseQoderSessionFile(filePath, options) {
8903
9024
  const toolUseId = stringField(toolUse, "id") || `tool_${createStableHash([filePath, lineNumber, tool]).slice(0, 24)}`;
8904
9025
  const input = objectField(toolUse, "input");
8905
9026
  const command = stringField(input, "command");
8906
- const fileActivities = fileActivitiesFromQoderToolUse(tool, input, ts, cwd);
9027
+ const fileActivities = claudeStyleToolFileActivities(tool, input, ts, cwd);
8907
9028
  const agentInstanceId = tool === "Agent" ? `agent_${createStableHash([sessionId, toolUseId]).slice(0, 24)}` : void 0;
8908
9029
  pendingTools.set(toolUseId, {
8909
9030
  id: toolUseId,
@@ -9040,62 +9161,6 @@ function qoderSubagentMetrics(toolResult, fallbackDurationMs) {
9040
9161
  tokensTotal: numberField(toolResult, "totalTokens") || totalInputTokens + outputTokens || void 0
9041
9162
  };
9042
9163
  }
9043
- function fileActivitiesFromQoderToolUse(tool, input, ts, cwd) {
9044
- const changes = /* @__PURE__ */ new Map();
9045
- const operation = operationForTool(tool);
9046
- const workdir = stringField(input, "cwd") || cwd;
9047
- for (const key of ["file_path", "path", "notebook_path"]) {
9048
- const filePath = stringField(input, key);
9049
- if (!filePath) {
9050
- continue;
9051
- }
9052
- const metrics = fileMetricsFromQoderToolInput(tool, input);
9053
- addResolvedPathActivity(changes, filePath, operation, ts, cwd, workdir, metrics);
9054
- }
9055
- for (const edit of [...arrayField5(input, "edits"), ...arrayField5(input, "replacements")]) {
9056
- if (!isPlainObject(edit)) {
9057
- continue;
9058
- }
9059
- const filePath = stringField(input, "file_path") || stringField(input, "path");
9060
- if (!filePath) {
9061
- continue;
9062
- }
9063
- addResolvedPathActivity(changes, filePath, "edit", ts, cwd, workdir, {
9064
- linesAdded: countTextLines(stringField(edit, "new_string") || stringField(edit, "new_text")),
9065
- linesRemoved: countTextLines(stringField(edit, "old_string") || stringField(edit, "original_text"))
9066
- });
9067
- }
9068
- const command = stringField(input, "command");
9069
- if (command) {
9070
- for (const item of fileActivitiesFromShellCommand(command, ts, cwd, workdir)) {
9071
- changes.set(item.path, mergeFileActivity(changes.get(item.path), item));
9072
- }
9073
- }
9074
- return [...changes.values()];
9075
- }
9076
- function fileMetricsFromQoderToolInput(tool, input) {
9077
- const normalized = tool.toLowerCase();
9078
- if (normalized === "read") {
9079
- return { linesRead: numberField(input, "limit") };
9080
- }
9081
- if (normalized === "write") {
9082
- const content = stringField(input, "content") || stringField(input, "file_content");
9083
- return {
9084
- linesAdded: countTextLines(content),
9085
- charsWritten: content?.length
9086
- };
9087
- }
9088
- if (normalized === "edit" || normalized === "multiedit" || normalized === "searchreplace") {
9089
- const oldString = stringField(input, "old_string");
9090
- const newString = stringField(input, "new_string");
9091
- return {
9092
- linesAdded: countTextLines(newString),
9093
- linesRemoved: countTextLines(oldString),
9094
- charsWritten: newString?.length
9095
- };
9096
- }
9097
- return {};
9098
- }
9099
9164
  function arrayField5(object, key) {
9100
9165
  if (!isPlainObject(object) || !Array.isArray(object[key])) {
9101
9166
  return [];
@@ -9222,14 +9287,14 @@ function qoderProjectRootFromCwds(projectDir, cwds) {
9222
9287
  return void 0;
9223
9288
  }
9224
9289
  function encodeQoderProjectPath(value) {
9225
- return path18.resolve(value).split(path18.sep).join("-").replace(/_/g, "-");
9290
+ return path18.resolve(value).split(path18.sep).join("-").replaceAll("_", "-");
9226
9291
  }
9227
9292
  function rawQoderProjectPath(value) {
9228
9293
  return path18.resolve(value).split(path18.sep).join("-");
9229
9294
  }
9230
9295
  function qoderEncodedVariants(value) {
9231
9296
  const raw = rawQoderProjectPath(value);
9232
- const normalized = raw.replace(/_/g, "-");
9297
+ const normalized = raw.replaceAll("_", "-");
9233
9298
  return raw === normalized ? [raw] : [raw, normalized];
9234
9299
  }
9235
9300
  function qoderEncodedProjectSuffix(projectDir, home) {
@@ -9385,7 +9450,7 @@ function normalizeId(id) {
9385
9450
  }
9386
9451
 
9387
9452
  // src/adapters/workbuddy.ts
9388
- import { readFile as readFile12, readdir as readdir9, stat as stat10 } from "node:fs/promises";
9453
+ import { readdir as readdir9, readFile as readFile12, stat as stat10 } from "node:fs/promises";
9389
9454
  import path19 from "node:path";
9390
9455
  function workbuddyProjectsDir(home, env) {
9391
9456
  const override = env?.WORKBUDDY_PROJECTS_DIR || env?.WORKBUDDY_HOME;
@@ -9591,7 +9656,9 @@ async function parseWorkbuddySessionFile(filePath, options) {
9591
9656
  confidence: "derived",
9592
9657
  refs: { sourceId: `${sessionPrefix}:session`, ...subagentRefs }
9593
9658
  }, lines[0], filePath, options);
9594
- if (event) events.push(event);
9659
+ if (event) {
9660
+ events.push(event);
9661
+ }
9595
9662
  }
9596
9663
  for (const line of lines) {
9597
9664
  const record = line.record;
@@ -9606,7 +9673,9 @@ async function parseWorkbuddySessionFile(filePath, options) {
9606
9673
  continue;
9607
9674
  }
9608
9675
  const ended = closeTurn(line);
9609
- if (ended) events.push(ended);
9676
+ if (ended) {
9677
+ events.push(ended);
9678
+ }
9610
9679
  turnIndex += 1;
9611
9680
  currentTurnId = `${sessionPrefix}:turn:${turnIndex}`;
9612
9681
  turnLastTs = ts;
@@ -9644,7 +9713,9 @@ async function parseWorkbuddySessionFile(filePath, options) {
9644
9713
  refs: { sourceId: `${sourceId}:prompt` }
9645
9714
  }, line, filePath, options)
9646
9715
  ]) {
9647
- if (event) events.push(event);
9716
+ if (event) {
9717
+ events.push(event);
9718
+ }
9648
9719
  }
9649
9720
  continue;
9650
9721
  }
@@ -9671,7 +9742,9 @@ async function parseWorkbuddySessionFile(filePath, options) {
9671
9742
  confidence: "partial",
9672
9743
  refs: { sourceId: `${sourceId}:usage` }
9673
9744
  }, line, filePath, options);
9674
- if (usage) events.push(usage);
9745
+ if (usage) {
9746
+ events.push(usage);
9747
+ }
9675
9748
  }
9676
9749
  continue;
9677
9750
  }
@@ -9698,7 +9771,40 @@ async function parseWorkbuddySessionFile(filePath, options) {
9698
9771
  confidence: "exact",
9699
9772
  refs: { sourceId: `${callId}:start` }
9700
9773
  }, line, filePath, options);
9701
- if (started) events.push(started);
9774
+ if (started) {
9775
+ events.push(started);
9776
+ }
9777
+ let toolInput;
9778
+ try {
9779
+ toolInput = JSON.parse(stringField(record, "arguments") || "");
9780
+ } catch {
9781
+ toolInput = void 0;
9782
+ }
9783
+ if (isPlainObject(toolInput)) {
9784
+ const fileActivities = claudeStyleToolFileActivities(tool, toolInput, ts, cwd);
9785
+ if (fileActivities.length > 0) {
9786
+ const fileEvent = makeEvent({
9787
+ schemaVersion: AGENT_TIME_SCHEMA_VERSION,
9788
+ ts,
9789
+ type: eventTypeFromFileActivities(fileActivities),
9790
+ source: "workbuddy",
9791
+ workspaceId,
9792
+ project,
9793
+ cwd,
9794
+ sessionId: sessionPrefix,
9795
+ turnId: currentTurnId,
9796
+ agent: "workbuddy",
9797
+ tool,
9798
+ confidence: "derived",
9799
+ fileActivities,
9800
+ metrics: summarizeFileActivities(fileActivities),
9801
+ refs: { sourceId: `${callId}:files` }
9802
+ }, line, filePath, options);
9803
+ if (fileEvent) {
9804
+ events.push(fileEvent);
9805
+ }
9806
+ }
9807
+ }
9702
9808
  }
9703
9809
  const metrics = workbuddyUsageMetrics(record);
9704
9810
  const model = stringField(objectField(record, "providerData"), "model");
@@ -9719,7 +9825,9 @@ async function parseWorkbuddySessionFile(filePath, options) {
9719
9825
  confidence: metrics ? "partial" : "derived",
9720
9826
  refs: { sourceId: `${sourceId}:usage:${callId || line.lineNumber}` }
9721
9827
  }, line, filePath, options);
9722
- if (usage) events.push(usage);
9828
+ if (usage) {
9829
+ events.push(usage);
9830
+ }
9723
9831
  }
9724
9832
  continue;
9725
9833
  }
@@ -9748,13 +9856,17 @@ async function parseWorkbuddySessionFile(filePath, options) {
9748
9856
  confidence: start ? "derived" : "partial",
9749
9857
  refs: { sourceId: `${callId}:result` }
9750
9858
  }, line, filePath, options);
9751
- if (completed) events.push(completed);
9859
+ if (completed) {
9860
+ events.push(completed);
9861
+ }
9752
9862
  }
9753
9863
  }
9754
9864
  const lastLine = lines.at(-1);
9755
9865
  if (lastLine) {
9756
9866
  const ended = closeTurn(lastLine);
9757
- if (ended) events.push(ended);
9867
+ if (ended) {
9868
+ events.push(ended);
9869
+ }
9758
9870
  }
9759
9871
  const lastTs = lastLine ? timestampFrom(numberField(lastLine.record, "timestamp")) : void 0;
9760
9872
  if (lastLine && lastTs) {
@@ -9771,7 +9883,9 @@ async function parseWorkbuddySessionFile(filePath, options) {
9771
9883
  confidence: "derived",
9772
9884
  refs: { sourceId: `${sessionPrefix}:ended` }
9773
9885
  }, lastLine, filePath, options);
9774
- if (event) events.push(event);
9886
+ if (event) {
9887
+ events.push(event);
9888
+ }
9775
9889
  }
9776
9890
  return events;
9777
9891
  }
@@ -12425,8 +12539,8 @@ function selectBackfillFilesForImport(files, watermarkTs) {
12425
12539
  }
12426
12540
  }
12427
12541
  const order = /* @__PURE__ */ new Map();
12428
- for (let i = 0; i < files.length; i += 1) {
12429
- order.set(files[i].path, i);
12542
+ for (const [i, file] of files.entries()) {
12543
+ order.set(file.path, i);
12430
12544
  }
12431
12545
  picked.sort((a, b) => (order.get(a.path) ?? 0) - (order.get(b.path) ?? 0));
12432
12546
  return picked;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@yhong91/vibetime",
3
3
  "type": "module",
4
- "version": "0.1.44",
4
+ "version": "0.1.46",
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": {