@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.
- package/bin/vibetime.mjs +453 -10
- 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: () =>
|
|
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(
|
|
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
|
|
151
|
+
var GENERATED_MARKER2, LEGACY_GENERATED_MARKER;
|
|
152
152
|
var init_fs = __esm({
|
|
153
153
|
"src/lib/fs.ts"() {
|
|
154
154
|
"use strict";
|
|
155
|
-
|
|
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.
|
|
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
|
|
7561
|
+
return kimiCodeConfigPath(home, env);
|
|
7391
7562
|
},
|
|
7392
|
-
async isInstalled() {
|
|
7393
|
-
return
|
|
7563
|
+
async isInstalled(home, env) {
|
|
7564
|
+
return hasTomlHookCommand(kimiCodeConfigPath(home, env), HOOK_COMMAND2);
|
|
7394
7565
|
},
|
|
7395
|
-
installEntries(
|
|
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