@yhong91/vibetime 0.1.2 → 0.1.4

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 +864 -62
  2. package/package.json +1 -1
package/bin/vibetime.mjs CHANGED
@@ -159,9 +159,9 @@ var init_fs = __esm({
159
159
 
160
160
  // src/cli.ts
161
161
  import { spawn } from "node:child_process";
162
- import { mkdir as mkdir4, rename as rename2, rm, stat as stat8, writeFile as writeFile3 } from "node:fs/promises";
162
+ import { mkdir as mkdir4, rename as rename2, rm, stat as stat10, writeFile as writeFile3 } from "node:fs/promises";
163
163
  import os7 from "node:os";
164
- import path16 from "node:path";
164
+ import path18 from "node:path";
165
165
  import { fileURLToPath } from "node:url";
166
166
 
167
167
  // ../shared/src/index.ts
@@ -197,7 +197,7 @@ var TELEMETRY_EVENT_TYPES = [
197
197
  "agent.operation"
198
198
  ];
199
199
  var FILE_ACTIVITY_OPERATIONS = ["read", "search", "create", "write", "edit", "delete"];
200
- var BACKFILL_SOURCE_IDS = ["codex", "claude-code", "claude-cowork", "opencode", "pi", "agy", "codebuddy", "qoder", "qoder-cn"];
200
+ var BACKFILL_SOURCE_IDS = ["codex", "claude-code", "claude-cowork", "opencode", "pi", "agy", "codebuddy", "qoder", "qoder-cn", "workbuddy", "zcode"];
201
201
  function createWorkspaceId(input) {
202
202
  const basis = input.repoUrl || input.repoRoot || input.projectName || "unknown";
203
203
  return `workspace_${fnv1a(basis)}`;
@@ -349,9 +349,9 @@ function removeVolatileHashFields(value) {
349
349
  continue;
350
350
  }
351
351
  if (key === "refs" && isRecord(item)) {
352
- const refs = { ...item };
353
- delete refs.payloadHash;
354
- result[key] = removeVolatileHashFields(refs);
352
+ const refs2 = { ...item };
353
+ delete refs2.payloadHash;
354
+ result[key] = removeVolatileHashFields(refs2);
355
355
  continue;
356
356
  }
357
357
  result[key] = removeVolatileHashFields(item);
@@ -1195,7 +1195,7 @@ function countTextLines(text) {
1195
1195
  }
1196
1196
 
1197
1197
  // src/lib/constants.ts
1198
- var PACKAGE_VERSION = true ? "0.1.2" : "0.1.1";
1198
+ var PACKAGE_VERSION = true ? "0.1.4" : "0.1.1";
1199
1199
  var DEFAULT_API_URL = "http://121.196.224.82:3001";
1200
1200
  var DEFAULT_BACKFILL_BATCH_SIZE = 50;
1201
1201
  var DEFAULT_BACKFILL_BATCH_BYTES = 800 * 1024;
@@ -1224,13 +1224,13 @@ function isPlainObject(value) {
1224
1224
  return value !== null && typeof value === "object" && !Array.isArray(value);
1225
1225
  }
1226
1226
  function stringRefs(value) {
1227
- const refs = {};
1227
+ const refs2 = {};
1228
1228
  for (const [key, item] of Object.entries(value || {})) {
1229
1229
  if (typeof item === "string" && item.length > 0) {
1230
- refs[key] = item;
1230
+ refs2[key] = item;
1231
1231
  }
1232
1232
  }
1233
- return refs;
1233
+ return refs2;
1234
1234
  }
1235
1235
  function stringOption(value) {
1236
1236
  if (Array.isArray(value)) {
@@ -1308,7 +1308,7 @@ function withBackfillRefs(event, context) {
1308
1308
  event.type,
1309
1309
  event.refs?.sourceId
1310
1310
  ]);
1311
- const refs = {
1311
+ const refs2 = {
1312
1312
  ...stringRefs(event.refs),
1313
1313
  importKey,
1314
1314
  sourcePathHash: context.sourcePathHash,
@@ -1318,12 +1318,12 @@ function withBackfillRefs(event, context) {
1318
1318
  parserVersion: PACKAGE_VERSION
1319
1319
  };
1320
1320
  if (context.options.includeSourcePath) {
1321
- refs.transcriptPath = context.filePath;
1321
+ refs2.transcriptPath = context.filePath;
1322
1322
  }
1323
1323
  return {
1324
1324
  ...event,
1325
1325
  id: createStableEventId(importKey),
1326
- refs
1326
+ refs: refs2
1327
1327
  };
1328
1328
  }
1329
1329
  function matchesBackfillFilters(event, options) {
@@ -2956,9 +2956,9 @@ async function parseClaudeCoworkSessionFile(filePath, options) {
2956
2956
  const sessionId = prefixCoworkId(event.sessionId);
2957
2957
  const turnId = prefixCoworkId(event.turnId);
2958
2958
  const workspaceId = createWorkspaceId({ projectName: project, repoRoot: cwd });
2959
- const refs = { ...event.refs };
2959
+ const refs2 = { ...event.refs };
2960
2960
  if (metadata.title) {
2961
- refs.coworkTitleHash = `sha256:${createStableHash(metadata.title)}`;
2961
+ refs2.coworkTitleHash = `sha256:${createStableHash(metadata.title)}`;
2962
2962
  }
2963
2963
  const mapped = {
2964
2964
  ...event,
@@ -2970,7 +2970,7 @@ async function parseClaudeCoworkSessionFile(filePath, options) {
2970
2970
  project,
2971
2971
  sessionId,
2972
2972
  turnId,
2973
- refs
2973
+ refs: refs2
2974
2974
  };
2975
2975
  return rebuildEventIdentity(mapped);
2976
2976
  });
@@ -3507,8 +3507,8 @@ async function codebuddyBackfillFiles(sourceRoot, home, env) {
3507
3507
  }
3508
3508
  const filePath = path8.join(traceDir, entry);
3509
3509
  try {
3510
- const stat9 = await import("node:fs/promises").then((fs) => fs.stat(filePath));
3511
- files.push({ path: filePath, modifiedAt: stat9.mtime.toISOString() });
3510
+ const stat11 = await import("node:fs/promises").then((fs) => fs.stat(filePath));
3511
+ files.push({ path: filePath, modifiedAt: stat11.mtime.toISOString() });
3512
3512
  } catch {
3513
3513
  }
3514
3514
  }
@@ -4513,19 +4513,19 @@ function opencodeDataCandidates(home, env) {
4513
4513
  return [primary, path10.join(home, ".opencode", "opencode.db")];
4514
4514
  }
4515
4515
  async function opencodeBackfillFiles(sourceRoot, home = os4.homedir(), env) {
4516
- const { stat: stat9 } = await import("node:fs/promises");
4516
+ const { stat: stat11 } = await import("node:fs/promises");
4517
4517
  if (sourceRoot) {
4518
4518
  if (!sourceRoot.endsWith(".db")) {
4519
4519
  return [];
4520
4520
  }
4521
- const info = await stat9(sourceRoot).catch(() => null);
4521
+ const info = await stat11(sourceRoot).catch(() => null);
4522
4522
  if (!info) {
4523
4523
  return [];
4524
4524
  }
4525
4525
  return [{ path: sourceRoot, modifiedAt: info.mtime.toISOString() }];
4526
4526
  }
4527
4527
  for (const candidatePath of opencodeDataCandidates(home, env)) {
4528
- const info = await stat9(candidatePath).catch(() => null);
4528
+ const info = await stat11(candidatePath).catch(() => null);
4529
4529
  if (info) {
4530
4530
  return [{ path: candidatePath, modifiedAt: info.mtime.toISOString() }];
4531
4531
  }
@@ -6580,6 +6580,758 @@ function normalizeId(id) {
6580
6580
  return id;
6581
6581
  }
6582
6582
 
6583
+ // src/adapters/workbuddy.ts
6584
+ import { readFile as readFile10, readdir as readdir7, stat as stat7 } from "node:fs/promises";
6585
+ import path14 from "node:path";
6586
+ init_fs();
6587
+ function workbuddyProjectsDir(home, env) {
6588
+ const override = env?.WORKBUDDY_PROJECTS_DIR || env?.WORKBUDDY_HOME;
6589
+ if (override && override.trim()) {
6590
+ return path14.resolve(override, override.endsWith("projects") ? "" : "projects");
6591
+ }
6592
+ return path14.join(home, ".workbuddy", "projects");
6593
+ }
6594
+ function projectFromCwd(cwd, fallback) {
6595
+ if (!cwd) {
6596
+ return fallback;
6597
+ }
6598
+ return path14.basename(cwd) || fallback;
6599
+ }
6600
+ function sourceHash(filePath) {
6601
+ return `sha256:${createStableHash(filePath)}`;
6602
+ }
6603
+ function makeEvent(event, line, filePath, options) {
6604
+ const withRefs = withBackfillRefs(event, {
6605
+ filePath,
6606
+ sourcePathHash: sourceHash(filePath),
6607
+ lineNumber: line.lineNumber,
6608
+ topType: stringField(line.record, "type"),
6609
+ payloadType: stringField(line.record, "role") || stringField(line.record, "name"),
6610
+ options
6611
+ });
6612
+ return validateCanonicalEvent(withRefs).valid ? withRefs : void 0;
6613
+ }
6614
+ function workbuddyUsageMetrics(record) {
6615
+ const providerData = objectField(record, "providerData");
6616
+ const usage = objectField(providerData, "usage");
6617
+ const rawUsage = objectField(providerData, "rawUsage");
6618
+ if (Object.keys(usage).length === 0 && Object.keys(rawUsage).length === 0) {
6619
+ return void 0;
6620
+ }
6621
+ const input = numberField(usage, "inputTokens") ?? numberField(rawUsage, "prompt_tokens") ?? 0;
6622
+ const output = numberField(usage, "outputTokens") ?? numberField(rawUsage, "completion_tokens") ?? 0;
6623
+ const total = numberField(usage, "totalTokens") ?? numberField(rawUsage, "total_tokens") ?? 0;
6624
+ const cacheRead = workbuddyCachedTokens(usage, rawUsage);
6625
+ const cacheCreate = numberField(rawUsage, "cache_creation_input_tokens") ?? numberField(rawUsage, "prompt_cache_write_tokens") ?? 0;
6626
+ const reasoning = workbuddyReasoningTokens(usage, rawUsage);
6627
+ if (!input && !output && !total && !cacheRead && !cacheCreate && !reasoning) {
6628
+ return void 0;
6629
+ }
6630
+ return {
6631
+ tokensInput: input,
6632
+ tokensOutput: output,
6633
+ tokensCachedInput: cacheRead + cacheCreate,
6634
+ tokensCacheCreationInput: cacheCreate,
6635
+ tokensCacheReadInput: cacheRead,
6636
+ tokensReasoningOutput: reasoning,
6637
+ tokensTotal: total || input + output,
6638
+ modelCalls: 1
6639
+ };
6640
+ }
6641
+ function workbuddyCachedTokens(usage, rawUsage) {
6642
+ const rawCache = numberField(rawUsage, "cache_read_input_tokens") ?? numberField(rawUsage, "prompt_cache_hit_tokens") ?? numberField(rawUsage, "cached_tokens");
6643
+ if (rawCache !== void 0) {
6644
+ return rawCache;
6645
+ }
6646
+ const details = usage.inputTokensDetails;
6647
+ if (Array.isArray(details) && isPlainObject(details[0])) {
6648
+ return numberField(details[0], "cached_tokens") || 0;
6649
+ }
6650
+ const rawDetails = objectField(rawUsage, "prompt_tokens_details");
6651
+ return numberField(rawDetails, "cached_tokens") || 0;
6652
+ }
6653
+ function workbuddyReasoningTokens(usage, rawUsage) {
6654
+ const completionDetails = objectField(rawUsage, "completion_tokens_details");
6655
+ const raw = numberField(completionDetails, "reasoning_tokens") ?? numberField(rawUsage, "completion_thinking_tokens");
6656
+ if (raw !== void 0) {
6657
+ return raw;
6658
+ }
6659
+ const details = usage.outputTokensDetails;
6660
+ if (Array.isArray(details) && isPlainObject(details[0])) {
6661
+ return numberField(details[0], "reasoning_tokens") || 0;
6662
+ }
6663
+ return 0;
6664
+ }
6665
+ function contentLength(value) {
6666
+ if (typeof value === "string") {
6667
+ return value.length;
6668
+ }
6669
+ if (Array.isArray(value)) {
6670
+ return value.reduce((total, item) => {
6671
+ if (!isPlainObject(item)) {
6672
+ return total;
6673
+ }
6674
+ return total + (stringField(item, "text") || stringField(item, "input_text") || stringField(item, "output_text") || "").length;
6675
+ }, 0);
6676
+ }
6677
+ return 0;
6678
+ }
6679
+ async function readWorkbuddyLines(filePath) {
6680
+ const text = await readFile10(filePath, "utf8");
6681
+ return text.split(/\r?\n/).map((line, index) => {
6682
+ if (!line.trim()) {
6683
+ return void 0;
6684
+ }
6685
+ try {
6686
+ const record = JSON.parse(line);
6687
+ return isPlainObject(record) ? { lineNumber: index + 1, record } : void 0;
6688
+ } catch {
6689
+ return void 0;
6690
+ }
6691
+ }).filter((line) => Boolean(line));
6692
+ }
6693
+ async function parseWorkbuddySessionFile(filePath, options) {
6694
+ const lines = await readWorkbuddyLines(filePath);
6695
+ if (lines.length === 0) {
6696
+ return [];
6697
+ }
6698
+ const events = [];
6699
+ const first = lines[0].record;
6700
+ const sessionId = stringField(first, "sessionId") || path14.basename(filePath, ".jsonl");
6701
+ const fallbackProject = path14.basename(path14.dirname(filePath));
6702
+ const cwd = lines.map((line) => stringField(line.record, "cwd")).find(Boolean);
6703
+ const project = projectFromCwd(cwd, fallbackProject);
6704
+ const workspaceId = createWorkspaceId({ projectName: project, repoRoot: cwd });
6705
+ const sessionPrefix = `workbuddy:${sessionId}`;
6706
+ const toolStarts = /* @__PURE__ */ new Map();
6707
+ let currentTurnId = `${sessionPrefix}:turn:0`;
6708
+ let turnIndex = 0;
6709
+ const firstTs = lines.map((line) => timestampFrom(numberField(line.record, "timestamp"))).find(Boolean);
6710
+ if (firstTs) {
6711
+ const event = makeEvent({
6712
+ schemaVersion: AGENT_TIME_SCHEMA_VERSION,
6713
+ ts: firstTs,
6714
+ type: "session.started",
6715
+ source: "workbuddy",
6716
+ workspaceId,
6717
+ project,
6718
+ cwd,
6719
+ sessionId: sessionPrefix,
6720
+ agent: "workbuddy",
6721
+ confidence: "derived",
6722
+ refs: { sourceId: `${sessionPrefix}:session` }
6723
+ }, lines[0], filePath, options);
6724
+ if (event) events.push(event);
6725
+ }
6726
+ for (const line of lines) {
6727
+ const record = line.record;
6728
+ const ts = timestampFrom(numberField(record, "timestamp"));
6729
+ if (!ts) {
6730
+ continue;
6731
+ }
6732
+ const type = stringField(record, "type");
6733
+ const sourceId = stringField(record, "id") || `${line.lineNumber}`;
6734
+ if (type === "message" && stringField(record, "role") === "user") {
6735
+ turnIndex += 1;
6736
+ currentTurnId = `${sessionPrefix}:turn:${turnIndex}`;
6737
+ const promptChars = contentLength(record.content);
6738
+ for (const event of [
6739
+ makeEvent({
6740
+ schemaVersion: AGENT_TIME_SCHEMA_VERSION,
6741
+ ts,
6742
+ type: "turn.started",
6743
+ source: "workbuddy",
6744
+ workspaceId,
6745
+ project,
6746
+ cwd,
6747
+ sessionId: sessionPrefix,
6748
+ turnId: currentTurnId,
6749
+ agent: "workbuddy",
6750
+ confidence: "derived",
6751
+ refs: { sourceId: `${sourceId}:turn` }
6752
+ }, line, filePath, options),
6753
+ makeEvent({
6754
+ schemaVersion: AGENT_TIME_SCHEMA_VERSION,
6755
+ ts,
6756
+ type: "prompt.submitted",
6757
+ source: "workbuddy",
6758
+ workspaceId,
6759
+ project,
6760
+ cwd,
6761
+ sessionId: sessionPrefix,
6762
+ turnId: currentTurnId,
6763
+ agent: "workbuddy",
6764
+ metrics: { promptChars, prompts: 1 },
6765
+ confidence: "partial",
6766
+ refs: { sourceId: `${sourceId}:prompt` }
6767
+ }, line, filePath, options)
6768
+ ]) {
6769
+ if (event) events.push(event);
6770
+ }
6771
+ continue;
6772
+ }
6773
+ if (type === "function_call") {
6774
+ const tool = stringField(record, "name");
6775
+ const callId = stringField(record, "callId");
6776
+ if (tool && callId) {
6777
+ toolStarts.set(`${sessionId}:${callId}`, { line, ts, tool, callId });
6778
+ const started = makeEvent({
6779
+ schemaVersion: AGENT_TIME_SCHEMA_VERSION,
6780
+ ts,
6781
+ type: "tool.started",
6782
+ source: "workbuddy",
6783
+ workspaceId,
6784
+ project,
6785
+ cwd,
6786
+ sessionId: sessionPrefix,
6787
+ turnId: currentTurnId,
6788
+ spanId: `${sessionPrefix}:tool:${callId}`,
6789
+ agent: "workbuddy",
6790
+ tool,
6791
+ confidence: "exact",
6792
+ refs: { sourceId: `${callId}:start` }
6793
+ }, line, filePath, options);
6794
+ if (started) events.push(started);
6795
+ }
6796
+ const metrics = workbuddyUsageMetrics(record);
6797
+ const model = stringField(objectField(record, "providerData"), "model");
6798
+ if (metrics || model) {
6799
+ const usage = makeEvent({
6800
+ schemaVersion: AGENT_TIME_SCHEMA_VERSION,
6801
+ ts,
6802
+ type: "model.usage",
6803
+ source: "workbuddy",
6804
+ workspaceId,
6805
+ project,
6806
+ cwd,
6807
+ sessionId: sessionPrefix,
6808
+ turnId: currentTurnId,
6809
+ agent: "workbuddy",
6810
+ model,
6811
+ metrics,
6812
+ confidence: metrics ? "partial" : "derived",
6813
+ refs: { sourceId: `${sourceId}:usage:${callId || line.lineNumber}` }
6814
+ }, line, filePath, options);
6815
+ if (usage) events.push(usage);
6816
+ }
6817
+ continue;
6818
+ }
6819
+ if (type === "function_call_result") {
6820
+ const tool = stringField(record, "name") || "tool";
6821
+ const callId = stringField(record, "callId") || `${line.lineNumber}`;
6822
+ const start = toolStarts.get(`${sessionId}:${callId}`);
6823
+ const durationMs = start?.ts ? Math.max(0, Date.parse(ts) - Date.parse(start.ts)) : void 0;
6824
+ const status = stringField(record, "status");
6825
+ const completed = makeEvent({
6826
+ schemaVersion: AGENT_TIME_SCHEMA_VERSION,
6827
+ ts,
6828
+ type: status === "failed" ? "tool.failed" : "tool.completed",
6829
+ source: "workbuddy",
6830
+ workspaceId,
6831
+ project,
6832
+ cwd,
6833
+ sessionId: sessionPrefix,
6834
+ turnId: currentTurnId,
6835
+ spanId: `${sessionPrefix}:tool:${callId}`,
6836
+ agent: "workbuddy",
6837
+ tool,
6838
+ success: status !== "failed",
6839
+ metrics: { toolDurationMs: durationMs, durationMs },
6840
+ confidence: start ? "derived" : "partial",
6841
+ refs: { sourceId: `${callId}:result` }
6842
+ }, line, filePath, options);
6843
+ if (completed) events.push(completed);
6844
+ }
6845
+ }
6846
+ const lastLine = lines.at(-1);
6847
+ const lastTs = lastLine ? timestampFrom(numberField(lastLine.record, "timestamp")) : void 0;
6848
+ if (lastLine && lastTs) {
6849
+ const event = makeEvent({
6850
+ schemaVersion: AGENT_TIME_SCHEMA_VERSION,
6851
+ ts: lastTs,
6852
+ type: "session.ended",
6853
+ source: "workbuddy",
6854
+ workspaceId,
6855
+ project,
6856
+ cwd,
6857
+ sessionId: sessionPrefix,
6858
+ agent: "workbuddy",
6859
+ confidence: "derived",
6860
+ refs: { sourceId: `${sessionPrefix}:ended` }
6861
+ }, lastLine, filePath, options);
6862
+ if (event) events.push(event);
6863
+ }
6864
+ return events;
6865
+ }
6866
+ async function workbuddyBackfillFiles(sourceRoot, home, env) {
6867
+ const base = sourceRoot || workbuddyProjectsDir(home, env);
6868
+ const files = [];
6869
+ try {
6870
+ const projects = await readdir7(base, { withFileTypes: true });
6871
+ for (const project of projects) {
6872
+ if (!project.isDirectory()) {
6873
+ continue;
6874
+ }
6875
+ const projectDir = path14.join(base, project.name);
6876
+ const entries = await readdir7(projectDir, { withFileTypes: true });
6877
+ for (const entry of entries) {
6878
+ if (entry.isFile() && entry.name.endsWith(".jsonl")) {
6879
+ const filePath = path14.join(projectDir, entry.name);
6880
+ const info = await stat7(filePath);
6881
+ files.push({ path: filePath, modifiedAt: info.mtime.toISOString() });
6882
+ }
6883
+ }
6884
+ }
6885
+ } catch {
6886
+ return [];
6887
+ }
6888
+ return files.sort((a, b) => a.path.localeCompare(b.path));
6889
+ }
6890
+ function createWorkbuddyAdapter() {
6891
+ return {
6892
+ id: "workbuddy",
6893
+ label: "WorkBuddy",
6894
+ agentName: "workbuddy",
6895
+ kind: "agent",
6896
+ detectPath(home, env) {
6897
+ return workbuddyProjectsDir(home, env);
6898
+ },
6899
+ installedPath(home, env) {
6900
+ return workbuddyProjectsDir(home, env);
6901
+ },
6902
+ async isInstalled(home, env) {
6903
+ return pathExists(workbuddyProjectsDir(home, env));
6904
+ },
6905
+ installEntries() {
6906
+ return [];
6907
+ },
6908
+ sourcePaths(home, env) {
6909
+ return [workbuddyProjectsDir(home, env)];
6910
+ },
6911
+ parseSessionFile: parseWorkbuddySessionFile
6912
+ };
6913
+ }
6914
+
6915
+ // src/adapters/zcode.ts
6916
+ import { execFile } from "node:child_process";
6917
+ import { stat as stat8 } from "node:fs/promises";
6918
+ import path15 from "node:path";
6919
+ import { promisify as promisify2 } from "node:util";
6920
+ init_fs();
6921
+ var execFileAsync = promisify2(execFile);
6922
+ function zcodeCliDir(home, env) {
6923
+ const override = env?.ZCODE_CLI_DIR || env?.ZCODE_HOME;
6924
+ if (override && override.trim()) {
6925
+ return path15.resolve(override, override.endsWith("cli") ? "" : "cli");
6926
+ }
6927
+ return path15.join(home, ".zcode", "cli");
6928
+ }
6929
+ function zcodeDbPath(home, env) {
6930
+ return path15.join(zcodeCliDir(home, env), "db", "db.sqlite");
6931
+ }
6932
+ function sourceHash2(filePath) {
6933
+ return `sha256:${createStableHash(filePath)}`;
6934
+ }
6935
+ function projectFromDirectory(directory) {
6936
+ return directory ? path15.basename(directory) || "zcode" : "zcode";
6937
+ }
6938
+ function isoFromMs(value) {
6939
+ return timestampFrom(typeof value === "number" ? value : Number(value));
6940
+ }
6941
+ function makeEvent2(event, row, rowNumber, filePath, options) {
6942
+ const withRefs = withBackfillRefs(event, {
6943
+ filePath,
6944
+ sourcePathHash: sourceHash2(filePath),
6945
+ lineNumber: rowNumber,
6946
+ topType: row.kind,
6947
+ payloadType: stringField(row, "status") || stringField(row, "query_source") || stringField(row, "tool_name"),
6948
+ options
6949
+ });
6950
+ return validateCanonicalEvent(withRefs).valid ? withRefs : void 0;
6951
+ }
6952
+ function refs(values) {
6953
+ const out = {};
6954
+ for (const [key, value] of Object.entries(values)) {
6955
+ if (value !== void 0 && value !== "") {
6956
+ out[key] = String(value);
6957
+ }
6958
+ }
6959
+ return out;
6960
+ }
6961
+ async function sqliteJsonRows(dbPath, sql) {
6962
+ try {
6963
+ const { stdout } = await execFileAsync("sqlite3", ["-readonly", dbPath, sql], {
6964
+ maxBuffer: 32 * 1024 * 1024
6965
+ });
6966
+ return stdout.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).map((line) => {
6967
+ try {
6968
+ const parsed = JSON.parse(line);
6969
+ return isPlainObject(parsed) ? parsed : void 0;
6970
+ } catch {
6971
+ return void 0;
6972
+ }
6973
+ }).filter((row) => Boolean(row));
6974
+ } catch {
6975
+ return [];
6976
+ }
6977
+ }
6978
+ async function readZCodeRows(dbPath) {
6979
+ const queries = [
6980
+ `select json_object(
6981
+ 'kind','session',
6982
+ 'id',id,
6983
+ 'parent_id',parent_id,
6984
+ 'directory',directory,
6985
+ 'title',title,
6986
+ 'task_type',task_type,
6987
+ 'time_created',time_created,
6988
+ 'time_updated',time_updated
6989
+ ) from session order by time_created, id;`,
6990
+ `select json_object(
6991
+ 'kind','turn',
6992
+ 'session_id',session_id,
6993
+ 'turn_id',turn_id,
6994
+ 'status',status,
6995
+ 'started_at',started_at,
6996
+ 'completed_at',completed_at,
6997
+ 'duration_ms',duration_ms,
6998
+ 'model_request_count',model_request_count,
6999
+ 'tool_call_count',tool_call_count,
7000
+ 'input_tokens',input_tokens,
7001
+ 'output_tokens',output_tokens,
7002
+ 'reasoning_tokens',reasoning_tokens,
7003
+ 'cache_creation_input_tokens',cache_creation_input_tokens,
7004
+ 'cache_read_input_tokens',cache_read_input_tokens,
7005
+ 'computed_total_tokens',computed_total_tokens
7006
+ ) from turn_usage order by started_at, session_id, turn_id;`,
7007
+ `select json_object(
7008
+ 'kind','model',
7009
+ 'id',id,
7010
+ 'session_id',session_id,
7011
+ 'turn_id',turn_id,
7012
+ 'query_source',query_source,
7013
+ 'provider_id',provider_id,
7014
+ 'model_id',model_id,
7015
+ 'agent',agent,
7016
+ 'mode',mode,
7017
+ 'status',status,
7018
+ 'started_at',started_at,
7019
+ 'completed_at',completed_at,
7020
+ 'duration_ms',duration_ms,
7021
+ 'input_tokens',input_tokens,
7022
+ 'output_tokens',output_tokens,
7023
+ 'reasoning_tokens',reasoning_tokens,
7024
+ 'cache_creation_input_tokens',cache_creation_input_tokens,
7025
+ 'cache_read_input_tokens',cache_read_input_tokens,
7026
+ 'computed_total_tokens',computed_total_tokens,
7027
+ 'tool_call_count',tool_call_count
7028
+ ) from model_usage order by started_at, id;`,
7029
+ `select json_object(
7030
+ 'kind','tool',
7031
+ 'id',id,
7032
+ 'session_id',session_id,
7033
+ 'turn_id',turn_id,
7034
+ 'tool_call_id',tool_call_id,
7035
+ 'tool_name',tool_name,
7036
+ 'side_effect_scope',side_effect_scope,
7037
+ 'read_only',read_only,
7038
+ 'destructive',destructive,
7039
+ 'approval_status',approval_status,
7040
+ 'status',status,
7041
+ 'started_at',started_at,
7042
+ 'completed_at',completed_at,
7043
+ 'duration_ms',duration_ms,
7044
+ 'output_bytes',output_bytes,
7045
+ 'exit_code',exit_code
7046
+ ) from tool_usage order by started_at, id;`
7047
+ ];
7048
+ const results = await Promise.all(queries.map((query) => sqliteJsonRows(dbPath, query)));
7049
+ return results.flat();
7050
+ }
7051
+ function modelMetrics(row) {
7052
+ const input = numberField(row, "input_tokens") || 0;
7053
+ const output = numberField(row, "output_tokens") || 0;
7054
+ const reasoning = numberField(row, "reasoning_tokens") || 0;
7055
+ const cacheCreation = numberField(row, "cache_creation_input_tokens") || 0;
7056
+ const cacheRead = numberField(row, "cache_read_input_tokens") || 0;
7057
+ const total = numberField(row, "computed_total_tokens") || input + output + reasoning;
7058
+ const durationMs = numberField(row, "duration_ms");
7059
+ return {
7060
+ tokensInput: input,
7061
+ tokensOutput: output,
7062
+ tokensReasoningOutput: reasoning,
7063
+ tokensCachedInput: cacheCreation + cacheRead,
7064
+ tokensCacheCreationInput: cacheCreation,
7065
+ tokensCacheReadInput: cacheRead,
7066
+ tokensTotal: total,
7067
+ modelDurationMs: durationMs,
7068
+ durationMs,
7069
+ modelCalls: 1,
7070
+ toolCalls: numberField(row, "tool_call_count")
7071
+ };
7072
+ }
7073
+ function sessionContext(row, sessions) {
7074
+ const sessionId = stringField(row, "session_id") || stringField(row, "id") || "unknown";
7075
+ const session = sessions.get(sessionId);
7076
+ const cwd = stringField(session, "directory");
7077
+ const project = projectFromDirectory(cwd);
7078
+ return {
7079
+ rawSessionId: sessionId,
7080
+ sessionId: `zcode:${sessionId}`,
7081
+ cwd,
7082
+ project,
7083
+ workspaceId: createWorkspaceId({ projectName: project, repoRoot: cwd }),
7084
+ parentSessionId: stringField(session, "parent_id") ? `zcode:${stringField(session, "parent_id")}` : void 0
7085
+ };
7086
+ }
7087
+ async function parseZCodeDb(filePath, options) {
7088
+ const rows = await readZCodeRows(filePath);
7089
+ if (rows.length === 0) {
7090
+ return [];
7091
+ }
7092
+ const sessions = /* @__PURE__ */ new Map();
7093
+ for (const row of rows) {
7094
+ if (row.kind === "session") {
7095
+ const id = stringField(row, "id");
7096
+ if (id) {
7097
+ sessions.set(id, row);
7098
+ }
7099
+ }
7100
+ }
7101
+ const events = [];
7102
+ rows.forEach((row, index) => {
7103
+ const rowNumber = index + 1;
7104
+ if (row.kind === "session") {
7105
+ const id = stringField(row, "id");
7106
+ const ts = isoFromMs(numberField(row, "time_created"));
7107
+ if (!id || !ts) {
7108
+ return;
7109
+ }
7110
+ const ctx = sessionContext(row, sessions);
7111
+ const event = makeEvent2({
7112
+ schemaVersion: AGENT_TIME_SCHEMA_VERSION,
7113
+ ts,
7114
+ type: "session.started",
7115
+ source: "zcode",
7116
+ workspaceId: ctx.workspaceId,
7117
+ project: ctx.project,
7118
+ cwd: ctx.cwd,
7119
+ sessionId: ctx.sessionId,
7120
+ parentAgentInstanceId: ctx.parentSessionId,
7121
+ agent: "zcode",
7122
+ confidence: "exact",
7123
+ refs: refs({
7124
+ sourceId: `${id}:session`,
7125
+ zcodeTaskType: stringField(row, "task_type"),
7126
+ zcodeTitleHash: stringField(row, "title") ? `sha256:${createStableHash(stringField(row, "title"))}` : void 0
7127
+ })
7128
+ }, row, rowNumber, filePath, options);
7129
+ if (event) events.push(event);
7130
+ const completedTs = isoFromMs(numberField(row, "time_updated"));
7131
+ if (completedTs && completedTs !== ts) {
7132
+ const completed = makeEvent2({
7133
+ schemaVersion: AGENT_TIME_SCHEMA_VERSION,
7134
+ ts: completedTs,
7135
+ type: "session.ended",
7136
+ source: "zcode",
7137
+ workspaceId: ctx.workspaceId,
7138
+ project: ctx.project,
7139
+ cwd: ctx.cwd,
7140
+ sessionId: ctx.sessionId,
7141
+ parentAgentInstanceId: ctx.parentSessionId,
7142
+ agent: "zcode",
7143
+ confidence: "exact",
7144
+ refs: { sourceId: `${id}:session:ended` }
7145
+ }, row, rowNumber, filePath, options);
7146
+ if (completed) events.push(completed);
7147
+ }
7148
+ return;
7149
+ }
7150
+ if (row.kind === "turn") {
7151
+ const rawTurnId = stringField(row, "turn_id");
7152
+ const ts = isoFromMs(numberField(row, "started_at"));
7153
+ if (!rawTurnId || !ts) {
7154
+ return;
7155
+ }
7156
+ const ctx = sessionContext(row, sessions);
7157
+ const turnId = `zcode:${rawTurnId}`;
7158
+ const started = makeEvent2({
7159
+ schemaVersion: AGENT_TIME_SCHEMA_VERSION,
7160
+ ts,
7161
+ type: "turn.started",
7162
+ source: "zcode",
7163
+ workspaceId: ctx.workspaceId,
7164
+ project: ctx.project,
7165
+ cwd: ctx.cwd,
7166
+ sessionId: ctx.sessionId,
7167
+ turnId,
7168
+ agent: "zcode",
7169
+ confidence: "exact",
7170
+ refs: { sourceId: `${rawTurnId}:start` }
7171
+ }, row, rowNumber, filePath, options);
7172
+ if (started) events.push(started);
7173
+ const completedTs = isoFromMs(numberField(row, "completed_at"));
7174
+ if (completedTs) {
7175
+ const status = stringField(row, "status");
7176
+ const completed = makeEvent2({
7177
+ schemaVersion: AGENT_TIME_SCHEMA_VERSION,
7178
+ ts: completedTs,
7179
+ type: "turn.completed",
7180
+ source: "zcode",
7181
+ workspaceId: ctx.workspaceId,
7182
+ project: ctx.project,
7183
+ cwd: ctx.cwd,
7184
+ sessionId: ctx.sessionId,
7185
+ turnId,
7186
+ agent: "zcode",
7187
+ success: status === "completed",
7188
+ metrics: {
7189
+ durationMs: numberField(row, "duration_ms"),
7190
+ turns: 1,
7191
+ modelCalls: numberField(row, "model_request_count"),
7192
+ toolCalls: numberField(row, "tool_call_count")
7193
+ },
7194
+ confidence: "exact",
7195
+ refs: refs({ sourceId: `${rawTurnId}:completed`, zcodeStatus: status })
7196
+ }, row, rowNumber, filePath, options);
7197
+ if (completed) events.push(completed);
7198
+ }
7199
+ return;
7200
+ }
7201
+ if (row.kind === "model") {
7202
+ const ts = isoFromMs(numberField(row, "completed_at")) || isoFromMs(numberField(row, "started_at"));
7203
+ if (!ts) {
7204
+ return;
7205
+ }
7206
+ const ctx = sessionContext(row, sessions);
7207
+ const modelId = stringField(row, "model_id");
7208
+ const usage = makeEvent2({
7209
+ schemaVersion: AGENT_TIME_SCHEMA_VERSION,
7210
+ ts,
7211
+ type: "model.usage",
7212
+ source: "zcode",
7213
+ workspaceId: ctx.workspaceId,
7214
+ project: ctx.project,
7215
+ cwd: ctx.cwd,
7216
+ sessionId: ctx.sessionId,
7217
+ turnId: stringField(row, "turn_id") ? `zcode:${stringField(row, "turn_id")}` : void 0,
7218
+ agent: stringField(row, "agent") || "zcode",
7219
+ provider: stringField(row, "provider_id"),
7220
+ model: modelId,
7221
+ success: stringField(row, "status") === "completed",
7222
+ metrics: modelMetrics(row),
7223
+ confidence: "exact",
7224
+ refs: refs({
7225
+ sourceId: stringField(row, "id") || `${ctx.rawSessionId}:model:${rowNumber}`,
7226
+ zcodeQuerySource: stringField(row, "query_source"),
7227
+ zcodeMode: stringField(row, "mode"),
7228
+ zcodeStatus: stringField(row, "status")
7229
+ })
7230
+ }, row, rowNumber, filePath, options);
7231
+ if (usage) events.push(usage);
7232
+ return;
7233
+ }
7234
+ if (row.kind === "tool") {
7235
+ const startedTs = isoFromMs(numberField(row, "started_at"));
7236
+ const completedTs = isoFromMs(numberField(row, "completed_at")) || startedTs;
7237
+ const tool = stringField(row, "tool_name") || "tool";
7238
+ const toolCallId = stringField(row, "tool_call_id") || stringField(row, "id") || `${rowNumber}`;
7239
+ if (!startedTs) {
7240
+ return;
7241
+ }
7242
+ const ctx = sessionContext(row, sessions);
7243
+ const turnId = stringField(row, "turn_id") ? `zcode:${stringField(row, "turn_id")}` : void 0;
7244
+ const spanId = `${ctx.sessionId}:tool:${toolCallId}`;
7245
+ const started = makeEvent2({
7246
+ schemaVersion: AGENT_TIME_SCHEMA_VERSION,
7247
+ ts: startedTs,
7248
+ type: "tool.started",
7249
+ source: "zcode",
7250
+ workspaceId: ctx.workspaceId,
7251
+ project: ctx.project,
7252
+ cwd: ctx.cwd,
7253
+ sessionId: ctx.sessionId,
7254
+ turnId,
7255
+ spanId,
7256
+ agent: "zcode",
7257
+ tool,
7258
+ confidence: "exact",
7259
+ refs: refs({
7260
+ sourceId: `${toolCallId}:start`,
7261
+ zcodeSideEffectScope: stringField(row, "side_effect_scope"),
7262
+ zcodeApprovalStatus: stringField(row, "approval_status")
7263
+ })
7264
+ }, row, rowNumber, filePath, options);
7265
+ if (started) events.push(started);
7266
+ if (completedTs) {
7267
+ const status = stringField(row, "status");
7268
+ const completed = makeEvent2({
7269
+ schemaVersion: AGENT_TIME_SCHEMA_VERSION,
7270
+ ts: completedTs,
7271
+ type: status === "completed" ? "tool.completed" : "tool.failed",
7272
+ source: "zcode",
7273
+ workspaceId: ctx.workspaceId,
7274
+ project: ctx.project,
7275
+ cwd: ctx.cwd,
7276
+ sessionId: ctx.sessionId,
7277
+ turnId,
7278
+ spanId,
7279
+ agent: "zcode",
7280
+ tool,
7281
+ success: status === "completed",
7282
+ metrics: {
7283
+ toolDurationMs: numberField(row, "duration_ms"),
7284
+ durationMs: numberField(row, "duration_ms"),
7285
+ bytesRead: numberField(row, "output_bytes")
7286
+ },
7287
+ confidence: "exact",
7288
+ refs: refs({
7289
+ sourceId: `${toolCallId}:completed`,
7290
+ zcodeStatus: status,
7291
+ zcodeExitCode: numberField(row, "exit_code")
7292
+ })
7293
+ }, row, rowNumber, filePath, options);
7294
+ if (completed) events.push(completed);
7295
+ }
7296
+ }
7297
+ });
7298
+ return events.sort((a, b) => a.ts.localeCompare(b.ts) || (a.id || "").localeCompare(b.id || ""));
7299
+ }
7300
+ async function zcodeBackfillFiles(sourceRoot, home, env) {
7301
+ const candidate = sourceRoot || zcodeDbPath(home, env);
7302
+ const filePath = candidate.endsWith(".sqlite") ? candidate : path15.join(candidate, "db", "db.sqlite");
7303
+ try {
7304
+ const info = await stat8(filePath);
7305
+ return [{ path: filePath, modifiedAt: info.mtime.toISOString() }];
7306
+ } catch {
7307
+ return [];
7308
+ }
7309
+ }
7310
+ function createZCodeAdapter() {
7311
+ return {
7312
+ id: "zcode",
7313
+ label: "ZCode",
7314
+ agentName: "zcode",
7315
+ kind: "agent",
7316
+ detectPath(home, env) {
7317
+ return zcodeCliDir(home, env);
7318
+ },
7319
+ installedPath(home, env) {
7320
+ return zcodeDbPath(home, env);
7321
+ },
7322
+ async isInstalled(home, env) {
7323
+ return pathExists(zcodeDbPath(home, env));
7324
+ },
7325
+ installEntries() {
7326
+ return [];
7327
+ },
7328
+ sourcePaths(home, env) {
7329
+ return [zcodeDbPath(home, env)];
7330
+ },
7331
+ parseSessionFile: parseZCodeDb
7332
+ };
7333
+ }
7334
+
6583
7335
  // src/lib/pricing.ts
6584
7336
  function estimateEventCostUsd(event) {
6585
7337
  if (event.type !== "model.usage") {
@@ -7016,15 +7768,15 @@ function hookCommandFromGroup(group) {
7016
7768
  import { randomUUID } from "node:crypto";
7017
7769
  import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
7018
7770
  import { homedir, hostname } from "node:os";
7019
- import path14 from "node:path";
7771
+ import path16 from "node:path";
7020
7772
  function configDir(home = homedir()) {
7021
- return path14.join(home, ".vibetime");
7773
+ return path16.join(home, ".vibetime");
7022
7774
  }
7023
7775
  function configPath(home = homedir()) {
7024
- return path14.join(configDir(home), "config.json");
7776
+ return path16.join(configDir(home), "config.json");
7025
7777
  }
7026
7778
  function machineIdPath(home = homedir()) {
7027
- return path14.join(configDir(home), "machine-id");
7779
+ return path16.join(configDir(home), "machine-id");
7028
7780
  }
7029
7781
  function readConfig(home = homedir()) {
7030
7782
  const file = configPath(home);
@@ -7071,15 +7823,15 @@ function defaultMachineName() {
7071
7823
  init_fs();
7072
7824
 
7073
7825
  // src/lib/logger.ts
7074
- import { appendFile, mkdir as mkdir3, rename, stat as stat7 } from "node:fs/promises";
7826
+ import { appendFile, mkdir as mkdir3, rename, stat as stat9 } from "node:fs/promises";
7075
7827
  import { homedir as homedir2 } from "node:os";
7076
- import path15 from "node:path";
7828
+ import path17 from "node:path";
7077
7829
  var MAX_BYTES = 1 * 1024 * 1024;
7078
7830
  function logDir(home = homedir2()) {
7079
- return path15.join(home, ".vibetime", "logs");
7831
+ return path17.join(home, ".vibetime", "logs");
7080
7832
  }
7081
7833
  function logPath(home = homedir2(), name = "cli.log") {
7082
- return path15.join(logDir(home), name);
7834
+ return path17.join(logDir(home), name);
7083
7835
  }
7084
7836
  function serializeError(error) {
7085
7837
  if (error instanceof Error) {
@@ -7089,7 +7841,7 @@ function serializeError(error) {
7089
7841
  }
7090
7842
  async function rotateIfNeeded(file) {
7091
7843
  try {
7092
- const info = await stat7(file);
7844
+ const info = await stat9(file);
7093
7845
  if (info.size > MAX_BYTES) {
7094
7846
  await rename(file, `${file}.1`).catch(() => {
7095
7847
  });
@@ -7254,8 +8006,8 @@ function buildHeaders(token, machine) {
7254
8006
  ...machine?.platform ? { "x-machine-platform": machine.platform } : {}
7255
8007
  };
7256
8008
  }
7257
- function joinUrl(base, path17) {
7258
- return new URL(path17, base.endsWith("/") ? base : `${base}/`).toString();
8009
+ function joinUrl(base, path19) {
8010
+ return new URL(path19, base.endsWith("/") ? base : `${base}/`).toString();
7259
8011
  }
7260
8012
  async function postRollupBatch(remote, rollups, options = {}) {
7261
8013
  const response = await remote.fetchImpl(joinUrl(remote.baseUrl, "/v3/agent/ingest"), {
@@ -7320,7 +8072,7 @@ async function deleteMachine(remote, id) {
7320
8072
  }
7321
8073
 
7322
8074
  // src/lib/types.ts
7323
- var BACKFILL_STATE_SCHEMA_VERSION = 4;
8075
+ var BACKFILL_STATE_SCHEMA_VERSION = 5;
7324
8076
 
7325
8077
  // src/cli.ts
7326
8078
  function createRegistry() {
@@ -7334,6 +8086,8 @@ function createRegistry() {
7334
8086
  registry.register(createCodebuddyAdapter());
7335
8087
  registry.register(createQoderAdapter());
7336
8088
  registry.register(createQoderCnAdapter());
8089
+ registry.register(createWorkbuddyAdapter());
8090
+ registry.register(createZCodeAdapter());
7337
8091
  return registry;
7338
8092
  }
7339
8093
  var defaultContext = {
@@ -7745,11 +8499,17 @@ async function listBackfillSourceFiles(source, options, ctx) {
7745
8499
  if (source.id === "codebuddy") {
7746
8500
  return codebuddyBackfillFiles(stringOption(options["source-root"]), resolveHome2(options, ctx), ctx.env);
7747
8501
  }
8502
+ if (source.id === "workbuddy") {
8503
+ return workbuddyBackfillFiles(stringOption(options["source-root"]), resolveHome2(options, ctx), ctx.env);
8504
+ }
8505
+ if (source.id === "zcode") {
8506
+ return zcodeBackfillFiles(stringOption(options["source-root"]), resolveHome2(options, ctx), ctx.env);
8507
+ }
7748
8508
  const roots = stringOption(options["source-root"]) ? [requiredOption(options, "source-root")] : source.paths;
7749
8509
  const fileLists = await Promise.all(roots.map((r) => listJsonlFiles(r)));
7750
8510
  const files = fileLists.flat().sort().slice(0, numberOption(options.limit) || void 0);
7751
8511
  return Promise.all(files.map(async (filePath) => {
7752
- const info = await stat8(filePath);
8512
+ const info = await stat10(filePath);
7753
8513
  return { path: filePath, modifiedAt: info.mtime.toISOString() };
7754
8514
  }));
7755
8515
  }
@@ -7833,10 +8593,12 @@ async function importBackfillPlan(plan, options, ctx, registry) {
7833
8593
  return 1;
7834
8594
  }
7835
8595
  const sourceDefs = registry.all().filter((a) => supportedSources.has(a.id) && (source === "all" || a.id === source)).map((a) => ({ id: a.id, label: a.label, paths: a.sourcePaths(home, ctx.env) }));
8596
+ const remote = resolveRemoteFromOptions(options, ctx);
8597
+ const remoteKey = backfillRemoteKey(remote?.baseUrl ?? DEFAULT_API_URL);
7836
8598
  if (options.force) {
7837
- await purgeForcedSources(sourceDefs, home, options, ctx);
8599
+ await purgeForcedSources(sourceDefs, home, remoteKey, options, ctx);
7838
8600
  }
7839
- const incrementalState = shouldUseIncrementalBackfill(options) ? await readBackfillIncrementalState(home, ctx) : void 0;
8601
+ const incrementalState = shouldUseIncrementalBackfill(options) ? await readBackfillIncrementalState(home, remoteKey, ctx) : void 0;
7840
8602
  if (!options.json) {
7841
8603
  write(ctx.stdout, `importRun ${plan.importRun.importRunId}
7842
8604
  `);
@@ -7864,13 +8626,15 @@ async function importBackfillPlan(plan, options, ctx, registry) {
7864
8626
  `);
7865
8627
  }
7866
8628
  if (counts.failed === 0 && counts.conflicts === 0 && incrementalState) {
7867
- await updateBackfillIncrementalState(home, incrementalState, selectedFilesBySource);
8629
+ await updateBackfillIncrementalState(home, remoteKey, incrementalState, selectedFilesBySource);
7868
8630
  }
7869
8631
  return counts.failed > 0 || counts.conflicts > 0 && !options["skip-conflicts"] ? 1 : 0;
7870
8632
  }
7871
- async function purgeForcedSources(sourceDefs, home, options, ctx) {
8633
+ async function purgeForcedSources(sourceDefs, home, remoteKey, options, ctx) {
7872
8634
  try {
7873
- await rm(backfillIncrementalStatePath(home), { force: true });
8635
+ const file = await readBackfillIncrementalStateFile(home);
8636
+ delete file.remotes[remoteKey];
8637
+ await writeBackfillIncrementalStateFile(home, file);
7874
8638
  } catch (error) {
7875
8639
  debug(ctx, `Failed to clear backfill watermark: ${error.message}
7876
8640
  `);
@@ -8058,44 +8822,83 @@ function selectBackfillFilesForImport(files, watermarkTs) {
8058
8822
  });
8059
8823
  }
8060
8824
  function backfillIncrementalStatePath(home) {
8061
- return path16.join(home, ".vibetime", "backfill-state.json");
8825
+ return path18.join(home, ".vibetime", "backfill-state.json");
8062
8826
  }
8063
8827
  function syncLocalTriggerStatePath(home) {
8064
- return path16.join(home, ".vibetime", "sync-local-trigger.json");
8828
+ return path18.join(home, ".vibetime", "sync-local-trigger.json");
8065
8829
  }
8066
8830
  function syncLocalTriggerLockPath(home) {
8067
- return path16.join(home, ".vibetime", "sync-local-trigger.lock");
8831
+ return path18.join(home, ".vibetime", "sync-local-trigger.lock");
8832
+ }
8833
+ function backfillRemoteKey(baseUrl) {
8834
+ try {
8835
+ const url = new URL(baseUrl);
8836
+ return `${url.protocol}//${url.host}${url.pathname.replace(/\/+$/, "")}`;
8837
+ } catch {
8838
+ return baseUrl.replace(/\/+$/, "");
8839
+ }
8840
+ }
8841
+ function sanitizeBackfillSources(raw) {
8842
+ const sources = {};
8843
+ if (!isPlainObject(raw)) {
8844
+ return sources;
8845
+ }
8846
+ for (const source of BACKFILL_SOURCE_IDS) {
8847
+ const item = raw[source];
8848
+ if (isPlainObject(item) && typeof item.watermarkTs === "string" && !Number.isNaN(Date.parse(item.watermarkTs))) {
8849
+ sources[source] = { watermarkTs: item.watermarkTs };
8850
+ }
8851
+ }
8852
+ return sources;
8068
8853
  }
8069
- async function readBackfillIncrementalState(home, ctx) {
8854
+ async function readBackfillIncrementalStateFile(home, ctx) {
8855
+ const empty = { version: BACKFILL_STATE_SCHEMA_VERSION, remotes: {} };
8070
8856
  const statePath = backfillIncrementalStatePath(home);
8071
8857
  const state = await readJsonIfExists(statePath);
8072
8858
  if (state === null) {
8073
- return { version: BACKFILL_STATE_SCHEMA_VERSION, sources: {} };
8859
+ return empty;
8074
8860
  }
8075
- if (!isPlainObject(state) || !isPlainObject(state.sources)) {
8861
+ if (!isPlainObject(state)) {
8076
8862
  if (ctx) {
8077
8863
  debug(ctx, `backfill-state malformed at ${statePath}; ignoring watermarks
8078
8864
  `);
8079
8865
  }
8080
- return { version: BACKFILL_STATE_SCHEMA_VERSION, sources: {} };
8866
+ return empty;
8081
8867
  }
8082
8868
  if (state.version !== BACKFILL_STATE_SCHEMA_VERSION) {
8083
8869
  if (ctx) {
8084
8870
  debug(ctx, `backfill-state version ${String(state.version)} at ${statePath} differs from current v${BACKFILL_STATE_SCHEMA_VERSION}; dropping watermarks so the next sync re-imports under the new parser
8085
8871
  `);
8086
8872
  }
8087
- return { version: BACKFILL_STATE_SCHEMA_VERSION, sources: {} };
8873
+ return empty;
8088
8874
  }
8089
- const sources = {};
8090
- for (const source of BACKFILL_SOURCE_IDS) {
8091
- const item = state.sources[source];
8092
- if (isPlainObject(item) && typeof item.watermarkTs === "string" && !Number.isNaN(Date.parse(item.watermarkTs))) {
8093
- sources[source] = { watermarkTs: item.watermarkTs };
8875
+ if (!isPlainObject(state.remotes)) {
8876
+ if (ctx) {
8877
+ debug(ctx, `backfill-state malformed at ${statePath}; ignoring watermarks
8878
+ `);
8094
8879
  }
8880
+ return empty;
8095
8881
  }
8096
- return { version: BACKFILL_STATE_SCHEMA_VERSION, sources };
8882
+ const remotes = {};
8883
+ for (const [remoteKey, entry] of Object.entries(state.remotes)) {
8884
+ if (!isPlainObject(entry)) {
8885
+ continue;
8886
+ }
8887
+ remotes[remoteKey] = { sources: sanitizeBackfillSources(entry.sources) };
8888
+ }
8889
+ return { version: BACKFILL_STATE_SCHEMA_VERSION, remotes };
8890
+ }
8891
+ async function writeBackfillIncrementalStateFile(home, file) {
8892
+ const statePath = backfillIncrementalStatePath(home);
8893
+ await mkdir4(path18.dirname(statePath), { recursive: true });
8894
+ await writeFile3(statePath, `${JSON.stringify(file, null, 2)}
8895
+ `, "utf8");
8896
+ }
8897
+ async function readBackfillIncrementalState(home, remoteKey, ctx) {
8898
+ const file = await readBackfillIncrementalStateFile(home, ctx);
8899
+ return { version: BACKFILL_STATE_SCHEMA_VERSION, sources: file.remotes[remoteKey]?.sources ?? {} };
8097
8900
  }
8098
- async function updateBackfillIncrementalState(home, state, selectedFilesBySource) {
8901
+ async function updateBackfillIncrementalState(home, remoteKey, state, selectedFilesBySource) {
8099
8902
  for (const [source, files] of selectedFilesBySource.entries()) {
8100
8903
  const latest = maxTimestamp(files.map((f) => f.modifiedAt));
8101
8904
  if (!latest) {
@@ -8103,10 +8906,9 @@ async function updateBackfillIncrementalState(home, state, selectedFilesBySource
8103
8906
  }
8104
8907
  state.sources[source] = { watermarkTs: latest };
8105
8908
  }
8106
- const statePath = backfillIncrementalStatePath(home);
8107
- await mkdir4(path16.dirname(statePath), { recursive: true });
8108
- await writeFile3(statePath, `${JSON.stringify(state, null, 2)}
8109
- `, "utf8");
8909
+ const file = await readBackfillIncrementalStateFile(home);
8910
+ file.remotes[remoteKey] = { sources: state.sources };
8911
+ await writeBackfillIncrementalStateFile(home, file);
8110
8912
  }
8111
8913
  async function readSyncLocalTriggerState(statePath) {
8112
8914
  let state;
@@ -8137,7 +8939,7 @@ async function readSyncLocalTriggerState(statePath) {
8137
8939
  return nextState;
8138
8940
  }
8139
8941
  async function writeSyncLocalTriggerState(statePath, state) {
8140
- await mkdir4(path16.dirname(statePath), { recursive: true });
8942
+ await mkdir4(path18.dirname(statePath), { recursive: true });
8141
8943
  const tmpPath = `${statePath}.tmp`;
8142
8944
  await writeFile3(tmpPath, `${JSON.stringify(state, null, 2)}
8143
8945
  `, "utf8");
@@ -8154,7 +8956,7 @@ async function readSyncLocalLock(lockPath) {
8154
8956
  return { pid: lock.pid, startedAt: lock.startedAt };
8155
8957
  }
8156
8958
  async function writeSyncLocalLock(lockPath, lock) {
8157
- await mkdir4(path16.dirname(lockPath), { recursive: true });
8959
+ await mkdir4(path18.dirname(lockPath), { recursive: true });
8158
8960
  await writeFile3(lockPath, `${JSON.stringify(lock, null, 2)}
8159
8961
  `, "utf8");
8160
8962
  }
@@ -8214,10 +9016,10 @@ function syncLocalRunnerEntryArgs(cliPath) {
8214
9016
  if (cliPath.endsWith(".ts")) {
8215
9017
  return ["--import", "tsx", cliPath];
8216
9018
  }
8217
- return [path16.resolve(path16.dirname(cliPath), "../bin/vibetime.mjs")];
9019
+ return [path18.resolve(path18.dirname(cliPath), "../bin/vibetime.mjs")];
8218
9020
  }
8219
9021
  function resolveHome2(options, ctx) {
8220
- return path16.resolve(stringOption(options.home) || ctx.env.HOME || os7.homedir());
9022
+ return path18.resolve(stringOption(options.home) || ctx.env.HOME || os7.homedir());
8221
9023
  }
8222
9024
  function requestedTargets(options) {
8223
9025
  const value = options.target || options.targets;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@yhong91/vibetime",
3
3
  "type": "module",
4
- "version": "0.1.2",
4
+ "version": "0.1.4",
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": {