@yhong91/vibetime 0.1.52 → 0.1.53

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 +453 -10
  2. package/package.json +1 -1
package/bin/vibetime.mjs CHANGED
@@ -18,7 +18,7 @@ var __export = (target, all) => {
18
18
  // src/lib/fs.ts
19
19
  var fs_exports = {};
20
20
  __export(fs_exports, {
21
- GENERATED_MARKER: () => GENERATED_MARKER,
21
+ GENERATED_MARKER: () => GENERATED_MARKER2,
22
22
  countDirectoryEntries: () => countDirectoryEntries,
23
23
  listFilesByExtensions: () => listFilesByExtensions,
24
24
  listJsonlFiles: () => listJsonlFiles,
@@ -72,7 +72,7 @@ async function writeGeneratedFile(filePath, content, { dryRun, force, onWrite })
72
72
  onWrite(`Already installed ${filePath}`);
73
73
  return;
74
74
  }
75
- if (existing !== null && !existing.includes(GENERATED_MARKER) && !existing.includes(LEGACY_GENERATED_MARKER) && !force) {
75
+ if (existing !== null && !existing.includes(GENERATED_MARKER2) && !existing.includes(LEGACY_GENERATED_MARKER) && !force) {
76
76
  throw new Error(
77
77
  `Refusing to overwrite non-vibetime file: ${filePath}. Re-run with --force if this is intentional.`
78
78
  );
@@ -148,11 +148,11 @@ async function countDirectoryEntries(candidatePath) {
148
148
  throw error;
149
149
  }
150
150
  }
151
- var GENERATED_MARKER, LEGACY_GENERATED_MARKER;
151
+ var GENERATED_MARKER2, LEGACY_GENERATED_MARKER;
152
152
  var init_fs = __esm({
153
153
  "src/lib/fs.ts"() {
154
154
  "use strict";
155
- GENERATED_MARKER = "Generated by vibetime.";
155
+ GENERATED_MARKER2 = "Generated by vibetime.";
156
156
  LEGACY_GENERATED_MARKER = "Generated by codetime.";
157
157
  }
158
158
  });
@@ -2047,7 +2047,8 @@ function claudeStyleFileMetrics(tool, input) {
2047
2047
  }
2048
2048
 
2049
2049
  // src/lib/constants.ts
2050
- var PACKAGE_VERSION = true ? "0.1.52" : "0.1.1";
2050
+ var PACKAGE_VERSION = true ? "0.1.53" : "0.1.1";
2051
+ var GENERATED_MARKER = "Generated by vibetime.";
2051
2052
  var DEFAULT_API_URL = "http://121.196.224.82:3001";
2052
2053
  var DEFAULT_BACKFILL_BATCH_SIZE = 50;
2053
2054
  var DEFAULT_BACKFILL_BATCH_BYTES = 800 * 1024;
@@ -6938,6 +6939,154 @@ function createGrokBuildAdapter() {
6938
6939
  // src/adapters/kimi-code.ts
6939
6940
  import { readFile as readFile9 } from "node:fs/promises";
6940
6941
  import path14 from "node:path";
6942
+
6943
+ // src/lib/toml-hooks.ts
6944
+ init_fs();
6945
+ async function hasTomlHookCommand(filePath, command) {
6946
+ const text = await readTextIfExists(filePath);
6947
+ if (!text) {
6948
+ return false;
6949
+ }
6950
+ return parseTomlHookRules(text).some((rule) => rule.command === command);
6951
+ }
6952
+ function parseTomlHookRules(text) {
6953
+ const rules = [];
6954
+ const chunks = text.split(/^\[\[hooks\]\][ \t]*\r?\n?/m);
6955
+ for (const chunk of chunks.slice(1)) {
6956
+ const body = chunk.split(/^\[[^\]]+\]/m)[0] ?? "";
6957
+ const event = tomlStringField(body, "event");
6958
+ const command = tomlStringField(body, "command");
6959
+ if (!event || !command) {
6960
+ continue;
6961
+ }
6962
+ const matcher = tomlStringField(body, "matcher");
6963
+ const timeout = tomlNumberField(body, "timeout");
6964
+ const rule = { event, command };
6965
+ if (matcher !== void 0) {
6966
+ rule.matcher = matcher;
6967
+ }
6968
+ if (timeout !== void 0) {
6969
+ rule.timeout = timeout;
6970
+ }
6971
+ rules.push(rule);
6972
+ }
6973
+ return rules;
6974
+ }
6975
+ function mergeTomlHookRules(existingText, desired) {
6976
+ const existingKeys = new Set(parseTomlHookRules(existingText).map(ruleKey));
6977
+ const toAppend = desired.filter((rule) => !existingKeys.has(ruleKey(rule)));
6978
+ if (toAppend.length === 0) {
6979
+ return existingText;
6980
+ }
6981
+ let base = existingText;
6982
+ if (base.length > 0 && !base.endsWith("\n")) {
6983
+ base += "\n";
6984
+ }
6985
+ if (base.length > 0 && !base.endsWith("\n\n") && base.trim().length > 0) {
6986
+ base += "\n";
6987
+ }
6988
+ const blocks = toAppend.map(serializeTomlHookRule).join("\n\n");
6989
+ return `${base}${blocks}
6990
+ `;
6991
+ }
6992
+ function removeTomlHookRulesByCommand(existingText, commands) {
6993
+ const commandSet = new Set(commands.filter(Boolean));
6994
+ if (commandSet.size === 0 || !existingText) {
6995
+ return existingText;
6996
+ }
6997
+ const parts = [];
6998
+ let cursor = 0;
6999
+ const headerRe = /^\[\[hooks\]\][ \t]*\r?\n?/gm;
7000
+ let match = headerRe.exec(existingText);
7001
+ let removed = 0;
7002
+ while (match) {
7003
+ const headerStart = match.index;
7004
+ const bodyStart = headerRe.lastIndex;
7005
+ const rest = existingText.slice(bodyStart);
7006
+ const nextHeader = rest.search(/^\[[^\]]+\]/m);
7007
+ const bodyEnd = nextHeader === -1 ? existingText.length : bodyStart + nextHeader;
7008
+ const body = existingText.slice(bodyStart, bodyEnd);
7009
+ const command = tomlStringField(body, "command");
7010
+ parts.push(existingText.slice(cursor, headerStart));
7011
+ if (command && commandSet.has(command)) {
7012
+ removed += 1;
7013
+ } else {
7014
+ parts.push(existingText.slice(headerStart, bodyEnd));
7015
+ }
7016
+ cursor = bodyEnd;
7017
+ match = headerRe.exec(existingText);
7018
+ }
7019
+ if (removed === 0) {
7020
+ return existingText;
7021
+ }
7022
+ parts.push(existingText.slice(cursor));
7023
+ let next = parts.join("");
7024
+ next = next.replace(/\n{3,}/g, "\n\n");
7025
+ if (next.length > 0 && !next.endsWith("\n")) {
7026
+ next += "\n";
7027
+ }
7028
+ return next;
7029
+ }
7030
+ function looksLikeToml(text) {
7031
+ const sample = text.trim();
7032
+ if (!sample) {
7033
+ return true;
7034
+ }
7035
+ if (sample.startsWith("{")) {
7036
+ return false;
7037
+ }
7038
+ if (/^\s*\[\s*\{/.test(sample)) {
7039
+ return false;
7040
+ }
7041
+ return true;
7042
+ }
7043
+ function ruleKey(rule) {
7044
+ return `${rule.event}\0${rule.command}`;
7045
+ }
7046
+ function serializeTomlHookRule(rule) {
7047
+ const lines = [
7048
+ "[[hooks]]",
7049
+ `event = "${escapeTomlString(rule.event)}"`
7050
+ ];
7051
+ if (rule.matcher !== void 0 && rule.matcher !== "") {
7052
+ lines.push(`matcher = "${escapeTomlString(rule.matcher)}"`);
7053
+ }
7054
+ lines.push(`command = "${escapeTomlString(rule.command)}"`);
7055
+ if (rule.timeout !== void 0) {
7056
+ lines.push(`timeout = ${Math.floor(rule.timeout)}`);
7057
+ }
7058
+ return lines.join("\n");
7059
+ }
7060
+ function escapeTomlString(value) {
7061
+ return value.replaceAll("\\", "\\\\").replaceAll('"', '\\"').replaceAll("\n", "\\n").replaceAll(" ", "\\t");
7062
+ }
7063
+ function tomlStringField(body, key) {
7064
+ const re = new RegExp(
7065
+ `^\\s*${key}\\s*=\\s*(?:"((?:\\\\.|[^"\\\\])*)"|'([^']*)')\\s*$`,
7066
+ "m"
7067
+ );
7068
+ const match = body.match(re);
7069
+ if (!match) {
7070
+ return void 0;
7071
+ }
7072
+ if (match[1] !== void 0) {
7073
+ return match[1].replaceAll("\\n", "\n").replaceAll("\\t", " ").replaceAll('\\"', '"').replaceAll("\\\\", "\\");
7074
+ }
7075
+ return match[2];
7076
+ }
7077
+ function tomlNumberField(body, key) {
7078
+ const re = new RegExp(`^\\s*${key}\\s*=\\s*(-?\\d+)\\s*$`, "m");
7079
+ const match = body.match(re);
7080
+ if (!match?.[1]) {
7081
+ return void 0;
7082
+ }
7083
+ const n = Number.parseInt(match[1], 10);
7084
+ return Number.isFinite(n) ? n : void 0;
7085
+ }
7086
+
7087
+ // src/adapters/kimi-code.ts
7088
+ var HOOK_COMMAND2 = "vibetime hook --agent kimi-code";
7089
+ var HOOK_TIMEOUT_SECONDS = 10;
6941
7090
  function kimiCodeHome(home, env) {
6942
7091
  const override = env?.KIMI_CODE_HOME || env?.KIMI_HOME;
6943
7092
  if (override && override.trim()) {
@@ -6948,6 +7097,28 @@ function kimiCodeHome(home, env) {
6948
7097
  function kimiCodeSessionsDir(home, env) {
6949
7098
  return path14.join(kimiCodeHome(home, env), "sessions");
6950
7099
  }
7100
+ function kimiCodeConfigPath(home, env) {
7101
+ return path14.join(kimiCodeHome(home, env), "config.toml");
7102
+ }
7103
+ function kimiHookRules() {
7104
+ const events = [
7105
+ "SessionStart",
7106
+ "SessionEnd",
7107
+ "UserPromptSubmit",
7108
+ "TurnStarted",
7109
+ "PostToolUse",
7110
+ "PostToolUseFailure",
7111
+ "Stop",
7112
+ "StopFailure",
7113
+ "PermissionResult",
7114
+ "SubagentStop"
7115
+ ];
7116
+ return events.map((event) => ({
7117
+ event,
7118
+ command: HOOK_COMMAND2,
7119
+ timeout: HOOK_TIMEOUT_SECONDS
7120
+ }));
7121
+ }
6951
7122
  function baseKimiEvent(event) {
6952
7123
  return {
6953
7124
  schemaVersion: AGENT_TIME_SCHEMA_VERSION,
@@ -7387,13 +7558,17 @@ function createKimiCodeAdapter() {
7387
7558
  return kimiCodeHome(home, env);
7388
7559
  },
7389
7560
  installedPath(home, env) {
7390
- return path14.join(kimiCodeHome(home, env), "vibetime-marker");
7561
+ return kimiCodeConfigPath(home, env);
7391
7562
  },
7392
- async isInstalled() {
7393
- return false;
7563
+ async isInstalled(home, env) {
7564
+ return hasTomlHookCommand(kimiCodeConfigPath(home, env), HOOK_COMMAND2);
7394
7565
  },
7395
- installEntries(_home, _env) {
7396
- return [];
7566
+ installEntries(home, env) {
7567
+ return [{
7568
+ kind: "hooks-toml",
7569
+ path: kimiCodeConfigPath(home, env),
7570
+ content: { hooks: kimiHookRules() }
7571
+ }];
7397
7572
  },
7398
7573
  sourcePaths(home, env) {
7399
7574
  return [kimiCodeSessionsDir(home, env)];
@@ -12260,11 +12435,26 @@ async function installEntry(entry, options) {
12260
12435
  await mergeHooksJson(entry.path, entry.content, options);
12261
12436
  return;
12262
12437
  }
12438
+ if (entry.kind === "hooks-toml" && typeof entry.content === "object") {
12439
+ await mergeHooksToml(entry.path, entry.content, options);
12440
+ return;
12441
+ }
12263
12442
  await writeGeneratedFile(entry.path, String(entry.content), {
12264
12443
  ...options,
12265
12444
  onWrite: options.onWrite
12266
12445
  });
12267
12446
  }
12447
+ async function uninstallEntry(entry, options) {
12448
+ if (entry.kind === "hooks-toml" && typeof entry.content === "object") {
12449
+ await uninstallHooksToml(entry.path, entry.content, options);
12450
+ return;
12451
+ }
12452
+ if (entry.kind === "hooks-json" && typeof entry.content === "object") {
12453
+ await uninstallHooksJson(entry.path, entry.content, options);
12454
+ return;
12455
+ }
12456
+ await uninstallGeneratedFile(entry.path, options);
12457
+ }
12268
12458
  async function mergeHooksJson(filePath, content, { dryRun, force, onWrite }) {
12269
12459
  const { mkdir: mkdir6, writeFile: writeFile5 } = await import("node:fs/promises");
12270
12460
  const pathMod = await import("node:path");
@@ -12361,6 +12551,218 @@ function hookCommandFromGroup(group) {
12361
12551
  const hook = group.hooks[0];
12362
12552
  return isPlainObject(hook) && typeof hook.command === "string" ? hook.command : void 0;
12363
12553
  }
12554
+ async function mergeHooksToml(filePath, content, { dryRun, force, onWrite }) {
12555
+ const { mkdir: mkdir6, writeFile: writeFile5 } = await import("node:fs/promises");
12556
+ const pathMod = await import("node:path");
12557
+ if (dryRun) {
12558
+ onWrite(`Would merge ${filePath}`);
12559
+ return;
12560
+ }
12561
+ const existingText = await readTextIfExists(filePath);
12562
+ if (existingText !== null && existingText.trim() !== "" && !looksLikeToml(existingText) && !force) {
12563
+ throw new Error(
12564
+ `Refusing to update non-TOML file: ${filePath}. Re-run with --force if this is intentional.`
12565
+ );
12566
+ }
12567
+ const desired = Array.isArray(content.hooks) ? content.hooks : [];
12568
+ const nextText = mergeTomlHookRules(existingText ?? "", desired);
12569
+ if (existingText === nextText || existingText === null && nextText === "") {
12570
+ onWrite(`Already installed ${filePath}`);
12571
+ return;
12572
+ }
12573
+ if (existingText !== null) {
12574
+ const existingKeys = new Set(
12575
+ parseTomlHookRules(existingText).map((rule) => `${rule.event}\0${rule.command}`)
12576
+ );
12577
+ const allPresent = desired.every((rule) => existingKeys.has(`${rule.event}\0${rule.command}`));
12578
+ if (allPresent && desired.length > 0) {
12579
+ onWrite(`Already installed ${filePath}`);
12580
+ return;
12581
+ }
12582
+ }
12583
+ await mkdir6(pathMod.dirname(filePath), { recursive: true });
12584
+ await writeFile5(filePath, nextText, "utf8");
12585
+ onWrite(`Installed ${filePath}`);
12586
+ }
12587
+ async function uninstallHooksToml(filePath, content, { dryRun, onWrite }) {
12588
+ const existingText = await readTextIfExists(filePath);
12589
+ if (existingText === null) {
12590
+ onWrite(`Already uninstalled ${filePath}`);
12591
+ return;
12592
+ }
12593
+ const commands = Array.from(new Set(
12594
+ (Array.isArray(content.hooks) ? content.hooks : []).map((rule) => rule.command).filter((cmd) => typeof cmd === "string" && cmd.length > 0)
12595
+ ));
12596
+ if (commands.length === 0) {
12597
+ onWrite(`Already uninstalled ${filePath}`);
12598
+ return;
12599
+ }
12600
+ const nextText = removeTomlHookRulesByCommand(existingText, commands);
12601
+ if (nextText === existingText) {
12602
+ onWrite(`Already uninstalled ${filePath}`);
12603
+ return;
12604
+ }
12605
+ if (dryRun) {
12606
+ onWrite(`Would uninstall ${filePath}`);
12607
+ return;
12608
+ }
12609
+ const { writeFile: writeFile5 } = await import("node:fs/promises");
12610
+ await writeFile5(filePath, nextText, "utf8");
12611
+ onWrite(`Uninstalled ${filePath}`);
12612
+ }
12613
+ function collectHookCommandsFromJsonContent(content) {
12614
+ const commands = /* @__PURE__ */ new Set();
12615
+ const walkGroups = (groups) => {
12616
+ if (!Array.isArray(groups)) {
12617
+ return;
12618
+ }
12619
+ for (const group of groups) {
12620
+ if (!isPlainObject(group) || !Array.isArray(group.hooks)) {
12621
+ continue;
12622
+ }
12623
+ for (const hook of group.hooks) {
12624
+ if (isPlainObject(hook) && typeof hook.command === "string" && hook.command) {
12625
+ commands.add(hook.command);
12626
+ }
12627
+ }
12628
+ }
12629
+ };
12630
+ if (isPlainObject(content.hooks)) {
12631
+ for (const groups of Object.values(content.hooks)) {
12632
+ walkGroups(groups);
12633
+ }
12634
+ }
12635
+ for (const [key, value] of Object.entries(content)) {
12636
+ if (key === "hooks" || key === "enable_json_hooks" || !isPlainObject(value)) {
12637
+ continue;
12638
+ }
12639
+ for (const groups of Object.values(value)) {
12640
+ walkGroups(groups);
12641
+ }
12642
+ }
12643
+ return [...commands];
12644
+ }
12645
+ function stripHookCommandsFromGroups(groups, commands) {
12646
+ if (!Array.isArray(groups)) {
12647
+ return groups;
12648
+ }
12649
+ return groups.map((group) => {
12650
+ if (!isPlainObject(group) || !Array.isArray(group.hooks)) {
12651
+ return group;
12652
+ }
12653
+ const nextHooks = group.hooks.filter(
12654
+ (hook) => !(isPlainObject(hook) && typeof hook.command === "string" && commands.has(hook.command))
12655
+ );
12656
+ if (nextHooks.length === 0) {
12657
+ return null;
12658
+ }
12659
+ return { ...group, hooks: nextHooks };
12660
+ }).filter(Boolean);
12661
+ }
12662
+ async function uninstallHooksJson(filePath, content, { dryRun, onWrite }) {
12663
+ const existingText = await readTextIfExists(filePath);
12664
+ if (existingText === null) {
12665
+ onWrite(`Already uninstalled ${filePath}`);
12666
+ return;
12667
+ }
12668
+ let existing;
12669
+ try {
12670
+ existing = JSON.parse(existingText);
12671
+ } catch {
12672
+ onWrite(`Skipped non-JSON file ${filePath}`);
12673
+ return;
12674
+ }
12675
+ if (!isPlainObject(existing)) {
12676
+ onWrite(`Skipped non-object JSON file ${filePath}`);
12677
+ return;
12678
+ }
12679
+ const commands = new Set(collectHookCommandsFromJsonContent(content));
12680
+ if (commands.size === 0) {
12681
+ onWrite(`Already uninstalled ${filePath}`);
12682
+ return;
12683
+ }
12684
+ const next = structuredClone(existing);
12685
+ let changed = false;
12686
+ if (isPlainObject(next.hooks)) {
12687
+ for (const [event, groups] of Object.entries(next.hooks)) {
12688
+ const stripped = stripHookCommandsFromGroups(groups, commands);
12689
+ if (JSON.stringify(stripped) !== JSON.stringify(groups)) {
12690
+ changed = true;
12691
+ if (Array.isArray(stripped) && stripped.length === 0) {
12692
+ delete next.hooks[event];
12693
+ } else {
12694
+ next.hooks[event] = stripped;
12695
+ }
12696
+ }
12697
+ }
12698
+ if (isPlainObject(next.hooks) && Object.keys(next.hooks).length === 0) {
12699
+ delete next.hooks;
12700
+ changed = true;
12701
+ }
12702
+ }
12703
+ for (const [key, value] of Object.entries(next)) {
12704
+ if (key === "hooks" || key === "enable_json_hooks" || !isPlainObject(value)) {
12705
+ continue;
12706
+ }
12707
+ if (!Object.prototype.hasOwnProperty.call(content, key)) {
12708
+ continue;
12709
+ }
12710
+ for (const [event, groups] of Object.entries(value)) {
12711
+ const stripped = stripHookCommandsFromGroups(groups, commands);
12712
+ if (JSON.stringify(stripped) !== JSON.stringify(groups)) {
12713
+ changed = true;
12714
+ if (Array.isArray(stripped) && stripped.length === 0) {
12715
+ delete value[event];
12716
+ } else {
12717
+ value[event] = stripped;
12718
+ }
12719
+ }
12720
+ }
12721
+ if (Object.keys(value).length === 0) {
12722
+ delete next[key];
12723
+ changed = true;
12724
+ }
12725
+ }
12726
+ if (Object.prototype.hasOwnProperty.call(content, "enable_json_hooks") && next.enable_json_hooks === true) {
12727
+ const stillHasNamedHooks = Object.entries(next).some(
12728
+ ([key, value]) => key !== "hooks" && key !== "enable_json_hooks" && isPlainObject(value) && Object.values(value).some((groups) => Array.isArray(groups) && groups.length > 0)
12729
+ );
12730
+ if (!stillHasNamedHooks) {
12731
+ delete next.enable_json_hooks;
12732
+ changed = true;
12733
+ }
12734
+ }
12735
+ if (!changed) {
12736
+ onWrite(`Already uninstalled ${filePath}`);
12737
+ return;
12738
+ }
12739
+ if (dryRun) {
12740
+ onWrite(`Would uninstall ${filePath}`);
12741
+ return;
12742
+ }
12743
+ const { writeFile: writeFile5 } = await import("node:fs/promises");
12744
+ await writeFile5(filePath, `${JSON.stringify(next, null, 2)}
12745
+ `, "utf8");
12746
+ onWrite(`Uninstalled ${filePath}`);
12747
+ }
12748
+ async function uninstallGeneratedFile(filePath, { dryRun, onWrite }) {
12749
+ const existingText = await readTextIfExists(filePath);
12750
+ if (existingText === null) {
12751
+ onWrite(`Already uninstalled ${filePath}`);
12752
+ return;
12753
+ }
12754
+ if (!existingText.includes(GENERATED_MARKER) && !existingText.includes("Generated by codetime.")) {
12755
+ onWrite(`Skipped non-vibetime file ${filePath}`);
12756
+ return;
12757
+ }
12758
+ if (dryRun) {
12759
+ onWrite(`Would uninstall ${filePath}`);
12760
+ return;
12761
+ }
12762
+ const { unlink } = await import("node:fs/promises");
12763
+ await unlink(filePath);
12764
+ onWrite(`Uninstalled ${filePath}`);
12765
+ }
12364
12766
 
12365
12767
  // src/lib/config.ts
12366
12768
  import { randomUUID } from "node:crypto";
@@ -12753,6 +13155,7 @@ function createCli(ctx, registry) {
12753
13155
  });
12754
13156
  cli.command("detect", "Show supported local targets and install status").action((options) => detectCommand(normalizeOptions(options), ctx, registry).then(() => 0));
12755
13157
  cli.command("install", "Install integration files into detected or requested targets").option("--target <targets>", "Target integrations, comma-separated").option("--targets <targets>", "Target integrations, comma-separated").option("--all", "Install all supported integrations").option("--force", "Overwrite existing non-generated files when needed").action((options) => installCommand(normalizeOptions(options), ctx, registry));
13158
+ cli.command("uninstall", "Remove vibetime integration hooks/files from targets").option("--target <targets>", "Target integrations, comma-separated").option("--targets <targets>", "Target integrations, comma-separated").option("--all", "Uninstall all supported integrations").action((options) => uninstallCommand(normalizeOptions(options), ctx, registry));
12756
13159
  cli.command("upgrade", "Check for updates and upgrade to the latest version").option("--check", "Only check for updates, do not install").action((options) => upgradeCommand(normalizeOptions(options), ctx, registry));
12757
13160
  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));
12758
13161
  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));
@@ -12849,6 +13252,44 @@ async function installCommand(options, ctx, registry) {
12849
13252
  }
12850
13253
  return 0;
12851
13254
  }
13255
+ async function uninstallCommand(options, ctx, registry) {
13256
+ const home = resolveHome3(options, ctx);
13257
+ const env = ctx.env;
13258
+ const dryRun = Boolean(options["dry-run"]);
13259
+ const allAdapters = registry.all();
13260
+ const requested = requestedTargets(options);
13261
+ const unknown = requested.filter((id) => !allAdapters.some((a) => a.id === id));
13262
+ if (unknown.length > 0) {
13263
+ throw new Error(`Unknown target(s): ${unknown.join(", ")}`);
13264
+ }
13265
+ const installed = [];
13266
+ for (const adapter of allAdapters) {
13267
+ if (await adapter.isInstalled(home, env)) {
13268
+ installed.push(adapter.id);
13269
+ }
13270
+ }
13271
+ const selectedIds = requested.length > 0 ? requested : options.all ? allAdapters.map((a) => a.id) : installed;
13272
+ if (selectedIds.length === 0) {
13273
+ write(ctx.stderr, "No installed vibetime integrations were found. Use --target <id> or --all.\n");
13274
+ return 1;
13275
+ }
13276
+ for (const adapter of allAdapters.filter((a) => selectedIds.includes(a.id))) {
13277
+ const entries = adapter.installEntries(home, env);
13278
+ if (entries.length === 0) {
13279
+ write(ctx.stdout, `Nothing to uninstall for ${adapter.id}
13280
+ `);
13281
+ continue;
13282
+ }
13283
+ for (const entry of entries) {
13284
+ await uninstallEntry(entry, {
13285
+ dryRun,
13286
+ onWrite: (msg) => write(ctx.stdout, `${msg}
13287
+ `)
13288
+ });
13289
+ }
13290
+ }
13291
+ return 0;
13292
+ }
12852
13293
  var NPM_PACKAGE = "@yhong91/vibetime";
12853
13294
  async function fetchLatestVersion() {
12854
13295
  try {
@@ -13982,6 +14423,7 @@ function helpText() {
13982
14423
  Usage:
13983
14424
  vibetime detect [--json] [--home <path>]
13984
14425
  vibetime install [--target codex,claude,opencode,pi] [--all] [--dry-run] [--force] [--home <path>]
14426
+ vibetime uninstall [--target codex,claude,opencode,pi] [--all] [--dry-run] [--home <path>]
13985
14427
  vibetime upgrade [--check]
13986
14428
  vibetime hook --agent <name>
13987
14429
  vibetime backfill discover|plan|import|verify --source codex|claude-code|opencode|pi|all --dry-run [--json] [--batch-size <count>] [--force [--purge-all]]
@@ -13996,6 +14438,7 @@ Setup:
13996
14438
  Commands:
13997
14439
  detect Show supported local targets and install status.
13998
14440
  install Install integration files into detected or requested targets.
14441
+ uninstall Remove vibetime hooks/files from detected or requested targets.
13999
14442
  upgrade Check for updates and upgrade to the latest version.
14000
14443
  hook Read agent hook JSON from stdin and report a throttled event.
14001
14444
  backfill Discover local history and create metadata-only import plans.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@yhong91/vibetime",
3
3
  "type": "module",
4
- "version": "0.1.52",
4
+ "version": "0.1.53",
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": {