@yhong91/vibetime 0.1.54 → 0.1.56

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 +61 -59
  2. package/package.json +1 -1
package/bin/vibetime.mjs CHANGED
@@ -2047,7 +2047,7 @@ function claudeStyleFileMetrics(tool, input) {
2047
2047
  }
2048
2048
 
2049
2049
  // src/lib/constants.ts
2050
- var PACKAGE_VERSION = true ? "0.1.54" : "0.1.1";
2050
+ var PACKAGE_VERSION = true ? "0.1.56" : "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;
@@ -8154,6 +8154,29 @@ import { readdir as readdir7, readFile as readFile10, stat as stat8 } from "node
8154
8154
  import os7 from "node:os";
8155
8155
  import path16 from "node:path";
8156
8156
  init_fs();
8157
+ function piHostSessionIdFromBasename(basename) {
8158
+ const withoutExt = basename.endsWith(".jsonl") ? basename.slice(0, -".jsonl".length) : basename;
8159
+ if (!withoutExt || withoutExt === "session") {
8160
+ return void 0;
8161
+ }
8162
+ const underscore = withoutExt.indexOf("_");
8163
+ if (underscore <= 0 || underscore === withoutExt.length - 1) {
8164
+ return withoutExt;
8165
+ }
8166
+ return withoutExt.slice(underscore + 1);
8167
+ }
8168
+ function resolvePiBackfillGroupId(filePath) {
8169
+ const normalized = filePath.replaceAll("\\", "/");
8170
+ const base = path16.basename(filePath);
8171
+ const nest = normalized.match(/([^/]+)\/[^/]+\/run-\d+\/session\.jsonl$/);
8172
+ if (nest) {
8173
+ return piHostSessionIdFromBasename(nest[1]);
8174
+ }
8175
+ if (base.endsWith(".jsonl") && base !== "session.jsonl") {
8176
+ return piHostSessionIdFromBasename(base);
8177
+ }
8178
+ return void 0;
8179
+ }
8157
8180
  function parsePiSubagentLink(filePath, headerParentSession) {
8158
8181
  if (headerParentSession) {
8159
8182
  const parentFile = path16.isAbsolute(headerParentSession) ? headerParentSession : void 0;
@@ -8805,6 +8828,17 @@ function piSessionDir(home, env) {
8805
8828
  function piWorkflowProjectsDir(home) {
8806
8829
  return path16.join(home, ".pi", "workflows", "projects");
8807
8830
  }
8831
+ async function workflowRunGroupId(filePath) {
8832
+ try {
8833
+ const raw = JSON.parse(await readFile10(filePath, "utf8"));
8834
+ if (!isPlainObject(raw)) {
8835
+ return void 0;
8836
+ }
8837
+ return stringField(raw, "sessionId");
8838
+ } catch {
8839
+ return void 0;
8840
+ }
8841
+ }
8808
8842
  async function piBackfillFiles(sourceRoot, home = os7.homedir(), env) {
8809
8843
  const lists = sourceRoot ? [await listFilesByExtensions(sourceRoot, [".jsonl", ".json"])] : await Promise.all([
8810
8844
  listFilesByExtensions(piSessionDir(home, env), [".jsonl"]),
@@ -8813,7 +8847,15 @@ async function piBackfillFiles(sourceRoot, home = os7.homedir(), env) {
8813
8847
  const files = lists.flat().sort();
8814
8848
  return Promise.all(files.map(async (filePath) => {
8815
8849
  const info = await stat8(filePath);
8816
- return { path: filePath, modifiedAt: info.mtime.toISOString() };
8850
+ let groupId = resolvePiBackfillGroupId(filePath);
8851
+ if (!groupId && filePath.endsWith(".json")) {
8852
+ groupId = await workflowRunGroupId(filePath);
8853
+ }
8854
+ return {
8855
+ path: filePath,
8856
+ modifiedAt: info.mtime.toISOString(),
8857
+ ...groupId ? { groupId } : {}
8858
+ };
8817
8859
  }));
8818
8860
  }
8819
8861
  function createPiAdapter() {
@@ -9532,10 +9574,11 @@ async function parseQoderCnSessionFile(filePath, options) {
9532
9574
  const { sessionId: parentSessionId, mainTranscriptPath } = parseQoderCnPaths(filePath);
9533
9575
  if (parentSessionId && mainTranscriptPath) {
9534
9576
  const parentSourcePathHash = `sha256:${createStableHash(mainTranscriptPath)}`;
9535
- return validEvents.map((event) => {
9577
+ return withoutSubagentTurnEvents(validEvents).map((event) => {
9536
9578
  const mapped = {
9537
9579
  ...event,
9538
9580
  sessionId: parentSessionId,
9581
+ turnId: void 0,
9539
9582
  refs: {
9540
9583
  ...event.refs,
9541
9584
  sourcePathHash: parentSourcePathHash,
@@ -9550,9 +9593,10 @@ async function parseQoderCnSessionFile(filePath, options) {
9550
9593
  if (dbModelCalls.rootSessionId) {
9551
9594
  const parentPath = path18.join(path18.dirname(filePath), `${dbModelCalls.rootSessionId}.jsonl`);
9552
9595
  const parentSourcePathHash = `sha256:${createStableHash(parentPath)}`;
9553
- return validEvents.map((event) => rebuildEventIdentity3({
9596
+ return withoutSubagentTurnEvents(validEvents).map((event) => rebuildEventIdentity3({
9554
9597
  ...event,
9555
9598
  sessionId: dbModelCalls.rootSessionId,
9599
+ turnId: void 0,
9556
9600
  refs: {
9557
9601
  ...event.refs,
9558
9602
  sourcePathHash: parentSourcePathHash,
@@ -10386,10 +10430,11 @@ async function parseQoderSessionFile(filePath, options) {
10386
10430
  const { sessionId: parentSessionId, mainTranscriptPath } = parseQoderPaths(filePath);
10387
10431
  if (parentSessionId && mainTranscriptPath) {
10388
10432
  const parentSourcePathHash = `sha256:${createStableHash(mainTranscriptPath)}`;
10389
- return validEvents.map((event) => {
10433
+ return withoutSubagentTurnEvents(validEvents).map((event) => {
10390
10434
  const mapped = {
10391
10435
  ...event,
10392
10436
  sessionId: parentSessionId,
10437
+ turnId: void 0,
10393
10438
  refs: {
10394
10439
  ...event.refs,
10395
10440
  sourcePathHash: parentSourcePathHash,
@@ -10404,9 +10449,10 @@ async function parseQoderSessionFile(filePath, options) {
10404
10449
  if (dbModelCalls.rootSessionId) {
10405
10450
  const parentPath = path19.join(path19.dirname(filePath), `${dbModelCalls.rootSessionId}.jsonl`);
10406
10451
  const parentSourcePathHash = `sha256:${createStableHash(parentPath)}`;
10407
- return validEvents.map((event) => rebuildEventIdentity4({
10452
+ return withoutSubagentTurnEvents(validEvents).map((event) => rebuildEventIdentity4({
10408
10453
  ...event,
10409
10454
  sessionId: dbModelCalls.rootSessionId,
10455
+ turnId: void 0,
10410
10456
  refs: {
10411
10457
  ...event.refs,
10412
10458
  sourcePathHash: parentSourcePathHash,
@@ -13205,10 +13251,9 @@ async function deleteMachine(remote, id) {
13205
13251
  }
13206
13252
 
13207
13253
  // src/lib/types.ts
13208
- var BACKFILL_STATE_SCHEMA_VERSION = 6;
13254
+ var BACKFILL_STATE_SCHEMA_VERSION = 7;
13209
13255
 
13210
13256
  // src/cli.ts
13211
- var SESSION_REWRITE_DAYS = 7;
13212
13257
  function createRegistry() {
13213
13258
  const registry = new AdapterRegistry();
13214
13259
  registry.register(createCodexAdapter());
@@ -13288,7 +13333,7 @@ function createCli(ctx, registry) {
13288
13333
  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));
13289
13334
  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));
13290
13335
  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));
13291
- 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));
13336
+ 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));
13292
13337
  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));
13293
13338
  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));
13294
13339
  return cli;
@@ -13307,8 +13352,7 @@ function normalizeOptions(options) {
13307
13352
  importRun: "import-run",
13308
13353
  batchSize: "batch-size",
13309
13354
  batchBytes: "batch-bytes",
13310
- skipConflicts: "skip-conflicts",
13311
- purgeAll: "purge-all"
13355
+ skipConflicts: "skip-conflicts"
13312
13356
  };
13313
13357
  for (const [camel, dashed] of Object.entries(aliases)) {
13314
13358
  if (normalized[camel] !== void 0 && normalized[dashed] === void 0) {
@@ -13616,10 +13660,6 @@ async function backfillCommand(options, ctx, registry) {
13616
13660
  if (action === "verify") {
13617
13661
  return backfillVerifyCommand(options, ctx);
13618
13662
  }
13619
- if (options["purge-all"] && !options.force) {
13620
- write(ctx.stderr, "--purge-all requires --force\n");
13621
- return 1;
13622
- }
13623
13663
  if (action === "import" && !options["dry-run"]) {
13624
13664
  const requested = normalizeBackfillSource(stringOption(options.source) || "all");
13625
13665
  const supported = /* @__PURE__ */ new Set(["all", ...BACKFILL_SOURCE_IDS]);
@@ -13882,7 +13922,7 @@ async function importBackfillPlan(plan, options, ctx, registry) {
13882
13922
  const remote = resolveRemoteFromOptions(options, ctx);
13883
13923
  const remoteKey = backfillRemoteKey(remote?.baseUrl ?? DEFAULT_API_URL);
13884
13924
  if (options.force) {
13885
- await purgeForcedSources(sourceDefs, home, remoteKey, options, ctx);
13925
+ await resetForcedWatermarks(home, remoteKey, options, ctx);
13886
13926
  }
13887
13927
  const incrementalState = shouldUseIncrementalBackfill(options) ? await readBackfillIncrementalState(home, remoteKey, ctx) : void 0;
13888
13928
  if (!options.json) {
@@ -13898,7 +13938,7 @@ async function importBackfillPlan(plan, options, ctx, registry) {
13898
13938
  options,
13899
13939
  ctx
13900
13940
  );
13901
- const rollups = selectRollupsForUpload(buildSessionRollups(canonicalEvents), options);
13941
+ const rollups = buildSessionRollups(canonicalEvents);
13902
13942
  const counts = await uploadSessionRollups(rollups, canonicalEvents.length, options, ctx);
13903
13943
  const result = {
13904
13944
  importRunId: plan.importRun.importRunId,
@@ -13916,7 +13956,7 @@ async function importBackfillPlan(plan, options, ctx, registry) {
13916
13956
  }
13917
13957
  return counts.failed > 0 || counts.conflicts > 0 && !options["skip-conflicts"] ? 1 : 0;
13918
13958
  }
13919
- async function purgeForcedSources(sourceDefs, home, remoteKey, options, ctx) {
13959
+ async function resetForcedWatermarks(home, remoteKey, options, ctx) {
13920
13960
  try {
13921
13961
  const file = await readBackfillIncrementalStateFile(home);
13922
13962
  delete file.remotes[remoteKey];
@@ -13925,20 +13965,6 @@ async function purgeForcedSources(sourceDefs, home, remoteKey, options, ctx) {
13925
13965
  debug(ctx, `Failed to clear backfill watermark: ${error.message}
13926
13966
  `);
13927
13967
  }
13928
- const preserveTokens = !options["purge-all"];
13929
- for (const item of sourceDefs) {
13930
- try {
13931
- const deleted = await deleteSessionRollupsBySourceAPI(item.id, options, ctx, preserveTokens);
13932
- if (!options.json) {
13933
- const suffix = preserveTokens ? " (token-bearing and sessions older than 7 days kept)" : "";
13934
- write(ctx.stdout, `purged ${item.id}: ${deleted} old rollups${suffix}
13935
- `);
13936
- }
13937
- } catch (error) {
13938
- debug(ctx, `Failed to purge ${item.id} rollups: ${error.message}
13939
- `);
13940
- }
13941
- }
13942
13968
  }
13943
13969
  async function collectCanonicalEvents(sourceDefs, registry, incrementalState, options, ctx) {
13944
13970
  const selectedFilesBySource = /* @__PURE__ */ new Map();
@@ -14058,7 +14084,7 @@ async function sendSessionRollupBatch(rollups, options, ctx) {
14058
14084
  const result = await postRollupBatch(remote, rollups, {
14059
14085
  replace: options["skip-conflicts"] !== true,
14060
14086
  machine,
14061
- allowHistoricalRewrite: options["purge-all"] === true
14087
+ allowHistoricalRewrite: options.force === true
14062
14088
  });
14063
14089
  if (options.force && result.conflicts === 0) {
14064
14090
  for (const rollup of rollups) {
@@ -14066,7 +14092,7 @@ async function sendSessionRollupBatch(rollups, options, ctx) {
14066
14092
  try {
14067
14093
  await deleteRollupsBySource(remote, rollup.source, machine, {
14068
14094
  rollupKey: createImportKey(["rollup", rollup.source, sessionId]),
14069
- allowHistoricalRewrite: options["purge-all"] === true
14095
+ allowHistoricalRewrite: options.force === true
14070
14096
  });
14071
14097
  } catch (error) {
14072
14098
  debug(ctx, `Failed to delete superseded ${rollup.source} session ${sessionId}: ${error.message}
@@ -14077,28 +14103,6 @@ async function sendSessionRollupBatch(rollups, options, ctx) {
14077
14103
  }
14078
14104
  return result;
14079
14105
  }
14080
- async function deleteSessionRollupsBySourceAPI(source, options, ctx, preserveTokens) {
14081
- const remote = resolveRemoteFromOptions(options, ctx);
14082
- if (!remote) {
14083
- throw new Error("No fetch available for HTTP delete");
14084
- }
14085
- const home = resolveHome3(options, ctx);
14086
- return deleteRollupsBySource(remote, source, {
14087
- id: ensureLocalMachineId(home),
14088
- hostname: defaultMachineName(),
14089
- platform: process.platform
14090
- }, {
14091
- preserveTokens,
14092
- allowHistoricalRewrite: options["purge-all"] === true
14093
- });
14094
- }
14095
- function selectRollupsForUpload(rollups, options, now = /* @__PURE__ */ new Date()) {
14096
- if (!options.force || options["purge-all"]) {
14097
- return rollups;
14098
- }
14099
- const cutoff = now.getTime() - SESSION_REWRITE_DAYS * 24 * 60 * 60 * 1e3;
14100
- return rollups.filter((rollup) => Date.parse(rollup.lastEventAt) >= cutoff);
14101
- }
14102
14106
  function shouldUseIncrementalBackfill(options) {
14103
14107
  return !stringOption(options.since) && !stringOption(options.until) && !stringOption(options["source-root"]) && numberOption(options.limit) === void 0;
14104
14108
  }
@@ -14557,7 +14561,7 @@ Usage:
14557
14561
  vibetime uninstall [--target codex,claude,opencode,pi] [--all] [--dry-run] [--home <path>]
14558
14562
  vibetime upgrade [--check]
14559
14563
  vibetime hook --agent <name>
14560
- vibetime backfill discover|plan|import|verify --source codex|claude-code|opencode|pi|all --dry-run [--json] [--batch-size <count>] [--force [--purge-all]]
14564
+ vibetime backfill discover|plan|import|verify --source codex|claude-code|opencode|pi|all --dry-run [--json] [--batch-size <count>] [--force]
14561
14565
  vibetime token set <token>
14562
14566
  vibetime token show
14563
14567
  vibetime token clear
@@ -14591,10 +14595,8 @@ Environment:
14591
14595
  `;
14592
14596
  }
14593
14597
  export {
14594
- SESSION_REWRITE_DAYS,
14595
14598
  run,
14596
14599
  selectBackfillFilesForImport,
14597
- selectRollupsForUpload,
14598
14600
  syncLocalRunnerEntryArgs
14599
14601
  };
14600
14602
  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.54",
4
+ "version": "0.1.56",
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": {