@yhong91/vibetime 0.1.53 → 0.1.55

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 +189 -102
  2. package/package.json +1 -1
package/bin/vibetime.mjs CHANGED
@@ -884,8 +884,8 @@ var init_esm = __esm({
884
884
 
885
885
  // src/cli.ts
886
886
  import { spawn as spawn2, spawnSync } from "node:child_process";
887
- import { mkdir as mkdir5, open, rm, stat as stat13, writeFile as writeFile4 } from "node:fs/promises";
888
- import os11 from "node:os";
887
+ import { mkdir as mkdir5, open, rm, stat as stat14, writeFile as writeFile4 } from "node:fs/promises";
888
+ import os12 from "node:os";
889
889
  import path25 from "node:path";
890
890
  import { fileURLToPath } from "node:url";
891
891
 
@@ -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.53" : "0.1.1";
2050
+ var PACKAGE_VERSION = true ? "0.1.55" : "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;
@@ -3331,6 +3331,9 @@ function isTurnIdle(lastEventAt) {
3331
3331
  }
3332
3332
  return Date.now() - lastMs > TURN_IDLE_MS;
3333
3333
  }
3334
+ function withoutSubagentTurnEvents(events) {
3335
+ return events.filter((event) => event.type !== "turn.started" && event.type !== "turn.completed");
3336
+ }
3334
3337
  function sessionIdFromFilePath(filePath, prefix) {
3335
3338
  const match = path6.basename(filePath).match(/([0-9a-f]{8}-[0-9a-f-]{27,})/);
3336
3339
  return match?.[1] || `${prefix}_${createStableHash(filePath).slice(0, 24)}`;
@@ -4253,7 +4256,8 @@ async function parseClaudeCoworkSessionFile(filePath, options) {
4253
4256
  const parentSourcePathHash = subagentInfo ? `sha256:${createStableHash(subagentInfo.mainTranscriptPath)}` : void 0;
4254
4257
  const parentSessionId = subagentInfo ? prefixCoworkId(subagentInfo.cliSessionId) : void 0;
4255
4258
  const events = await parser(filePath, options);
4256
- return events.map((event) => {
4259
+ const foldable = subagentInfo ? withoutSubagentTurnEvents(events).map((event) => ({ ...event, turnId: void 0 })) : events;
4260
+ return foldable.map((event) => {
4257
4261
  const sessionId = parentSessionId || prefixCoworkId(event.sessionId);
4258
4262
  const turnId = prefixCoworkId(event.turnId);
4259
4263
  const workspaceId = createWorkspaceId({ projectName: project, repoRoot: cwd });
@@ -5007,8 +5011,8 @@ async function codebuddyBackfillFiles(sourceRoot, home, env) {
5007
5011
  }
5008
5012
  const filePath = path9.join(traceDir, entry);
5009
5013
  try {
5010
- const stat14 = await import("node:fs/promises").then((fs) => fs.stat(filePath));
5011
- files.push({ path: filePath, modifiedAt: stat14.mtime.toISOString(), groupId: pidDir.name });
5014
+ const stat15 = await import("node:fs/promises").then((fs) => fs.stat(filePath));
5015
+ files.push({ path: filePath, modifiedAt: stat15.mtime.toISOString(), groupId: pidDir.name });
5012
5016
  } catch {
5013
5017
  }
5014
5018
  }
@@ -7635,6 +7639,8 @@ async function parseOpenCodeSessionFile(dbPath, options) {
7635
7639
  for (const session of sessions) {
7636
7640
  const rawSessionId = session.id;
7637
7641
  const sessionId = rootIdByRawId.get(rawSessionId) || rawSessionId;
7642
+ const isSubagent = sessionId !== rawSessionId;
7643
+ const sessionEventStart = events.length;
7638
7644
  const cwd = session.directory || session.path || void 0;
7639
7645
  const project = cwd ? path15.basename(cwd) : void 0;
7640
7646
  const sessionTs = msToIso(session.time_created);
@@ -7951,6 +7957,9 @@ async function parseOpenCodeSessionFile(dbPath, options) {
7951
7957
  operation: "session end"
7952
7958
  }));
7953
7959
  }
7960
+ if (isSubagent) {
7961
+ events.push(...withoutSubagentTurnEvents(events.splice(sessionEventStart)).map((event) => ({ ...event, turnId: void 0 })));
7962
+ }
7954
7963
  }
7955
7964
  } finally {
7956
7965
  db.close();
@@ -8029,19 +8038,19 @@ function opencodeDataCandidates(home, env) {
8029
8038
  return [primary, path15.join(home, ".opencode", "opencode.db")];
8030
8039
  }
8031
8040
  async function opencodeBackfillFiles(sourceRoot, home = os6.homedir(), env) {
8032
- const { stat: stat14 } = await import("node:fs/promises");
8041
+ const { stat: stat15 } = await import("node:fs/promises");
8033
8042
  if (sourceRoot) {
8034
8043
  if (!sourceRoot.endsWith(".db")) {
8035
8044
  return [];
8036
8045
  }
8037
- const info = await stat14(sourceRoot).catch(() => null);
8046
+ const info = await stat15(sourceRoot).catch(() => null);
8038
8047
  if (!info) {
8039
8048
  return [];
8040
8049
  }
8041
8050
  return [{ path: sourceRoot, modifiedAt: info.mtime.toISOString() }];
8042
8051
  }
8043
8052
  for (const candidatePath of opencodeDataCandidates(home, env)) {
8044
- const info = await stat14(candidatePath).catch(() => null);
8053
+ const info = await stat15(candidatePath).catch(() => null);
8045
8054
  if (info) {
8046
8055
  return [{ path: candidatePath, modifiedAt: info.mtime.toISOString() }];
8047
8056
  }
@@ -8141,8 +8150,10 @@ function createOpenCodeAdapter() {
8141
8150
  }
8142
8151
 
8143
8152
  // src/adapters/pi.ts
8144
- import { readFile as readFile10 } from "node:fs/promises";
8153
+ import { readdir as readdir7, readFile as readFile10, stat as stat8 } from "node:fs/promises";
8154
+ import os7 from "node:os";
8145
8155
  import path16 from "node:path";
8156
+ init_fs();
8146
8157
  function parsePiSubagentLink(filePath, headerParentSession) {
8147
8158
  if (headerParentSession) {
8148
8159
  const parentFile = path16.isAbsolute(headerParentSession) ? headerParentSession : void 0;
@@ -8187,11 +8198,15 @@ async function resolveParentContext(link, options) {
8187
8198
  return void 0;
8188
8199
  }
8189
8200
  function foldEventsIntoParent(events, link) {
8190
- const childSessionId = events.find((event) => event.sessionId)?.sessionId;
8191
- return events.map((event) => {
8201
+ const folded = withoutSubagentTurnEvents(events);
8202
+ const childSessionId = folded.find((event) => event.sessionId)?.sessionId;
8203
+ return folded.map((event) => {
8192
8204
  const rewritten = {
8193
8205
  ...event,
8194
- sessionId: link.parentSessionId
8206
+ sessionId: link.parentSessionId,
8207
+ // Subagent turns don't exist in the host session; drop the reference so
8208
+ // buildSessionRollups doesn't synthesize phantom turn rollups.
8209
+ turnId: void 0
8195
8210
  };
8196
8211
  if (childSessionId && childSessionId !== link.parentSessionId) {
8197
8212
  rewritten.refs = {
@@ -8447,6 +8462,100 @@ async function parsePiSessionFile(filePath, options) {
8447
8462
  }
8448
8463
  return state.events.filter((event) => matchesBackfillFilters(event, options));
8449
8464
  }
8465
+ async function parsePiFile(filePath, options) {
8466
+ if (filePath.endsWith(".json")) {
8467
+ return parsePiWorkflowRunFile(filePath, options);
8468
+ }
8469
+ return parsePiSessionFile(filePath, options);
8470
+ }
8471
+ async function parsePiWorkflowRunFile(filePath, options) {
8472
+ let raw;
8473
+ try {
8474
+ raw = JSON.parse(await readFile10(filePath, "utf8"));
8475
+ } catch {
8476
+ return [];
8477
+ }
8478
+ if (!isPlainObject(raw)) {
8479
+ return [];
8480
+ }
8481
+ const sessionId = stringField(raw, "sessionId");
8482
+ const agents = Array.isArray(raw.agents) ? raw.agents.filter((agent) => isPlainObject(agent)) : [];
8483
+ if (!sessionId || agents.length === 0) {
8484
+ return [];
8485
+ }
8486
+ const runUpdatedAt = timestampFrom(raw.updatedAt);
8487
+ const parentSessionFile = await findPiSessionFileById(sessionId, options);
8488
+ const host = await resolveParentContext(
8489
+ { parentSessionId: sessionId, parentSessionFile, explicit: true },
8490
+ options
8491
+ );
8492
+ const cwd = host?.cwd;
8493
+ const project = host?.project;
8494
+ const state = new SessionParserState(filePath, options, (event) => basePiEvent({ ...event, cwd, project, model: event.model }));
8495
+ const push = (event, ln) => {
8496
+ state.push(
8497
+ { ...event, workspaceId: event.workspaceId || createWorkspaceId({ projectName: project, repoRoot: cwd }) },
8498
+ ln,
8499
+ "workflow-run",
8500
+ event.type
8501
+ );
8502
+ };
8503
+ for (const [index, agent] of agents.entries()) {
8504
+ const lineNumber = index + 1;
8505
+ const usage = objectField(agent, "tokenUsage");
8506
+ const input = numberField(usage, "input") || 0;
8507
+ const output = numberField(usage, "output") || 0;
8508
+ const cacheRead = numberField(usage, "cacheRead") || 0;
8509
+ const cacheWrite = numberField(usage, "cacheWrite") || 0;
8510
+ const breakdown = input + output + cacheRead + cacheWrite;
8511
+ const total = numberField(usage, "total") || breakdown || numberField(agent, "tokens") || 0;
8512
+ if (total <= 0) {
8513
+ continue;
8514
+ }
8515
+ const model = stringField(agent, "model")?.replace(/:(off|minimal|low|medium|high|xhigh|max)$/, "");
8516
+ const ts = timestampFrom(agent.endedAt) || timestampFrom(agent.startedAt) || runUpdatedAt || (/* @__PURE__ */ new Date()).toISOString();
8517
+ push(basePiEvent({
8518
+ ts,
8519
+ type: "model.usage",
8520
+ sessionId,
8521
+ cwd,
8522
+ project,
8523
+ model,
8524
+ confidence: breakdown > 0 ? "exact" : "derived",
8525
+ metrics: {
8526
+ tokensInput: breakdown > 0 ? input + cacheRead + cacheWrite || void 0 : void 0,
8527
+ tokensOutput: breakdown > 0 ? output || void 0 : void 0,
8528
+ tokensCachedInput: breakdown > 0 ? cacheRead + cacheWrite || void 0 : void 0,
8529
+ tokensCacheReadInput: breakdown > 0 ? cacheRead || void 0 : void 0,
8530
+ tokensCacheCreationInput: breakdown > 0 ? cacheWrite || void 0 : void 0,
8531
+ tokensTotal: total,
8532
+ costUsd: numberField(usage, "cost") || void 0
8533
+ },
8534
+ refs: stringRefs({
8535
+ sourceId: stringField(agent, "callId") || stringField(agent, "label")
8536
+ })
8537
+ }), lineNumber);
8538
+ }
8539
+ return state.events.filter((event) => matchesBackfillFilters(event, options));
8540
+ }
8541
+ async function findPiSessionFileById(sessionId, options) {
8542
+ const sessionsDir = piSessionDir(path16.resolve(stringOption(options.home) || os7.homedir()));
8543
+ try {
8544
+ const projectDirs = await readdir7(sessionsDir, { withFileTypes: true });
8545
+ for (const dir of projectDirs) {
8546
+ if (!dir.isDirectory()) {
8547
+ continue;
8548
+ }
8549
+ const names = await readdir7(path16.join(sessionsDir, dir.name));
8550
+ const hit = names.find((name) => name.endsWith(`_${sessionId}.jsonl`));
8551
+ if (hit) {
8552
+ return path16.join(sessionsDir, dir.name, hit);
8553
+ }
8554
+ }
8555
+ } catch {
8556
+ }
8557
+ return void 0;
8558
+ }
8450
8559
  function basePiEvent(event) {
8451
8560
  return {
8452
8561
  schemaVersion: AGENT_TIME_SCHEMA_VERSION,
@@ -8693,6 +8802,20 @@ function piSessionDir(home, env) {
8693
8802
  }
8694
8803
  return path16.join(piAgentDir(home, env), "sessions");
8695
8804
  }
8805
+ function piWorkflowProjectsDir(home) {
8806
+ return path16.join(home, ".pi", "workflows", "projects");
8807
+ }
8808
+ async function piBackfillFiles(sourceRoot, home = os7.homedir(), env) {
8809
+ const lists = sourceRoot ? [await listFilesByExtensions(sourceRoot, [".jsonl", ".json"])] : await Promise.all([
8810
+ listFilesByExtensions(piSessionDir(home, env), [".jsonl"]),
8811
+ listFilesByExtensions(piWorkflowProjectsDir(home), [".json"])
8812
+ ]);
8813
+ const files = lists.flat().sort();
8814
+ return Promise.all(files.map(async (filePath) => {
8815
+ const info = await stat8(filePath);
8816
+ return { path: filePath, modifiedAt: info.mtime.toISOString() };
8817
+ }));
8818
+ }
8696
8819
  function createPiAdapter() {
8697
8820
  return {
8698
8821
  id: "pi",
@@ -8721,20 +8844,20 @@ function createPiAdapter() {
8721
8844
  }];
8722
8845
  },
8723
8846
  sourcePaths(home, env) {
8724
- return [piSessionDir(home, env)];
8847
+ return [piSessionDir(home, env), piWorkflowProjectsDir(home)];
8725
8848
  },
8726
- parseSessionFile: parsePiSessionFile
8849
+ parseSessionFile: parsePiFile
8727
8850
  };
8728
8851
  }
8729
8852
 
8730
8853
  // src/adapters/qoder-cn.ts
8731
- import { readdir as readdir7, readFile as readFile11, stat as stat8 } from "node:fs/promises";
8732
- import os8 from "node:os";
8854
+ import { readdir as readdir8, readFile as readFile11, stat as stat9 } from "node:fs/promises";
8855
+ import os9 from "node:os";
8733
8856
  import path18 from "node:path";
8734
8857
 
8735
8858
  // src/adapters/qoder-local-db.ts
8736
8859
  import { access } from "node:fs/promises";
8737
- import os7 from "node:os";
8860
+ import os8 from "node:os";
8738
8861
  import path17 from "node:path";
8739
8862
  function takeQoderDbModelCall(calls, requestId, blockStart) {
8740
8863
  if (requestId) {
@@ -8752,7 +8875,7 @@ function takeQoderDbModelCall(calls, requestId, blockStart) {
8752
8875
  }
8753
8876
  return calls.ordered.shift();
8754
8877
  }
8755
- function appDataRoot(appDirName, home = os7.homedir()) {
8878
+ function appDataRoot(appDirName, home = os8.homedir()) {
8756
8879
  if (process.platform === "darwin") {
8757
8880
  return path17.join(home, "Library", "Application Support", appDirName);
8758
8881
  }
@@ -9005,7 +9128,7 @@ async function loadQoderCnSegmentModelCalls(filePath, isSubagentSession, modelMa
9005
9128
  const segmentsPath = path18.join(configDir2, "logs", "sessions", projectName, sessionId, "segments");
9006
9129
  const modelCalls = [];
9007
9130
  try {
9008
- const files = await readdir7(segmentsPath);
9131
+ const files = await readdir8(segmentsPath);
9009
9132
  for (const file of files) {
9010
9133
  if (!file.endsWith(".jsonl")) {
9011
9134
  continue;
@@ -9055,7 +9178,7 @@ async function parseQoderCnSessionFile(filePath, options) {
9055
9178
  let cwd;
9056
9179
  let project = projectContext.project;
9057
9180
  let model;
9058
- const home = path18.resolve(stringOption(options.home) || os8.homedir());
9181
+ const home = path18.resolve(stringOption(options.home) || os9.homedir());
9059
9182
  const modelMap = await loadQoderCnModelNames(configDir2, home);
9060
9183
  const isSubagentSession = filePath.includes("subagents");
9061
9184
  const segmentModelCalls = await loadQoderCnSegmentModelCalls(filePath, isSubagentSession, modelMap);
@@ -9623,7 +9746,7 @@ async function gitRootFromCwds2(cwds) {
9623
9746
  while (!seen.has(current)) {
9624
9747
  seen.add(current);
9625
9748
  try {
9626
- await stat8(path18.join(current, ".git"));
9749
+ await stat9(path18.join(current, ".git"));
9627
9750
  return current;
9628
9751
  } catch {
9629
9752
  }
@@ -9657,7 +9780,7 @@ function encodeQoderCnProjectPath(value) {
9657
9780
  }
9658
9781
  async function qoderCnProjectFromFilePath(filePath, options) {
9659
9782
  const projectDir = path18.basename(path18.dirname(filePath));
9660
- const home = options ? path18.resolve(stringOption(options.home) || os8.homedir()) : os8.homedir();
9783
+ const home = options ? path18.resolve(stringOption(options.home) || os9.homedir()) : os9.homedir();
9661
9784
  const resolved = await resolveQoderCnProjectPath(projectDir, home);
9662
9785
  if (resolved) {
9663
9786
  return path18.basename(resolved);
@@ -9680,7 +9803,7 @@ async function resolveQoderCnProjectPath(projectDir, home) {
9680
9803
  while (projectDir.startsWith(`${currentEncoded}-`)) {
9681
9804
  let matchedChild;
9682
9805
  try {
9683
- const entries = await readdir7(current, { withFileTypes: true });
9806
+ const entries = await readdir8(current, { withFileTypes: true });
9684
9807
  for (const entry of entries) {
9685
9808
  if (!entry.isDirectory()) {
9686
9809
  continue;
@@ -9773,8 +9896,8 @@ function createQoderCnAdapter() {
9773
9896
 
9774
9897
  // src/adapters/qoder.ts
9775
9898
  import { existsSync } from "node:fs";
9776
- import { readdir as readdir8, readFile as readFile12, stat as stat9 } from "node:fs/promises";
9777
- import os9 from "node:os";
9899
+ import { readdir as readdir9, readFile as readFile12, stat as stat10 } from "node:fs/promises";
9900
+ import os10 from "node:os";
9778
9901
  import path19 from "node:path";
9779
9902
  function parseQoderPaths(filePath) {
9780
9903
  const parts = filePath.split(path19.sep);
@@ -9883,7 +10006,7 @@ async function loadQoderSegmentModelCalls(filePath, isSubagentSession, modelMap)
9883
10006
  const segmentsPath = path19.join(configDir2, "logs", "sessions", projectName, sessionId, "segments");
9884
10007
  const modelCalls = [];
9885
10008
  try {
9886
- const files = await readdir8(segmentsPath);
10009
+ const files = await readdir9(segmentsPath);
9887
10010
  for (const file of files) {
9888
10011
  if (!file.endsWith(".jsonl")) {
9889
10012
  continue;
@@ -9933,7 +10056,7 @@ async function parseQoderSessionFile(filePath, options) {
9933
10056
  let cwd;
9934
10057
  let project = projectContext.project;
9935
10058
  let model;
9936
- const home = path19.resolve(stringOption(options.home) || os9.homedir());
10059
+ const home = path19.resolve(stringOption(options.home) || os10.homedir());
9937
10060
  const modelMap = await loadQoderModelNames(configDir2, home);
9938
10061
  const qwenworkRoot = isQwenworkConfigRoot(configDir2);
9939
10062
  const isSubagentSession = filePath.includes("subagents");
@@ -10467,7 +10590,7 @@ async function gitRootFromCwds3(cwds) {
10467
10590
  while (!seen.has(current)) {
10468
10591
  seen.add(current);
10469
10592
  try {
10470
- await stat9(path19.join(current, ".git"));
10593
+ await stat10(path19.join(current, ".git"));
10471
10594
  return current;
10472
10595
  } catch {
10473
10596
  }
@@ -10519,7 +10642,7 @@ function qoderEncodedProjectSuffix(projectDir, home) {
10519
10642
  }
10520
10643
  async function qoderProjectFromFilePath(filePath, options) {
10521
10644
  const projectDir = path19.basename(path19.dirname(filePath));
10522
- const home = options ? path19.resolve(stringOption(options.home) || os9.homedir()) : os9.homedir();
10645
+ const home = options ? path19.resolve(stringOption(options.home) || os10.homedir()) : os10.homedir();
10523
10646
  const resolved = await resolveQoderProjectPath(projectDir, home);
10524
10647
  if (resolved) {
10525
10648
  return path19.basename(resolved);
@@ -10543,7 +10666,7 @@ async function resolveQoderProjectPath(projectDir, home) {
10543
10666
  while (currentEncodedVariants.some((prefix) => projectDir.startsWith(`${prefix}-`))) {
10544
10667
  let matchedChild;
10545
10668
  try {
10546
- const entries = await readdir8(current, { withFileTypes: true });
10669
+ const entries = await readdir9(current, { withFileTypes: true });
10547
10670
  for (const entry of entries) {
10548
10671
  if (!entry.isDirectory()) {
10549
10672
  continue;
@@ -10675,7 +10798,7 @@ function normalizeId(id) {
10675
10798
  }
10676
10799
 
10677
10800
  // src/adapters/workbuddy.ts
10678
- import { readdir as readdir9, readFile as readFile13, stat as stat10 } from "node:fs/promises";
10801
+ import { readdir as readdir10, readFile as readFile13, stat as stat11 } from "node:fs/promises";
10679
10802
  import path20 from "node:path";
10680
10803
  function workbuddyProjectsDir(home, env) {
10681
10804
  const override = env?.WORKBUDDY_PROJECTS_DIR || env?.WORKBUDDY_HOME;
@@ -11118,17 +11241,17 @@ async function workbuddyBackfillFiles(sourceRoot, home, env) {
11118
11241
  const base = sourceRoot || workbuddyProjectsDir(home, env);
11119
11242
  const files = [];
11120
11243
  try {
11121
- const projects = await readdir9(base, { withFileTypes: true });
11244
+ const projects = await readdir10(base, { withFileTypes: true });
11122
11245
  for (const project of projects) {
11123
11246
  if (!project.isDirectory()) {
11124
11247
  continue;
11125
11248
  }
11126
11249
  const projectDir = path20.join(base, project.name);
11127
- const entries = await readdir9(projectDir, { withFileTypes: true });
11250
+ const entries = await readdir10(projectDir, { withFileTypes: true });
11128
11251
  for (const entry of entries) {
11129
11252
  if (entry.isFile() && entry.name.endsWith(".jsonl")) {
11130
11253
  const filePath = path20.join(projectDir, entry.name);
11131
- const info = await stat10(filePath);
11254
+ const info = await stat11(filePath);
11132
11255
  files.push({ path: filePath, modifiedAt: info.mtime.toISOString() });
11133
11256
  }
11134
11257
  }
@@ -11189,7 +11312,7 @@ function createWorkbuddyAdapter() {
11189
11312
 
11190
11313
  // src/adapters/zcode.ts
11191
11314
  import { execFile } from "node:child_process";
11192
- import { readFile as readFile14, stat as stat11 } from "node:fs/promises";
11315
+ import { readFile as readFile14, stat as stat12 } from "node:fs/promises";
11193
11316
  import path21 from "node:path";
11194
11317
  import { promisify as promisify2 } from "node:util";
11195
11318
  init_fs();
@@ -11208,7 +11331,7 @@ var providerNameCache = null;
11208
11331
  async function loadProviderNames(configPath2) {
11209
11332
  let fileMtime = 0;
11210
11333
  try {
11211
- const info = await stat11(configPath2);
11334
+ const info = await stat12(configPath2);
11212
11335
  fileMtime = info.mtimeMs;
11213
11336
  } catch {
11214
11337
  return /* @__PURE__ */ new Map();
@@ -11408,6 +11531,7 @@ function sessionContext(row, sessions) {
11408
11531
  return {
11409
11532
  rawSessionId: sessionId,
11410
11533
  sessionId: `zcode:${rootSessionId}`,
11534
+ isSubagent: rootSessionId !== sessionId,
11411
11535
  cwd,
11412
11536
  project,
11413
11537
  workspaceId: createWorkspaceId({ projectName: project, repoRoot: cwd }),
@@ -11424,7 +11548,7 @@ async function parseZCodeDb(filePath, options) {
11424
11548
  for (let i = 0; i < 12; i++) {
11425
11549
  const probe = path21.join(candidate, ".zcode", "v2", "config.json");
11426
11550
  try {
11427
- await stat11(probe);
11551
+ await stat12(probe);
11428
11552
  configPath2 = probe;
11429
11553
  break;
11430
11554
  } catch {
@@ -11499,6 +11623,9 @@ async function parseZCodeDb(filePath, options) {
11499
11623
  return;
11500
11624
  }
11501
11625
  const ctx = sessionContext(row, sessions);
11626
+ if (ctx.isSubagent) {
11627
+ return;
11628
+ }
11502
11629
  const turnId = `zcode:${rawTurnId}`;
11503
11630
  const started = makeEvent2({
11504
11631
  schemaVersion: AGENT_TIME_SCHEMA_VERSION,
@@ -11559,7 +11686,8 @@ async function parseZCodeDb(filePath, options) {
11559
11686
  project: ctx.project,
11560
11687
  cwd: ctx.cwd,
11561
11688
  sessionId: ctx.sessionId,
11562
- turnId: stringField(row, "turn_id") ? `zcode:${stringField(row, "turn_id")}` : void 0,
11689
+ // Subagent turns are dropped; keep their events out of turn rollups.
11690
+ turnId: !ctx.isSubagent && stringField(row, "turn_id") ? `zcode:${stringField(row, "turn_id")}` : void 0,
11563
11691
  agent: stringField(row, "agent") || "zcode",
11564
11692
  provider: nameMap.get(stringField(row, "provider_id") || "") || stringField(row, "provider_id"),
11565
11693
  model: modelId,
@@ -11585,7 +11713,7 @@ async function parseZCodeDb(filePath, options) {
11585
11713
  return;
11586
11714
  }
11587
11715
  const ctx = sessionContext(row, sessions);
11588
- const turnId = stringField(row, "turn_id") ? `zcode:${stringField(row, "turn_id")}` : void 0;
11716
+ const turnId = !ctx.isSubagent && stringField(row, "turn_id") ? `zcode:${stringField(row, "turn_id")}` : void 0;
11589
11717
  const spanId = `${ctx.sessionId}:tool:${toolCallId}`;
11590
11718
  const started = makeEvent2({
11591
11719
  schemaVersion: AGENT_TIME_SCHEMA_VERSION,
@@ -11646,7 +11774,7 @@ async function zcodeBackfillFiles(sourceRoot, home, env) {
11646
11774
  const candidate = sourceRoot || zcodeDbPath(home, env);
11647
11775
  const filePath = candidate.endsWith(".sqlite") ? candidate : path21.join(candidate, "db", "db.sqlite");
11648
11776
  try {
11649
- const info = await stat11(filePath);
11777
+ const info = await stat12(filePath);
11650
11778
  return [{ path: filePath, modifiedAt: info.mtime.toISOString() }];
11651
11779
  } catch {
11652
11780
  return [];
@@ -11678,7 +11806,7 @@ function createZCodeAdapter() {
11678
11806
  }
11679
11807
 
11680
11808
  // src/adapters/zed.ts
11681
- import os10 from "node:os";
11809
+ import os11 from "node:os";
11682
11810
  import path22 from "node:path";
11683
11811
  function zedThreadsCandidates(home, env) {
11684
11812
  const candidates = [];
@@ -12030,20 +12158,20 @@ async function parseZedSessionFile(dbPath, options) {
12030
12158
  }
12031
12159
  return events.filter((event) => matchesBackfillFilters(event, options));
12032
12160
  }
12033
- async function zedBackfillFiles(sourceRoot, home = os10.homedir(), env) {
12034
- const { stat: stat14 } = await import("node:fs/promises");
12161
+ async function zedBackfillFiles(sourceRoot, home = os11.homedir(), env) {
12162
+ const { stat: stat15 } = await import("node:fs/promises");
12035
12163
  if (sourceRoot) {
12036
12164
  if (!sourceRoot.endsWith(".db")) {
12037
12165
  return [];
12038
12166
  }
12039
- const info = await stat14(sourceRoot).catch(() => null);
12167
+ const info = await stat15(sourceRoot).catch(() => null);
12040
12168
  if (!info) {
12041
12169
  return [];
12042
12170
  }
12043
12171
  return [{ path: sourceRoot, modifiedAt: info.mtime.toISOString() }];
12044
12172
  }
12045
12173
  for (const candidatePath of zedThreadsCandidates(home, env)) {
12046
- const info = await stat14(candidatePath).catch(() => null);
12174
+ const info = await stat15(candidatePath).catch(() => null);
12047
12175
  if (info) {
12048
12176
  return [{ path: candidatePath, modifiedAt: info.mtime.toISOString() }];
12049
12177
  }
@@ -12823,7 +12951,7 @@ function defaultMachineName() {
12823
12951
  init_fs();
12824
12952
 
12825
12953
  // src/lib/logger.ts
12826
- import { appendFile, mkdir as mkdir4, rename, stat as stat12 } from "node:fs/promises";
12954
+ import { appendFile, mkdir as mkdir4, rename, stat as stat13 } from "node:fs/promises";
12827
12955
  import { homedir as homedir2 } from "node:os";
12828
12956
  import path24 from "node:path";
12829
12957
  var MAX_BYTES = 1 * 1024 * 1024;
@@ -12841,7 +12969,7 @@ function serializeError(error) {
12841
12969
  }
12842
12970
  async function rotateIfNeeded(file) {
12843
12971
  try {
12844
- const info = await stat12(file);
12972
+ const info = await stat13(file);
12845
12973
  if (info.size > MAX_BYTES) {
12846
12974
  await rename(file, `${file}.1`).catch(() => {
12847
12975
  });
@@ -13080,7 +13208,6 @@ async function deleteMachine(remote, id) {
13080
13208
  var BACKFILL_STATE_SCHEMA_VERSION = 6;
13081
13209
 
13082
13210
  // src/cli.ts
13083
- var SESSION_REWRITE_DAYS = 7;
13084
13211
  function createRegistry() {
13085
13212
  const registry = new AdapterRegistry();
13086
13213
  registry.register(createCodexAdapter());
@@ -13160,7 +13287,7 @@ function createCli(ctx, registry) {
13160
13287
  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));
13161
13288
  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));
13162
13289
  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));
13163
- 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));
13290
+ 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", "Re-import everything and rewrite matching server rollups (history lock unlocked; nothing is deleted)").action((action, options) => backfillCommand({ ...normalizeOptions(options), action }, ctx, registry));
13164
13291
  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));
13165
13292
  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));
13166
13293
  return cli;
@@ -13179,8 +13306,7 @@ function normalizeOptions(options) {
13179
13306
  importRun: "import-run",
13180
13307
  batchSize: "batch-size",
13181
13308
  batchBytes: "batch-bytes",
13182
- skipConflicts: "skip-conflicts",
13183
- purgeAll: "purge-all"
13309
+ skipConflicts: "skip-conflicts"
13184
13310
  };
13185
13311
  for (const [camel, dashed] of Object.entries(aliases)) {
13186
13312
  if (normalized[camel] !== void 0 && normalized[dashed] === void 0) {
@@ -13488,10 +13614,6 @@ async function backfillCommand(options, ctx, registry) {
13488
13614
  if (action === "verify") {
13489
13615
  return backfillVerifyCommand(options, ctx);
13490
13616
  }
13491
- if (options["purge-all"] && !options.force) {
13492
- write(ctx.stderr, "--purge-all requires --force\n");
13493
- return 1;
13494
- }
13495
13617
  if (action === "import" && !options["dry-run"]) {
13496
13618
  const requested = normalizeBackfillSource(stringOption(options.source) || "all");
13497
13619
  const supported = /* @__PURE__ */ new Set(["all", ...BACKFILL_SOURCE_IDS]);
@@ -13660,11 +13782,14 @@ async function listBackfillSourceFiles(source, options, ctx) {
13660
13782
  if (source.id === "zed") {
13661
13783
  return zedBackfillFiles(stringOption(options["source-root"]), resolveHome3(options, ctx), ctx.env);
13662
13784
  }
13785
+ if (source.id === "pi") {
13786
+ return piBackfillFiles(stringOption(options["source-root"]), resolveHome3(options, ctx), ctx.env);
13787
+ }
13663
13788
  const roots = stringOption(options["source-root"]) ? [requiredOption(options, "source-root")] : source.paths;
13664
13789
  const fileLists = await Promise.all(roots.map((r) => listJsonlFiles(r)));
13665
13790
  const files = fileLists.flat().sort().slice(0, numberOption(options.limit) || void 0);
13666
13791
  return Promise.all(files.map(async (filePath) => {
13667
- const info = await stat13(filePath);
13792
+ const info = await stat14(filePath);
13668
13793
  return { path: filePath, modifiedAt: info.mtime.toISOString() };
13669
13794
  }));
13670
13795
  }
@@ -13751,7 +13876,7 @@ async function importBackfillPlan(plan, options, ctx, registry) {
13751
13876
  const remote = resolveRemoteFromOptions(options, ctx);
13752
13877
  const remoteKey = backfillRemoteKey(remote?.baseUrl ?? DEFAULT_API_URL);
13753
13878
  if (options.force) {
13754
- await purgeForcedSources(sourceDefs, home, remoteKey, options, ctx);
13879
+ await resetForcedWatermarks(home, remoteKey, options, ctx);
13755
13880
  }
13756
13881
  const incrementalState = shouldUseIncrementalBackfill(options) ? await readBackfillIncrementalState(home, remoteKey, ctx) : void 0;
13757
13882
  if (!options.json) {
@@ -13767,7 +13892,7 @@ async function importBackfillPlan(plan, options, ctx, registry) {
13767
13892
  options,
13768
13893
  ctx
13769
13894
  );
13770
- const rollups = selectRollupsForUpload(buildSessionRollups(canonicalEvents), options);
13895
+ const rollups = buildSessionRollups(canonicalEvents);
13771
13896
  const counts = await uploadSessionRollups(rollups, canonicalEvents.length, options, ctx);
13772
13897
  const result = {
13773
13898
  importRunId: plan.importRun.importRunId,
@@ -13785,7 +13910,7 @@ async function importBackfillPlan(plan, options, ctx, registry) {
13785
13910
  }
13786
13911
  return counts.failed > 0 || counts.conflicts > 0 && !options["skip-conflicts"] ? 1 : 0;
13787
13912
  }
13788
- async function purgeForcedSources(sourceDefs, home, remoteKey, options, ctx) {
13913
+ async function resetForcedWatermarks(home, remoteKey, options, ctx) {
13789
13914
  try {
13790
13915
  const file = await readBackfillIncrementalStateFile(home);
13791
13916
  delete file.remotes[remoteKey];
@@ -13794,20 +13919,6 @@ async function purgeForcedSources(sourceDefs, home, remoteKey, options, ctx) {
13794
13919
  debug(ctx, `Failed to clear backfill watermark: ${error.message}
13795
13920
  `);
13796
13921
  }
13797
- const preserveTokens = !options["purge-all"];
13798
- for (const item of sourceDefs) {
13799
- try {
13800
- const deleted = await deleteSessionRollupsBySourceAPI(item.id, options, ctx, preserveTokens);
13801
- if (!options.json) {
13802
- const suffix = preserveTokens ? " (token-bearing and sessions older than 7 days kept)" : "";
13803
- write(ctx.stdout, `purged ${item.id}: ${deleted} old rollups${suffix}
13804
- `);
13805
- }
13806
- } catch (error) {
13807
- debug(ctx, `Failed to purge ${item.id} rollups: ${error.message}
13808
- `);
13809
- }
13810
- }
13811
13922
  }
13812
13923
  async function collectCanonicalEvents(sourceDefs, registry, incrementalState, options, ctx) {
13813
13924
  const selectedFilesBySource = /* @__PURE__ */ new Map();
@@ -13927,7 +14038,7 @@ async function sendSessionRollupBatch(rollups, options, ctx) {
13927
14038
  const result = await postRollupBatch(remote, rollups, {
13928
14039
  replace: options["skip-conflicts"] !== true,
13929
14040
  machine,
13930
- allowHistoricalRewrite: options["purge-all"] === true
14041
+ allowHistoricalRewrite: options.force === true
13931
14042
  });
13932
14043
  if (options.force && result.conflicts === 0) {
13933
14044
  for (const rollup of rollups) {
@@ -13935,7 +14046,7 @@ async function sendSessionRollupBatch(rollups, options, ctx) {
13935
14046
  try {
13936
14047
  await deleteRollupsBySource(remote, rollup.source, machine, {
13937
14048
  rollupKey: createImportKey(["rollup", rollup.source, sessionId]),
13938
- allowHistoricalRewrite: options["purge-all"] === true
14049
+ allowHistoricalRewrite: options.force === true
13939
14050
  });
13940
14051
  } catch (error) {
13941
14052
  debug(ctx, `Failed to delete superseded ${rollup.source} session ${sessionId}: ${error.message}
@@ -13946,28 +14057,6 @@ async function sendSessionRollupBatch(rollups, options, ctx) {
13946
14057
  }
13947
14058
  return result;
13948
14059
  }
13949
- async function deleteSessionRollupsBySourceAPI(source, options, ctx, preserveTokens) {
13950
- const remote = resolveRemoteFromOptions(options, ctx);
13951
- if (!remote) {
13952
- throw new Error("No fetch available for HTTP delete");
13953
- }
13954
- const home = resolveHome3(options, ctx);
13955
- return deleteRollupsBySource(remote, source, {
13956
- id: ensureLocalMachineId(home),
13957
- hostname: defaultMachineName(),
13958
- platform: process.platform
13959
- }, {
13960
- preserveTokens,
13961
- allowHistoricalRewrite: options["purge-all"] === true
13962
- });
13963
- }
13964
- function selectRollupsForUpload(rollups, options, now = /* @__PURE__ */ new Date()) {
13965
- if (!options.force || options["purge-all"]) {
13966
- return rollups;
13967
- }
13968
- const cutoff = now.getTime() - SESSION_REWRITE_DAYS * 24 * 60 * 60 * 1e3;
13969
- return rollups.filter((rollup) => Date.parse(rollup.lastEventAt) >= cutoff);
13970
- }
13971
14060
  function shouldUseIncrementalBackfill(options) {
13972
14061
  return !stringOption(options.since) && !stringOption(options.until) && !stringOption(options["source-root"]) && numberOption(options.limit) === void 0;
13973
14062
  }
@@ -14252,7 +14341,7 @@ function syncLocalRunnerEntryArgs(cliPath) {
14252
14341
  return [path25.resolve(path25.dirname(cliPath), "../bin/vibetime.mjs")];
14253
14342
  }
14254
14343
  function resolveHome3(options, ctx) {
14255
- return path25.resolve(stringOption(options.home) || ctx.env.HOME || os11.homedir());
14344
+ return path25.resolve(stringOption(options.home) || ctx.env.HOME || os12.homedir());
14256
14345
  }
14257
14346
  function requestedTargets(options) {
14258
14347
  const value = options.target || options.targets;
@@ -14426,7 +14515,7 @@ Usage:
14426
14515
  vibetime uninstall [--target codex,claude,opencode,pi] [--all] [--dry-run] [--home <path>]
14427
14516
  vibetime upgrade [--check]
14428
14517
  vibetime hook --agent <name>
14429
- vibetime backfill discover|plan|import|verify --source codex|claude-code|opencode|pi|all --dry-run [--json] [--batch-size <count>] [--force [--purge-all]]
14518
+ vibetime backfill discover|plan|import|verify --source codex|claude-code|opencode|pi|all --dry-run [--json] [--batch-size <count>] [--force]
14430
14519
  vibetime token set <token>
14431
14520
  vibetime token show
14432
14521
  vibetime token clear
@@ -14460,10 +14549,8 @@ Environment:
14460
14549
  `;
14461
14550
  }
14462
14551
  export {
14463
- SESSION_REWRITE_DAYS,
14464
14552
  run,
14465
14553
  selectBackfillFilesForImport,
14466
- selectRollupsForUpload,
14467
14554
  syncLocalRunnerEntryArgs
14468
14555
  };
14469
14556
  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.53",
4
+ "version": "0.1.55",
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": {